Files
dano2025/alternative/contact_frequency_orders/analysis.ipynb
2025-12-12 22:24:37 +03:00

82 lines
6.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Частота контактов и заказы\n\n**Вопрос:** влияет ли среднее число кликов на контактный день на вероятность заказа?\n\n**Гипотеза:** клиенты, которые кликают чаще каждого контактного дня, чаще совершают заказ (позитивная зависимость), даже при контроле общего объёма показов."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sqlite3\nfrom pathlib import Path\nimport sys\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, OneHotEncoder\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import roc_auc_score\n\nsns.set_theme(style=\"whitegrid\")\nplt.rcParams[\"figure.figsize\"] = (10, 5)\n\nproject_root = Path.cwd().resolve()\nwhile not (project_root / \"preanalysis\").exists() and project_root.parent != project_root:\n project_root = project_root.parent\nsys.path.append(str(project_root / \"preanalysis\"))\nimport eda_utils as eda\n\ndb_path = project_root / \"dataset\" / \"ds.sqlite\"\nconn = sqlite3.connect(db_path)\ndf = pd.read_sql_query(\"select * from communications\", conn, parse_dates=[\"business_dt\"])\nconn.close()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for cols, name in [\n (eda.ACTIVE_IMP_COLS, \"active_imp_total\"),\n (eda.PASSIVE_IMP_COLS, \"passive_imp_total\"),\n (eda.ACTIVE_CLICK_COLS, \"active_click_total\"),\n (eda.PASSIVE_CLICK_COLS, \"passive_click_total\"),\n (eda.ORDER_COLS, \"orders_amt_total\"),\n]:\n df[name] = df[cols].sum(axis=1)\n\ndf[\"imp_total\"] = df[\"active_imp_total\"] + df[\"passive_imp_total\"]\ndf[\"click_total\"] = df[\"active_click_total\"] + df[\"passive_click_total\"]\n\ncontact_days = df.groupby(\"id\")[\"business_dt\"].nunique().rename(\"contact_days\")\nclient = df.groupby(\"id\").agg(\n {\n \"imp_total\": \"sum\",\n \"click_total\": \"sum\",\n \"orders_amt_total\": \"sum\",\n \"age\": \"median\",\n \"gender_cd\": lambda s: s.mode().iat[0],\n \"device_platform_cd\": lambda s: s.mode().iat[0],\n }\n).reset_index().merge(contact_days, on=\"id\", how=\"left\")\n\nclient[\"clicks_per_day\"] = eda.safe_divide(client[\"click_total\"], client[\"contact_days\"])\nclient[\"has_order\"] = (client[\"orders_amt_total\"] > 0).astype(int)\nclient.head()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Визуализация: заказы vs клики на контактный день"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"bins = pd.qcut(client[\"clicks_per_day\"], 8, duplicates=\"drop\")\norder_rate = client.groupby(bins)[\"has_order\"].mean().reset_index()\norder_rate[\"clicks_per_day\"] = order_rate[\"clicks_per_day\"].astype(str)\nplt.figure(figsize=(12, 4))\nsns.lineplot(data=order_rate, x=\"clicks_per_day\", y=\"has_order\", marker=\"o\")\nplt.xticks(rotation=40)\nplt.title(\"Доля клиентов с заказом vs клики на контактный день\")\nplt.tight_layout()\nplt.show()\norder_rate\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## ML-модель: клики/день → заказ\nTarget: `has_order`. Фичи: клики/день, объём показов, возраст, пол, платформа."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X = client[[\"clicks_per_day\", \"imp_total\", \"age\", \"gender_cd\", \"device_platform_cd\"]]\ny = client[\"has_order\"]\nX = X.copy()\nX[\"gender_cd\"] = eda.normalize_gender(X[\"gender_cd\"])\nX[\"device_platform_cd\"] = eda.normalize_device(X[\"device_platform_cd\"])\n\nnumeric_cols = [\"clicks_per_day\", \"imp_total\", \"age\"]\ncat_cols = [\"gender_cd\", \"device_platform_cd\"]\n\npre = ColumnTransformer(\n [\n (\"num\", Pipeline([(\"scaler\", StandardScaler())]), numeric_cols),\n (\"cat\", OneHotEncoder(handle_unknown=\"ignore\"), cat_cols),\n ]\n)\n\nmodel = Pipeline([(\"pre\", pre), (\"clf\", LogisticRegression(max_iter=1000))])\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)\nmodel.fit(X_train, y_train)\nproba = model.predict_proba(X_test)[:, 1]\nauc = roc_auc_score(y_test, proba)\ncoef = model.named_steps[\"clf\"].coef_[0]\nfeatures = model.named_steps[\"pre\"].get_feature_names_out()\ncoef_series = pd.Series(coef, index=features).sort_values(key=abs, ascending=False)\nauc, coef_series.head(10)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Вывод по гипотезе\n- Доля клиентов с заказом растёт с увеличением кликов на контактный день.\n- В модели `clicks_per_day` — топовый позитивный фактор, AUC ~0.69: клики/день значимо предсказывают заказ при контроле объёма показов и демографии.\n- Гипотеза подтверждается: частота кликов на контактный день прямо связана с вероятностью заказа."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}