Зачем аналитику pandas, если есть SQL
SQL — для получения данных из базы. Pandas — для трансформации, анализа и визуализации после выгрузки. В реальной работе цепочка: SQL-запрос → DataFrame в pandas → анализ → графики → выводы. Без pandas вы застреваете на первом шаге.
Загрузка данных
Первый шаг — загрузить данные в DataFrame. Три основных источника: CSV-файл, SQL-запрос к базе, Excel.
import pandas as pd # Из CSV-файла orders = pd.read_csv('orders.csv', parse_dates=['order_date']) users = pd.read_csv('users.csv', parse_dates=['registration_date']) # Первый взгляд на данные orders.head() # первые 5 строк orders.shape # (строки, столбцы) orders.dtypes # типы данных orders.describe() # статистика по числовым orders.isna().sum() # пропуски по столбцам
import pandas as pd from sqlalchemy import create_engine engine = create_engine('postgresql://user:pass@host:5432/db') # Весь запрос — в pandas orders = pd.read_sql(''' SELECT * FROM orders WHERE order_date >= '2025-01-01' ''', engine, parse_dates=['order_date'])
Фильтрация и выбор данных
В SQL — WHERE. В pandas — булева индексация. Выглядит иначе, но логика та же: задаёте условие, получаете подмножество строк.
SELECT * FROM orders WHERE status = 'completed' AND amount > 1000;
mask = ( (orders['status'] == 'completed') & (orders['amount'] > 1000) ) result = orders[mask]
# По значениям из списка (аналог IN) ios_android = users[users['platform'].isin(['ios', 'android'])] # По дате (аналог WHERE date BETWEEN) jan = orders[orders['order_date'].between('2025-01-01', '2025-01-31')] # Исключить NULL (аналог WHERE col IS NOT NULL) clean = orders[orders['amount'].notna()] # По подстроке (аналог LIKE '%promo%') promo = orders[orders['channel'].str.contains('promo', na=False)] # Выбор столбцов (аналог SELECT col1, col2) subset = orders[['user_id', 'amount', 'order_date']]
mask = ( (orders['status'] == 'completed') & (orders['amount'] > 500) & (orders['order_date'] >= '2025-03-01') & (orders['order_date'] < '2025-04-01') ) result = orders.loc[mask, ['user_id', 'order_date', 'amount']]
Группировка и агрегация
groupby в pandas — это GROUP BY в SQL. Группируете по одному или нескольким столбцам, потом применяете агрегатную функцию: sum, mean, count, nunique.
SELECT DATE_TRUNC('month', order_date) AS m, COUNT(*) AS orders, SUM(amount) AS revenue FROM orders GROUP BY 1;
monthly = (orders .groupby(orders['order_date'].dt.to_period('M')) .agg( orders=('order_id', 'count'), revenue=('amount', 'sum'), ) )
# Несколько агрегатов с именованием (pandas >= 0.25) stats = (orders .groupby('platform') .agg( total_revenue = ('amount', 'sum'), avg_check = ('amount', 'mean'), unique_buyers = ('user_id', 'nunique'), order_count = ('order_id', 'count'), ) .round(0) ) # Группировка по нескольким колонкам by_month_platform = (orders .assign(month=orders['order_date'].dt.to_period('M')) .groupby(['month', 'platform']) .agg(revenue=('amount', 'sum')) .reset_index() ) # Добавить новую колонку (аналог оконной функции) orders['user_total'] = orders.groupby('user_id')['amount'].transform('sum')
df = orders[orders['status'] == 'completed'].merge(users, on='user_id') top5 = (df .groupby('acquisition_channel') .agg(revenue=('amount', 'sum')) .sort_values('revenue', ascending=False) .head(5) )
Merge: объединение таблиц
merge в pandas — это JOIN в SQL. Поддерживает inner, left, right, outer. По умолчанию — inner.
SELECT o.*, u.platform FROM orders o LEFT JOIN users u ON o.user_id = u.user_id;
df = orders.merge( users[['user_id', 'platform']], on='user_id', how='left' )
# inner (по умолчанию) — только совпадения df = orders.merge(users, on='user_id') # left — все строки из левой + совпадения из правой df = orders.merge(users, on='user_id', how='left') # Разные имена колонок df = orders.merge(users, left_on='buyer_id', right_on='user_id') # Проверка: не «раздулся» ли DataFrame после merge print(len(orders), '→', len(df)) # должно быть ≈ одинаково
df = orders.merge(users, on='user_id') # Только первые 6 месяцев df['months_since'] = ( (df['order_date'] - df['registration_date']).dt.days / 30 ) df_6m = df[(df['months_since'] >= 0) & (df['months_since'] < 6)] ltv = (df_6m .groupby('acquisition_channel') .agg( total_rev = ('amount', 'sum'), users = ('user_id', 'nunique'), ) ) ltv['ltv_6m'] = (ltv['total_rev'] / ltv['users']).round(0)
Pivot: таблицы для отчётов и когорт
pivot_table — один из самых мощных инструментов pandas. Строит сводные таблицы: строки = одно измерение, столбцы = другое, ячейки = агрегат. Идеален для когортных таблиц, кросс-табуляции и отчётов.
# Шаг 1: определяем когорту и период df = events.merge(users[['user_id', 'registration_date']], on='user_id') df['cohort'] = df['registration_date'].dt.to_period('M') df['active_month'] = df['event_time'].dt.to_period('M') df['period'] = (df['active_month'] - df['cohort']).apply(lambda x: x.n) # Шаг 2: считаем уникальных юзеров по когорте × периоду cohort_data = (df .groupby(['cohort', 'period']) .agg(users=('user_id', 'nunique')) .reset_index() ) # Шаг 3: pivot — строки = когорты, столбцы = периоды cohort_table = cohort_data.pivot_table( index='cohort', columns='period', values='users', ) # Шаг 4: делим на размер когорты → retention в процентах cohort_sizes = cohort_table[0] # период 0 = вся когорта retention = cohort_table.div(cohort_sizes, axis=0).round(3) * 100
orders['month'] = orders['order_date'].dt.to_period('M') revenue_pivot = orders.pivot_table( index='month', columns='platform', values='amount', aggfunc='sum', fill_value=0, )
df = orders[orders['status']=='completed'].merge(users, on='user_id') df['cohort'] = df['registration_date'].dt.to_period('M') df['period'] = ((df['order_date']-df['registration_date']).dt.days // 30) # ARPU по когорте × периоду rev = (df.groupby(['cohort','period']) .agg(revenue=('amount','sum')) .reset_index() ) sizes = users.groupby(users['registration_date'].dt.to_period('M')).size() rev['arpu'] = rev.apply( lambda r: r['revenue'] / sizes.get(r['cohort'], 1), axis=1 ) # Pivot + cumsum по строке = кумулятивный LTV ltv_pivot = (rev.pivot_table( index='cohort', columns='period', values='arpu') .cumsum(axis=1) .round(0) )
Создание новых колонок и цепочки
# Новая колонка через assign (chainable) df = (orders .assign( month = lambda x: x['order_date'].dt.to_period('M'), is_big = lambda x: x['amount'] > 5000, week_day = lambda x: x['order_date'].dt.day_name(), ) ) # Бины (аналог CASE WHEN в SQL) orders['amount_bucket'] = pd.cut( orders['amount'], bins=[0, 500, 2000, 10000, float('inf')], labels=['0-500', '500-2K', '2K-10K', '10K+'], ) # Процент от общего (аналог SUM OVER()) stats = orders.groupby('platform')['amount'].sum() stats_pct = (stats / stats.sum() * 100).round(1) # Быстрая визуализация monthly_rev = orders.groupby(orders['order_date'].dt.to_period('M'))['amount'].sum() monthly_rev.plot(kind='bar', title='Revenue by month')
Шпаргалка: SQL → pandas
| SQL | pandas | Пример |
|---|---|---|
| SELECT col | df[['col']] | orders[['user_id', 'amount']] |
| WHERE col = 'x' | df[df['col'] == 'x'] | orders[orders['status'] == 'completed'] |
| WHERE col IN (...) | df[df['col'].isin([...])] | users[users['platform'].isin(['ios','android'])] |
| ORDER BY col DESC | df.sort_values('col', ascending=False) | orders.sort_values('amount', ascending=False) |
| LIMIT 10 | df.head(10) | orders.head(10) |
| GROUP BY + SUM | df.groupby('col')['val'].sum() | orders.groupby('platform')['amount'].sum() |
| COUNT(DISTINCT col) | df['col'].nunique() | orders['user_id'].nunique() |
| LEFT JOIN | df.merge(df2, on='key', how='left') | orders.merge(users, on='user_id', how='left') |
| DATE_TRUNC('month') | df['date'].dt.to_period('M') | orders['order_date'].dt.to_period('M') |
| CASE WHEN | pd.cut() / np.where() | pd.cut(df['amount'], bins=[0,500,5000,inf]) |
| SUM() OVER(PARTITION BY) | df.groupby('col')['val'].transform('sum') | orders.groupby('user_id')['amount'].transform('sum') |
| Pivot (CASE WHEN) | df.pivot_table(...) | df.pivot_table(index='month', columns='platform') |
Связанные материалы
Главное про pandas для аналитика
Вам не нужно знать весь pandas. Для 90% задач продуктового аналитика достаточно: read_csv/read_sql → фильтрация через булеву маску → groupby + agg → merge → pivot_table. Пять инструментов — и вы закрываете когорты, воронки, LTV, сегментацию.
Ключевой совет: если вы знаете SQL, думайте «как бы я написал это в SQL?» — и ищите pandas-эквивалент в шпаргалке. Со временем pandas-способ станет привычным и часто — более удобным для трансформаций.
Следующий шаг: скачайте любой e-commerce датасет (Olist на Kaggle — отлично подходит) и решите все 4 задачи из этой статьи на реальных данных. Это займёт 30 минут — и после этого вы будете уверены в pandas.