{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":4120,"databundleVersionId":44256,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:42:58.876094Z","iopub.execute_input":"2025-11-19T04:42:58.876387Z","iopub.status.idle":"2025-11-19T04:43:00.464502Z","shell.execute_reply.started":"2025-11-19T04:42:58.876356Z","shell.execute_reply":"2025-11-19T04:43:00.463731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport random\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.preprocessing import OrdinalEncoder\nfrom sklearn.metrics import log_loss, roc_auc_score\n\nimport xgboost as xgb","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:43:00.465906Z","iopub.execute_input":"2025-11-19T04:43:00.466217Z","iopub.status.idle":"2025-11-19T04:43:02.205590Z","shell.execute_reply.started":"2025-11-19T04:43:00.466197Z","shell.execute_reply":"2025-11-19T04:43:02.204753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/avazu-ctr-prediction\"\nTRAIN_PATH = os.path.join(DATA_DIR, \"train.gz\")\nTEST_PATH = os.path.join(DATA_DIR, \"test.gz\")\nSUB_PATH = os.path.join(DATA_DIR, \"sampleSubmission.gz\")\n\nRANDOM_STATE = 42\nSAMPLE_FRAC = 0.05    # 5% sample from the full 40M rows (~2M rows)\nCHUNKSIZE = 1_000_000 # number of rows per chunk when reading\n\nsns.set(style=\"whitegrid\")\nplt.rcParams[\"figure.figsize\"] = (10, 4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:43:02.206403Z","iopub.execute_input":"2025-11-19T04:43:02.206822Z","iopub.status.idle":"2025-11-19T04:43:02.212537Z","shell.execute_reply.started":"2025-11-19T04:43:02.206796Z","shell.execute_reply":"2025-11-19T04:43:02.211800Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_everything(seed: int = 42):\n    random.seed(seed)\n    np.random.seed(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n\nseed_everything(RANDOM_STATE)\n\nprint(\"Using XGBoost version:\", xgb.__version__)\nprint(\"Train path:\", TRAIN_PATH)\nprint(\"Test path:\", TEST_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:43:20.267767Z","iopub.execute_input":"2025-11-19T04:43:20.268458Z","iopub.status.idle":"2025-11-19T04:43:20.273256Z","shell.execute_reply.started":"2025-11-19T04:43:20.268431Z","shell.execute_reply":"2025-11-19T04:43:20.272482Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1. Load a sampled training set (chunked to handle 40M+ rows)","metadata":{}},{"cell_type":"code","source":"sampled_chunks = []\ntotal_rows = 0\nsampled_rows = 0\n\nfor i, chunk in enumerate(pd.read_csv(TRAIN_PATH,\n                                      compression=\"gzip\",\n                                      chunksize=CHUNKSIZE)):\n    total_rows += len(chunk)\n    # Sample a fixed fraction from each chunk for a balanced sample\n    chunk_sample = chunk.sample(frac=SAMPLE_FRAC,\n                                random_state=RANDOM_STATE)\n    sampled_chunks.append(chunk_sample)\n    sampled_rows += len(chunk_sample)\n\n    print(f\"Processed chunk {i + 1:02d}, \"\n          f\"chunk rows = {len(chunk):,}, \"\n          f\"sampled rows so far = {sampled_rows:,}\")\n\ntrain_df = pd.concat(sampled_chunks, ignore_index=True)\ndel sampled_chunks, chunk\ngc.collect()\n\nprint(\"\\nFull train (sampled) shape:\", train_df.shape)\nprint(train_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:46:13.162572Z","iopub.execute_input":"2025-11-19T04:46:13.163313Z","iopub.status.idle":"2025-11-19T04:48:40.629840Z","shell.execute_reply.started":"2025-11-19T04:46:13.163286Z","shell.execute_reply":"2025-11-19T04:48:40.629099Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. Add time-based features from \"hour\" column hour is YYMMDDHH (e.g., 14091123 = 2014-09-11 23:00)","metadata":{}},{"cell_type":"code","source":"def add_time_features(df: pd.DataFrame) -> pd.DataFrame:\n    # Make sure hour is a zero-padded 8-char string\n    hour_str = df[\"hour\"].astype(str).str.zfill(8)\n\n    # Build full datetime string: \"20YY-MM-DD HH:00:00\"\n    dt_str = (\n        \"20\"\n        + hour_str.str[0:2] + \"-\"\n        + hour_str.str[2:4] + \"-\"\n        + hour_str.str[4:6] + \" \"\n        + hour_str.str[6:8] + \":00:00\"\n    )\n    dt = pd.to_datetime(dt_str, format=\"%Y-%m-%d %H:%M:%S\")\n\n    df[\"day_of_week\"] = dt.dt.dayofweek.astype(\"int8\")  # 0=Mon, 6=Sun\n    df[\"hour_of_day\"] = dt.dt.hour.astype(\"int8\")       # 0..23\n    # Use date as an integer (YYYYMMDD) to build time-based splits later\n    df[\"date_int\"] = (dt.dt.year * 10000 +\n                      dt.dt.month * 100 +\n                      dt.dt.day).astype(\"int32\")\n\n    # We won't keep raw hour anymore\n    df = df.drop(columns=[\"hour\"])\n    return df\n\ntrain_df = add_time_features(train_df)\nprint(train_df[[\"click\", \"day_of_week\", \"hour_of_day\", \"date_int\"]].head())\nprint(\"Date range in sample:\", train_df[\"date_int\"].min(), \"->\", train_df[\"date_int\"].max())\n\n# Global CTR in sampled train\nglobal_ctr = train_df[\"click\"].mean()\nprint(f\"Global CTR in sample: {global_ctr:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:51:35.443494Z","iopub.execute_input":"2025-11-19T04:51:35.444268Z","iopub.status.idle":"2025-11-19T04:51:41.265817Z","shell.execute_reply.started":"2025-11-19T04:51:35.444240Z","shell.execute_reply":"2025-11-19T04:51:41.265163Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. EDA on a subset","metadata":{}},{"cell_type":"code","source":"EDA_N = 500_000\nif len(train_df) > EDA_N:\n    eda_df = train_df.sample(n=EDA_N, random_state=RANDOM_STATE)\nelse:\n    eda_df = train_df.copy()\n\nprint(\"EDA sample shape:\", eda_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:52:15.748763Z","iopub.execute_input":"2025-11-19T04:52:15.749363Z","iopub.status.idle":"2025-11-19T04:52:16.511817Z","shell.execute_reply.started":"2025-11-19T04:52:15.749341Z","shell.execute_reply":"2025-11-19T04:52:16.511181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== Global CTR on EDA sample ===\")\nglobal_ctr_eda = eda_df[\"click\"].mean()\nprint(f\"Global CTR (click=1 ratio): {global_ctr_eda:.4f}\")\n\nclass_counts = eda_df[\"click\"].value_counts().sort_index()\nclass_ratios = eda_df[\"click\"].value_counts(normalize=True).sort_index()\n\nsummary_cls = pd.DataFrame({\n    \"count\": class_counts,\n    \"ratio\": class_ratios\n})\nsummary_cls.index = [\"no_click (0)\", \"click (1)\"]\nprint(summary_cls)\n\n# Plot class imbalance\nfig, ax = plt.subplots()\nsns.barplot(x=summary_cls.index, y=summary_cls[\"count\"], ax=ax)\nax.set_title(\"Class Counts (EDA sample)\")\nax.set_ylabel(\"Number of impressions\")\nax.set_xlabel(\"Click label\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:09:40.523452Z","iopub.execute_input":"2025-11-19T05:09:40.524043Z","iopub.status.idle":"2025-11-19T05:09:40.729158Z","shell.execute_reply.started":"2025-11-19T05:09:40.524005Z","shell.execute_reply":"2025-11-19T05:09:40.728365Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== Feature cardinality (number of unique values) ===\")\n# Use a subset to keep it fast if you want\ncard_df = train_df.drop(columns=[\"click\"]).nunique().sort_values(ascending=False)\ncard_df = card_df.to_frame(name=\"n_unique\")\nprint(card_df)\n\n# Show top 15 highest-cardinality features\ndisplay(card_df.head(15))\n\nfig, ax = plt.subplots(figsize=(10, 6))\ncard_df.head(15).plot(kind=\"barh\", ax=ax)\nax.invert_yaxis()\nax.set_title(\"Top 15 Features by Cardinality\")\nax.set_xlabel(\"Number of unique values\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:10:01.854818Z","iopub.execute_input":"2025-11-19T05:10:01.855121Z","iopub.status.idle":"2025-11-19T05:10:02.823360Z","shell.execute_reply.started":"2025-11-19T05:10:01.855101Z","shell.execute_reply":"2025-11-19T05:10:02.822707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== CTR by site_category ===\")\nplot_ctr_by_feature(eda_df, \"site_category\", top_n=20)\n\nprint(\"=== CTR by app_category ===\")\nplot_ctr_by_feature(eda_df, \"app_category\", top_n=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:10:15.028178Z","iopub.execute_input":"2025-11-19T05:10:15.028779Z","iopub.status.idle":"2025-11-19T05:10:16.676726Z","shell.execute_reply.started":"2025-11-19T05:10:15.028721Z","shell.execute_reply":"2025-11-19T05:10:16.675971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def top_ctr_table(df: pd.DataFrame, feature: str, top_n: int = 15, min_impressions: int = 1000):\n    grouped = (\n        df.groupby(feature)[\"click\"]\n        .agg(impressions=\"count\", ctr=\"mean\")\n        .reset_index()\n    )\n    grouped = grouped[grouped[\"impressions\"] >= min_impressions]\n    grouped = grouped.sort_values(\"impressions\", ascending=False).head(top_n)\n    return grouped\n\nprint(\"=== Top site_domain by impressions (with CTR) ===\")\ntop_sites = top_ctr_table(eda_df, \"site_domain\", top_n=15, min_impressions=2000)\ndisplay(top_sites)\n\nprint(\"=== Top app_domain by impressions (with CTR) ===\")\ntop_apps = top_ctr_table(eda_df, \"app_domain\", top_n=15, min_impressions=2000)\ndisplay(top_apps)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:10:35.309157Z","iopub.execute_input":"2025-11-19T05:10:35.309437Z","iopub.status.idle":"2025-11-19T05:10:35.450403Z","shell.execute_reply.started":"2025-11-19T05:10:35.309416Z","shell.execute_reply":"2025-11-19T05:10:35.449665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"heat_dw_hr = (\n    eda_df\n    .groupby([\"day_of_week\", \"hour_of_day\"])[\"click\"]\n    .mean()\n    .unstack(\"hour_of_day\")\n    .sort_index()\n)\n\nplt.figure(figsize=(12, 4))\nsns.heatmap(heat_dw_hr, annot=False, cmap=\"viridis\")\nplt.title(\"CTR by day_of_week x hour_of_day\")\nplt.ylabel(\"day_of_week (0=Mon)\")\nplt.xlabel(\"hour_of_day (0-23)\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:10:46.434404Z","iopub.execute_input":"2025-11-19T05:10:46.435112Z","iopub.status.idle":"2025-11-19T05:10:46.898558Z","shell.execute_reply.started":"2025-11-19T05:10:46.435085Z","shell.execute_reply":"2025-11-19T05:10:46.897939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"heat_dev_hr = (\n    eda_df\n    .groupby([\"device_type\", \"hour_of_day\"])[\"click\"]\n    .mean()\n    .unstack(\"hour_of_day\")\n    .sort_index()\n)\n\nplt.figure(figsize=(12, 4))\nsns.heatmap(heat_dev_hr, annot=False, cmap=\"magma\")\nplt.title(\"CTR by device_type x hour_of_day\")\nplt.ylabel(\"device_type\")\nplt.xlabel(\"hour_of_day\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:10:55.891908Z","iopub.execute_input":"2025-11-19T05:10:55.892557Z","iopub.status.idle":"2025-11-19T05:10:56.387191Z","shell.execute_reply.started":"2025-11-19T05:10:55.892535Z","shell.execute_reply":"2025-11-19T05:10:56.386423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"heat_conn_dw = (\n    eda_df\n    .groupby([\"device_conn_type\", \"day_of_week\"])[\"click\"]\n    .mean()\n    .unstack(\"day_of_week\")\n    .sort_index()\n)\n\nplt.figure(figsize=(8, 4))\nsns.heatmap(heat_conn_dw, annot=False, cmap=\"plasma\")\nplt.title(\"CTR by device_conn_type x day_of_week\")\nplt.ylabel(\"device_conn_type\")\nplt.xlabel(\"day_of_week (0=Mon)\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:11:09.215373Z","iopub.execute_input":"2025-11-19T05:11:09.215933Z","iopub.status.idle":"2025-11-19T05:11:09.517441Z","shell.execute_reply.started":"2025-11-19T05:11:09.215909Z","shell.execute_reply":"2025-11-19T05:11:09.516787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_ctr_by_feature(df: pd.DataFrame, feature: str, top_n: int = 10):\n    \"\"\"\n    Plots CTR (mean click) by a categorical or discrete feature,\n    showing top_n most frequent categories.\n    \"\"\"\n    tmp = df.copy()\n\n    # Limit to most frequent categories if needed\n    if tmp[feature].dtype == \"object\" or tmp[feature].nunique() > top_n:\n        top_values = tmp[feature].value_counts().index[:top_n]\n        tmp = tmp[tmp[feature].isin(top_values)]\n\n    grouped = (\n        tmp.groupby(feature)[\"click\"]\n        .agg([\"mean\", \"count\"])\n        .reset_index()\n        .sort_values(\"mean\", ascending=False)\n    )\n\n    fig, ax1 = plt.subplots()\n    sns.barplot(data=grouped, x=feature, y=\"mean\", ax=ax1)\n    ax1.set_title(f\"CTR by {feature}\")\n    ax1.set_ylabel(\"CTR\")\n    ax1.set_xlabel(feature)\n    plt.xticks(rotation=45)\n    plt.tight_layout()\n    plt.show()\n\n    print(grouped)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:52:25.563490Z","iopub.execute_input":"2025-11-19T04:52:25.564040Z","iopub.status.idle":"2025-11-19T04:52:25.569853Z","shell.execute_reply.started":"2025-11-19T04:52:25.564015Z","shell.execute_reply":"2025-11-19T04:52:25.569134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== CTR by hour_of_day ===\")\nplot_ctr_by_feature(eda_df, \"hour_of_day\", top_n=24)\n\nprint(\"=== CTR by day_of_week ===\")\nplot_ctr_by_feature(eda_df, \"day_of_week\", top_n=7)\n\nprint(\"=== CTR by banner_pos ===\")\nplot_ctr_by_feature(eda_df, \"banner_pos\", top_n=10)\n\nprint(\"=== CTR by device_type ===\")\nplot_ctr_by_feature(eda_df, \"device_type\", top_n=10)\n\nprint(\"=== CTR by device_conn_type ===\")\nplot_ctr_by_feature(eda_df, \"device_conn_type\", top_n=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:52:38.214590Z","iopub.execute_input":"2025-11-19T04:52:38.215238Z","iopub.status.idle":"2025-11-19T04:52:40.358227Z","shell.execute_reply.started":"2025-11-19T04:52:38.215213Z","shell.execute_reply":"2025-11-19T04:52:40.357417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. Load test set and add same time features","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(TEST_PATH, compression=\"gzip\")\nprint(\"Raw test shape:\", test_df.shape)\n\ntest_df = add_time_features(test_df)\nprint(\"Test with time features shape:\", test_df.shape)\n\ntest_ids = test_df[\"id\"].values","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:53:39.259709Z","iopub.execute_input":"2025-11-19T04:53:39.260449Z","iopub.status.idle":"2025-11-19T04:54:05.662875Z","shell.execute_reply.started":"2025-11-19T04:53:39.260425Z","shell.execute_reply":"2025-11-19T04:54:05.662210Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. Preprocessing & encoding\n# - Drop ID from both train/test\n#    - Treat most features as categorical + a few numeric time features\n#    - Use OrdinalEncoder with unknown_value = -1 so we can handle new cats","metadata":{}},{"cell_type":"code","source":"# Drop id from both; click is only in train\ntrain_df = train_df.drop(columns=[\"id\"])\ntest_df = test_df.drop(columns=[\"id\"])\n\ntarget_col = \"click\"\n\n# We will NOT use 'date_int' as a model feature to avoid leaking the time split\nnumeric_cols = [\"hour_of_day\", \"day_of_week\"]  # can behave like numeric\ntime_split_col = \"date_int\"\n\n# All other columns except target & date_int are treated as categorical\nfeature_cols = [c for c in train_df.columns if c not in [target_col]]\ncat_cols = [c for c in feature_cols if c not in numeric_cols + [time_split_col]]\n\nprint(\"All feature columns:\", feature_cols)\nprint(\"Numeric feature columns:\", numeric_cols)\nprint(\"Categorical feature columns:\", cat_cols)\n\n# Ensure categorical columns are strings\nfor col in cat_cols:\n    train_df[col] = train_df[col].astype(str)\n    test_df[col] = test_df[col].astype(str)\n\n# Ordinal encode categorical features\nenc = OrdinalEncoder(\n    handle_unknown=\"use_encoded_value\",\n    unknown_value=-1\n)\n\nprint(\"Fitting OrdinalEncoder on categorical features...\")\ntrain_df[cat_cols] = enc.fit_transform(train_df[cat_cols])\ntest_df[cat_cols] = enc.transform(test_df[cat_cols])\n\n# Convert everything (features) to float32 for XGBoost GPU\nfor col in feature_cols:\n    if col != time_split_col:  # we will use date_int only for splitting\n        train_df[col] = train_df[col].astype(\"float32\")\n        test_df[col] = test_df[col].astype(\"float32\")\n\n# Keep labels and dates separately\ny = train_df[target_col].values.astype(\"float32\")\ndates = train_df[time_split_col].values  # for time-based split\n\nprint(\"Train label distribution (click=1 ratio):\", y.mean())\nprint(\"Unique dates in sample:\", np.unique(dates))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:54:24.028936Z","iopub.execute_input":"2025-11-19T04:54:24.029217Z","iopub.status.idle":"2025-11-19T04:55:45.471247Z","shell.execute_reply.started":"2025-11-19T04:54:24.029194Z","shell.execute_reply":"2025-11-19T04:55:45.470373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. Time-based train/validation split\n#    - Use the last date in the sampled train as the validation day","metadata":{}},{"cell_type":"code","source":"unique_dates = np.sort(np.unique(dates))\nmax_date = unique_dates[-1]\nprint(\"Using last date for validation:\", max_date)\n\nval_mask = dates == max_date\ntrain_mask = ~val_mask\n\ntrain_idx = np.where(train_mask)[0]\nval_idx = np.where(val_mask)[0]\n\nprint(f\"Train rows: {train_idx.shape[0]:,}, Val rows: {val_idx.shape[0]:,}\")\n\n# Final feature set excludes target and date_int for modeling\nmodel_feature_cols = [c for c in feature_cols if c != time_split_col]\n\nX_train = train_df.loc[train_idx, model_feature_cols]\ny_train = y[train_idx]\n\nX_valid = train_df.loc[val_idx, model_feature_cols]\ny_valid = y[val_idx]\n\nprint(\"X_train shape:\", X_train.shape)\nprint(\"X_valid shape:\", X_valid.shape)\n\n# Free some memory\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:55:45.472386Z","iopub.execute_input":"2025-11-19T04:55:45.472598Z","iopub.status.idle":"2025-11-19T04:55:46.126469Z","shell.execute_reply.started":"2025-11-19T04:55:45.472581Z","shell.execute_reply":"2025-11-19T04:55:46.125905Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# # 7. Train GPU XGBoost model\n#    - binary:logistic + logloss","metadata":{}},{"cell_type":"code","source":"dtrain = xgb.DMatrix(X_train, label=y_train)\ndvalid = xgb.DMatrix(X_valid, label=y_valid)\n\nwatchlist = [(dtrain, \"train\"), (dvalid, \"valid\")]\n\nparams = {\n    \"objective\": \"binary:logistic\",\n    \"eval_metric\": \"logloss\",  # main metric for the competition\n    \"tree_method\": \"gpu_hist\",  # GPU acceleration\n    \"predictor\": \"gpu_predictor\",\n    \"learning_rate\": 0.1,\n    \"max_depth\": 8,\n    \"subsample\": 0.8,\n    \"colsample_bytree\": 0.8,\n    \"min_child_weight\": 10,\n    \"lambda\": 10.0,\n    \"gamma\": 0.0,\n    \"max_bin\": 256\n}\n\nnum_boost_round = 500\nearly_stopping_rounds = 30\n\nprint(\"Starting XGBoost training on GPU...\")\nmodel = xgb.train(\n    params=params,\n    dtrain=dtrain,\n    num_boost_round=num_boost_round,\n    evals=watchlist,\n    early_stopping_rounds=early_stopping_rounds,\n    verbose_eval=50\n)\n\nprint(\"Best iteration:\", model.best_iteration)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T04:56:20.875043Z","iopub.execute_input":"2025-11-19T04:56:20.875850Z","iopub.status.idle":"2025-11-19T04:56:30.201490Z","shell.execute_reply.started":"2025-11-19T04:56:20.875822Z","shell.execute_reply":"2025-11-19T04:56:30.200253Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. Validation metrics (logloss + AUC)","metadata":{}},{"cell_type":"code","source":"valid_pred_proba = model.predict(dvalid, iteration_range=(0, model.best_iteration + 1))\n\nval_logloss = log_loss(y_valid, valid_pred_proba)\nval_auc = roc_auc_score(y_valid, valid_pred_proba)\n\nprint(f\"Validation Logloss: {val_logloss:.6f}\")\nprint(f\"Validation AUC:     {val_auc:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:01:04.766956Z","iopub.execute_input":"2025-11-19T05:01:04.767660Z","iopub.status.idle":"2025-11-19T05:01:04.924897Z","shell.execute_reply.started":"2025-11-19T05:01:04.767638Z","shell.execute_reply":"2025-11-19T05:01:04.924112Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 9. Train on full sampled train & predict on test","metadata":{}},{"cell_type":"code","source":"dtest = xgb.DMatrix(test_df[model_feature_cols])\ntest_pred_proba = model.predict(\n    dtest,\n    iteration_range=(0, model.best_iteration + 1)\n)\n\nprint(\"Test predictions shape:\", test_pred_proba.shape)\nprint(\"Pred proba stats: min={:.4f}, max={:.4f}, mean={:.4f}\".format(\n    test_pred_proba.min(), test_pred_proba.max(), test_pred_proba.mean()\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:02:07.019830Z","iopub.execute_input":"2025-11-19T05:02:07.020196Z","iopub.status.idle":"2025-11-19T05:02:09.677434Z","shell.execute_reply.started":"2025-11-19T05:02:07.020175Z","shell.execute_reply":"2025-11-19T05:02:09.676710Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10. Build submission file","metadata":{}},{"cell_type":"code","source":"submission = pd.read_csv(SUB_PATH, compression=\"gzip\")\nprint(\"Sample submission shape:\", submission.shape)\nprint(submission.head())\n\nsubmission[\"click\"] = test_pred_proba\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Saved submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:02:44.823138Z","iopub.execute_input":"2025-11-19T05:02:44.823867Z","iopub.status.idle":"2025-11-19T05:02:55.437984Z","shell.execute_reply.started":"2025-11-19T05:02:44.823840Z","shell.execute_reply":"2025-11-19T05:02:55.437319Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Pseudo A/B Testing (proper two-proportion test)","metadata":{}},{"cell_type":"code","source":"from scipy.stats import norm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:11:56.092172Z","iopub.execute_input":"2025-11-19T05:11:56.092869Z","iopub.status.idle":"2025-11-19T05:11:56.096557Z","shell.execute_reply.started":"2025-11-19T05:11:56.092844Z","shell.execute_reply":"2025-11-19T05:11:56.095885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def ab_test_ctr(\n    df: pd.DataFrame,\n    group_col: str,\n    group_a,\n    group_b,\n    label_col: str = \"click\",\n    alpha: float = 0.05\n):\n    \"\"\"\n    Two-proportion z-test for CTR difference between group_a and group_b.\n    group_a = \"treatment-like\" group\n    group_b = \"control-like\" group\n    \"\"\"\n    df_ab = df[df[group_col].isin([group_a, group_b])].copy()\n\n    a = df_ab[df_ab[group_col] == group_a]\n    b = df_ab[df_ab[group_col] == group_b]\n\n    n_a = len(a)\n    n_b = len(b)\n    k_a = a[label_col].sum()\n    k_b = b[label_col].sum()\n\n    p_a = k_a / n_a\n    p_b = k_b / n_b\n    diff = p_a - p_b\n\n    # pooled proportion\n    p_pool = (k_a + k_b) / (n_a + n_b)\n    se = np.sqrt(p_pool * (1 - p_pool) * (1 / n_a + 1 / n_b))\n\n    z = diff / se\n    p_value = 2 * (1 - norm.cdf(abs(z)))  # two-sided\n\n    # 95% CI for the difference\n    z_alpha = norm.ppf(1 - alpha / 2)\n    ci_low = diff - z_alpha * se\n    ci_high = diff + z_alpha * se\n\n    print(f\"A/B on {group_col}: {group_a} (A) vs {group_b} (B)\")\n    print(f\"Group A size: {n_a:,}, CTR_A: {p_a:.4f}\")\n    print(f\"Group B size: {n_b:,}, CTR_B: {p_b:.4f}\")\n    print(f\"Difference (A - B): {diff:.4f}\")\n    print(f\"95% CI for diff: [{ci_low:.4f}, {ci_high:.4f}]\")\n    print(f\"z-stat: {z:.3f}, p-value: {p_value:.4e}\")\n    if p_value < alpha:\n        print(f\"Result: statistically significant at alpha = {alpha}\")\n    else:\n        print(f\"Result: NOT statistically significant at alpha = {alpha}\")\n\n    return {\n        \"n_a\": n_a,\n        \"n_b\": n_b,\n        \"ctr_a\": p_a,\n        \"ctr_b\": p_b,\n        \"diff\": diff,\n        \"ci_low\": ci_low,\n        \"ci_high\": ci_high,\n        \"z\": z,\n        \"p_value\": p_value,\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:12:03.773107Z","iopub.execute_input":"2025-11-19T05:12:03.773713Z","iopub.status.idle":"2025-11-19T05:12:03.781043Z","shell.execute_reply.started":"2025-11-19T05:12:03.773690Z","shell.execute_reply":"2025-11-19T05:12:03.780235Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"device_conn_type value_counts:\", eda_df[\"device_conn_type\"].value_counts())\n\nab_result_conn = ab_test_ctr(\n    eda_df,\n    group_col=\"device_conn_type\",\n    group_a=0,\n    group_b=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:12:14.934032Z","iopub.execute_input":"2025-11-19T05:12:14.934517Z","iopub.status.idle":"2025-11-19T05:12:15.526180Z","shell.execute_reply.started":"2025-11-19T05:12:14.934494Z","shell.execute_reply":"2025-11-19T05:12:15.525524Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"banner_pos value_counts:\", eda_df[\"banner_pos\"].value_counts())\n\nab_result_banner = ab_test_ctr(\n    eda_df,\n    group_col=\"banner_pos\",\n    group_a=1,  # say position 1\n    group_b=0   # baseline position 0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-19T05:12:45.414086Z","iopub.execute_input":"2025-11-19T05:12:45.414627Z","iopub.status.idle":"2025-11-19T05:12:46.023618Z","shell.execute_reply.started":"2025-11-19T05:12:45.414605Z","shell.execute_reply":"2025-11-19T05:12:46.022973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}