# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# 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" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix
"""
**Datenaufbereitung**
"""
# Read in data as pandas dataframe and display first 5 rows
features_raw = pd.read_csv('/kaggle/input/datareduced/DataCalculator_Red.CSV', sep=';', decimal=',', nrows=100000)
features_raw.head(5)
"""
**Korrelationsmatrix**
"""
features_raw.corr()
import seaborn as sns

f, ax = plt.subplots(figsize=(10, 8))
corr = features_raw.corr()
sns.heatmap(corr, mask=np.zeros_like(corr, dtype=np.bool), cmap=sns.diverging_palette(220, 10, as_cmap=True),
            square=True, ax=ax)
"""
**Datenaufbereitung**
* Scaling der X-Werte
* Kategorisierung der nicht-numerischen Werte
"""
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, FunctionTransformer, RobustScaler
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
from sklearn.model_selection import train_test_split

# Featurematrix X: alle Zeilen, Spalten ohne tba
X_data = features_raw.drop(["tba"], axis=1)

# Zielvektor y: tba
y_data = features_raw['tba']

# KLassifizierung der Spalten
cat_columns =  ['RKZ', 'bzw', 'Sex']
num_columns = list(X_data.drop(cat_columns,axis=1).columns)

X_data[num_columns] = X_data[num_columns].astype('float64')
#y_data = y_data.astype('float64')

# Pipeline definieren
#scaler = StandardScaler()
scaler = RobustScaler()
num_pipeline = Pipeline([('scaler', scaler)])
cat_pipeline = Pipeline([('onehot', OneHotEncoder())])

data_preproc_pipeline = ColumnTransformer([
    ('cat_values', cat_pipeline, cat_columns),
    ('num_values', num_pipeline, num_columns)
])

# Aufteilung: 80% Training, 20% Test
X_train_raw, X_test_raw, train_labels, test_labels = train_test_split(X_data, y_data, test_size=0.20, random_state=1)

# Preprocessing (Klassifizierung und Scaling)
data_preproc_pipeline.fit(X_train_raw, train_labels)
feature_names = list(data_preproc_pipeline.named_transformers_["cat_values"].named_steps["onehot"].get_feature_names()) \
+ data_preproc_pipeline.transformers_[1][2]

#train_features, test_features, train_labels, test_labels

train_features = pd.DataFrame(data_preproc_pipeline.transform(X_train_raw), columns=feature_names)
test_features = pd.DataFrame(data_preproc_pipeline.transform(X_test_raw), columns=feature_names)

feature_list = list(train_features.columns)
print('Training Features Shape:', train_features.shape)
print('Training Labels Shape:', train_labels.shape)
print('Testing Features Shape:', test_features.shape)
print('Testing Labels Shape:', test_labels.shape)
"""
**Training the Forest**

Hier versuchen wir erstmal ein einfaches Modell mit den Default-Parametern.
"""
# Import the model we are using
from sklearn.ensemble import RandomForestRegressor

# Instantiate model 
rf = RandomForestRegressor(random_state=42, verbose=1, n_jobs = -1)
rf.fit(train_features, train_labels)


"""
**Make Predictions on Test Data**
"""
# Use the forest's predict method on the test data
predictions = rf.predict(test_features)

from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

print('Mean Squared Error:', mean_squared_error(test_labels, predictions))

# Calculate the absolute errors
errors = abs(predictions - test_labels)

# Print out the mean absolute error (mae)
print('Mean Absolute Error:', mean_absolute_error(test_labels, predictions))

# Calculate mean absolute percentage error (MAPE)
mape = 100 * (errors / test_labels)

# Calculate and display accuracy
accuracy = 100 - np.mean(mape)

# Calculate Score
score = rf.score(test_features, test_labels)

print('Accuracy:', round(accuracy, 2), '%')

print('Score:', round(score*100, 2), '%')
"""
**Predictions vs. Testlabels**
"""
x_ax = range(len(test_labels))
plt.scatter(x_ax, test_labels, s=5, color="blue", label="original")
plt.scatter(x_ax, predictions, s=2, color="red", label="predicted")
plt.legend()
plt.show()
"""
**Einfluss der Variablen**

Wie zu erwarten, haben die Beitragszahlweise (wegen Berechnung technischer Beitrag) und das Geschlecht (Unisex-Tafel) keinen Einfluss.
"""
# Get numerical feature importances
importances = list(rf.feature_importances_)

# List of tuples with variable and importance
feature_importances = [(feature, round(importance, 2)) for feature, importance in zip(feature_list, importances)]

# Sort the feature importances by most important first
feature_importances = sorted(feature_importances, key = lambda x: x[1], reverse = True)

# Print out the feature and importances 
[print('Variable: {:20} Importance: {}'.format(*pair)) for pair in feature_importances];
"""
**Beispiel-Liste für Testwerte vs. vorhergesagte Werte**
"""
#df_results = pd.DataFrame(test_features)
df_results = pd.DataFrame(X_test_raw)

test_labels = np.array(test_labels)

df_results["TBA erwartet"] = test_labels
df_results["TBA berechnet"] = np.round(predictions,2)
df_results["abs. Differenz"] = np.round(abs(predictions - test_labels),2)
df_results["abs. Differenz%"] = np.round(df_results["abs. Differenz"] / test_labels * 100,3)

print('Zufällige Auswahl:')
display(df_results.sample(n=10))


print('Fünf größte und kleinste abs. Differenzen%:')
# sortiert nach abs. Differenz%
df_sort = df_results.sort_values('abs. Differenz%', ascending=False)
#display(df_sort.head(10))
display(df_sort)

display(df_results[["abs. Differenz", "abs. Differenz%"]].describe())
"""
**Bisher verwendete Hyper-Parameter**
"""
from pprint import pprint
# Look at parameters used by our current forest
print('Parameters currently in use:\n')
pprint(rf.get_params())
"""
Mit einem Random Search Cross Validation wurden "optimale" Hyperparameter wie folgt ermittelt:

**Tuning-Modell**
Parameter:
{'n_estimators': 700,
 'min_samples_split': 2,
 'min_samples_leaf': 1,
 'max_features': 'auto',
 'max_depth': 110,
 'bootstrap': True}
 
 Diese werden nachfolgend verwendet und erneut trainiert und vorhergesagt.
"""
# Instantiate model 
rf = RandomForestRegressor(n_estimators=700, min_samples_split=2, min_samples_leaf=1, max_features='auto', max_depth=110, bootstrap=True, random_state=42, verbose=1, n_jobs = -1)
rf.fit(train_features, train_labels)

# Use the forest's predict method on the test data
predictions = rf.predict(test_features)

from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

print('Mean Squared Error:', mean_squared_error(test_labels, predictions))

# Calculate the absolute errors
errors = abs(predictions - test_labels)

# Print out the mean absolute error (mae)
print('Mean Absolute Error:', mean_absolute_error(test_labels, predictions))

# Calculate mean absolute percentage error (MAPE)
mape = 100 * (errors / test_labels)

# Calculate and display accuracy
accuracy = 100 - np.mean(mape)

# Calculate Score
score = rf.score(test_features, test_labels)

print('Accuracy:', round(accuracy, 2), '%')

print('Score:', round(score*100, 2), '%')
"""
Ab hier versuchen wir noch weitere Verfahren mit weitgehend Default-Parametern.
"""
"""
**Gradient Boost**
"""
from sklearn.ensemble import GradientBoostingRegressor
grb = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1,
                                max_depth=1, random_state=0, loss='ls', verbose=0).fit(train_features, train_labels)

predictions = grb.predict(test_features)
print("GradientBoost:")
print('Mean Squared Error:', mean_squared_error(test_labels, predictions))
print('Mean Absolute Error:', mean_absolute_error(test_labels, predictions))

"""
**Decision Tree Regression with AdaBoost**
"""
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

# Fit regression model
regr_1 = DecisionTreeRegressor(max_depth=4)
regr_2 = AdaBoostRegressor(DecisionTreeRegressor(max_depth=4),
                          n_estimators=100, random_state=0)

regr_1.fit(train_features, train_labels)
regr_2.fit(train_features, train_labels)

# Predict
y_1 = regr_1.predict(test_features)
y_2 = regr_2.predict(test_features)

print("Decision Tree: ")
print('Mean Squared Error:', mean_squared_error(test_labels, y_1))
print('Mean Absolute Error:', mean_absolute_error(test_labels, y_1))

print("AdaBoost: ")
print('Mean Squared Error:', mean_squared_error(test_labels, y_2))
print('Mean Absolute Error:', mean_absolute_error(test_labels, y_2))
