Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pandas

🎯 Цель

После прочтения этой главы:

  • Научитесь использовать DataFrame и Series как “in-memory SQL table”
  • Сможете работать с реальными CSV/JSON/Parquet файлами
  • Сможете управлять missing data, duplicates, type conversion
  • Будете писать сложные запросы с groupby, pivot_table, merge
  • Сможете работать с time series данными

Что нужно изучить

  • Структура Series и DataFrame
  • I/O: read_csv, read_json, read_parquet, read_sql, варианты to_*
  • Indexing: .loc[], .iloc[], boolean indexing, query()
  • Missing data: isna(), fillna(), dropna()
  • Aggregation: groupby, agg, transform, apply
  • Joining: merge, concat, join
  • Reshaping: pivot_table, melt, stack, unstack
  • Time series: pd.to_datetime, resample, rolling windows
  • Categorical data, ordering, ranking

Библиотеки

pip install pandas pyarrow openpyxl
  • pandas — основное
  • pyarrow — более быстрый engine для parquet файлов
  • openpyxl — работа с Excel файлами

Важные темы

Ментальная модель для backend dev

SQLPandas
SELECT * FROM users LIMIT 5df.head()
SELECT name, age FROM usersdf[['name', 'age']]
WHERE age > 30df[df.age > 30] или df.query('age > 30')
GROUP BY countrydf.groupby('country')
JOIN ... ONdf.merge(other, on='id')
ORDER BY date DESCdf.sort_values('date', ascending=False)
COUNT, SUM, AVGdf.agg(['count', 'sum', 'mean'])

.loc vs .iloc

  • .loc[]label-based(по имени индекса или имени столбца)
  • .iloc[]integer position(по номеру строки/столбца)
df.loc[5, 'name']      # строка с label 5, столбец 'name'
df.iloc[5, 0]          # строка 5, столбец 0 (как Python list)

Проблема inplace

В старых Pandas паттерн df.fillna(0, inplace=True) был широко распространён. В новой версии(2.0+) он deprecated. Вместо этого:

df = df.fillna(0)              # правильно
# или используйте copy-on-write mode
pd.set_option('mode.copy_on_write', True)

Method chaining

В ML трансформации обычно записываются цепочкой:

result = (
    df
    .dropna(subset=['price'])
    .query('price > 0')
    .assign(price_log=lambda x: np.log(x.price))
    .groupby('category')
    .agg(avg_price=('price', 'mean'), count=('id', 'count'))
    .sort_values('avg_price', ascending=False)
)

Это — использование pipe, assign, transform — “best practice” в ML data preparation.

Примеры кода

Создание DataFrame и основные операции

import pandas as pd
import numpy as np

# Из dict
df = pd.DataFrame({
    "name": ["Ali", "Vali", "Salim", "Karim"],
    "age": [25, 30, 35, 28],
    "city": ["Tashkent", "Samarkand", "Bukhara", "Tashkent"],
    "salary": [1000, 1500, 2000, 1200],
})

# Из CSV
# df = pd.read_csv("users.csv")

# Базовый просмотр
print(df.head())          # первые 5 строк
print(df.info())          # shape, dtype, memory
print(df.describe())      # статистическая сводка
print(df.shape)           # (4, 4)

Filtering и groupby

# Filtering
adults = df[df.age >= 30]
tashkent_users = df.query("city == 'Tashkent' and salary > 1000")

# Groupby aggregation
by_city = df.groupby("city").agg(
    avg_salary=("salary", "mean"),
    max_age=("age", "max"),
    count=("name", "count"),
).reset_index()
print(by_city)

# Multiple aggregation
stats = df.groupby("city")["salary"].agg(["mean", "std", "min", "max"])

Missing data и очистка данных

# Искусственный missing data
df.loc[0, "salary"] = np.nan
df.loc[2, "city"] = None

# Обнаружение
print(df.isna().sum())            # количество NaN в каждом столбце

# Стратегии заполнения
df["salary"] = df["salary"].fillna(df["salary"].median())
df["city"] = df["city"].fillna("Unknown")

# Или удалить
df_clean = df.dropna()

Merge и join

orders = pd.DataFrame({
    "order_id": [1, 2, 3, 4],
    "user_name": ["Ali", "Vali", "Ali", "Karim"],
    "amount": [100, 200, 150, 75],
})

# INNER JOIN (default)
merged = df.merge(orders, left_on="name", right_on="user_name")

# LEFT JOIN
all_users = df.merge(orders, left_on="name", right_on="user_name", how="left")

# Пользователи и их общая сумма заказов
user_totals = (
    df.merge(orders, left_on="name", right_on="user_name", how="left")
      .groupby("name")["amount"].sum()
      .fillna(0)
      .reset_index()
)

Time series

# Случайные daily sales data
dates = pd.date_range("2024-01-01", periods=365, freq="D")
sales = pd.DataFrame({
    "date": dates,
    "sales": np.random.poisson(100, size=365) + np.sin(np.arange(365) / 30) * 20,
})

sales = sales.set_index("date")

# Недельная агрегация
weekly = sales.resample("W").sum()

# Rolling mean за 30 дней
sales["rolling_30"] = sales["sales"].rolling(window=30).mean()

# Извлечение year, month, weekday
sales["month"] = sales.index.month
sales["weekday"] = sales.index.day_name()

Интеграция с backend

1. Django ORM → Pandas

from django.db.models import Sum
import pandas as pd

# Django QuerySet → DataFrame
qs = Order.objects.values('user_id', 'amount', 'created_at')
df = pd.DataFrame(list(qs))

# Или сразу SQL
df = pd.read_sql("SELECT * FROM orders WHERE created_at >= NOW() - INTERVAL '30 days'",
                 connection)

2. Endpoint экспорта CSV в FastAPI

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import pandas as pd
import io

app = FastAPI()

@app.get("/reports/orders.csv")
async def export_orders():
    df = pd.read_sql("SELECT * FROM orders", engine)
    # Обогащение: добавление нового столбца
    df["revenue_per_item"] = df["total"] / df["quantity"]
    
    stream = io.StringIO()
    df.to_csv(stream, index=False)
    
    return StreamingResponse(
        iter([stream.getvalue()]),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=orders.csv"},
    )

3. Background job — daily report

# Celery task
@app.task
def generate_daily_report():
    df = pd.read_sql("SELECT * FROM events WHERE date = CURRENT_DATE - 1", engine)
    
    report = (
        df.groupby("country")
          .agg(users=("user_id", "nunique"),
               revenue=("amount", "sum"),
               avg_session=("duration_sec", "mean"))
          .sort_values("revenue", ascending=False)
    )
    
    report.to_excel(f"/reports/daily_{date.today()}.xlsx")
    # Отправка email через Celery beat

Ресурсы

  • Official Pandas docspandas.pydata.org/docs/user_guide/
  • “Python for Data Analysis” — Wes McKinney (создатель Pandas, 3-е издание) — MUST READ
  • “Modern Pandas” — Tom Augspurger (серия в блоге) — best practices
  • Kaggle Learn — Pandas — бесплатный мини-курс
  • DataCamp / pandas tutorpandastutor.com — визуальный debug

🏋️ Упражнения

🟢 Easy

  1. Прочитайте CSV-файл (например, Titanic dataset), посмотрите первые 10 строк и выведите info(), describe().
  2. Отфильтруйте по одному столбцу (age > 18).
  3. Создайте новый столбец (bmi = weight / height **2).

🟡 Medium

  1. На Titanic выведите сравнение Sex и Pclass по Survived (pivot table).
  2. Сравните стратегии для missing values: fillna(mean) vs fillna(median) vs dropna() — сравните статистику по каждому.
  3. Time series: сгенерируйте искусственные данные о продажах за 1 год и найдите/нарисуйте недельные тренды.

🔴 Hard

  1. Django/FastAPI endpoint: /api/analytics/cohort/ — разделите пользователей на когорты по месяцу регистрации и верните retention каждой когорты за следующие 6 месяцев в виде heatmap data. Используйте Pandas pivot_table и groupby.
  2. Streaming CSV: прочитайте файл CSV размером 1 ГБ chunks-ами так, чтобы он не помещался в память (chunksize), агрегируйте каждый chunk, верните итоговый результат.

Capstone

notebooks/month-01/02_pandas_practice.ipynb:

  • Загрузите e-commerce dataset (Olist Brazilian e-commerce Kaggle)
  • Сделайте merge между 5 таблицами
  • Для каждой категории продукта:
  • Средняя цена
  • Количество заказов
  • Среднее время доставки (в днях)
  • Рейтинг удовлетворённости клиента (mean review_score)
  • Сделайте ranking топ-10 категорий по выручке

✅ Чек-лист

  • Знаю разницу между DataFrame и Series
  • Понимаю разницу между .loc и .iloc, использую каждое к месту
  • Освоил паттерн groupby + agg
  • Знаю минимум 3 стратегии для missing data
  • Знаю параметры how у merge (inner, left, right, outer)
  • Использую resample и rolling в time series
  • Пишу читаемый код через method chaining
  • Преобразую SQL-результат из Django/FastAPI в DataFrame

Переходим к Matplotlib и Seaborn.