82 lines
6.0 KiB
Plaintext
82 lines
6.0 KiB
Plaintext
{
|
||
"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
|
||
}
|