"""
### 1. Exploratory Data Analysis
"""
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

import warnings
warnings.filterwarnings('ignore')
warnings.filterwarnings('ignore', category=DeprecationWarning)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pylab as plot
data=pd.read_csv("../input/titanic/train.csv")
data.shape
data.head(10)
"""
#### Checking missing values in each variable
"""
plt.figure(figsize=(10,10))
sns.heatmap(data.isnull(), yticklabels=False, cbar=False)
"""
###### Some observations
1. There are total of 891 passengers in our training set.<br>
2. The age feature is missing approximately 20% of its values.<br> 
3. Cabin feature has most of its value missing.<br>
4. Embarkes feature is missing small amount of its values.<br>
"""
# Check for any other unusable values
pd.isnull(data).sum()
data['Age']=data['Age'].fillna(data['Age'].median())
data.describe()
"""
### Data Visualization
"""
"""
##### Gender - Survival
"""
data['Died'] = 1-data['Survived']
data.groupby('Sex').agg('sum')[['Survived','Died']].plot(kind='bar',figsize=(25, 7), stacked = True);
"""
From the above plot we can see female passengers a more likely to survive
"""
data.groupby('Sex').agg('mean')[['Survived','Died']].plot(kind='bar', figsize=(25, 7), stacked=True);
"""
##### Age - Survival
"""
figure = plt.figure(figsize=(25,7))
sns.violinplot(x='Sex', y='Age', hue='Survived', 
               data=data, 
               split=True, palette={0: 'r', 1:'g'});
"""
Younger male tend to survive<br>
The age doesn't seems to have a direct impact on the female survival
"""
"""
##### Fare - Survival
"""
figure = plt.figure(figsize=(25,7))
plt.hist([data[data['Survived']==1]['Fare'],
          data[data['Survived']==0]['Fare']],
            stacked=True,
            bins=50, label=['Survived','Dead'])
plt.xlabel('Fare')
plt.ylabel('Number of passengers')
plt.legend();
"""
Passenger with more expensive ticket are more likely to survive
"""
"""
##### Age - Fare - Survival
"""
plt.figure(figsize=(25,7))
ax=plt.subplot()

ax.scatter(data[data['Survived']==1]['Age'],
          data[data['Survived']==1]['Fare'],
          c='green', s=data[data['Survived']==1]['Fare']);

ax.scatter(data[data['Survived']==0]['Age'],
          data[data['Survived']==0]['Fare'],
          c='red', s=data[data['Survived']==0]['Fare']);
"""
The size of the circles is proportional to the ticket fare.

On the x-axis, we have the ages and the y-axis, we consider the ticket fare.

We can observe different clusters:<br>

1. Large green dots between x=20 and x=45: adults with the largest ticket fares<br>
2. Small red dots between x=10 and x=45, adults from lower classes on the boat<br>
3. Small greed dots between x=0 and x=7: these are the children that were saved<br>

As a matter of fact, the ticket fare correlates with the class as we see it in the chart below.
"""
ax = plt.subplot()
ax.set_ylabel('Average fare')
data.groupby('Pclass').mean()['Fare'].plot(kind='bar',figsize=(25,7),ax=ax);
"""
##### Embarkation - Survival
"""
plt.figure(figsize=(25,7))
sns.violinplot(x='Embarked', y='Fare', hue='Survived', data=data, split=True, palette={0:'r', 1:'g'});
"""
### 2. Feature Engineering
"""
def status(feature):
    print('Processing', feature, ': Ok')
train = pd.read_csv('../input/titanic/train.csv')
test  = pd.read_csv('../input/titanic/test.csv' )
target = train.Survived
train.drop(['Survived'], 1, inplace=True)
def get_combined_data():
    combined = train.append(test)
    combined.reset_index(inplace=True)
    
    return combined
combined = get_combined_data()
combined.drop(['index','PassengerId'],inplace=True,axis=1)
combined.shape
combined.head()
"""
##### Extracting the passengers title
"""
titles = set()
for name in data['Name']:
    titles.add(name.split(',')[1].split('.')[0].strip())
titles
Title_Dictionary = {
    'Capt':'Officer',
    'Col' :'Officer',
    'Don' :'Royalty',
    'Dr':'Officer',
    'Jonkheer':'Royalty',
    'Lady':'Royalty',
    'Major':'Officer',
    'Master':'Master',
    'Miss':'Miss',
    'Mlle':'Miss',
    'Mme':'Mrs',
    'Mr':'Mr',
    'Mrs':'Mrs',
    'Ms':'Mrs',
    'Rev':'Officer',
    'Sir':'Royalty',
    'the Countess':'Royalty'
}

def get_titles():
    combined['Title'] = combined['Name'].map(lambda name:name.split(',')[1].split('.')[0].strip())
    
    combined['Title'] = combined.Title.map(Title_Dictionary)
    status('Title')
    return combined
combined=get_titles()
combined.head()
"""
##### Processing age
"""
grouped_train=combined.iloc[:891].groupby(['Sex','Pclass','Title'])
grouped_median_train=grouped_train.median()
grouped_median_train=grouped_median_train.reset_index()[['Sex','Pclass','Title','Age']]
grouped_median_train.head()
def fill_age(row):
    condition = (
    (grouped_median_train['Sex']==row['Sex']) & 
    (grouped_median_train['Title']==row['Title']) & 
    (grouped_median_train['Pclass']==row['Pclass']))
    return grouped_median_train[condition]['Age'].values[0]

def process_age():
    global combined
    
    combined['Age']=combined.apply(lambda row: fill_age(row) if np.isnan(row['Age']) else row['Age'], axis=1)
    status('age')
    return combined
combined = process_age()
"""
##### Processing Names
"""
def process_names():
    global combined
    combined.drop('Name', axis=1, inplace=True)
    
    titles_dummies=pd.get_dummies(combined['Title'],prefix='Title')
    combined=pd.concat([combined, titles_dummies], axis=1)
    
    combined.drop('Title', axis=1, inplace= True)
    
    status('names')
    return combined
combined = process_names()
combined.head()
"""
##### Processing Fares
"""
def process_fares():
    global combined
    combined.Fare.fillna(combined.iloc[:891].Fare.mean(), inplace=True)
    status('Fare')
    return combined
combined=process_fares()
"""
##### Processing Embarked
"""
def process_embarked():
    global combined
    
    combined.Embarked.fillna('S', inplace=True)
    embarked_dummies = pd.get_dummies(combined['Embarked'],prefix='Embarked')
    combined=pd.concat([combined, embarked_dummies], axis=1)
    combined.drop('Embarked', axis=1, inplace=True)
    status('embarked')
    return combined
combined = process_embarked()
combined.head()
"""
##### Processing Cabin
"""
train_cabin=set()
test_cabin=set()

for c in combined.iloc[:891]['Cabin']:
    try:
        train_cabin.add(c[0])
    except:
        train_cabin.add('U')

for c in combined.iloc[891:]['Cabin']:
    try:
        test_cabin.add(c[0])
    except:
        test_cabin.add('U')
train_cabin
test_cabin
def process_cabin():
    global combined
    
    combined.Cabin.fillna('U',inplace=True)
    combined['Cabin']=combined['Cabin'].map(lambda c: c[0])
    
    cabin_dummies = pd.get_dummies(combined['Cabin'],prefix='Cabin')
    combined = pd.concat([combined, cabin_dummies], axis=1)
    
    combined.drop('Cabin', axis=1, inplace=True)
    status('cabin')
    return combined
combined = process_cabin()
combined.head()
"""
##### Processing Sex
"""
def process_sex():
    global combined
    
    combined['Sex']=combined['Sex'].map({'male':1, 'female':0})
    status('Sex')
    
    return combined
combined = process_sex()
"""
##### Processing Pclass
"""
def process_pclass():
    global combined
    
    pclass_dummies = pd.get_dummies(combined['Pclass'], prefix='Pclass')
    combined = pd.concat([combined, pclass_dummies], axis=1)
    combined.drop('Pclass', axis=1, inplace=True)
    
    status('Pclass')
    return combined
combined = process_pclass()
"""
##### Processing Ticket
"""
def cleanTicket(ticket):
    ticket = ticket.replace('.','')
    ticket = ticket.replace('/','')
    ticket = ticket.split()
    ticket = map(lambda t : t.strip(), ticket)
    ticket = list(filter(lambda t : not t.isdigit(), ticket))
    if len(ticket) > 0:
        return ticket[0]
    else:
        return 'XXX'
tickets = set()
for t in combined['Ticket']:
    tickets.add(cleanTicket(t))
print(len(tickets))
def process_ticket():
    global combined
    
    def cleanTicket(ticket):
        ticket = ticket.replace('.','')
        ticket = ticket.replace('/','')
        ticket = ticket.split()
        ticket = map(lambda t : t.strip(), ticket)
        ticket = list(filter(lambda t : not t.isdigit(), ticket))
        if len(ticket) > 0:
            return ticket[0]
        else:
            return 'XXX'
    
    combined['Ticket']=combined['Ticket'].map(cleanTicket)
    tickets_dummies = pd.get_dummies(combined['Ticket'], prefix='Ticket')
    combined = pd.concat([combined, tickets_dummies], axis=1)
    combined.drop('Ticket', inplace=True, axis=1)
    
    status('Ticket')
    return combined
    
combined = process_ticket()
"""
##### Processing Family
"""
def process_family():
    global combined
    
    combined['FamilySize']=combined['Parch']+combined['SibSp'] + 1
    
    combined['Singleton'] = combined['FamilySize'].map(lambda s:1 if s == 1 else 0)
    combined['SmallFamily'] = combined['FamilySize'].map(lambda s:1 if 2 <= s <= 4 else 0)
    combined['LargeFamily'] = combined['FamilySize'].map(lambda s:1 if 5 <= s else 0)
    status('Family')
    return combined
combined = process_family()
combined.shape
combined.head()
"""
### 3. Modeling
"""
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble.gradient_boosting import GradientBoostingClassifier
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV
def compute_score(clf, X, y, scoring='accuracy'):
    xval = cross_val_score(clf, X, y, cv = 5, scoring=scoring)
    return np.mean(xval)
def recover_train_test_target():
    global combined
    
    targets = pd.read_csv('../input/titanic/train.csv', usecols=['Survived'])['Survived'].values
    train = combined.iloc[:891]
    test = combined.iloc[891:]
    
    return train, test, targets
train, test, targets = recover_train_test_target()
"""
### Feature Selection
"""
clf = RandomForestClassifier(n_estimators=50, max_features='sqrt')
clf = clf.fit(train, targets)
features = pd.DataFrame()
features['feature'] = train.columns
features['importance'] = clf.feature_importances_
features.sort_values(by=['importance'], ascending=True, 
                     inplace=True)
features.set_index('feature', inplace=True)
features.plot(kind='barh', figsize=(20, 25));
model = SelectFromModel(clf, prefit=True)
train_reduced = model.transform(train)
train_reduced.shape
test_reduced = model.transform(test)
test_reduced.shape
"""
##### Trying different base models
"""
logreg = LogisticRegression()
logreg_cv = LogisticRegressionCV()
rf = RandomForestClassifier()
gboost = GradientBoostingClassifier()

models = [logreg, logreg_cv, rf, gboost]
for model in models:
    print ('Cross-validation of : {0}'.format(model.__class__))
    score = compute_score(clf=model, X=train_reduced, y=targets, scoring='accuracy')
    print ('CV score = {0}'.format(score))
    print ('****')
"""
### Hyperparameters tuning
"""
run_gs = False

if run_gs:
    parameter_grid = {
                 'max_depth' : [4, 6, 8],
                 'n_estimators': [50, 10],
                 'max_features': ['sqrt', 'auto', 'log2'],
                 'min_samples_split': [2, 3, 10],
                 'min_samples_leaf': [1, 3, 10],
                 'bootstrap': [True, False],
                 }
    forest = RandomForestClassifier()
    cross_validation = StratifiedKFold(n_splits=5)

    grid_search = GridSearchCV(forest,
                               scoring='accuracy',
                               param_grid=parameter_grid,
                               cv=cross_validation,
                               verbose=1
                              )

    grid_search.fit(train, targets)
    model = grid_search
    parameters = grid_search.best_params_

    print('Best score: {}'.format(grid_search.best_score_))
    print('Best parameters: {}'.format(grid_search.best_params_))
    
else: 
    parameters = {'bootstrap': False, 'min_samples_leaf': 3, 'n_estimators': 50, 
                  'min_samples_split': 10, 'max_features': 'sqrt', 'max_depth': 6}
    
    model = RandomForestClassifier(**parameters)
    model.fit(train, targets)