Основи роботи з pandas для аналізу даних

Лекція з практичними прикладами на Python

Лекції
Аналітика даних
Author

Богдан Красюк

Published

20 жовтня 2025 р.

1 Презентація

🐼 Мета лекції. Навчитися базовим прийомам роботи з бібліотекою pandas для завантаження, огляду, очищення та агрегування табличних даних, а також підготовки датасетів до візуалізації й моделювання.

2 Вступ: що таке pandas і коли її використовувати?

pandas — це бібліотека Python для високопродуктивної роботи з табличними даними в оперативній пам’яті. Вона надає два ключові контейнери даних: Series (1D стовпчик) та DataFrame (2D таблиця), з індексами, типами даних, векторизованими операціями та зручними методами статистичного аналізу. Практично це «mini-таблична БД у Python» з можливостями фільтрації, групування, злиття, перетворення форми даних, роботи з датами/часом тощо.

💡 Коли брати pandas? Коли дані вміщуються у пам’ять однієї машини (десятки–сотні МБ, інколи ГБ), коли потрібна швидка розвідка даних (EDA), побудова звітів, підготовка фіч для ML або заміна повторюваних дій в Excel на відтворюваний код.

3 Підготовка середовища

Code
import pandas as pd
import numpy as np

# Для відображення всіх колонок/рядків (регулюйте за потребою)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 30)

4 Завантаження даних з CSV

У прикладах нижче будемо використовувати файл з вашого проєкту: data/input_data.csv.

Code
from pathlib import Path

DATA_PATH = Path("data/input_data.csv")
assert DATA_PATH.exists(), f"Не знайдено файл {DATA_PATH.resolve()}"

df = pd.read_csv(DATA_PATH)
df.head()
Age Attrition BusinessTravel DailyRate Department DistanceFromHome Education EducationField EmployeeCount EmployeeNumber EnvironmentSatisfaction Gender HourlyRate JobInvolvement JobLevel JobRole JobSatisfaction MaritalStatus MonthlyIncome MonthlyRate NumCompaniesWorked Over18 OverTime PercentSalaryHike PerformanceRating RelationshipSatisfaction StandardHours StockOptionLevel TotalWorkingYears TrainingTimesLastYear WorkLifeBalance YearsAtCompany YearsInCurrentRole YearsSinceLastPromotion YearsWithCurrManager
0 41 Yes Travel_Rarely 1102 Sales 1 2 Life Sciences 1 1 2 Female 94 3 2 Sales Executive 4 Single 5993 19479 8 Y Yes 11 3 1 80 0 8 0 1 6 4 0 5
1 49 No Travel_Frequently 279 Research & Development 8 1 Life Sciences 1 2 3 Male 61 2 2 Research Scientist 2 Married 5130 24907 1 Y No 23 4 4 80 1 10 3 3 10 7 1 7
2 37 Yes Travel_Rarely 1373 Research & Development 2 2 Other 1 4 4 Male 92 2 1 Laboratory Technician 3 Single 2090 2396 6 Y Yes 15 3 2 80 0 7 3 3 0 0 0 0
3 33 No Travel_Frequently 1392 Research & Development 3 4 Life Sciences 1 5 4 Female 56 3 1 Research Scientist 3 Married 2909 23159 1 Y Yes 11 3 3 80 0 8 3 3 8 7 3 0
4 27 No Travel_Rarely 591 Research & Development 2 1 Medical 1 7 1 Male 40 3 1 Laboratory Technician 2 Married 3468 16632 9 Y No 12 3 4 80 1 6 3 3 2 2 2 2

4.1 Перевірка розміру та типів даних

Code
df.shape, df.dtypes
((1470, 35),
 Age                         int64
 Attrition                  object
 BusinessTravel             object
 DailyRate                   int64
 Department                 object
                             ...  
 WorkLifeBalance             int64
 YearsAtCompany              int64
 YearsInCurrentRole          int64
 YearsSinceLastPromotion     int64
 YearsWithCurrManager        int64
 Length: 35, dtype: object)
Code
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1470 entries, 0 to 1469
Data columns (total 35 columns):
 #   Column                    Non-Null Count  Dtype 
---  ------                    --------------  ----- 
 0   Age                       1470 non-null   int64 
 1   Attrition                 1470 non-null   object
 2   BusinessTravel            1470 non-null   object
 3   DailyRate                 1470 non-null   int64 
 4   Department                1470 non-null   object
 5   DistanceFromHome          1470 non-null   int64 
 6   Education                 1470 non-null   int64 
 7   EducationField            1470 non-null   object
 8   EmployeeCount             1470 non-null   int64 
 9   EmployeeNumber            1470 non-null   int64 
 10  EnvironmentSatisfaction   1470 non-null   int64 
 11  Gender                    1470 non-null   object
 12  HourlyRate                1470 non-null   int64 
 13  JobInvolvement            1470 non-null   int64 
 14  JobLevel                  1470 non-null   int64 
 15  JobRole                   1470 non-null   object
 16  JobSatisfaction           1470 non-null   int64 
 17  MaritalStatus             1470 non-null   object
 18  MonthlyIncome             1470 non-null   int64 
 19  MonthlyRate               1470 non-null   int64 
 20  NumCompaniesWorked        1470 non-null   int64 
 21  Over18                    1470 non-null   object
 22  OverTime                  1470 non-null   object
 23  PercentSalaryHike         1470 non-null   int64 
 24  PerformanceRating         1470 non-null   int64 
 25  RelationshipSatisfaction  1470 non-null   int64 
 26  StandardHours             1470 non-null   int64 
 27  StockOptionLevel          1470 non-null   int64 
 28  TotalWorkingYears         1470 non-null   int64 
 29  TrainingTimesLastYear     1470 non-null   int64 
 30  WorkLifeBalance           1470 non-null   int64 
 31  YearsAtCompany            1470 non-null   int64 
 32  YearsInCurrentRole        1470 non-null   int64 
 33  YearsSinceLastPromotion   1470 non-null   int64 
 34  YearsWithCurrManager      1470 non-null   int64 
dtypes: int64(26), object(9)
memory usage: 402.1+ KB
Code
df.describe(include='all').T
count unique top freq mean std min 25% 50% 75% max
Age 1470.0 NaN NaN NaN 36.92381 9.135373 18.0 30.0 36.0 43.0 60.0
Attrition 1470 2 No 1233 NaN NaN NaN NaN NaN NaN NaN
BusinessTravel 1470 3 Travel_Rarely 1043 NaN NaN NaN NaN NaN NaN NaN
DailyRate 1470.0 NaN NaN NaN 802.485714 403.5091 102.0 465.0 802.0 1157.0 1499.0
Department 1470 3 Research & Development 961 NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ...
WorkLifeBalance 1470.0 NaN NaN NaN 2.761224 0.706476 1.0 2.0 3.0 3.0 4.0
YearsAtCompany 1470.0 NaN NaN NaN 7.008163 6.126525 0.0 3.0 5.0 9.0 40.0
YearsInCurrentRole 1470.0 NaN NaN NaN 4.229252 3.623137 0.0 2.0 3.0 7.0 18.0
YearsSinceLastPromotion 1470.0 NaN NaN NaN 2.187755 3.22243 0.0 0.0 1.0 3.0 15.0
YearsWithCurrManager 1470.0 NaN NaN NaN 4.123129 3.568136 0.0 2.0 3.0 7.0 17.0

35 rows × 11 columns

5 Індексація та вибірка: loc і iloc

  • df.loc[рядки, колонки] — вибірка за мітками індексу/назвами колонок.
  • df.iloc[рядки, колонки] — вибірка за позиціями (інтегральними індексами).
Code
# Перші 5 рядків, кілька перших колонок (позиційно)
df.iloc[:5, :3]
Age Attrition BusinessTravel
0 41 Yes Travel_Rarely
1 49 No Travel_Frequently
2 37 Yes Travel_Rarely
3 33 No Travel_Frequently
4 27 No Travel_Rarely
Code
# Якщо у вас є колонка 'id' або інша назва — покажемо адресний вибір
cols = df.columns.tolist()
some_col = cols[0] if cols else None
if some_col is not None:
    df.loc[:4, [some_col]]
else:
    df.head(3)

⚠️ Рекомендація: використовуйте loc/iloc, а не «коротку» індексацію [], щоб уникати плутанини між позиціями та мітками.

6 Базові трансформації: фільтрація, сортування, нові колонки

6.1 Фільтрація

Code
# Приклад: автоматично оберемо першу числову колонку і відфільтруємо за медіаною
num_cols = df.select_dtypes(include='number').columns.tolist()
if num_cols:
    col = num_cols[0]
    median_val = df[col].median()
    filtered = df[df[col] > median_val]
    filtered[[col]].head()
else:
    df.head(3)

6.2 Сортування

Code
if num_cols:
    df.sort_values(num_cols[0], ascending=False).head()
else:
    df.head(3)

6.3 Нові колонки

Code
# Створимо приклад фічі: частка від максимуму в обраній числовій колонці
if num_cols:
    col = num_cols[0]
    df[f"{col}_ratio"] = df[col] / df[col].max()
    df[[col, f"{col}_ratio"]].head()
else:
    df.head(3)

7 Робота з пропусками (NaN)

7.1 Діагностика пропусків

Code
missing_by_col = df.isna().sum().sort_values(ascending=False)
missing_total = int(df.isna().sum().sum())
missing_by_col.head(), missing_total
(Age               0
 Attrition         0
 BusinessTravel    0
 DailyRate         0
 Department        0
 dtype: int64,
 0)

7.2 Стратегії заповнення/видалення

  • Заповнення константою: df.fillna(0) або df['col'].fillna('N/A')
  • Заповнення статистикою: середнє/медіана/мода
  • Видалення: df.dropna() або з порогом заповнення thresh=
  • Індикатор відсутності: додатковий бінарний стовпчик для MNAR-випадків
Code
# Демонстрація: оберемо числові колонки та заповнимо їх медіаною
num_cols = df.select_dtypes(include='number').columns
df_imputed = df.copy()
df_imputed[num_cols] = df_imputed[num_cols].apply(lambda s: s.fillna(s.median()))
df_imputed.head()
Age Attrition BusinessTravel DailyRate Department DistanceFromHome Education EducationField EmployeeCount EmployeeNumber EnvironmentSatisfaction Gender HourlyRate JobInvolvement JobLevel JobRole JobSatisfaction MaritalStatus MonthlyIncome MonthlyRate NumCompaniesWorked Over18 OverTime PercentSalaryHike PerformanceRating RelationshipSatisfaction StandardHours StockOptionLevel TotalWorkingYears TrainingTimesLastYear WorkLifeBalance YearsAtCompany YearsInCurrentRole YearsSinceLastPromotion YearsWithCurrManager Age_ratio
0 41 Yes Travel_Rarely 1102 Sales 1 2 Life Sciences 1 1 2 Female 94 3 2 Sales Executive 4 Single 5993 19479 8 Y Yes 11 3 1 80 0 8 0 1 6 4 0 5 0.683333
1 49 No Travel_Frequently 279 Research & Development 8 1 Life Sciences 1 2 3 Male 61 2 2 Research Scientist 2 Married 5130 24907 1 Y No 23 4 4 80 1 10 3 3 10 7 1 7 0.816667
2 37 Yes Travel_Rarely 1373 Research & Development 2 2 Other 1 4 4 Male 92 2 1 Laboratory Technician 3 Single 2090 2396 6 Y Yes 15 3 2 80 0 7 3 3 0 0 0 0 0.616667
3 33 No Travel_Frequently 1392 Research & Development 3 4 Life Sciences 1 5 4 Female 56 3 1 Research Scientist 3 Married 2909 23159 1 Y Yes 11 3 3 80 0 8 3 3 8 7 3 0 0.550000
4 27 No Travel_Rarely 591 Research & Development 2 1 Medical 1 7 1 Male 40 3 1 Laboratory Technician 2 Married 3468 16632 9 Y No 12 3 4 80 1 6 3 3 2 2 2 2 0.450000
Code
# Приклад індикатора пропуску для однієї колонки (якщо є пропуски)
if num_cols.size:
    col = num_cols[0]
    df_imputed[f"{col}_was_na"] = df[col].isna().astype(int)
    df_imputed[[col, f"{col}_was_na"]].head()
else:
    df_imputed.head(3)

💬 Порада. Вибір стратегії залежить від контексту й типу моделі. Для деревоподібних алгоритмів інколи краще залишити пропуски або використовувати спеціальний маркер; для регресійних — частіше спрацьовують статистики.

8 Групування та агрегування: groupby, agg

Групування реалізує шаблон split → apply → combine: розбили за категоріями, застосували функції, об’єднали назад.

Code
# Знайдемо першу категоріальну колонку і порахуємо агрегати для першої числової
cat_cols = df_imputed.select_dtypes(include=['object', 'category', 'bool']).columns.tolist()
num_cols = df_imputed.select_dtypes(include='number').columns.tolist()

if cat_cols and num_cols:
    gcol, vcol = cat_cols[0], num_cols[0]
    grouped = (
        df_imputed.groupby(gcol)[vcol]
        .agg(['count', 'mean', 'median', 'min', 'max'])
        .sort_values('mean', ascending=False)
    )
    grouped.head(10)
else:
    df_imputed.head(3)

8.1 Комплексні агрегації кількома функціями

Code
if cat_cols and num_cols:
    gcol = cat_cols[0]
    agg_dict = {c: ['count', 'mean', 'median'] for c in num_cols[:3]}  # приклад
    multi = df_imputed.groupby(gcol).agg(agg_dict)
    multi.head()
else:
    df_imputed.head(3)

9 Перетворення форми даних: pivot_table, melt

Code
# Приклад: зведена таблиця (якщо є категоріальна та числова колонки)
if cat_cols and num_cols:
    idx, col, val = cat_cols[0], (cat_cols[1] if len(cat_cols) > 1 else cat_cols[0]), num_cols[0]
    pivot = pd.pivot_table(df_imputed, index=idx, columns=col, values=val, aggfunc='mean')
    pivot.head()
else:
    df_imputed.head(3)
Code
# "Розплавлення" широкого формату у довгий
melt_cols = num_cols[:3] if len(num_cols) >= 2 else num_cols
if len(melt_cols) >= 2:
    melted = df_imputed.melt(value_vars=melt_cols, var_name='feature', value_name='value')
    melted.head()
else:
    df_imputed.head(3)

10 Об’єднання таблиць: merge, concat, join

Code
# Штучний приклад: створимо довідник унікальних категорій та "джойнимо" його
if cat_cols:
    gcol = cat_cols[0]
    lookup = pd.DataFrame({gcol: df_imputed[gcol].dropna().unique()})
    lookup['is_special'] = np.random.choice([0, 1], size=len(lookup), p=[0.7, 0.3])
    merged = df_imputed.merge(lookup, on=gcol, how='left')
    merged[[gcol, 'is_special']].head()
else:
    df_imputed.head(3)

11 Збереження результатів

Code
OUT_DIR = Path("data")
OUT_DIR.mkdir(parents=True, exist_ok=True)

df_imputed.to_csv(OUT_DIR / "input_data_imputed.csv", index=False)
"Збережено до data/input_data_imputed.csv"
'Збережено до data/input_data_imputed.csv'

12 Міні‑практикум 🧪

  1. Знайдіть 3–5 ключових числових показників (mean/median/min/max/std) для вашого домену.
  2. Оберіть категоріальну колонку й побудуйте топ‑10 груп за середнім значенням метрики.
  3. Створіть 2–3 прості нові фічі (відношення, різниця, лог‑перетворення) й порівняйте їхню кореляцію з цільовою.
  4. Підготуйте чистий датасет без пропусків і з чіткими типами даних для наступної лекції (візуалізація/ML).

13 Корисні патерни та нагадування ✅

  • Працюйте з копіями: df2 = df.copy() перед ризикованими перетвореннями.
  • Фіксуйте випадковість для відтворюваності: np.random.seed(42).
  • Зберігайте «сирі» дані незмінними, результати — у власну теку data/processed/.
  • Додавайте тести перевірки якості даних: діапазони, унікальність ключів, відсоток пропусків.
  • Документуйте кроки в коді, щоб інші могли повторити ваш аналіз.

13.1 Додаткові ресурси


Лого

Лого

Проєкт реалізується за підтримки Європейського Союзу в межах програми Дім Європи.