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
| SQL | Pandas |
|---|---|
SELECT * FROM users LIMIT 5 | df.head() |
SELECT name, age FROM users | df[['name', 'age']] |
WHERE age > 30 | df[df.age > 30] или df.query('age > 30') |
GROUP BY country | df.groupby('country') |
JOIN ... ON | df.merge(other, on='id') |
ORDER BY date DESC | df.sort_values('date', ascending=False) |
COUNT, SUM, AVG | df.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 docs — pandas.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 tutor — pandastutor.com — визуальный debug
🏋️ Упражнения
🟢 Easy
- Прочитайте CSV-файл (например, Titanic dataset), посмотрите первые 10 строк и выведите
info(),describe(). - Отфильтруйте по одному столбцу (
age > 18). - Создайте новый столбец (
bmi = weight / height **2).
🟡 Medium
- На Titanic выведите сравнение
SexиPclassпоSurvived(pivot table). - Сравните стратегии для missing values:
fillna(mean)vsfillna(median)vsdropna()— сравните статистику по каждому. - Time series: сгенерируйте искусственные данные о продажах за 1 год и найдите/нарисуйте недельные тренды.
🔴 Hard
- Django/FastAPI endpoint:
/api/analytics/cohort/— разделите пользователей на когорты по месяцу регистрации и вернитеretentionкаждой когорты за следующие 6 месяцев в виде heatmap data. Используйте Pandaspivot_tableиgroupby. - 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.