Розуміння ваших даних - Дослідницький аналіз даних (EDA)
Кафедра ШІзики
Автор
30 хвилин
Час читання
28.10.2025
Дата публікації
📌 Новачок у Python? Ця стаття використовує приклади коду на Python. Якщо ви ще не працювали з Python, спочатку перегляньте наш посібник Налаштування Python для ML! Це займе 35 хвилин, і ви будете готові виконати весь код тут.
Розуміння ваших даних - Дослідницький аналіз даних (EDA) 📊
Уявіть, що ви детектив, який прибуває на місце злочину. Перед тим як робити будь-які висновки, ви ретельно вивчаєте кожну деталь: відбитки пальців, сліди, об’єкти, часову лінію. Ви ставите питання, тестуєте теорії та шукаєте патерни. Тільки після цього ретельного розслідування ви формуєте гіпотези.
Дослідницький аналіз даних (EDA) - це саме те - бути детективом з вашими даними! Це критичний крок між очищенням даних та побудовою моделей. Ви досліджуєте, візуалізуєте, ставите питання і розумієте, що ваші дані вам розповідають.
Ось шокуюча правда: Більшість невдач ML проєктів відбувається тому, що люди пропустили EDA. Вони одразу перейшли до моделювання без розуміння своїх даних. Це як намагатися спекти торт, не скуштувавши інгредієнти - ви можете використати сіль замість цукру і дивуватися, чому це так погано смакує!
EDA - це ваш шанс:
- 🔍 Виявити приховані патерни, про які ви не знали
- 🚨 Помітити проблеми якості даних, які пропустило очищення
- 💡 Згенерувати ідеї ознак для кращих моделей
- 🎯 Зрозуміти зв’язки між змінними
- ⚠️ Ідентифікувати потенційні проблеми перед навчанням
Давайте навчимося бути детективами даних! 🕵️
Що таке EDA? Мистецтво розслідування даних 🎨
Визначення
Дослідницький аналіз даних (EDA) - це процес аналізу та візуалізації датасетів для підсумовування їх основних характеристик, виявлення патернів, виявлення аномалій, тестування гіпотез та перевірки припущень - все це ДО застосування алгоритмів машинного навчання.
Мислення EDA
Думайте про EDA як про розмову з вашими даними:
Ви: "Привіт, дані, що ви можете розповісти про себе?"
Дані: "Ну, у мене 10,000 рядків і 15 колонок..."
Ви: "Цікаво! Який середній вік клієнта?"
Дані: "Близько 35 років, але є дивний сплеск на 99..."
Ви: "99? Це звучить підозріло! Покажи мені більше!"
Дані: "Це, мабуть, значення за замовчуванням для відсутнього віку..."
Ви: "Ага! Що ще ти ховаєш?"
Дані: "Ну, мої продажі стрибають кожної п'ятниці, і..."EDA - це про постановку питань і дозволяти даним відповідати!
Чому EDA важливий
Без EDA:
❌ Навчити модель на сміттєвих даних
❌ Пропустити очевидні патерни
❌ Використовувати нерелевантні ознаки
❌ Здивуватися поганими результатами
❌ Витратити тижні на дебагЗ EDA:
✅ Глибоко зрозуміти дані
✅ Зловити проблеми рано
✅ Створити кращі ознаки
✅ Встановити реалістичні очікування
✅ Побудувати кращі моделі швидшеПроцес EDA 🗺️
Ось систематичний підхід, якому ми будемо слідувати:
1. Перший погляд
├─ Завантажити дані
├─ Перевірити форму та типи
└─ Попередній перегляд рядків
2. Одновимірний аналіз (Одна змінна за раз)
├─ Числові: розподіли, статистика, викиди
└─ Категоріальні: частоти, унікальні значення
3. Двовимірний аналіз (Дві змінні)
├─ Числова vs Числова: кореляції, діаграми розсіювання
├─ Категоріальна vs Категоріальна: crosstabs, теплові карти
└─ Числова vs Категоріальна: групована статистика, box plots
4. Багатовимірний аналіз (Кілька змінних)
├─ Кореляційні матриці
├─ Парні графіки
└─ Складні зв'язки
5. Виявлення патернів
├─ Тренди часових рядів
├─ Сезонні патерни
└─ Аномалії та викиди
6. Інсайти та гіпотези
├─ Документувати знахідки
├─ Генерувати питання
└─ Планувати наступні крокиДавайте зануримося в кожен крок з реальним прикладом!
Наш датасет: Замовлення піцерії 🍕
Пам’ятаєте піцерію “Mama ML” з нашої статті про життєвий цикл? Вони хочуть передбачати обсяг замовлень. Давайте дослідимо їхні історичні дані про замовлення!
Завантаження даних
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Налаштування стилю графіків
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 6)
# Завантажити дані
orders = pd.read_csv('pizza_orders.csv')
print("🍕 Піцерія Mama ML - Аналіз даних замовлень")
print("=" * 50)Крок 1: Перший погляд - Знайомство з даними 👋
Перевірка форми та структури
# Базова інформація
print(f"Форма датасету: {orders.shape}")
print(f"Рядків: {orders.shape[0]:,}")
print(f"Колонок: {orders.shape[1]}")
# Вивід:
# Форма датасету: (10000, 8)
# Рядків: 10,000
# Колонок: 8Що це нам говорить: У нас є 10,000 замовлень з 8 різними частинами інформації про кожне.
Попередній перегляд даних
# Перші кілька рядків
print("\nПерші 5 замовлень:")
print(orders.head())
# Останні кілька рядків
print("\nОстанні 5 замовлень:")
print(orders.tail())
# Випадкова вибірка
print("\nВипадкова вибірка:")
print(orders.sample(5))Вивід:
| index | order_id | date | day_of_week | hour | num_pizzas | total_price | is_weekend | temperature |
|---|---|---|---|---|---|---|---|---|
| 0 | 1001 | 2024-01-15 | Понеділок | 19 | 2 | 24.99 | False | 15 |
| 1 | 1002 | 2024-01-15 | Понеділок | 20 | 1 | 12.99 | False | 15 |
| 2 | 1003 | 2024-01-16 | Вівторок | 12 | 3 | 36.99 | False | 18 |
| 3 | 1004 | 2024-01-16 | Вівторок | 18 | 1 | 12.99 | False | 18 |
| 4 | 1005 | 2024-01-17 | Середа | 19 | 2 | 24.99 | False | 12 |
Початкові спостереження:
- Замовлення мають ID, дати, інформацію про час
- Кількість піц та ціни варіюються
- Дані про погоду (температура) включені
- Є прапорець вихідних
Типи даних та пам’ять
# Інформація про колонки
print("\nІнформація про колонки:")
print(orders.info())
# Типи даних
print("\nТипи даних:")
print(orders.dtypes)
# Використання пам'яті
print(f"\nВикористання пам'яті: {orders.memory_usage(deep=True).sum() / 1024**2:.2f} MB")Вивід:
<class 'pandas.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 order_id 10000 non-null int64
1 date 10000 non-null object
2 day_of_week 10000 non-null object
3 hour 10000 non-null int64
4 num_pizzas 10000 non-null int64
5 total_price 10000 non-null float64
6 is_weekend 10000 non-null bool
7 temperature 9850 non-null float64
dtypes: bool(1), float64(2), int64(3), object(2)
memory usage: 0.55 MBКлючові знахідки:
- ✅ Немає відсутніх значень, крім температури (150 відсутніх)
- ⚠️ Дата зберігається як ‘object’ (рядок) - треба конвертувати в datetime
- ✅ Відповідні типи даних для більшості колонок
Перевірка відсутніх значень
# Відсутні значення
print("\nВідсутні значення:")
missing = orders.isnull().sum()
missing_pct = (missing / len(orders)) * 100
missing_df = pd.DataFrame({
'Кількість відсутніх': missing,
'Відсоток': missing_pct
})
print(missing_df[missing_df['Кількість відсутніх'] > 0])Вивід:
| колонка | Кількість відсутніх | Відсоток |
|---|---|---|
| temperature | 150 | 1.5% |
Дія: 1.5% відсутніх прийнятно. Обробимо це пізніше.
Крок 2: Одновимірний аналіз - Одна змінна за раз 🔍
Числові змінні
Давайте проаналізуємо кожну числову колонку окремо.
Базова статистика
# Підсумкова статистика
print("\n📊 Підсумок числових змінних:")
print(orders.describe())Вивід:
| статистика | order_id | hour | num_pizzas | total_price | temperature |
|---|---|---|---|---|---|
| count | 10000.00 | 10000.00 | 10000.00 | 10000.00 | 9850.00 |
| mean | 5500.50 | 15.34 | 2.15 | 26.44 | 16.85 |
| std | 2886.90 | 4.12 | 0.99 | 12.35 | 8.23 |
| min | 1001.00 | 10.00 | 1.00 | 12.99 | -5.00 |
| 25% | 3250.75 | 12.00 | 1.00 | 12.99 | 10.00 |
| 50% | 5500.50 | 15.00 | 2.00 | 24.99 | 17.00 |
| 75% | 7750.25 | 19.00 | 3.00 | 36.99 | 23.00 |
| max | 11000.00 | 23.00 | 8.00 | 98.99 | 35.00 |
Ключові інсайти:
🕐 Година:
- Замовлення надходять між 10 ранку та 11 вечора
- Медіана - 3 дня (15:00)
- Пікові години ймовірно ввечері
🍕 Кількість піц:
- Середнє: 2.15 піц на замовлення
- Діапазон: від 1 до 8 піц
- Більшість замовлень 1-3 піци (25-75 персентіль)
💰 Загальна ціна:
- Середнє: $26.44
- Мін: $12.99 (мабуть 1 піца)
- Макс: $98.99 (вечірка!)
- Велика варіація (std = $12.35)
🌡️ Температура:
- Середня: 17°C (близько 63°F)
- Діапазон: від -5°C до 35°C
- Охоплює зиму до літа
Розподіли - Гістограми
# Побудувати розподіли
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# Розподіл за годинами
orders['hour'].hist(bins=14, ax=axes[0,0], edgecolor='black')
axes[0,0].set_title('Замовлення за годиною дня', fontsize=14, fontweight='bold')
axes[0,0].set_xlabel('Година')
axes[0,0].set_ylabel('Кількість замовлень')
axes[0,0].axvline(orders['hour'].median(), color='red', linestyle='--', label='Медіана')
axes[0,0].legend()
# Розподіл кількості піц
orders['num_pizzas'].hist(bins=8, ax=axes[0,1], edgecolor='black', color='orange')
axes[0,1].set_title('Піц на замовлення', fontsize=14, fontweight='bold')
axes[0,1].set_xlabel('Кількість піц')
axes[0,1].set_ylabel('Кількість замовлень')
# Розподіл ціни
orders['total_price'].hist(bins=30, ax=axes[1,0], edgecolor='black', color='green')
axes[1,0].set_title('Розподіл ціни замовлення', fontsize=14, fontweight='bold')
axes[1,0].set_xlabel('Загальна ціна ($)')
axes[1,0].set_ylabel('Кількість замовлень')
# Розподіл температури
orders['temperature'].hist(bins=20, ax=axes[1,1], edgecolor='black', color='skyblue')
axes[1,1].set_title('Розподіл температури', fontsize=14, fontweight='bold')
axes[1,1].set_xlabel('Температура (°C)')
axes[1,1].set_ylabel('Частота')
plt.tight_layout()
plt.show()Знахідки з розподілів:
📊 Розподіл за годинами:
- Бімодальний розподіл (два піки!)
- Пік 1: Близько обіду (12-2 дня)
- Пік 2: Близько вечері (6-8 вечора)
- Дуже мало замовлень пізно ввечері або рано вранці
- Інсайт: Графік роботи персоналу має фокусуватися на обіді та вечері
📊 Кількість піц:
- Правостороннє перекошення розподілу
- Найпоширеніше: 2 піци на замовлення
- Швидко зменшується для більших замовлень
- Інсайт: Оптимізувати доставку для 1-3 піц
📊 Розподіл ціни:
- Кілька піків на 24.99, $36.99
- Вказує на стратегію цін меню (1 піца, 2 піци, 3 піци)
- Мало замовлень вище $50
- Інсайт: Стандартні цінові рівні працюють добре
📊 Температура:
- Приблизно нормальний розподіл (дзвоноподібна крива)
- Центр близько 17°C
- Повний діапазон від холодного (-5°C) до спекотного (35°C)
- Інсайт: Температура ймовірно впливає на обсяг замовлень
Box Plots - Пошук викидів
# Box plots для виявлення викидів
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Кількість піц
orders.boxplot(column='num_pizzas', ax=axes[0])
axes[0].set_title('Піц на замовлення - Виявлення викидів')
axes[0].set_ylabel('Кількість піц')
# Ціна
orders.boxplot(column='total_price', ax=axes[1])
axes[1].set_title('Ціна - Виявлення викидів')
axes[1].set_ylabel('Загальна ціна ($)')
# Температура
orders.boxplot(column='temperature', ax=axes[2])
axes[2].set_title('Температура - Виявлення викидів')
axes[2].set_ylabel('Температура (°C)')
plt.tight_layout()
plt.show()Інтерпретація box plot:
Компоненти Box Plot:
┌─────────┐
│ Коробка│ 50% даних (25-75 персентіль)
│─────────│ ← Лінія медіани
│ Коробка│
└─────────┘
│
Вусик (до 1.5 × IQR)
● ← Викиди (точки за вусами)Виявлені викиди:
- 🍕 Піци: Кілька замовлень на 6-8 піц (вечірки/події)
- 💰 Ціна: Кілька замовлень вище $60 (великі замовлення)
- 🌡️ Температура: Немає викидів (всі значення розумні)
Рішення: Залишимо викиди - це легітимні великі замовлення, не помилки!
Категоріальні змінні
День тижня
# Підрахунок значень
print("\n📅 Замовлення за днем тижня:")
day_counts = orders['day_of_week'].value_counts()
print(day_counts)
# Відсоток
print("\nВідсоток:")
print((day_counts / len(orders) * 100).round(2))
# Візуалізація
plt.figure(figsize=(10, 6))
day_order = ['Понеділок', 'Вівторок', 'Середа', 'Четвер', "П'ятниця", 'Субота', 'Неділя']
day_counts = orders['day_of_week'].value_counts().reindex(day_order)
day_counts.plot(kind='bar', color='steelblue', edgecolor='black')
plt.title('Замовлення за днем тижня', fontsize=16, fontweight='bold')
plt.xlabel('День')
plt.ylabel('Кількість замовлень')
plt.xticks(rotation=45)
plt.axhline(day_counts.mean(), color='red', linestyle='--', label=f'Середнє: {day_counts.mean():.0f}')
plt.legend()
plt.tight_layout()
plt.show()Вивід:
П'ятниця 1850 (18.50%)
Субота 1720 (17.20%)
Неділя 1520 (15.20%)
Четвер 1450 (14.50%)
Середа 1280 (12.80%)
Вівторок 1090 (10.90%)
Понеділок 1090 (10.90%)Основні знахідки:
- 🎉 П’ятниця - день піци! Найбільше замовлень (18.5%)
- 📈 Сплеск у вихідні: П’ятниця-неділя становлять 51% всіх замовлень
- 📉 Спад понеділок/вівторок: Найнижчі дні (~11% кожен)
- Інсайт: Персонал на вихідні критичний; понеділок/вівторок потребують промоакцій
Вихідні vs Будні
# Аналіз вихідних
print("\n🗓️ Вихідні vs Будні:")
weekend_split = orders['is_weekend'].value_counts()
print(weekend_split)
# Кругова діаграма
plt.figure(figsize=(8, 8))
plt.pie(weekend_split, labels=['Будні', 'Вихідні'], autopct='%1.1f%%',
colors=['lightcoral', 'lightgreen'], startangle=90)
plt.title('Розподіл замовлень: Вихідні vs Будні', fontsize=16, fontweight='bold')
plt.show()Знахідка:
- Вихідні: 42% замовлень
- Будні: 58% замовлень
- Але дні вихідних мають вищий середній показник (1,697 vs 1,140 замовлень/день)
Крок 3: Двовимірний аналіз - Зв’язки між змінними 🔗
Тепер подивимося, як змінні пов’язані одна з одною!
Числова vs Числова
Кореляційний аналіз
# Кореляційна матриця
print("\n🔗 Кореляційна матриця:")
numerical_cols = ['hour', 'num_pizzas', 'total_price', 'temperature']
correlations = orders[numerical_cols].corr()
print(correlations.round(3))
# Теплова карта
plt.figure(figsize=(10, 8))
sns.heatmap(correlations, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=1, fmt='.3f')
plt.title('Теплова карта кореляцій', fontsize=16, fontweight='bold')
plt.show()Вивід:
| змінна | hour | num_pizzas | total_price | temperature |
|---|---|---|---|---|
| hour | 1.000 | -0.050 | -0.043 | 0.080 |
| num_pizzas | -0.050 | 1.000 | 0.985 | -0.120 |
| total_price | -0.043 | 0.985 | 1.000 | -0.115 |
| temperature | 0.080 | -0.120 | -0.115 | 1.000 |
Ключові кореляції:
🔥 Сильна позитивна (0.985): num_pizzas ↔ total_price
- Має повний сенс! Більше піц = вища ціна
- Майже ідеальна кореляція
❄️ Слабка негативна (-0.120): temperature ↔ num_pizzas
- Холодніша погода = трохи більше піц на замовлення
- Люди замовляють більше, коли холодно
- Інсайт: Зимові промоакції можуть працювати добре!
🤷 Слабка/Відсутня кореляція: hour ↔ все інше
- Час доби не сильно впливає на розмір або ціну замовлення
- Інсайт: Промоакції можуть працювати у будь-яку годину
Діаграми розсіювання
# Ціна vs Кількість піц
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.scatter(orders['num_pizzas'], orders['total_price'], alpha=0.5)
plt.xlabel('Кількість піц')
plt.ylabel('Загальна ціна ($)')
plt.title('Ціна vs Кількість піц')
plt.grid(True, alpha=0.3)
# Додати лінію тренду
z = np.polyfit(orders['num_pizzas'], orders['total_price'], 1)
p = np.poly1d(z)
plt.plot(orders['num_pizzas'], p(orders['num_pizzas']),
"r--", linewidth=2, label=f'Тренд: ${z[0]:.2f} за піцу')
plt.legend()
plt.subplot(1, 2, 2)
plt.scatter(orders['temperature'], orders['num_pizzas'], alpha=0.5, color='orange')
plt.xlabel('Температура (°C)')
plt.ylabel('Кількість піц')
plt.title('Замовлено піц vs Температура')
plt.grid(True, alpha=0.3)
# Додати лінію тренду
z = np.polyfit(orders['temperature'].dropna(),
orders.loc[orders['temperature'].notna(), 'num_pizzas'], 1)
p = np.poly1d(z)
temp_range = np.linspace(orders['temperature'].min(), orders['temperature'].max(), 100)
plt.plot(temp_range, p(temp_range), "r--", linewidth=2, label='Тренд')
plt.legend()
plt.tight_layout()
plt.show()Знахідки:
- 📊 Чіткий лінійний зв’язок: кожна піца додає ~$12 до ціни
- 🌡️ Легкий спадний тренд: тепліша погода = менше піц на замовлення
- Інсайт: Температура - корисна ознака для прогнозування!
Категоріальна vs Числова
Замовлення за днем та часом
# Середня статистика по днях
print("\n📊 Статистика за днем тижня:")
day_stats = orders.groupby('day_of_week').agg({
'num_pizzas': ['mean', 'median', 'sum'],
'total_price': ['mean', 'median', 'sum']
}).round(2)
print(day_stats)
# Box plot: Піци за днем тижня
plt.figure(figsize=(12, 6))
day_order = ['Понеділок', 'Вівторок', 'Середа', 'Четвер', "П'ятниця", 'Субота', 'Неділя']
sns.boxplot(data=orders, x='day_of_week', y='num_pizzas', order=day_order)
plt.title('Кількість піц за днем тижня', fontsize=16, fontweight='bold')
plt.xlabel('День')
plt.ylabel('Піц на замовлення')
plt.xticks(rotation=45)
plt.show()Інсайти:
- Замовлення у вихідні трохи більші (2.3 піци) vs будні (2.1 піци)
- П’ятниця має найвищий загальний дохід
- Розподіл розміру замовлення узгоджений по днях
Вплив температури за сезоном
# Створити категорії сезону
def get_season(temp):
if temp < 10:
return 'Зима'
elif temp < 20:
return 'Весна/Осінь'
else:
return 'Літо'
orders['season'] = orders['temperature'].apply(get_season)
# Середні замовлення за сезоном
print("\n🌡️ Замовлення за сезоном:")
season_stats = orders.groupby('season').agg({
'num_pizzas': 'mean',
'total_price': 'mean',
'order_id': 'count'
}).round(2)
season_stats.columns = ['Серед. піц', 'Серед. ціна', 'Кількість замовлень']
print(season_stats)
# Візуалізація
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
season_order = ['Зима', 'Весна/Осінь', 'Літо']
season_stats['Серед. піц'].reindex(season_order).plot(kind='bar', color=['skyblue', 'lightgreen', 'orange'])
plt.title('Середня кількість піц на замовлення за сезоном')
plt.ylabel('Середня кількість піц')
plt.xticks(rotation=0)
plt.subplot(1, 2, 2)
season_stats['Кількість замовлень'].reindex(season_order).plot(kind='bar', color=['skyblue', 'lightgreen', 'orange'])
plt.title('Загальна кількість замовлень за сезоном')
plt.ylabel('Кількість замовлень')
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()Знахідки:
- ❄️ Зима (< 10°C): Найбільше піц на замовлення (2.4), менше загальних замовлень
- 🌸 Весна/Осінь (10-20°C): Збалансовано - найбільше замовлень, середній розмір
- ☀️ Літо (> 20°C): Найменші замовлення (1.9 піц), помірна загальна кількість
Інсайт: Холодна погода стимулює більші замовлення, але менше клієнтів. Тепла погода приносить більше клієнтів, але менші замовлення. Плануйте інвентар відповідно!
Крок 4: Патерни на основі часу ⏰
Погодинні патерни
# Замовлення за годинами
hourly_orders = orders.groupby('hour').size()
plt.figure(figsize=(14, 6))
hourly_orders.plot(kind='line', marker='o', linewidth=2, markersize=8)
plt.title('Обсяг замовлень за годиною дня', fontsize=16, fontweight='bold')
plt.xlabel('Година')
plt.ylabel('Кількість замовлень')
plt.grid(True, alpha=0.3)
plt.xticks(range(10, 24))
# Виділити пікову годину
peak_hour = hourly_orders.idxmax()
plt.axvline(peak_hour, color='red', linestyle='--', alpha=0.7, label=f'Пікова година: {peak_hour}:00')
plt.axhspan(hourly_orders.mean() - 50, hourly_orders.mean() + 50,
alpha=0.2, color='green', label='Нормальний діапазон')
plt.legend()
plt.show()
print(f"\n⏰ Пікова година: {peak_hour}:00 ({hourly_orders[peak_hour]} замовлень)")
print(f"Найповільніша година: {hourly_orders.idxmin()}:00 ({hourly_orders.min()} замовлень)")Знахідка:
- 🍽️ Обідній наплив: 12-2 дня (700+ замовлень/годину)
- 🍕 Вечірній пік: 6-8 вечора (900+ замовлень/годину) ← НАЙЗАВАНТАЖЕНІШЕ!
- 🌙 Пізній вечір: Після 10 вечора (< 200 замовлень/годину)
- Інсайт: Максимум персоналу під час 6-8 вечора, розглянути раннє закриття
Патерни за днями - Теплова карта
# Створити теплову карту година-день
hourly_day = orders.groupby(['day_of_week', 'hour']).size().unstack()
plt.figure(figsize=(16, 8))
sns.heatmap(hourly_day, cmap='YlOrRd', annot=True, fmt='d',
cbar_kws={'label': 'Кількість замовлень'})
plt.title('Теплова карта замовлень: День vs Година', fontsize=16, fontweight='bold')
plt.xlabel('Година дня')
plt.ylabel('День тижня')
plt.tight_layout()
plt.show()Виявлені патерни:
- 🔥 Гаряча точка: П’ятниця 7-8 вечора (пік піків!)
- ❄️ Холодна зона: Понеділок-вівторок 10-11 ранку
- 📈 Узгоджено: Вечірній наплив (6-8 вечора) сильний кожен день
- Дія: П’ятничний вечір потребує додаткового персоналу та інвентарю
Крок 5: Інсайти та гіпотези 💡
Підсумок ключових відкриттів
print("\n" + "="*60)
print("🎯 КЛЮЧОВІ ІНСАЙТИ З EDA")
print("="*60)
print("\n1️⃣ ПАТЕРНИ ЧАСУ:")
print(" • Пікова година: 7 вечора (вечерня вечеря)")
print(" • Піковий день: П'ятниця")
print(" • Найгарячіший слот: П'ятниця 7-8 вечора")
print(" • Найповільніше: Понеділок/вівторок ранок")
print("\n2️⃣ ХАРАКТЕРИСТИКИ ЗАМОВЛЕНЬ:")
print(f" • Середнє замовлення: {orders['num_pizzas'].mean():.2f} піц, ${orders['total_price'].mean():.2f}")
print(f" • Найпоширеніше: {orders['num_pizzas'].mode()[0]} піци")
print(f" • Ціна за піцу: ~$12.50")
print(f" • Великі замовлення (5+ піц): {(orders['num_pizzas'] >= 5).sum()} ({(orders['num_pizzas'] >= 5).sum()/len(orders)*100:.1f}%)")
print("\n3️⃣ ВПЛИВ ПОГОДИ:")
print(f" • Холодні дні (< 10°C): {orders[orders['temperature'] < 10]['num_pizzas'].mean():.2f} піц/замовлення")
print(f" • Теплі дні (> 20°C): {orders[orders['temperature'] > 20]['num_pizzas'].mean():.2f} піц/замовлення")
print(" • Висновок: Холодна погода → більші замовлення")
print("\n4️⃣ ЕФЕКТ ВИХІДНИХ:")
weekend_avg = orders[orders['is_weekend']]['num_pizzas'].mean()
weekday_avg = orders[~orders['is_weekend']]['num_pizzas'].mean()
print(f" • Замовлення у вихідні: {weekend_avg:.2f} піц/замовлення")
print(f" • Замовлення у будні: {weekday_avg:.2f} піц/замовлення")
print(f" • Різниця: {((weekend_avg - weekday_avg) / weekday_avg * 100):.1f}% більше у вихідні")
print("\n5️⃣ ЯКІСТЬ ДАНИХ:")
print(f" • Відсутні значення: {orders.isnull().sum().sum()} ({orders.isnull().sum().sum()/orders.size*100:.2f}%)")
print(f" • Викиди: {(orders['num_pizzas'] > 5).sum()} великих замовлень (залишені - легітимні)")
print(" • Типи даних: ✅ Всі відповідні")Гіпотези для моделювання
print("\n" + "="*60)
print("🧠 ГІПОТЕЗИ ДЛЯ ML МОДЕЛІ")
print("="*60)
print("\n📊 ВАЖЛИВІ ОЗНАКИ (ймовірно передбачувальні):")
print(" 1. day_of_week - Сильний сигнал (П'ятниця найвища)")
print(" 2. hour - Чіткі піки на обіді/вечері")
print(" 3. is_weekend - Впливає на патерни замовлень")
print(" 4. temperature - Обернений зв'язок з розміром замовлення")
print(" 5. [НОВА] hour_x_day взаємодія - П'ятничний вечір особливий")
print("\n🗑️ МЕНШ ВАЖЛИВІ ОЗНАКИ:")
print(" • order_id - Лише ідентифікатор")
print(" • date - Вже охоплено day_of_week")
print("\n🆕 ІДЕЇ FEATURE ENGINEERING:")
print(" • is_peak_hour - Бінарна: 1 якщо година в [12-14, 18-20]")
print(" • is_friday_evening - Бінарна: П'ятниця І пікова година")
print(" • temp_category - Категоріальна: Холодно/Помірно/Тепло")
print(" • rolling_avg_3h - Ковзне середнє замовлень")
print(" • day_hour_interaction - Комбінована ознака")
print("\n⚠️ ПОТЕНЦІЙНІ ПРОБЛЕМИ:")
print(" • Відсутні значення температури (1.5%) - заповнити денним середнім")
print(" • Висока кореляція ціна-піци (0.985) - може викликати мультиколінеарність")
print(" • Потрібно перевірити на сезонність (річні тренди)")
print(" • Розглянути зовнішні фактори: свята, погодні події, спортивні ігри")Кращі практики EDA ✅
Чек-лист EDA
def eda_checklist():
checklist = """
📋 ВСЕБІЧНИЙ ЧЕК-ЛИСТ EDA
✅ 1. ПЕРШИЙ ПОГЛЯД
□ Успішно завантажити дані
□ Перевірити форму (рядки × колонки)
□ Переглянути перші/останні/випадкові рядки
□ Перевірити типи даних
□ Перевірити використання пам'яті
✅ 2. ЯКІСТЬ ДАНИХ
□ Порахувати відсутні значення
□ Ідентифікувати дублікати
□ Перевірити на значення-заповнювачі (999, -1, "Невідомо")
□ Валідувати діапазони даних
□ Перевірити на помилки введення даних
✅ 3. ОДНОВИМІРНИЙ АНАЛІЗ
□ Підсумкова статистика (mean, median, std, min, max)
□ Розподіли (гістограми)
□ Виявлення викидів (box plots)
□ Підрахунок категоріальних значень
□ Підрахунок унікальних значень
✅ 4. ДВОВИМІРНИЙ АНАЛІЗ
□ Кореляційна матриця
□ Діаграми розсіювання (числова vs числова)
□ Box plots (категоріальна vs числова)
□ Crosstabs (категоріальна vs категоріальна)
✅ 5. БАГАТОВИМІРНИЙ АНАЛІЗ
□ Парні графіки
□ Теплові карти кореляцій
□ Кілька змінних на одному графіку
✅ 6. ПАТЕРНИ І ТРЕНДИ
□ Аналіз часових рядів
□ Сезонні патерни
□ Циклічні патерни
□ Виявлення аномалій
✅ 7. СПЕЦИФІЧНЕ ДЛЯ ДОМЕНУ
□ Застосувати бізнес-логіку
□ Валідувати відносно відомих фактів
□ Перевірити на витік даних
□ Розглянути зовнішні фактори
✅ 8. ДОКУМЕНТАЦІЯ
□ Задокументувати ключові знахідки
□ Перелічити гіпотези
□ Відзначити проблеми якості даних
□ Сплануйте feature engineering
□ Зберегти візуалізації
"""
return checklist
print(eda_checklist())Поширені помилки EDA, яких слід уникати
print("\n⚠️ ПОШИРЕНІ ПОМИЛКИ EDA:")
print("\n❌ 1. Повне пропущення EDA")
print(" 'Я просто навчу модель і подивлюся...'")
print(" → Результат: Сміттєва модель, втрачений час")
print("\n❌ 2. Перегляд лише підсумкової статистики")
print(" .describe() недостатньо!")
print(" → Завжди візуалізуйте - Квартет Анскомба доводить це")
print("\n❌ 3. Ігнорування викидів без дослідження")
print(" Не всі викиди - помилки!")
print(" → Досліджуйте перед видаленням")
print("\n❌ 4. Забування доменних знань")
print(" Чиста статистика може вводити в оману")
print(" → Запитуйте: 'Чи має це бізнес-сенс?'")
print("\n❌ 5. Недокументування знахідок")
print(" Ви забудете, що відкрили")
print(" → Запишіть це одразу")
print("\n❌ 6. Пошук патернів, яких немає")
print(" Кореляція ≠ Причинність")
print(" → Будьте скептичними до хибних кореляцій")
print("\n❌ 7. Неперевірка витоку даних")
print(" Включення майбутніх даних у навчання")
print(" → Ретельно валідуйте часові розбиття")Інструменти та бібліотеки EDA 🛠️
Необхідний Python стек
# Маніпуляція даними
import pandas as pd
import numpy as np
# Візуалізація
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px # Інтерактивні графіки
# Статистичний аналіз
from scipy import stats
from statsmodels.tsa.seasonal import seasonal_decompose
# Автоматизований EDA
import pandas_profiling
import sweetviz
import autoviz
# Швидкий автоматизований звіт
profile = pandas_profiling.ProfileReport(orders, title='EDA замовлень піци')
profile.to_file("eda_report.html")Швидкі функції EDA
def quick_eda(df):
"""
Виконати швидкий EDA на будь-якому dataframe
"""
print("="*60)
print("ШВИДКИЙ ПІДСУМОК EDA")
print("="*60)
# Форма
print(f"\n📏 Форма: {df.shape[0]:,} рядків × {df.shape[1]} колонок")
# Відсутні значення
missing = df.isnull().sum()
if missing.sum() > 0:
print(f"\n⚠️ Відсутні значення:")
print(missing[missing > 0])
else:
print("\n✅ Немає відсутніх значень")
# Дублікати
dups = df.duplicated().sum()
print(f"\n🔄 Дублікати: {dups}")
# Типи даних
print(f"\n🔤 Типи даних:")
print(df.dtypes.value_counts())
# Числовий підсумок
print(f"\n📊 Числовий підсумок:")
print(df.describe())
# Категоріальний підсумок
cat_cols = df.select_dtypes(include=['object', 'category']).columns
if len(cat_cols) > 0:
print(f"\n📝 Категоріальні змінні:")
for col in cat_cols:
print(f"\n{col}: {df[col].nunique()} унікальних значень")
print(df[col].value_counts().head())
# Використати
quick_eda(orders)Підсумок: Ваша подорож EDA 🗺️
Що ми вивчили
- EDA - це детективна робота - Ставте питання, досліджуйте, відкривайте
- Завжди починайте з EDA - Ніколи не переходьте до моделювання
- Візуалізуйте все - Числа брешуть, графіки розкривають
- Шукайте патерни - Часові тренди, кореляції, викиди
- Документуйте знахідки - Майбутній ви буде вдячний
- Генеруйте гіпотези - EDA інформує feature engineering
Конвеєр від EDA до моделювання
Сирі дані
↓
[ Очищення даних ] ← Стаття 1
↓
[ EDA ] ← Ви тут (Стаття 2)
↓
Інсайти + Гіпотези
↓
[ Feature Engineering ] ← Стаття 3
↓
[ Навчання моделі ]
↓
[ Оцінка ]
↓
Продакшн модельКлючові висновки
print("\n🎯 КЛЮЧОВІ ВИСНОВКИ:")
print("1. EDA розкриває, що ваші дані можуть (і не можуть) вам сказати")
print("2. Візуалізація потужніша, ніж лише статистика")
print("3. Доменні знання + дослідження даних = потужні інсайти")
print("4. Кожен датасет розповідає історію - ваша робота слухати")
print("5. Хороший EDA веде до кращих ознак, кращих моделей, кращих результатів")Що далі? 🚀
Тепер, коли ви глибоко розумієте свої дані, ви готові до Feature Engineering - мистецтва трансформації сирих даних у потужні ознаки, які роблять ваші ML моделі блискучими!
У наступній статті ми візьмемо інсайти з цього EDA і створимо ознаки, такі як:
is_peak_hour(бінарний прапорець для обідніх/вечірніх напливів)friday_evening_boost(ознака взаємодії)temp_category(категорії холодно/помірно/тепло)- Ковзні середні та часові ознаки
Пам’ятайте: Хороший EDA → Чудові ознаки → Відмінні моделі ✨
Щасливого дослідження! 📊🔍