# -*- coding: utf-8 -*-

"""
Titanic Dataset Classification

https://www.kaggle.com/c/titanic/data

This notebook features:
    detecting running on Kaggle or not, and adjusting file names.
    data preprocessing functions to apply to both train and test.
    applying a list of models.
    automatically choosing the best model from the list.
    applying GridSearchCV to the best model.

```
Columns:
survival | Survival | 0 = No, 1 = Yes
pclass   | Ticket class | 1 = 1st, 2 = 2nd, 3 = 3rd
sex      | Sex                            |
Age      | Age in years                   |
sibsp    | # of siblings / spouses aboard |
parch    | # of parents / children aboard |
ticket   | Ticket number                  |
fare     | Passenger fare                 |
cabin    | Cabin number                   |
embarked | Port of Embarkation
    | C = Cherbourg, Q = Queenstown, S = Southampton
```
"""

# import
import os
import pandas as pd
import numpy as np
# import matplotlib as plt
# import seaborn as sns
from sklearn import ensemble
from sklearn import tree
from sklearn import neighbors
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV

# read files
# import os
if os.path.isdir("/kaggle/input"):
    # we are on Kaggle.
    file_name_train = "/kaggle/input/titanic/train.csv"
    file_name_test = "/kaggle/input/titanic/test.csv"
    file_name_submission = "/kaggle/input/titanic/gender_submission.csv"
    file_name_output = "/kaggle/input/titanic/my_submission.csv"
    # import os
    # for dirname, _, filenames in os.walk('/kaggle/input'):
    #     for filename in filenames:
    #         print(os.path.join(dirname, filename))
else:
    # we are not on Kaggle, local machine possibly.
    file_name_train = "train.csv"
    file_name_test = "test.csv"
    file_name_submission = "gender_submission.csv"
    file_name_output = "my_submission.csv"

df = pd.read_csv(file_name_train)
df2 = pd.read_csv(file_name_test)
ids = df2["PassengerId"]
df0 = df.copy()

dfdesc = df.describe()
dfdesc

df.info()

# checking nulls of the train dataset, before any preprocessing:
dfnulls = pd.isnull(df).sum()
dfnulls

# checking nulls of the test dataset:
dfnulls = pd.isnull(df2).sum()
dfnulls

# fill Embarked
df["Embarked"].fillna("S", inplace=True)
df2["Embarked"].fillna("S", inplace=True)

# fill Fare
df["Fare"].fillna(df["Fare"].median(), inplace=True)
df2["Fare"].fillna(df["Fare"].median(), inplace=True)

"""
 consider dropping columns:
 - `PassengerId` is the ID of data, and should be dropped to prevent overfitting.
 - `Ticket`, is a string such as ticket number, we should drop it.
   - sample data: `A/5 21171` or `113803`
 Let's drop the columns.



"""
# drop columns
def drop_columns(df, column_names=None):
    if not column_names:
        # cols_to_drop = ['PassengerId', 'Name', 'Ticket', 'Cabin', 'Fare', 'Embarked']
        cols_to_drop = ['PassengerId', 'Name', 'Ticket', 'Cabin']
    df = df.drop(cols_to_drop, axis=1)
    return df


df = drop_columns(df)
df2 = drop_columns(df2)

# get_dummies
# all of the data should be numerical before applying machine learning algorithms.
# we already have columns that include categorical values.
# let's convert them to columns.
def prepare_dummies(df, cols_to_dummy=None):
    if not cols_to_dummy:
        cols_to_dummy = ['Sex', 'Embarked']
    dummies = []
    for col in cols_to_dummy:
        if col in df:
            dummies.append(pd.get_dummies(df[col], prefix=col))
        else:
            continue

    # dummies is now a list.
    # Let's convert it to a DataFrame:
    titanic_dummies = pd.concat(dummies, axis=1)

    # we concatenate to the original dataframe columnwise
    df = pd.concat((df, titanic_dummies), axis=1)

    # let's drop the columns which we have applied get_dummies:

    # we are not using this
    # since some of the columns may have already been dropped:
    # df = df.drop(cols_for_dummies, axis=1)

    for col in cols_to_dummy:
        if col in df:
            df = df.drop(col, axis=1)

    return df


df = prepare_dummies(df)
df2 = prepare_dummies(df2)

# checking nulls again:
dfnulls = pd.isnull(df).sum()
dfnulls

"""
 examine ages for survived females:
 dffemale = df[(df["Sex_female"] == 1) & (df["Survived"] == 1)]
 sns.distplot(dffemale["Age"], bins=20)
 plt.show()



"""
# Age has nan values, fill them:
def handle_nans(df):
    r"""
    # how does interpolate work?
    # https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.interpolate.html
    # # x = ((x-1) + (x+1)) / 2
    # data1 = {}
    # data1["age"] = [20, 20, np.nan, 40, np.nan, 42]
    # df1 = pd.DataFrame(data1)
    # df1["age2"] = df1["age"].interpolate()
    """
    # dfnulls1 = pd.isnull(df).sum()  # before (for checking)
    # df['Age'] = df['Age'].interpolate().astype("int64")
    df['Age'] = df['Age'].interpolate()
    # dfnulls2 = pd.isnull(df).sum()  # after (for checking)
    return df


df = handle_nans(df)
df2 = handle_nans(df2)

# handle family
def handle_family(df):
    df["family_size"] = df["SibSp"] + df["Parch"]
    df = df.drop(columns=["SibSp", "Parch"], axis=1)
    return df


df = handle_family(df)
df2 = handle_family(df2)

# set age groups:
def set_age_group(age):
    # groups = list(range(0, 100, 5))
    groups = list(range(0, 100, 10))
    for i in groups:
        if i >= age:
            group = i
            break
    return group


def set_age_groups(df):
    df["AgeGroup"] = df["Age"].apply(set_age_group)
    df = df.drop("Age", axis=1)
    return df


df = set_age_groups(df)
df2 = set_age_groups(df2)

# let's note the columns before modelling:
print(f"columns in df: {df.columns}")

# data split
# our target column is: "Survived"

# X_train -> y_train
# X_test  -> ?

y = df['Survived'].values  # makes a copy
X = df.drop(columns=["Survived"]).values  # makes another copy
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=19)

# model objects
models = []
models.append(tree.DecisionTreeClassifier(max_depth=5, random_state=19))
# models.append(ensemble.RandomForestClassifier(n_estimators=100, random_state=19))
models.append(ensemble.GradientBoostingClassifier(
    n_estimators=100, random_state=19))
# models.append(neighbors.KNeighborsClassifier())

# apply all the models and find the best model:
best_score = 0
best_model = None
for model in models:
    print("_" * 10, type(model))
    model.fit(X_train, y_train)
    model_score = model.score(X_test, y_test)
    print("  ", model_score)

    if model_score > best_score:
        best_score = model_score
        best_model = model

print("best model:", best_model)
best_model_str = str(type(best_model))

# tune the best model using GridSearchCV
# from sklearn.model_selection import GridSearchCV

if "DecisionTreeClassifier" in best_model_str:
    # https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html
    params = {
        "max_depth": range(2, 11, 1),
        "random_state": [19, 42],
    }
    gs = GridSearchCV(tree.DecisionTreeClassifier(), params, n_jobs=-1)
elif "RandomForestClassifier" in best_model_str:
    # https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html
    params = {
        "n_estimators": range(50, 550, 50),
        "random_state": [19, 42],
    }
    gs = GridSearchCV(ensemble.RandomForestClassifier(), params, n_jobs=-1)
elif "GradientBoostingClassifier" in best_model_str:
    # https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
    params = {
        "learning_rate": [0.01, 0.05, 0.1, 0.15, 0.2],
        "n_estimators": range(50, 550, 50),
        "random_state": [19, 42],
    }
    gs = GridSearchCV(ensemble.GradientBoostingClassifier(), params, n_jobs=-1)
elif "KNeighborsClassifier" in best_model_str:
    # https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html
    params = {
        "leaf_size": [10, 20, 30, 40, 50],
    }
    gs = GridSearchCV(neighbors.KNeighborsClassifier(), params, n_jobs=-1)


gs_results = gs.fit(X_train, y_train)
# print("GS results", gs_results)
print("best score: ", gs_results.best_score_)
print("best parameters: ", gs_results.best_params_)
best_model_of_gs = gs_results.best_estimator_

"""
 Now we will apply all these operations to real test file.



"""
# prediction
y_pred = best_model_of_gs.predict(df2)

# prepare submission
dfout = ids.to_frame()
dfout["Survived"] = y_pred

dfout.to_csv(file_name_output, index=False)

# last words
print("end.")

"""
# end

"""