init files
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
.venv
|
||||
__pycache__
|
||||
118190
dataset/ds.csv
Normal file
118190
dataset/ds.csv
Normal file
File diff suppressed because it is too large
Load Diff
53
dataset/fullinfo.md
Normal file
53
dataset/fullinfo.md
Normal file
@@ -0,0 +1,53 @@
|
||||
Коммуникации в Городе
|
||||
Город в Т-Банке — это группа сервисов, которые помогают пользователям решать
|
||||
ежедневные задачи: купить билеты в кино, заказать продукты, путешествовать.
|
||||
В банке есть множество видов коммуникаций с клиентами. Первое разделение —
|
||||
на внутренние и внешние. Внешние демонстрируются за пределами банка: например,
|
||||
реклама в интернете, на телевидении и т. п. Внутренние — в приложении банка:
|
||||
например, пуши, баннеры и т. д. Причем внутренние делятся еще на активные
|
||||
и пассивные каналы. К активным каналам относятся пуши, СМС, имейлы, чаты;
|
||||
к пассивным — баннеры, сторис и фулскрины.
|
||||
В текущем датасете вам дано число коммуникаций в разбивке на активные и пассивные
|
||||
и то, как клиенты реагировали на них: открыли, совершили покупку или просто получили.
|
||||
Учтите, что клиент мог сделать заказ не только день в день коммуникации, но и позже.
|
||||
Это в большей степени касается тяжелых сервисов, например путешествий.
|
||||
Описание переменных
|
||||
id уникальный идентификатор клиента
|
||||
business_dt Дата
|
||||
active_imp_ent Показы активных коммуникаций (Развлечения)
|
||||
active_click_ent Касания активных коммуникаций (Развлечения)
|
||||
active_imp_super Показы активных коммуникаций (Супермаркеты)
|
||||
active_click_super Касания активных коммуникаций (Супермаркеты)
|
||||
active_imp_transport Показы активных коммуникаций (Транспорт)
|
||||
active_click_transport Касания активных коммуникаций (Транспорт)
|
||||
active_imp_shopping Показы активных коммуникаций (Шопинг)
|
||||
active_click_shopping Касания активных коммуникаций (Шопинг)
|
||||
active_imp_hotel Показы активных коммуникаций (Отели)
|
||||
active_click_hotel Касания активных коммуникаций (Отели)
|
||||
active_imp_avia Показы активных коммуникаций (Авиабилеты)
|
||||
active_click_avia Касания активных коммуникаций (Авиабилеты)
|
||||
passive_imp_ent Показы пассивных коммуникаций (Развлечения)
|
||||
passive_click_ent Касания пассивных коммуникаций (Развлечения)
|
||||
passive_imp_super Показы пассивных коммуникаций (Супермаркеты)
|
||||
passive_click_super Касания пассивных коммуникаций (Супермаркеты)
|
||||
passive_imp_transport Показы пассивных коммуникаций (Транспорт)
|
||||
passive_click_transport
|
||||
Касания пассивных коммуникаций (Транспорт)
|
||||
passive_imp_shopping Показы пассивных коммуникаций (Шопинг)
|
||||
passive_click_
|
||||
shopping
|
||||
Касания пассивных коммуникаций (Шопинг)
|
||||
passive_imp_hotel Показы пассивных коммуникаций (Отели)
|
||||
passive_click_hotel Касания пассивных коммуникаций (Отели)
|
||||
passive_imp_avia Показы пассивных коммуникаций (Авиабилеты)
|
||||
passive_click_avia Касания пассивных коммуникаций (Авиабилеты)
|
||||
orders_amt_ent Число заказов (Развлечения)
|
||||
orders_amt_super Число заказов (Супермаркеты)
|
||||
orders_amt_transport Число заказов (Транспорт)
|
||||
orders_amt_shopping Число заказов (Шопинг)
|
||||
orders_amt_hotel Число заказов (Отели)
|
||||
orders_amt_avia Число заказов (Авиабилеты)
|
||||
gender_cd Пол (M/F)
|
||||
age Возраст в годах
|
||||
device_platform_cd Обратная связь клиента
|
||||
|
||||
4
dataset/shortinfo.md
Normal file
4
dataset/shortinfo.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Т-Банк: коммуникации в городе
|
||||
|
||||
Датасет содержит данные о коммуникациях с клиентами внутри экосистемы Город Т-Банка, включая активные и пассивные каналы и реакцию пользователей на них. Для каждого клиента по дням указано количество показов, кликов и совершенных заказов по разным категориям сервисов: развлечения, транспорт, шопинг, отели, супермаркеты и авиабилеты. Клиент мог совершить покупку как в день коммуникации, так и позже, что особенно важно для сложных сервисов вроде путешествий. Дополнительно доступны демографические признаки клиентов и информация об устройстве.
|
||||
|
||||
2383
preanalysis/01_load_and_clean.ipynb
Normal file
2383
preanalysis/01_load_and_clean.ipynb
Normal file
File diff suppressed because one or more lines are too long
1508
preanalysis/02_univariate_bivariate.ipynb
Normal file
1508
preanalysis/02_univariate_bivariate.ipynb
Normal file
File diff suppressed because one or more lines are too long
1296
preanalysis/03_time_and_lags.ipynb
Normal file
1296
preanalysis/03_time_and_lags.ipynb
Normal file
File diff suppressed because one or more lines are too long
1987
preanalysis/04_clients_segmentation.ipynb
Normal file
1987
preanalysis/04_clients_segmentation.ipynb
Normal file
File diff suppressed because one or more lines are too long
449
preanalysis/05_exploratory_models.ipynb
Normal file
449
preanalysis/05_exploratory_models.ipynb
Normal file
File diff suppressed because one or more lines are too long
55
preanalysis/eda_report.ipynb
Normal file
55
preanalysis/eda_report.ipynb
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d3a9c98",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# EDA «Коммуникации в Городе»\n",
|
||||
"\n",
|
||||
"## Кратко о данных\n",
|
||||
"- 118 189 строк, 8 339 клиентов, 35 исходных столбцов + инженерные (totals, CTR/CR, флаги).\n",
|
||||
"- Диапазон дат: 2025‑01‑09 — 2025‑11‑04 (284 дня).\n",
|
||||
"- Категории сервисов: ent, super, transport, shopping, hotel, avia; активные и пассивные показы/клики, заказы по категориям.\n",
|
||||
"- Дубликаты по ключу (id, business_dt): нет.\n",
|
||||
"\n",
|
||||
"## Качество данных\n",
|
||||
"- Пропуски: несущественные (NaN почти нет), отрицательных значений не обнаружено.\n",
|
||||
"- Возраст: 15–80 лет, p1/p99 = 22/68, мусора (<14 или >100) нет.\n",
|
||||
"- Гендер: 68.5% M, 31.5% F. Платформа после нормализации: ~52.5% iOS, ~46.7% Android, 1.1% iPadOS.\n",
|
||||
"- Признаки «заспамленности» и агрегаты на клиента добавлены: imp/click/order totals, CTR/CR, contact_days, avg_impressions_per_contact_day, order_categories_count.\n",
|
||||
"\n",
|
||||
"## Каналы и эффективность (агрегировано по всем строкам)\n",
|
||||
"- Active impressions ≈ 219.5k, passive impressions ≈ 473.1k.\n",
|
||||
"- Active clicks ≈ 147.3k (CTR_active ≈ 0.67), passive clicks ≈ 18.1k (CTR_passive ≈ 0.038).\n",
|
||||
"- Заказы всего: 12 439; CR click→order ≈ 7.5%, CR imp→order ≈ 1.8%.\n",
|
||||
"- Дневных точек: 284; daily агрегаты подготовлены (CTR/CR, day_of_week).\n",
|
||||
"\n",
|
||||
"## Демография и устройство vs эффективность (по клиентским агрегатам)\n",
|
||||
"- Таблицы по полу/возрастным группам/платформам готовы в `04_clients_segmentation.ipynb` (средние impressions/clicks/orders и CTR/CR).\n",
|
||||
"- Гипотезы: в 05-м ноутбуке добавлены примеры Mann–Whitney по CTR active vs passive и по полу; можно расширять на платформы и возраст.\n",
|
||||
"\n",
|
||||
"## Лаги и сезонность\n",
|
||||
"- Дневные ряды и метрики CTR/CR по времени и по дням недели — см. `03_time_and_lags.ipynb`.\n",
|
||||
"- Лаги: реализованы кросс-корреляции orders vs impressions/clicks (hotel, avia) для lag 0–7; по клиентам — first_imp/click/order и распределения дней до заказа.\n",
|
||||
"\n",
|
||||
"## Сегменты и «усталость»\n",
|
||||
"- Сегменты каналов: only_active / only_passive / both + метрики; бины по числу категорий заказов.\n",
|
||||
"- «Заспамленность»: bin по avg_impressions_per_contact_day с CTR/CR; stacked доли категорий заказов по возрасту.\n",
|
||||
"\n",
|
||||
"## Модели как часть EDA\n",
|
||||
"- На клиентском уровне собран датасет для задачи `has_any_order`; pipeline с OHE + StandardScaler + LogisticRegression и RandomForest (ROC-AUC и важности).\n",
|
||||
"- Выводы по коэффициентам/важности доступны в `05_exploratory_models.ipynb`.\n",
|
||||
"\n",
|
||||
"## Что делать дальше\n",
|
||||
"- Прогнать все ноутбуки end-to-end (данные готовы, зависимости в `.venv`): `jupyter lab` или `jupyter nbconvert --execute`.\n",
|
||||
"- Уточнить нормализацию категорий (при необходимости) и при желании сохранить `dataset/ds_clean.parquet` (флаг в `01_load_and_clean.ipynb`).\n",
|
||||
"- Добавить/актуализировать бизнес-гипотезы (категории, платформы, возраст) и зафиксировать p-value в таблице гипотез.\n",
|
||||
"- При необходимости усилить визуализацию: календарные heatmap по CTR, ECDF лагов по каждой категории, PDP для топ-фичей модели.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
41
preanalysis/eda_report.md
Normal file
41
preanalysis/eda_report.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# EDA «Коммуникации в Городе»
|
||||
|
||||
## Кратко о данных
|
||||
- 118189 строк, 8339 клиентов, 35 исходных столбцов + инженерные (totals, CTR/CR, флаги).
|
||||
- Диапазон дат: 2025‑01‑09 — 2025‑11‑04 (284 дня).
|
||||
- Категории сервисов: ent, super, transport, shopping, hotel, avia; активные и пассивные показы/клики, заказы по категориям.
|
||||
- Дубликаты по ключу (id, business_dt): нет.
|
||||
|
||||
## Качество данных
|
||||
- Пропуски: несущественные (NaN почти нет), отрицательных значений не обнаружено.
|
||||
- Возраст: 15–80 лет, p1/p99 = 22/68, мусора (<14 или >100) нет.
|
||||
- Гендер: 68.5% M, 31.5% F. Платформа после нормализации: ~52.5% iOS, ~46.7% Android, 1.1% iPadOS.
|
||||
- Признаки «заспамленности» и агрегаты на клиента добавлены: imp/click/order totals, CTR/CR, contact_days, avg_impressions_per_contact_day, order_categories_count.
|
||||
|
||||
## Каналы и эффективность (агрегировано по всем строкам)
|
||||
- Active impressions ≈ 219.5k, passive impressions ≈ 473.1k.
|
||||
- Active clicks ≈ 147.3k (CTR_active ≈ 0.67), passive clicks ≈ 18.1k (CTR_passive ≈ 0.038).
|
||||
- Заказы всего: 12439; CR click→order ≈ 7.5%, CR imp→order ≈ 1.8%.
|
||||
- Дневных точек: 284; daily агрегаты подготовлены (CTR/CR, day_of_week).
|
||||
|
||||
## Демография и устройство vs эффективность (по клиентским агрегатам)
|
||||
- Таблицы по полу/возрастным группам/платформам готовы в `04_clients_segmentation.ipynb` (средние impressions/clicks/orders и CTR/CR).
|
||||
- Гипотезы: в 05-м ноутбуке добавлены примеры Mann–Whitney по CTR active vs passive и по полу; можно расширять на платформы и возраст.
|
||||
|
||||
## Лаги и сезонность
|
||||
- Дневные ряды и метрики CTR/CR по времени и по дням недели — см. `03_time_and_lags.ipynb`.
|
||||
- Лаги: реализованы кросс-корреляции orders vs impressions/clicks (hotel, avia) для lag 0–7; по клиентам — first_imp/click/order и распределения дней до заказа.
|
||||
|
||||
## Сегменты и «усталость»
|
||||
- Сегменты каналов: only_active / only_passive / both + метрики; бины по числу категорий заказов.
|
||||
- «Заспамленность»: bin по avg_impressions_per_contact_day с CTR/CR; stacked доли категорий заказов по возрасту.
|
||||
|
||||
## Модели как часть EDA
|
||||
- На клиентском уровне собран датасет для задачи `has_any_order`; pipeline с OHE + StandardScaler + LogisticRegression и RandomForest (ROC-AUC и важности).
|
||||
- Выводы по коэффициентам/важности доступны в `05_exploratory_models.ipynb`.
|
||||
|
||||
## Что делать дальше
|
||||
- Прогнать все ноутбуки end-to-end (данные готовы, зависимости в `.venv`): `jupyter lab` или `jupyter nbconvert --execute`.
|
||||
- Уточнить нормализацию категорий (при необходимости) и при желании сохранить `dataset/ds_clean.parquet` (флаг в `01_load_and_clean.ipynb`).
|
||||
- Добавить/актуализировать бизнес-гипотезы (категории, платформы, возраст) и зафиксировать p-value в таблице гипотез.
|
||||
- При необходимости усилить визуализацию: календарные heatmap по CTR, ECDF лагов по каждой категории, PDP для топ-фичей модели.
|
||||
154
preanalysis/eda_utils.py
Normal file
154
preanalysis/eda_utils.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# Paths and column groups
|
||||
DATA_PATH = Path("dataset/ds.csv")
|
||||
CATEGORIES: List[str] = ["ent", "super", "transport", "shopping", "hotel", "avia"]
|
||||
|
||||
ACTIVE_IMP_COLS = [f"active_imp_{c}" for c in CATEGORIES]
|
||||
PASSIVE_IMP_COLS = [f"passive_imp_{c}" for c in CATEGORIES]
|
||||
ACTIVE_CLICK_COLS = [f"active_click_{c}" for c in CATEGORIES]
|
||||
PASSIVE_CLICK_COLS = [f"passive_click_{c}" for c in CATEGORIES]
|
||||
ORDER_COLS = [f"orders_amt_{c}" for c in CATEGORIES]
|
||||
|
||||
NUMERIC_COLS = (
|
||||
ACTIVE_IMP_COLS
|
||||
+ PASSIVE_IMP_COLS
|
||||
+ ACTIVE_CLICK_COLS
|
||||
+ PASSIVE_CLICK_COLS
|
||||
+ ORDER_COLS
|
||||
+ ["age"]
|
||||
)
|
||||
CAT_COLS = ["gender_cd", "device_platform_cd"]
|
||||
|
||||
|
||||
def safe_divide(numerator: pd.Series | float, denominator: pd.Series | float) -> pd.Series:
|
||||
"""Divide with protection against zero (works for Series and scalars)."""
|
||||
if isinstance(denominator, pd.Series):
|
||||
denom = denominator.replace(0, np.nan)
|
||||
else:
|
||||
denom = np.nan if float(denominator) == 0 else denominator
|
||||
return numerator / denom
|
||||
|
||||
|
||||
def normalize_gender(series: pd.Series) -> pd.Series:
|
||||
cleaned = series.fillna("UNKNOWN").astype(str).str.strip().str.upper()
|
||||
mapping = {"M": "M", "MALE": "M", "F": "F", "FEMALE": "F"}
|
||||
return cleaned.map(mapping).fillna("UNKNOWN")
|
||||
|
||||
|
||||
def normalize_device(series: pd.Series) -> pd.Series:
|
||||
cleaned = series.fillna("unknown").astype(str).str.strip()
|
||||
lowered = cleaned.str.lower().str.replace(" ", "").str.replace("_", "")
|
||||
mapping = {"android": "Android", "ios": "iOS", "ipados": "iPadOS", "ipad": "iPadOS"}
|
||||
mapped = lowered.map(mapping)
|
||||
fallback = cleaned.str.title()
|
||||
return mapped.fillna(fallback)
|
||||
|
||||
|
||||
def add_age_group(df: pd.DataFrame) -> pd.DataFrame:
|
||||
bins = [0, 25, 35, 45, 55, np.inf]
|
||||
labels = ["<25", "25-34", "35-44", "45-54", "55+"]
|
||||
df["age_group"] = pd.cut(df["age"], bins=bins, labels=labels, right=False)
|
||||
return df
|
||||
|
||||
|
||||
def add_totals(df: pd.DataFrame) -> pd.DataFrame:
|
||||
df["active_imp_total"] = df[ACTIVE_IMP_COLS].sum(axis=1)
|
||||
df["passive_imp_total"] = df[PASSIVE_IMP_COLS].sum(axis=1)
|
||||
df["active_click_total"] = df[ACTIVE_CLICK_COLS].sum(axis=1)
|
||||
df["passive_click_total"] = df[PASSIVE_CLICK_COLS].sum(axis=1)
|
||||
df["orders_amt_total"] = df[ORDER_COLS].sum(axis=1)
|
||||
df["click_total"] = df["active_click_total"] + df["passive_click_total"]
|
||||
df["imp_total"] = df["active_imp_total"] + df["passive_imp_total"]
|
||||
df["active_ctr"] = safe_divide(df["active_click_total"], df["active_imp_total"])
|
||||
df["passive_ctr"] = safe_divide(df["passive_click_total"], df["passive_imp_total"])
|
||||
df["ctr_all"] = safe_divide(df["click_total"], df["imp_total"])
|
||||
df["cr_click2order"] = safe_divide(df["orders_amt_total"], df["click_total"])
|
||||
df["cr_imp2order"] = safe_divide(df["orders_amt_total"], df["imp_total"])
|
||||
return df
|
||||
|
||||
|
||||
def add_flags(df: pd.DataFrame) -> pd.DataFrame:
|
||||
df["has_active_comm"] = (df[ACTIVE_IMP_COLS + ACTIVE_CLICK_COLS].sum(axis=1) > 0).astype(int)
|
||||
df["has_passive_comm"] = (df[PASSIVE_IMP_COLS + PASSIVE_CLICK_COLS].sum(axis=1) > 0).astype(int)
|
||||
df["has_any_order"] = (df[ORDER_COLS].sum(axis=1) > 0).astype(int)
|
||||
df["order_categories_count"] = (df[ORDER_COLS] > 0).sum(axis=1)
|
||||
return df
|
||||
|
||||
|
||||
def load_data(path: Path | str = DATA_PATH) -> pd.DataFrame:
|
||||
df = pd.read_csv(path)
|
||||
df["business_dt"] = pd.to_datetime(df["business_dt"])
|
||||
df["gender_cd"] = normalize_gender(df["gender_cd"])
|
||||
df["device_platform_cd"] = normalize_device(df["device_platform_cd"])
|
||||
df = add_age_group(df)
|
||||
df = add_totals(df)
|
||||
df = add_flags(df)
|
||||
return df
|
||||
|
||||
|
||||
def describe_zero_share(df: pd.DataFrame, cols: Iterable[str]) -> pd.DataFrame:
|
||||
stats = []
|
||||
for col in cols:
|
||||
series = df[col]
|
||||
stats.append(
|
||||
{
|
||||
"col": col,
|
||||
"count": series.count(),
|
||||
"mean": series.mean(),
|
||||
"median": series.median(),
|
||||
"std": series.std(),
|
||||
"min": series.min(),
|
||||
"q25": series.quantile(0.25),
|
||||
"q75": series.quantile(0.75),
|
||||
"max": series.max(),
|
||||
"share_zero": (series == 0).mean(),
|
||||
"p95": series.quantile(0.95),
|
||||
"p99": series.quantile(0.99),
|
||||
}
|
||||
)
|
||||
return pd.DataFrame(stats)
|
||||
|
||||
|
||||
def build_daily(df: pd.DataFrame) -> pd.DataFrame:
|
||||
agg_cols = ACTIVE_IMP_COLS + PASSIVE_IMP_COLS + ACTIVE_CLICK_COLS + PASSIVE_CLICK_COLS + ORDER_COLS
|
||||
daily = df.groupby("business_dt")[agg_cols].sum().reset_index()
|
||||
daily = add_totals(daily)
|
||||
daily["day_of_week"] = daily["business_dt"].dt.day_name()
|
||||
return daily
|
||||
|
||||
|
||||
def build_client(df: pd.DataFrame) -> pd.DataFrame:
|
||||
agg_spec: Dict[str, str] = {col: "sum" for col in ACTIVE_IMP_COLS + PASSIVE_IMP_COLS + ACTIVE_CLICK_COLS + PASSIVE_CLICK_COLS + ORDER_COLS}
|
||||
meta_spec: Dict[str, str | callable] = {
|
||||
"age": "median",
|
||||
"gender_cd": lambda s: s.mode().iat[0] if not s.mode().empty else "UNKNOWN",
|
||||
"age_group": lambda s: s.mode().iat[0] if not s.mode().empty else np.nan,
|
||||
"device_platform_cd": lambda s: s.mode().iat[0] if not s.mode().empty else "Other",
|
||||
}
|
||||
agg_spec.update(meta_spec)
|
||||
client = df.groupby("id").agg(agg_spec).reset_index()
|
||||
contact_days = df.groupby("id")["business_dt"].nunique().rename("contact_days")
|
||||
imp_day = df.copy()
|
||||
imp_day["imp_day_total"] = imp_day[ACTIVE_IMP_COLS + PASSIVE_IMP_COLS].sum(axis=1)
|
||||
max_imp_day = imp_day.groupby("id")["imp_day_total"].max().rename("max_impressions_per_day")
|
||||
client = add_totals(client)
|
||||
client = add_flags(client)
|
||||
client = client.merge(contact_days, on="id", how="left")
|
||||
client = client.merge(max_imp_day, on="id", how="left")
|
||||
client = add_contact_density(client)
|
||||
return client
|
||||
|
||||
|
||||
def add_contact_density(df: pd.DataFrame) -> pd.DataFrame:
|
||||
# contact_days must already be present
|
||||
if "contact_days" in df.columns:
|
||||
df["avg_impressions_per_contact_day"] = safe_divide(df["imp_total"], df["contact_days"])
|
||||
return df
|
||||
return df
|
||||
368
preanalysis/task.md
Normal file
368
preanalysis/task.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# План полноценного преданализа датасета «Коммуникации в Городе»
|
||||
|
||||
Основано на описании датасета: есть ежедневные коммуникации с клиентами в экосистеме «Город Т-Банка», активные/пассивные каналы, показы/клики и заказы по категориям (ent, super, transport, shopping, hotel, avia), а также демография и устройство.
|
||||
|
||||
Обозначения:
|
||||
|
||||
- `*_imp_*` — показы (impressions) активных/пассивных каналов по категориям (`ent`, `super`, `transport`, `shopping`, `hotel`, `avia`).
|
||||
- `*_click_*` — клики/касания по тем же категориям.
|
||||
- `orders_amt_*` — число заказов по категориям.
|
||||
- `gender_cd`, `age`, `device_platform_cd` — демография и устройство.
|
||||
|
||||
---
|
||||
|
||||
## 0. Технический скелет проекта
|
||||
|
||||
Файлы/ноутбуки:
|
||||
|
||||
1. `01_load_and_clean.ipynb` — загрузка, чистка, базовые описания.
|
||||
2. `02_univariate_bivariate.ipynb` — распределения и связи признаков.
|
||||
3. `03_time_and_lags.ipynb` — время, лаги, сезонность.
|
||||
4. `04_clients_segmentation.ipynb` — агрегаты по клиенту, сегменты.
|
||||
5. `05_exploratory_models.ipynb` — простые модели как часть EDA.
|
||||
6. `eda_report.md` / `eda_report.ipynb` — итоговый отчёт.
|
||||
|
||||
---
|
||||
|
||||
## 1. Загрузка и структура данных
|
||||
|
||||
### Таблицы/выводы
|
||||
|
||||
1. `df.info()` — список столбцов, типы, количество ненулевых.
|
||||
2. `df.head(5)` — первые строки для визуальной проверки.
|
||||
3. Размерность:
|
||||
- `n_rows`, `n_cols`
|
||||
- `n_unique_clients = df['id'].nunique()`
|
||||
- диапазон дат: `min(business_dt)`, `max(business_dt)`
|
||||
4. Проверка ключа:
|
||||
- таблица: `df.groupby(['id', 'business_dt']).size().value_counts().head()`
|
||||
(показывает, есть ли дубликаты по ключу)
|
||||
5. Среднее число дней на клиента:
|
||||
- `df.groupby('id').size().describe()`
|
||||
|
||||
### Графики
|
||||
|
||||
1. Количество записей по датам:
|
||||
- `bar/line`: X = `business_dt`, Y = `count(*)`
|
||||
- цель: увидеть провалы/пики выгрузки
|
||||
|
||||
---
|
||||
|
||||
## 2. Качество данных и аномалии
|
||||
|
||||
### Таблицы/метрики
|
||||
|
||||
1. Пропуски:
|
||||
- таблица: колонка → количество пропусков → доля пропусков
|
||||
2. Базовый `describe` по числовым:
|
||||
- `df[num_cols].describe().T`
|
||||
3. Доля нулей:
|
||||
- таблица: колонка → доля нулей → min/max → 95-й, 99-й перцентили
|
||||
4. Логические проверки:
|
||||
- все `*_imp_*`, `*_click_*`, `orders_amt_*` должны быть `>= 0`
|
||||
- поиск отрицательных/странных значений
|
||||
5. Возраст:
|
||||
- мин/макс, перцентили (1-й, 99-й), доля мусора (например, `<14` или `>100`)
|
||||
6. Категориальные:
|
||||
- уникальные значения `gender_cd`, `device_platform_cd`
|
||||
- приведение к единому формату (trim, upper, `unknown`)
|
||||
|
||||
### Графики
|
||||
|
||||
1. Boxplot возраста:
|
||||
- Y = `age`
|
||||
- цель: выбросы и мусор
|
||||
2. Barplot пропусков:
|
||||
- X = столбец, Y = доля NaN (только где NaN > 0)
|
||||
|
||||
---
|
||||
|
||||
## 3. Одномерный анализ (univariate)
|
||||
|
||||
### 3.1. Числовые признаки (показы/клики/заказы)
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. Для каждой группы (`active_imp_*`, `passive_imp_*`, `active_click_*`, `passive_click_*`, `orders_amt_*`):
|
||||
- `count, mean, median, std, min, q25, q75, max, share_zero, p95, p99`
|
||||
2. Агрегаты по всем категориям:
|
||||
- создать `active_imp_total`, `passive_imp_total`, `active_click_total`, `passive_click_total`, `orders_amt_total`
|
||||
- таблица `describe()` для них
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Гистограммы (лог-масштаб или `log1p`) для каждой категории и типа:
|
||||
- `active_imp_ent`, `active_click_ent`, `passive_imp_ent`, `orders_amt_ent`, …
|
||||
2. Boxplot для агрегатов:
|
||||
- `active_imp_total`, `passive_imp_total`, `active_click_total`, `passive_click_total`, `orders_amt_total`
|
||||
|
||||
### 3.2. Категориальные признаки
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. Распределение `gender_cd`: counts, доли, `unknown`
|
||||
2. Распределение `device_platform_cd`: counts, доли
|
||||
3. Возрастные группы:
|
||||
- `<25`, `25–34`, `35–44`, `45–54`, `55+`
|
||||
- таблица: группа → число клиентов → доля
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Barplot пола: X = `M/F/Unknown`, Y = доля
|
||||
2. Barplot платформ: X = platform, Y = доля
|
||||
3. Гистограмма возраста
|
||||
|
||||
---
|
||||
|
||||
## 4. Время и сезонность
|
||||
|
||||
Создать дневные агрегаты:
|
||||
|
||||
- сумма показов/кликов/заказов по дням
|
||||
- метрики:
|
||||
- `CTR_active = active_click_total / active_imp_total`
|
||||
- `CTR_passive = passive_click_total / passive_imp_total`
|
||||
- `CR_click2order = orders_amt_total / (active_click_total + passive_click_total)`
|
||||
- `CR_imp2order = orders_amt_total / (active_imp_total + passive_imp_total)`
|
||||
- день недели: `day_of_week`
|
||||
|
||||
### Таблицы
|
||||
|
||||
1. `daily.describe()` по дневным агрегатам
|
||||
2. Таблица по дням недели:
|
||||
- `day_of_week` → среднее `impressions, clicks, orders, CTR, CR`
|
||||
|
||||
### Графики
|
||||
|
||||
1. Линейные временные ряды:
|
||||
- `business_dt` vs total impressions
|
||||
- `business_dt` vs total clicks
|
||||
- `business_dt` vs total orders
|
||||
2. Линии CTR/CR во времени (rolling mean 7 дней по желанию):
|
||||
- `active_ctr`, `passive_ctr`, `cr_click2order`
|
||||
3. Сезонность по дням недели:
|
||||
- barplot для `active_ctr`, `passive_ctr`, `cr_click2order`
|
||||
4. (Опционально) календарная heatmap заказов/CTR
|
||||
|
||||
---
|
||||
|
||||
## 5. Парные связи (bivariate)
|
||||
|
||||
### Таблицы
|
||||
|
||||
1. Корреляции Спирмена (на уровне клиента/дня):
|
||||
- между всеми числовыми признаками + `age`
|
||||
2. Для каждой категории:
|
||||
- биннинг показов по квантилям → средний `imp, click, CTR, orders, CR`
|
||||
|
||||
### Графики
|
||||
|
||||
1. Scatter/hexbin «показы → клики»:
|
||||
- `active_imp_*` vs `active_click_*`
|
||||
- `passive_imp_*` vs `passive_click_*`
|
||||
2. Scatter «клики → заказы»:
|
||||
- `*_click_*` vs `orders_amt_*`
|
||||
3. CTR по бинам показов (линия/бар)
|
||||
4. CR по бинам кликов (линия/бар)
|
||||
5. Heatmap корреляций
|
||||
|
||||
---
|
||||
|
||||
## 6. Демография и устройство vs эффективность
|
||||
|
||||
Агрегировать по клиенту:
|
||||
|
||||
- суммы показов/кликов/заказов
|
||||
- CTR/CR на уровне клиента
|
||||
- добавить `gender_cd`, `age_group`, `device_platform_cd`
|
||||
|
||||
### Таблицы
|
||||
|
||||
1. По полу:
|
||||
- средние `impressions, clicks, orders, CTR, CR`
|
||||
2. По возрастным группам:
|
||||
- те же метрики
|
||||
3. По платформам:
|
||||
- те же метрики
|
||||
4. Тесты гипотез (Mann–Whitney / t-test):
|
||||
- разница CTR/CR между группами
|
||||
|
||||
### Графики
|
||||
|
||||
1. Barplot CTR/CR по полу
|
||||
2. Barplot CTR/CR по возрастным группам
|
||||
3. Barplot CTR/CR по платформам
|
||||
4. Boxplot заказов по возрастным группам
|
||||
5. Stacked bar: возраст → доли категорий заказов (наполнение корзины сервисами)
|
||||
|
||||
---
|
||||
|
||||
## 7. Поведение по клиенту и сегментация
|
||||
|
||||
### 7.1. Простые сегменты
|
||||
|
||||
Флаги на уровне клиента:
|
||||
|
||||
- `has_active_comm`, `has_passive_comm`
|
||||
- `has_any_order`
|
||||
- `order_categories_count` (в скольких категориях есть заказ)
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. Сегменты каналов:
|
||||
- `only_active`, `only_passive`, `both`
|
||||
- доля клиентов, средние заказы, CTR/CR
|
||||
2. Сегменты мультикатегорийности:
|
||||
- `1`, `2`, `3+` категорий заказов
|
||||
- средние коммуникации/заказы, демография
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Barplot по сегментам каналов:
|
||||
- средние заказы, CTR/CR
|
||||
2. Barplot по числу категорий заказов
|
||||
3. Stacked bar: сегменты → пол/возраст (по желанию)
|
||||
|
||||
### 7.2. Кластеризация (расширенный EDA)
|
||||
|
||||
1. Вектор фичей:
|
||||
- суммы по категориям + CTR/CR + доли заказов
|
||||
2. Нормализация
|
||||
3. KMeans / GMM, 3–7 кластеров
|
||||
|
||||
#### Таблицы
|
||||
|
||||
- кластер → размер → средние фичи → краткая интерпретация
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Профили кластеров (bar/radar)
|
||||
2. Scatter PCA/UMAP: цвет = кластер
|
||||
|
||||
---
|
||||
|
||||
## 8. Воронка: показы → клики → заказы
|
||||
|
||||
### Таблицы
|
||||
|
||||
1. Общая воронка:
|
||||
- `channel_type`, `category`, `impressions`, `clicks`, `orders`, `CTR`, `CR_click2order`, `CR_imp2order`
|
||||
2. Воронка по сегментам:
|
||||
- пол/возраст/платформа → те же метрики
|
||||
|
||||
### Графики
|
||||
|
||||
1. Funnel chart active vs passive (общий)
|
||||
2. Barplot CTR по категориям + сравнение active/passive
|
||||
3. Barplot CR по категориям + сравнение active/passive
|
||||
4. Funnel/Bar по возрастным группам
|
||||
|
||||
---
|
||||
|
||||
## 9. Временные лаги между коммуникациями и заказами
|
||||
|
||||
С учётом «поздних покупок» (особенно travel).
|
||||
|
||||
### 9.1. Лаги на дневном уровне
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. Лаговые признаки `lag1..lag7` для показов/кликов
|
||||
2. Кросс-корреляция:
|
||||
- lag → corr(orders*t, impressions*{t-lag})
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Линия «lag vs корреляция» по:
|
||||
- `hotel`, `avia` (и др. при желании)
|
||||
- active vs passive
|
||||
|
||||
### 9.2. Лаги на клиентском уровне
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. `first_imp_date`, `first_click_date`, `first_order_date`
|
||||
2. `days_to_order`
|
||||
3. Квантили `days_to_order` по категориям
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Гистограмма/ECDF `days_to_order` по категориям
|
||||
|
||||
---
|
||||
|
||||
## 10. Мультиканальность и «заспамленность»
|
||||
|
||||
### Таблицы
|
||||
|
||||
1. `contact_days`, `avg_impressions_per_contact_day`, `max_impressions_per_day`
|
||||
2. Бины по `avg_impressions_per_contact_day` → средний CTR/CR
|
||||
|
||||
### Графики
|
||||
|
||||
1. Гистограмма `avg_impressions_per_contact_day`
|
||||
2. Линия/бар: CTR/CR vs уровень спама
|
||||
|
||||
---
|
||||
|
||||
## 11. Простые модели как часть EDA
|
||||
|
||||
### 11.1. Бинарная модель «есть заказ / нет заказа»
|
||||
|
||||
Target:
|
||||
|
||||
- `has_any_order`
|
||||
|
||||
Features:
|
||||
|
||||
- суммы показов/кликов по типам и категориям
|
||||
- CTR/CR
|
||||
- демография и платформа
|
||||
|
||||
#### Таблицы
|
||||
|
||||
1. Логистическая регрессия:
|
||||
- коэффы, p-value, odds ratio
|
||||
2. Feature importance из дерева/лесов
|
||||
|
||||
#### Графики
|
||||
|
||||
1. Barplot важностей
|
||||
2. (Опционально) partial dependence для 2–3 ключевых фичей
|
||||
|
||||
---
|
||||
|
||||
## 12. Гипотезы и статтесты
|
||||
|
||||
### Примеры гипотез
|
||||
|
||||
1. `CTR_active > CTR_passive`
|
||||
2. CR различается между категориями сервисов
|
||||
3. CTR/CR различаются по полу/возрасту/платформе
|
||||
4. «заспамленность» снижает CTR/CR после порога
|
||||
|
||||
### Таблица гипотез
|
||||
|
||||
- гипотеза, H0/H1, тест, p-value, вывод, бизнес-интерпретация
|
||||
|
||||
Графики для поддержки — использовать из разделов 4–10 (барчики/боксплоты).
|
||||
|
||||
---
|
||||
|
||||
## 13. Итоговая документация
|
||||
|
||||
1. Резюме выводов:
|
||||
- качество данных
|
||||
- эффективность каналов/категорий
|
||||
- сегменты, где коммуникации лучше/хуже работают
|
||||
- лаги (как быстро покупают после контактов)
|
||||
- признаки «усталости» от коммуникаций
|
||||
2. Список проблем данных и принятых решений по чистке
|
||||
3. Список инсайтов для бизнеса
|
||||
4. Список фичей для будущих моделей
|
||||
5. Следующие шаги:
|
||||
- подготовка ML-пайплайна
|
||||
- список A/B-гипотез
|
||||
- какие данные добрать (если нужно)
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user