{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":[{"sourceType":"competition","sourceId":131653,"databundleVersionId":15802687,"isSourceIdPinned":false}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"ebcc5070","cell_type":"markdown","source":"# AI in Emergency Medicine - Türkiye (AI-EMT)\n## Competition Setup and Solution Notebook\n### Official Kaggle Starter | Version 1.0\n\n**Competition Task**: Predict adverse outcomes (ICU admission or in-hospital death) and 30-day ED readmission for emergency department encounters across Türkiye.\n\n**Evaluation Metric**: Macro-averaged AUC-ROC across both binary targets.\n\n**Dataset**: 1,000,000 ED encounters from 25 cities across Türkiye (2019-2024), comprising 135 clinical, demographic, temporal, and institutional features.\n\n---\n*Refer to the competition description for full feature dictionary and data generation methodology.*\n","metadata":{}},{"id":"3f728d01","cell_type":"code","source":"\"\"\"\n=============================================================================\nAI IN EMERGENCY MEDICINE - TURKIYE (AI-EMT)\nKaggle Competition | Setup Solution Notebook\n=============================================================================\nThis notebook demonstrates:\n  1. Data loading and validation\n  2. Exploratory data analysis\n  3. Baseline feature engineering\n  4. Baseline model (Logistic Regression)\n  5. Gradient Boosting model (LightGBM)\n  6. Proper submission construction\n  7. Evaluation metric (AUC-ROC, macro average)\n\nCompetition target:\n  Primary   -> adverse_outcome  (binary: ICU admission OR in-hospital death)\n  Secondary -> readmission_30d  (binary: 30-day ED readmission)\n\nEvaluation:\n  Macro-averaged AUC-ROC across both targets\n=============================================================================\n\"\"\"\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 0. ENVIRONMENT SETUP\n# ─────────────────────────────────────────────────────────────────────────────\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\n\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.model_selection import StratifiedKFold, cross_val_score\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.impute import SimpleImputer\n\ntry:\n    import lightgbm as lgb\n    LGBM_AVAILABLE = True\nexcept ImportError:\n    LGBM_AVAILABLE = False\n    print(\"LightGBM not installed. Run: pip install lightgbm\")\n\nSEED = 42\nnp.random.seed(SEED)\n\n# Kaggle input path (adjust for local use)\nINPUT = Path(\"/kaggle/input/ai-emergency-medicine-turkiye\")\nif not INPUT.exists():\n    INPUT = Path(\".\")  # local fallback\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 1. DATA LOADING\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"LOADING DATA\")\nprint(\"=\" * 60)\n\ntrain       = pd.read_csv(INPUT / \"train.csv\")\ntest        = pd.read_csv(INPUT / \"test.csv\")\nsample_sub  = pd.read_csv(INPUT / \"sample_submission.csv\")\nhosp_ref    = pd.read_csv(INPUT / \"hospital_reference.csv\")\nicd10_ref   = pd.read_csv(INPUT / \"icd10_reference.csv\")\n\nprint(f\"Train shape      : {train.shape}\")\nprint(f\"Test shape       : {test.shape}\")\nprint(f\"Sample sub shape : {sample_sub.shape}\")\nprint()\n\nTARGET_COLS = [\"adverse_outcome\", \"readmission_30d\"]\n\nprint(\"Target distributions (train):\")\nfor col in TARGET_COLS:\n    pos = train[col].mean() * 100\n    print(f\"  {col:25s} -> {pos:.2f}% positive ({train[col].sum():,} / {len(train):,})\")\nprint()\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 2. QUICK EDA\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"QUICK EDA\")\nprint(\"=\" * 60)\n\n# Missing value summary\nnull_counts = train.isnull().sum()\nnull_pct    = (null_counts / len(train) * 100).round(2)\nmissing_df  = pd.DataFrame({\"null_count\": null_counts, \"null_pct\": null_pct})\nmissing_df  = missing_df[missing_df.null_count > 0].sort_values(\"null_pct\", ascending=False)\nprint(f\"Columns with missing values: {len(missing_df)}\")\nprint(missing_df.head(15).to_string())\nprint()\n\n# Numeric summary\nnum_cols = train.select_dtypes(include=[np.number]).columns.tolist()\nfor t in TARGET_COLS:\n    if t in num_cols:\n        num_cols.remove(t)\nprint(f\"Numeric feature columns : {len(num_cols)}\")\ncat_cols = train.select_dtypes(include=[\"object\"]).columns.tolist()\nprint(f\"Categorical columns     : {len(cat_cols)}\")\nprint()\n\n# Adverse outcome by triage level\nprint(\"Adverse outcome rate by triage level:\")\ntbl = (train.groupby(\"triage_level\")[\"adverse_outcome\"]\n            .agg([\"mean\", \"count\"])\n            .rename(columns={\"mean\": \"positive_rate\", \"count\": \"n\"})\n            .assign(positive_rate=lambda x: (x.positive_rate * 100).round(2)))\nprint(tbl.to_string())\nprint()\n\n# Adverse outcome by age group\nprint(\"Adverse outcome rate by age group:\")\ntbl2 = (train.groupby(\"age_group\")[\"adverse_outcome\"]\n             .agg([\"mean\", \"count\"])\n             .rename(columns={\"mean\": \"positive_rate\", \"count\": \"n\"})\n             .assign(positive_rate=lambda x: (x.positive_rate * 100).round(2)))\nprint(tbl2.to_string())\nprint()\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 3. FEATURE ENGINEERING\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"FEATURE ENGINEERING\")\nprint(\"=\" * 60)\n\nDROP_COLS = [\n    \"encounter_id\",\n    \"triage_label\",         # direct leakage from triage_level\n    \"age_group\",            # derived from age\n    \"map_mmhg\",             # derived from sbp/dbp\n    \"pulse_pressure_mmhg\",  # derived\n    \"hematocrit_pct\",       # derived from hemoglobin\n    \"anion_gap_meql\",       # derived\n    \"consult_specialty\",    # handled separately\n    \"primary_diagnosis\",    # text version of icd10 code\n    \"disposition\",          # post-decision; would cause leakage in real-world use\n    \"pandemic_period\",      # encoded via covid_period_flag\n    \"arrival_day_of_week\",  # encoded as is_weekend\n    \"latitude\",             # replaced by city-level features\n    \"longitude\",\n]\n\nCAT_ENCODE = [\n    \"city\", \"nuts2_region\", \"urban_rural\", \"hospital_type\",\n    \"bed_count_category\", \"arrival_mode\", \"arrival_season\",\n    \"chief_complaint\", \"insurance_type\", \"primary_icd10_code\",\n]\n\n\ndef engineer_features(df, is_train=True, encoders=None):\n    df = df.copy()\n\n    # Drop columns\n    drop_here = [c for c in DROP_COLS if c in df.columns]\n    df.drop(columns=drop_here, inplace=True)\n\n    # Derived interaction features\n    df[\"age_x_charlson\"]         = df[\"age\"] * df[\"charlson_comorbidity_index\"]\n    df[\"sofa_x_triage\"]          = df[\"sofa_score_approx\"] * df[\"triage_level\"]\n    df[\"mews_x_triage\"]          = df[\"mews_score\"] * df[\"triage_level\"]\n    df[\"lactate_x_sbp\"]          = df[\"lactate_mmoll\"].fillna(df[\"lactate_mmoll\"].median()) * df[\"sbp_mmhg\"]\n    df[\"troponin_log\"]            = np.log1p(df[\"troponin_i_ngml\"].fillna(0))\n    df[\"bnp_log\"]                 = np.log1p(df[\"bnp_pgl\"].fillna(0))\n    df[\"crp_log\"]                 = np.log1p(df[\"crp_mgl\"])\n    df[\"creatinine_x_age\"]        = df[\"creatinine_mgdl\"] * df[\"age\"]\n    df[\"wbc_x_temp\"]              = df[\"wbc_k_ul\"] * df[\"temperature_celsius\"]\n    df[\"shock_index\"]             = df[\"heart_rate_bpm\"] / df[\"sbp_mmhg\"].clip(lower=1)\n    df[\"modified_shock_index\"]    = df[\"heart_rate_bpm\"] / ((df[\"sbp_mmhg\"] + 2 * df[\"dbp_mmhg\"]) / 3).clip(lower=1)\n    df[\"bmi_age_product\"]         = df[\"bmi\"] * df[\"age\"]\n    df[\"num_comorbidities\"]       = df[[c for c in df.columns if c.startswith(\"comorbidity_\")]].sum(axis=1)\n    df[\"num_medications\"]         = df[[c for c in df.columns if c.startswith(\"med_\")]].sum(axis=1)\n    df[\"num_procedures\"]          = df[[c for c in df.columns if c.startswith(\"proc_\")]].sum(axis=1)\n    df[\"lab_completeness\"]        = (\n        df[[\"troponin_i_ngml\",\"bnp_pgl\",\"lactate_mmoll\",\"d_dimer_mgfl\",\n            \"procalcitonin_ngl\",\"albumin_gdl\",\"egfr_ml_min\"]].notna().sum(axis=1) / 7.0\n    )\n    df[\"spo2_sbp_product\"]        = df[\"spo2_pct\"] * df[\"sbp_mmhg\"]\n    df[\"gcs_spo2_product\"]        = df[\"gcs_total\"] * df[\"spo2_pct\"]\n    df[\"respiratory_acidosis_flag\"] = ((df[\"bicarbonate_meql\"] < 18) & (df[\"respiratory_rate_rpm\"] > 20)).astype(int)\n    df[\"hyperglycemia_flag\"]      = (df[\"glucose_mgdl\"] > 200).astype(int)\n    df[\"hypoglycemia_flag\"]       = (df[\"glucose_mgdl\"] < 70).astype(int)\n    df[\"anemia_flag\"]             = ((df[\"hemoglobin_gdl\"] < 10) | (df[\"hemoglobin_gdl\"].isna())).astype(int)\n    df[\"aki_flag\"]                = (df[\"creatinine_mgdl\"] > 2.0).astype(int)\n    df[\"leukocytosis_flag\"]       = (df[\"wbc_k_ul\"] > 12).astype(int)\n    df[\"thrombocytopenia_flag\"]   = (df[\"platelets_k_ul\"] < 100).astype(int)\n    df[\"night_weekend_combo\"]     = df[\"is_night_shift\"] * df[\"is_weekend\"]\n    df[\"ed_los_hours\"]            = df[\"ed_los_minutes\"] / 60.0\n\n    # Encode gender as binary (Male=1, Female=0)\n    if \"gender\" in df.columns:\n        df[\"gender\"] = (df[\"gender\"] == \"Male\").astype(int)\n\n    # Encode categoricals\n    new_encoders = {}\n    for col in CAT_ENCODE:\n        if col not in df.columns:\n            continue\n        if is_train:\n            le = LabelEncoder()\n            df[col] = le.fit_transform(df[col].astype(str))\n            new_encoders[col] = le\n        else:\n            le = encoders[col]\n            known = set(le.classes_)\n            df[col] = df[col].astype(str).apply(lambda x: x if x in known else \"Unknown\")\n            if \"Unknown\" not in known:\n                le.classes_ = np.append(le.classes_, \"Unknown\")\n            df[col] = le.transform(df[col])\n\n    if is_train:\n        return df, new_encoders\n    return df\n\n\nprint(\"Engineering training features...\")\nX_train_full, encoders = engineer_features(train, is_train=True)\ny1 = X_train_full.pop(\"adverse_outcome\")\ny2 = X_train_full.pop(\"readmission_30d\")\nX_train_full.drop(columns=[c for c in TARGET_COLS if c in X_train_full.columns], inplace=True, errors=\"ignore\")\n\nprint(\"Engineering test features...\")\nX_test = engineer_features(test, is_train=False, encoders=encoders)\n\nprint(f\"Feature matrix shape  : {X_train_full.shape}\")\nprint(f\"Test matrix shape     : {X_test.shape}\")\nprint()\n\n# Fill remaining NaN with -999 sentinel (tree-friendly)\nX_train_full = X_train_full.fillna(-999)\nX_test       = X_test.fillna(-999)\n\n# Align columns\nshared_cols = [c for c in X_train_full.columns if c in X_test.columns]\nX_train_full = X_train_full[shared_cols]\nX_test       = X_test[shared_cols]\nprint(f\"Aligned shared columns: {len(shared_cols)}\")\nprint()\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 4. BASELINE MODEL: LOGISTIC REGRESSION\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"BASELINE MODEL: LOGISTIC REGRESSION\")\nprint(\"=\" * 60)\n\nBASELINE_FEATURES = [\n    \"age\", \"gender\", \"triage_level\", \"gcs_total\", \"sbp_mmhg\",\n    \"heart_rate_bpm\", \"spo2_pct\", \"respiratory_rate_rpm\",\n    \"charlson_comorbidity_index\", \"mews_score\", \"sofa_score_approx\",\n    \"news2_score\", \"shock_index\", \"num_comorbidities\",\n]\nBASELINE_FEATURES = [c for c in BASELINE_FEATURES if c in X_train_full.columns]\n\npipe_lr = Pipeline([\n    (\"imputer\", SimpleImputer(strategy=\"median\")),\n    (\"scaler\",  StandardScaler()),\n    (\"clf\",     LogisticRegression(max_iter=500, C=0.1, random_state=SEED)),\n])\n\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=SEED)\n\nfor target_name, y_target in [(\"adverse_outcome\", y1), (\"readmission_30d\", y2)]:\n    scores = cross_val_score(\n        pipe_lr, X_train_full[BASELINE_FEATURES], y_target,\n        cv=skf, scoring=\"roc_auc\"\n    )\n    print(f\"  LR 5-fold AUC ({target_name:25s}): \"\n          f\"{scores.mean():.4f} +/- {scores.std():.4f}\")\nprint()\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 5. GRADIENT BOOSTING MODEL (LightGBM)\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"GRADIENT BOOSTING: LightGBM\")\nprint(\"=\" * 60)\n\nif LGBM_AVAILABLE:\n    LGBM_PARAMS = {\n        \"objective\":          \"binary\",\n        \"metric\":             \"auc\",\n        \"learning_rate\":      0.05,\n        \"num_leaves\":         127,\n        \"max_depth\":          -1,\n        \"min_child_samples\":  50,\n        \"subsample\":          0.8,\n        \"subsample_freq\":     5,\n        \"colsample_bytree\":   0.7,\n        \"reg_alpha\":          0.1,\n        \"reg_lambda\":         1.0,\n        \"n_estimators\":       500,\n        \"random_state\":       SEED,\n        \"verbose\":            -1,\n    }\n\n    oof_preds_1 = np.zeros(len(X_train_full))\n    oof_preds_2 = np.zeros(len(X_train_full))\n    test_preds_1 = np.zeros(len(X_test))\n    test_preds_2 = np.zeros(len(X_test))\n\n    for fold, (tr_idx, val_idx) in enumerate(skf.split(X_train_full, y1)):\n        X_tr, X_val = X_train_full.iloc[tr_idx], X_train_full.iloc[val_idx]\n\n        # Target 1: adverse_outcome\n        model1 = lgb.LGBMClassifier(**LGBM_PARAMS)\n        model1.fit(\n            X_tr, y1.iloc[tr_idx],\n            eval_set=[(X_val, y1.iloc[val_idx])],\n            callbacks=[lgb.early_stopping(50, verbose=False),\n                       lgb.log_evaluation(period=-1)]\n        )\n        oof_preds_1[val_idx] = model1.predict_proba(X_val)[:, 1]\n        test_preds_1 += model1.predict_proba(X_test)[:, 1] / skf.n_splits\n\n        # Target 2: readmission_30d\n        model2 = lgb.LGBMClassifier(**LGBM_PARAMS)\n        model2.fit(\n            X_tr, y2.iloc[tr_idx],\n            eval_set=[(X_val, y2.iloc[val_idx])],\n            callbacks=[lgb.early_stopping(50, verbose=False),\n                       lgb.log_evaluation(period=-1)]\n        )\n        oof_preds_2[val_idx] = model2.predict_proba(X_val)[:, 1]\n        test_preds_2 += model2.predict_proba(X_test)[:, 1] / skf.n_splits\n\n        auc1 = roc_auc_score(y1.iloc[val_idx], oof_preds_1[val_idx])\n        auc2 = roc_auc_score(y2.iloc[val_idx], oof_preds_2[val_idx])\n        print(f\"  Fold {fold+1} | adverse_outcome AUC: {auc1:.4f} | readmission_30d AUC: {auc2:.4f}\")\n\n    oof_auc1 = roc_auc_score(y1, oof_preds_1)\n    oof_auc2 = roc_auc_score(y2, oof_preds_2)\n    macro_auc = (oof_auc1 + oof_auc2) / 2\n    print(f\"\\n  OOF AUC adverse_outcome : {oof_auc1:.4f}\")\n    print(f\"  OOF AUC readmission_30d : {oof_auc2:.4f}\")\n    print(f\"  Macro-avg OOF AUC       : {macro_auc:.4f}\")\n    print()\n\n    # Feature importance (Target 1)\n    fi = pd.DataFrame({\n        \"feature\":    X_train_full.columns,\n        \"importance\": model1.feature_importances_,\n    }).sort_values(\"importance\", ascending=False)\n    print(\"Top 20 features (adverse_outcome model):\")\n    print(fi.head(20).to_string(index=False))\n    print()\n\nelse:\n    print(\"LightGBM not available. Using logistic regression predictions as fallback.\")\n    pipe_lr.fit(X_train_full[BASELINE_FEATURES], y1)\n    test_preds_1 = pipe_lr.predict_proba(X_test[BASELINE_FEATURES])[:, 1]\n    pipe_lr.fit(X_train_full[BASELINE_FEATURES], y2)\n    test_preds_2 = pipe_lr.predict_proba(X_test[BASELINE_FEATURES])[:, 1]\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 6. SUBMISSION CONSTRUCTION\n# ─────────────────────────────────────────────────────────────────────────────\nprint(\"=\" * 60)\nprint(\"CONSTRUCTING SUBMISSION\")\nprint(\"=\" * 60)\n\nsubmission = sample_sub.copy()\nsubmission[\"adverse_outcome\"]  = test_preds_1\nsubmission[\"readmission_30d\"]  = test_preds_2\n\nprint(f\"Submission shape: {submission.shape}\")\nprint(f\"adverse_outcome  - min: {test_preds_1.min():.4f}, max: {test_preds_1.max():.4f}, \"\n      f\"mean: {test_preds_1.mean():.4f}\")\nprint(f\"readmission_30d  - min: {test_preds_2.min():.4f}, max: {test_preds_2.max():.4f}, \"\n      f\"mean: {test_preds_2.mean():.4f}\")\nprint()\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"Saved submission.csv\")\nprint()\n\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 7. ADVANCED TIPS FOR COMPETITORS\n# ─────────────────────────────────────────────────────────────────────────────\nTIPS = \"\"\"\n=============================================================================\nADVANCED TIPS FOR COMPETITORS\n=============================================================================\n\nFEATURE ENGINEERING OPPORTUNITIES:\n  - City-level aggregations (mean severity by city, seasonal patterns)\n  - Temporal features: hour-of-day cyclical encoding (sin/cos), holiday flags\n  - Lab ratio features: BUN/creatinine ratio, AST/ALT ratio, LDH/albumin\n  - Composite severity index beyond MEWS/SOFA/NEWS2\n  - Hospital-level encoding (annual_ed_volume as continuous predictor)\n  - Interaction terms: age x comorbidity burden, triage x arrival mode\n  - Missing value indicators as binary features (MCAR vs MNAR distinction)\n\nMODELING APPROACHES:\n  - LightGBM / XGBoost / CatBoost ensemble\n  - Neural networks with embedding layers for categorical variables\n  - Multi-task learning: joint optimization of both targets simultaneously\n  - Stacking: blend of tree-based models with a linear meta-learner\n  - Calibration: Platt scaling or isotonic regression for probability calibration\n\nVALIDATION STRATEGY:\n  - Time-based split (train on 2019-2022, validate on 2023-2024)\n  - Group k-fold stratified by city to test geographic generalization\n  - Stratified k-fold on both targets simultaneously\n\nEVALUATION METRIC:\n  Macro-averaged AUC-ROC = (AUC(adverse_outcome) + AUC(readmission_30d)) / 2\n  Use roc_auc_score(y_true, y_score) from sklearn.metrics\n\nDOMAIN KNOWLEDGE HINTS:\n  - Shock index (HR/SBP) > 1.0 is a strong predictor of adverse outcomes\n  - Lactate > 4 mmol/L indicates severe tissue hypoperfusion\n  - GCS < 9 often triggers intubation decision\n  - NEWS2 >= 7 mandates urgent clinical review in most protocols\n  - The COVID-19 period (2020-2021) substantially altered admission patterns\n  - Night shift and weekend presentations have documented outcome differences\n\nDATA NOTES:\n  - supplemental_data.csv contains 100,000 additional labeled records\n    (not in training/test split) for external validation or pseudo-labeling\n  - Missing values in lab columns follow clinically realistic MCAR/MNAR patterns\n  - Troponin, BNP, D-dimer, and Procalcitonin are selectively ordered\n    (ordered more often for sicker patients - informative missingness)\n=============================================================================\n\"\"\"\nprint(TIPS)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T18:32:48.520540Z","iopub.execute_input":"2026-02-23T18:32:48.520854Z"}},"outputs":[],"execution_count":null}]}