{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":127091,"databundleVersionId":15179667,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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":"2026-01-25T07:48:47.130564Z","iopub.execute_input":"2026-01-25T07:48:47.130934Z","iopub.status.idle":"2026-01-25T07:48:48.917908Z","shell.execute_reply.started":"2026-01-25T07:48:47.130897Z","shell.execute_reply":"2026-01-25T07:48:48.916671Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 0.933 冲刺版：基于物理规则的绝对覆盖 (Rule Override)\n# 策略：CatBoost 预测 + 强力规则修正\n# ==========================================\nimport os\nimport pandas as pd\nimport numpy as np\nfrom catboost import CatBoostClassifier, Pool\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.metrics import accuracy_score\nimport warnings\n\nwarnings.filterwarnings('ignore')\n\n# 1. 加载数据\nprint(\"Loading Data...\")\ntrain = pd.read_csv('/kaggle/input/cuhkml-2025-classification/dataframe_train.csv')\ntest = pd.read_csv('/kaggle/input/cuhkml-2025-classification/dataframe_test.csv')\n\n# 映射 Target\nif 'action_type' in train.columns:\n    train['action_type'] = train['action_type'].map({'PICKUP': 0, 'DELIVERY': 1}).astype(int)\n    y = train['action_type']\n\ntrain['is_train'] = 1\ntest['is_train'] = 0\n\n# ==========================================\n# 2. 构建“绝对地点库” (The Location Dictionary)\n# ==========================================\nprint(\"Building Location Determinism Map...\")\n\n# 将经纬度组合成唯一 Key\n# 注意：这里不进行 round，追求完全匹配。如果 GPS 漂移大，round(4) 会更好，但先试完全匹配。\ntrain['s_key'] = train['source_lat'].astype(str) + '_' + train['source_lng'].astype(str)\ntrain['t_key'] = train['target_lat'].astype(str) + '_' + train['target_lng'].astype(str)\ntest['s_key'] = test['source_lat'].astype(str) + '_' + test['source_lng'].astype(str)\ntest['t_key'] = test['target_lat'].astype(str) + '_' + test['target_lng'].astype(str)\n\n# --- 规则 A: Source 纯度检测 ---\n# 统计每个 Source 坐标在训练集中出现的 label 均值\n# mean=1.0 说明该地点作为起点全是 Delivery (大概率是仓库)\n# mean=0.0 说明该地点作为起点全是 Pickup (大概率是商家)\nsource_stats = train.groupby('s_key')['action_type'].agg(['mean', 'count'])\n# 筛选出“纯粹”的地点 (出现次数>1 且 均值为 0 或 1)\npure_sources = source_stats[source_stats['count'] > 1]\npure_source_map = pure_sources['mean'].to_dict() # Key -> 0.0 or 1.0\n\n# --- 规则 B: Target 纯度检测 ---\ntarget_stats = train.groupby('t_key')['action_type'].agg(['mean', 'count'])\npure_targets = target_stats[target_stats['count'] > 1]\npure_target_map = pure_targets['mean'].to_dict()\n\nprint(f\"Found {len(pure_source_map)} pure source locations and {len(pure_target_map)} pure target locations.\")\n\n# ==========================================\n# 3. 特征工程 (保持简单有效)\n# ==========================================\nprint(\"Feature Engineering...\")\ncombined = pd.concat([train.drop(['action_type'], axis=1), test], axis=0, ignore_index=True)\n\n# 基础距离\ndef haversine_array(lat1, lng1, lat2, lng2):\n    R = 6371\n    lat1, lng1, lat2, lng2 = map(np.radians, [lat1, lng1, lat2, lng2])\n    a = np.sin((lat2-lat1)/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin((lng2-lng1)/2)**2\n    return 2 * R * np.arcsin(np.sqrt(a))\n\ncombined['dist'] = haversine_array(combined['source_lat'], combined['source_lng'], combined['target_lat'], combined['target_lng'])\n\n# [关键特征] 频次编码 (Frequency Encoding)\n# 重新计算频次，这次加上 s_key 和 t_key\ns_freq = combined['s_key'].value_counts().to_dict()\nt_freq = combined['t_key'].value_counts().to_dict()\ncombined['s_freq'] = combined['s_key'].map(s_freq)\ncombined['t_freq'] = combined['t_key'].map(t_freq)\ncombined['freq_diff'] = combined['s_freq'] - combined['t_freq']\n\n# [关键特征] 最近邻特征 (KNN Feature)\n# 逻辑：对于测试集的一个点，找到训练集中离它最近的点，看它的 Label 是什么\nprint(\"Generating KNN Features (Distance to nearest pattern)...\")\n# 选取部分样本做 KNN 特征 (为了速度)\ncoords_train = train[['source_lat', 'source_lng', 'target_lat', 'target_lng']].values\ny_train_knn = y.values\n# 这是一个非常强的特征：离最近的同类样本的距离\nknn = KNeighborsClassifier(n_neighbors=1, n_jobs=-1)\nknn.fit(coords_train[:, :2], y_train_knn) # 只用 Source 坐标训练\ncombined['knn_source_pred'] = knn.predict(combined[['source_lat', 'source_lng']].values)\n\n# 清理\nfeatures = ['dist', 's_freq', 't_freq', 'freq_diff', 'knn_source_pred']\n# 如果有 id 列，加入 id 的 count 编码\nfor col in ['courier_id', 'wave_index']:\n    if col in combined.columns:\n        combined[col+'_cnt'] = combined.groupby(col)[col].transform('count')\n        features.append(col+'_cnt')\n\nX = combined[combined['is_train'] == 1][features]\nX_test = combined[combined['is_train'] == 0][features]\n\n# ==========================================\n# 4. 训练模型 (快速训练，因为后面有规则修正)\n# ==========================================\nprint(\"Training Model...\")\n# 使用 100% 训练集，不再切分验证集 (为了最大化规则覆盖率，我们需要所有数据都在 memory 里)\nmodel = CatBoostClassifier(\n    iterations=1500, learning_rate=0.05, depth=6, verbose=False, random_seed=42\n)\nmodel.fit(X, y)\npreds = model.predict(X_test) # 直接预测类别 (0/1)\n\n# ==========================================\n# 5. 规则覆盖 (Rule Override) - 0.933 的关键\n# ==========================================\nprint(\"Applying Rule Overrides...\")\nfinal_preds = preds.copy()\noverride_count = 0\n\ntest_s_keys = test['s_key'].values\ntest_t_keys = test['t_key'].values\n\nfor i in range(len(test)):\n    s_k = test_s_keys[i]\n    t_k = test_t_keys[i]\n    \n    # 规则 1: 如果起点在训练集中是“纯 Delivery 起点” (e.g. 仓库)\n    if s_k in pure_source_map:\n        ground_truth = pure_source_map[s_k]\n        if ground_truth == 1.0 or ground_truth == 0.0:\n            if final_preds[i] != int(ground_truth):\n                final_preds[i] = int(ground_truth)\n                override_count += 1\n                continue # 已修正，跳过后续规则\n\n    # 规则 2: 如果终点在训练集中是“纯 Pickup 终点” (e.g. 仓库)\n    if t_k in pure_target_map:\n        ground_truth = pure_target_map[t_k]\n        if ground_truth == 1.0 or ground_truth == 0.0:\n            if final_preds[i] != int(ground_truth):\n                final_preds[i] = int(ground_truth)\n                override_count += 1\n\nprint(f\"🔥 Overrode {override_count} predictions using Deterministic Location Rules.\")\n\n# ==========================================\n# 6. 保存\n# ==========================================\nsubmission = pd.DataFrame()\nsubmission['order'] = range(len(test))\nsubmission['action_type_DELIVERY'] = final_preds.astype(int)\nsubmission.to_csv('submission.csv', index=False)\nprint(\"Done. Check 'submission.csv'.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-25T07:48:48.920351Z","iopub.execute_input":"2026-01-25T07:48:48.920905Z","iopub.status.idle":"2026-01-25T07:50:48.453317Z","shell.execute_reply.started":"2026-01-25T07:48:48.920874Z","shell.execute_reply":"2026-01-25T07:50:48.452198Z"}},"outputs":[],"execution_count":null}]}