# -- Code Cell --
import pandas as pd
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# -- Code Cell --
test = pd.read_csv('test.csv')

# -- Code Cell --
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
cat = train.drop(columns=["HadHeartAttack"]).select_dtypes(include="object").columns
num = train.drop(columns=["HadHeartAttack"]).select_dtypes(exclude="object").columns
num_imputer = SimpleImputer(strategy="mean")
train[num]=num_imputer.fit_transform(train[num])
cat_imputer = SimpleImputer(strategy="most_frequent")
train[cat] = cat_imputer.fit_transform(train[cat])

# -- Code Cell --
train.info()

# -- Code Cell --
train['HadHeartAttack'].value_counts()

# -- Code Cell --
train['HadHeartAttack'].value_counts().iloc[train['HadHeartAttack'].value_counts().argmax()]

# -- Code Cell --
train['HadHeartAttack'].value_counts().argmin()

# -- Code Cell --
train['HadHeartAttack'].value_counts().argmax()

# -- Code Cell --
ratio = train['HadHeartAttack'].value_counts().iloc[train['HadHeartAttack'].value_counts().argmin()] / train['HadHeartAttack'].value_counts().iloc[train['HadHeartAttack'].value_counts().argmax()]
ratio

# -- Code Cell --
train.info()

# -- Code Cell --
train = train.dropna()

# -- Code Cell --
pd.set_option("display.max_columns", None)

# -- Code Cell --
train.head()

# -- Code Cell --
train['Ra']

# -- Code Cell --
train['CovidPos'].value_counts()

# -- Code Cell --
map_ch = {"Within past year (anytime less than 12 months ago)":1, "Within past 2 years (1 year but less than 2 years ago)":2, "Within past 5 years (2 years but less than 5 years ago)":5,"5 or more years ago":10}
map_health = {'Poor':0,'Fair':1, 'Excellent':2, 'Good':3, 'Very good':4}
map_sex = {'Female':0 , 'Male':1}
map_diab = {'Yes':10, 'No':0,'No, pre-diabetes or borderline diabetes':5,'Yes, but only during pregnancy (female)':2}
map_YES_NO = {'Yes':1, 'No':0}
map_covid ={'Yes':2, 'No':0, 'Tested positive using home test without a health professional':1}
map_theeth = {'None of them':0, '1 to 5': 2,"6 or more, but not all":3,"All": 10}
map_TETANUS = {'No, did not receive any tetanus shot in the past 10 years':0, 'Yes, received tetanus shot but not sure what type':1,'Yes, received Tdap':2,'Yes, received tetanus shot, but not Tdap':3}
map_SMOKER = {'Never smoked':10, 'Former smoker':6, 'Current smoker - now smokes every day':2,'Current smoker - now smokes some days':1 }
map_VAPE = {'Never used e-cigarettes in my entire life':10, 'Not at all (right now)':6, 'Use them some days':3,'Use them every day':1}
map_age= {
    "Age 18 to 24": 0,
    "Age 25 to 29": 1,
    "Age 30 to 34": 2,
    "Age 35 to 39": 3,
    "Age 40 to 44": 4,
    "Age 45 to 49": 5,
    "Age 50 to 54": 6,
    "Age 55 to 59": 7,
    "Age 60 to 64": 8,
    "Age 65 to 69": 9,
    "Age 70 to 74": 10,
    "Age 75 to 79": 11,
    "Age 80 or older": 12
}
train = pd.get_dummies(train, columns=["RaceEthnicityCategory"], drop_first=True)
train['LastCheckupTime'] = train['LastCheckupTime'].astype(str).map(map_ch)
train['Sex'] = train['Sex'].astype(str).map(map_sex)
train['GeneralHealth'] = train['GeneralHealth'].astype(str).map(map_health)
train['RemovedTeeth'] = train['RemovedTeeth'].astype(str).map(map_theeth)
yes_no_cols = [
    "HadHeartAttack", "HadAngina", "HadStroke", "HadAsthma",
    "HadSkinCancer", "HadCOPD", "HadDepressiveDisorder",
    "HadKidneyDisease", "HadArthritis",
    "DeafOrHardOfHearing", "BlindOrVisionDifficulty",
    "DifficultyConcentrating", "DifficultyWalking",'PhysicalActivities',
    "DifficultyDressingBathing", "DifficultyErrands","ChestScan","AlcoholDrinkers","HIVTesting","FluVaxLast12","PneumoVaxEver","HighRiskLastYear"
]

for col in yes_no_cols:
    train[col] = train[col].astype(str).map(map_YES_NO)
train['SmokerStatus'] = train['SmokerStatus'].astype(str).map(map_SMOKER)
train['ECigaretteUsage'] = train['ECigaretteUsage'].astype(str).map(map_VAPE)
train['AgeCategory'] = train['AgeCategory'].astype(str).map(map_age)
train['TetanusLast10Tdap'] = train['TetanusLast10Tdap'].astype(str).map(map_TETANUS)
train['HadDiabetes'] = train['HadDiabetes'].astype(str).map(map_diab)
train['CovidPos'] = train['CovidPos'].astype(str).map(map_covid)

# -- Code Cell --
train = train.drop(columns = ['ID','State'])

# -- Code Cell --
train = train.dropna()

# -- Code Cell --
train = train.astype(int)

# -- Code Cell --
len(train)

# -- Code Cell --
corr_target = train.corr()["HadHeartAttack"].sort_values(ascending=False)
corr_target

# -- Code Cell --
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from catboost import CatBoostClassifier
from sklearn.metrics import f1_score
import numpy as np

X = train.drop(columns="HadHeartAttack").copy()
y = train["HadHeartAttack"].astype(str).str.strip().map({"No": 0, "Yes": 1}).astype(int)

cat_cols = X.select_dtypes(include=["object", "category", "bool"]).columns.tolist()
num_cols = X.select_dtypes(include=["number"]).columns.tolist()
cat_features = [X.columns.get_loc(c) for c in cat_cols]

X_cb = X.copy()
for c in cat_cols:
    X_cb[c] = X_cb[c].astype(str).fillna("NA")

X_train_cb, X_test_cb, y_train, y_test = train_test_split(
    X_cb, y, test_size=0.2, random_state=42, stratify=y
)

pre = ColumnTransformer(
    transformers=[
        ("num", Pipeline([
            ("imputer", SimpleImputer(strategy="median")),
            ("scaler", StandardScaler())
        ]), num_cols),
        ("cat", Pipeline([
            ("imputer", SimpleImputer(strategy="most_frequent")),
            ("oh", OneHotEncoder(handle_unknown="ignore"))
        ]), cat_cols),
    ],
    remainder="drop"
)

X_train_ml = pre.fit_transform(X_train_cb)
X_test_ml = pre.transform(X_test_cb)

lr = LogisticRegression(solver="saga", class_weight="balanced", max_iter=2000, random_state=42)
rf = RandomForestClassifier(n_estimators=300, max_depth=12, class_weight="balanced", random_state=42, n_jobs=-1)
cb = CatBoostClassifier(random_state=42, verbose=0, auto_class_weights="Balanced")

lr.fit(X_train_ml, y_train)
rf.fit(X_train_ml, y_train)
cb.fit(X_train_cb, y_train, cat_features=cat_features)

p_lr = lr.predict_proba(X_test_ml)[:, 1]
p_rf = rf.predict_proba(X_test_ml)[:, 1]
p_cb = cb.predict_proba(X_test_cb)[:, 1]

p = 0.2 * p_lr + 0.2 * p_rf + 0.6 * p_cb

ths = np.linspace(0.05, 0.9, 86)
f1s = [f1_score(y_test, (p >= t).astype(int)) for t in ths]
best_i = int(np.argmax(f1s))
best_t = float(ths[best_i])
best_f1 = float(f1s[best_i])

print("thresh:", best_t)
print("F1:", best_f1)

y_pred = (p >= best_t).astype(int)


# -- Code Cell --
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
from sklearn.metrics import f1_score

def _to_numpy(X):
    if hasattr(X, "to_numpy"):
        return X.to_numpy()
    return np.asarray(X)

def fit_oof_stacking(X, y, base_models, meta_model, n_splits=5, random_state=42, include_original_X=True):
    Xn = _to_numpy(X)
    yn = np.asarray(y)

    skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=random_state)

    n = Xn.shape[0]
    m = len(base_models)
    oof_preds = np.zeros((n, m), dtype=float)

    for j, bm in enumerate(base_models):
        for tr_idx, te_idx in skf.split(Xn, yn):
            model = clone(bm)
            model.fit(Xn[tr_idx], yn[tr_idx])
            oof_preds[te_idx, j] = model.predict_proba(Xn[te_idx])[:, 1]

    meta_X = np.hstack([Xn, oof_preds]) if include_original_X else oof_preds

    fitted_base = []
    for bm in base_models:
        model = clone(bm)
        model.fit(Xn, yn)
        fitted_base.append(model)

    fitted_meta = clone(meta_model)
    fitted_meta.fit(meta_X, yn)

    return fitted_base, fitted_meta, oof_preds, yn

def stacking_predict_proba(X, fitted_base_models, fitted_meta_model, include_original_X=True):
    Xn = _to_numpy(X)
    base_probs = np.column_stack([m.predict_proba(Xn)[:, 1] for m in fitted_base_models])
    meta_X = np.hstack([Xn, base_probs]) if include_original_X else base_probs
    return fitted_meta_model.predict_proba(meta_X)[:, 1]

def tune_threshold_f1(y_true, y_proba, thresholds=None):
    y_true = np.asarray(y_true)
    y_proba = np.asarray(y_proba)

    if thresholds is None:
        thresholds = np.linspace(0.0, 1.0, 1001)

    best_t = 0.5
    best_f1 = -1.0

    for t in thresholds:
        y_pred = (y_proba >= t).astype(int)
        f1 = f1_score(y_true, y_pred, zero_division=0)
        if f1 > best_f1:
            best_f1 = f1
            best_t = float(t)

    return best_t, best_f1


# -- Code Cell --
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from catboost import CatBoostClassifier
from sklearn.metrics import f1_score, classification_report

base_models = [
    LogisticRegression(max_iter=2000, class_weight="balanced", solver="liblinear"),
    RandomForestClassifier(n_estimators=600, random_state=42, class_weight="balanced_subsample"),
    CatBoostClassifier(
        iterations=1200,
        learning_rate=0.03,
        depth=6,
        loss_function="Logloss",
        eval_metric="F1",
        verbose=False,
        random_seed=42
    )
]

meta_model = LogisticRegression(max_iter=2000, solver="liblinear", class_weight="balanced")

fitted_base, fitted_meta, oof_preds, oof_y = fit_oof_stacking(
    X_train, y_train,
    base_models=base_models,
    meta_model=meta_model,
    n_splits=5,
    random_state=42,
    include_original_X=True
)

val_proba = stacking_predict_proba(
    X_val,
    fitted_base_models=fitted_base,
    fitted_meta_model=fitted_meta,
    include_original_X=True
)

best_t, best_f1 = tune_threshold_f1(y_val, val_proba)
print("best_threshold:", best_t, "best_val_f1:", best_f1)

val_pred = (val_proba >= best_t).astype(int)
print("val_f1@best_threshold:", f1_score(y_val, val_pred))
print(classification_report(y_val, val_pred, digits=4))


# -- Code Cell --
import numpy as np

from sklearn.base import clone
from sklearn.model_selection import KFold, train_test_split
from sklearn.metrics import f1_score, classification_report

def _as_2d(a):
    a = np.asarray(a)
    if a.ndim == 1:
        return a.reshape(-1, 1)
    return a

def fit_oof_stacking(
    X, y,
    base_models,
    meta_model,
    n_splits=5,
    random_state=42,
    include_original_X=True
):
    X = np.asarray(X)
    y = np.asarray(y)

    kf = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
    n = X.shape[0]
    m = len(base_models)

    oof_base = np.zeros((n, m), dtype=float)

    for i, model in enumerate(base_models):
        for tr_idx, va_idx in kf.split(X, y):
            mdl = clone(model)
            mdl.fit(X[tr_idx], y[tr_idx])

            if hasattr(mdl, "predict_proba"):
                p = mdl.predict_proba(X[va_idx])[:, 1]
            else:
                p = mdl.predict(X[va_idx])
            oof_base[va_idx, i] = p

    meta_X = np.hstack([X, oof_base]) if include_original_X else oof_base

    fitted_meta = clone(meta_model)
    fitted_meta.fit(meta_X, y)

    fitted_base_models = []
    for model in base_models:
        mdl = clone(model)
        mdl.fit(X, y)
        fitted_base_models.append(mdl)

    return fitted_base_models, fitted_meta, oof_base, y

def stacking_predict_proba(
    X,
    fitted_base_models,
    fitted_meta_model,
    include_original_X=True
):
    X = np.asarray(X)
    base_preds = []
    for mdl in fitted_base_models:
        if hasattr(mdl, "predict_proba"):
            p = mdl.predict_proba(X)[:, 1]
        else:
            p = mdl.predict(X)
        base_preds.append(p)

    base_stack = np.column_stack(base_preds)
    meta_X = np.hstack([X, base_stack]) if include_original_X else base_stack

    if hasattr(fitted_meta_model, "predict_proba"):
        return fitted_meta_model.predict_proba(meta_X)[:, 1]
    return fitted_meta_model.predict(meta_X)

def tune_threshold_f1(y_true, y_proba, step=0.001):
    y_true = np.asarray(y_true).astype(int)
    y_proba = np.asarray(y_proba).astype(float)

    thresholds = np.arange(0.0, 1.0 + step, step)
    best_t, best_f1 = 0.5, -1.0

    for t in thresholds:
        y_pred = (y_proba >= t).astype(int)
        f1 = f1_score(y_true, y_pred)
        if f1 > best_f1:
            best_f1 = f1
            best_t = float(t)

    return best_t, float(best_f1)

if __name__ == "__main__":
    from sklearn.linear_model import LogisticRegression
    from sklearn.ensemble import RandomForestClassifier
    from catboost import CatBoostClassifier

    base_models = [
        LogisticRegression(max_iter=2000, class_weight="balanced", solver="liblinear"),
        RandomForestClassifier(n_estimators=600, random_state=42, class_weight="balanced_subsample"),
        CatBoostClassifier(
            iterations=1200,
            learning_rate=0.03,
            depth=6,
            loss_function="Logloss",
            eval_metric="F1",
            verbose=False,
            random_seed=42
        )
    ]

    meta_model = LogisticRegression(max_iter=2000, solver="liblinear", class_weight="balanced")

    X_tr, X_val, y_tr, y_val = train_test_split(
        X_train, y_train, test_size=0.2, random_state=42, stratify=y_train
    )

    fitted_base, fitted_meta, oof_preds, oof_y = fit_oof_stacking(
        X_tr, y_tr,
        base_models=base_models,
        meta_model=meta_model,
        n_splits=5,
        random_state=42,
        include_original_X=True
    )

    val_proba = stacking_predict_proba(
        X_val,
        fitted_base_models=fitted_base,
        fitted_meta_model=fitted_meta,
        include_original_X=True
    )

    best_t, best_f1 = tune_threshold_f1(y_val, val_proba)
    print("best_threshold:", best_t, "best_val_f1:", best_f1)

    val_pred = (val_proba >= best_t).astype(int)
    print("val_f1@best_threshold:", f1_score(y_val, val_pred))
    print(classification_report(y_val, val_pred, digits=4))

    fitted_base_full, fitted_meta_full, _, _ = fit_oof_stacking(
        X_train, y_train,
        base_models=base_models,
        meta_model=meta_model,
        n_splits=5,
        random_state=42,
        include_original_X=True
    )

    test_proba = stacking_predict_proba(
        X_test,
        fitted_base_models=fitted_base_full,
        fitted_meta_model=fitted_meta_full,
        include_original_X=True
    )

    test_pred = (test_proba >= best_t).astype(int)

    print("test_proba[:10]:", test_proba[:10])
    print("test_pred[:10]:", test_pred[:10])


# -- Code Cell --
test.head()

# -- Code Cell --
test_ids = test['ID'].copy()

# -- Code Cell --
test['RaceEthnicityCategory'].value_counts()

# -- Code Cell --
test.info()

# -- Code Cell --
test = pd.read_csv('test.csv')

# -- Code Cell --
X_test_raw = test.drop(columns=["ID", "State"]).copy()
test_ids = test["ID"].copy()
X_test_cb = X_test_raw.copy()
for c in cat_cols:
    X_test_cb[c] = X_test_cb[c].astype(str).fillna("NA")

X_test_ml = pre.transform(X_test_raw)

# -- Code Cell --
p_lr = lr.predict_proba(X_test_ml)[:, 1]
p_rf = rf.predict_proba(X_test_ml)[:, 1]
p_cb = cb.predict_proba(X_test_cb)[:, 1]
p = 0.2 * p_lr + 0.2 * p_rf + 0.6 * p_cb

# -- Code Cell --
y_test_pred = (p >= best_t).astype(int)

# -- Code Cell --
sub1 = pd.DataFrame({
    "subtaskID": [1],
    "datapointID": [0],
    "answer": [f"{ratio:.6f}"]
})

pred_yesno = pd.Series(y_test_pred).map({1: "Yes", 0: "No"})

sub2 = pd.DataFrame({
    "subtaskID": 2,
    "datapointID": test_ids.values,
    "answer": pred_yesno.values
})

output = pd.concat([sub1, sub2], ignore_index=True)
output.to_csv("output.csv", index=False)