"""
**Seminar Arbeit**

In diesem Notebook wird zum Einen die von Kaggle bereitgestellte Übung und zum Anderen selbst erlerntes Wissen verwendet, um einen von uns ausgewählten Datensatz zu bearbeiten. Der Datensatz enthält über 16.000 Daten über Videospiele und deren Einnahmen am Markt.
"""
# 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 in 

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

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

import os
print(os.listdir("../input"))

# Any results you write to the current directory are saved as output.
"""
Zunächst werden einige Pakete benötigt. Diese sind allerdings von Anfang an importiert und es musste keine Änderung vorgenommen werden. Die print-Funktion zeigt lediglich an, welche Dateien sich in dem Pfad befinden. Hier befindet sich unsere csv-Datei, welche im nächsten Schritt in die Variable "vgd" geladen und aufgerufen wird.
"""
# Dateipfad in einer Variable speichern
video_games_path = '../input/Video_Games_Sales_as_at_22_Dec_2016.csv'
# Daten auslesen
vgd = pd.read_csv(video_games_path)
# Zusammenfassung der Daten ausgeben
vgd.describe()
"""
Die Übersicht der Daten ist sehr ähnlich wie die der Übung. Auch hier ist auf den ersten Blick zu erkennen, dass einige Spalten nicht vollständig mit Daten befüllt sind (z.B. Critic_Score). Für ein Seminar an der FH-Wedel könnten bereits zu Anfang einige Fragen gestellt werden, damit sich die Studenten näher mit den Daten beschäftigen.

- Wie viele Daten sind vorhanden?
- Wie ist der durchschnittliche Critic Score?
- Wie alt ist das älteste Spiel in dem Datensatz?
"""
"""
Die unten stehende Funktion ist ein zusammenfassender Aufruf der Spaltennamen:
"""
vgd.columns
"""
**Daten für das Modell auswählen**

Für diesen Datensatz sollen die Preise der Spiele vorhergesagt werden. Dazu ist es notwendig zu wissen, welche Daten hierfür verwendet werden sollen. Zum einen muss das "Prediction Target" und zum anderen die "Features" bestimmt werden, mit dessen Hilfe die Vorhersage germacht wird.

"""
"""
**Fehlende Daten erkennen**

Wie bereits am Anfang erkannt besitzen die Spalten eine unterschiedliche Anzahl an vorhandenen Daten. Mit der nachfolgenden Funktion werden diese erkannt und anschließend ausgegeben.
"""

def missing_values_table(df):
        # Anzahl der fehlenden Daten
        mis_val = df.isnull().sum()
        
        # Anzahl der fehlenden Daten in %
        mis_val_percent = 100 * df.isnull().sum() / len(df)
        
        # Erstellen einer Tabelle mit den Ergebnissen
        mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)
        
        # Benennung der Spalten
        mis_val_table_ren_columns = mis_val_table.rename(
        columns = {0 : "Missing Values", 1 : "% of Total Values"})
        
        # Absteigendes Sortieren der Zeilen
        mis_val_table_ren_columns = mis_val_table_ren_columns[
            mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
        "% of Total Values", ascending=False).round(1)
        
        
        
        return mis_val_table_ren_columns
#Aufrufen der Tabelle
missing_values_table(vgd)
"""
Wie zu erkennen ist, fehlen in diesem Datensatz eine Menge an Daten - teilweise über 50%. Um mit einem Modell weiterzuarbeiten, werden diese vorerst entfernt. Daher werden alle fehlenden Daten entfernt und in eine neue Variable gespeichert.
"""
vgd_clean = vgd.dropna(axis=0)
"""
Die vorherige Funktion ruft erneut die Tabelle auf. Allerdings sind dieses Mal keine Daten enthalten, da alle fehlenden Daten gelöscht wurden.
"""
missing_values_table(vgd_clean)
"""
Der Datensatz wird nun erneut in einer zusammengefassten Darstellung aufzurufen. Jede Spalte hat jetzt die gleiche Anzahl an Daten, wobei diese von über 16.000 auf 6825 geschrumpft sind.
"""
vgd_clean.describe()
"""
**Prediction Target & Features festlegen**

Wie zu Anfang erwähnt, sollen die weltweiten Umsätze vorhergesagt werden. Wie in der Übung müssen hierfür zunächst das "Prediction Target" und die "Features" festgelegt werden. Das Prediction Target ist in diesem Fall die Spalte Global_Sales. Als Features werden folgende Werte genommen:


User Score, Critic Score, Rating, Year of Release, Genre
"""
#Prediction Target
y = vgd_clean.Global_Sales

# neue Features werden für das Modell genommen
vgd_features = ['User_Score', 'User_Count', 'Critic_Score', 'Critic_Count']

X = vgd_clean[vgd_features]
"""
Die Prediction ist in der Variable y gespeichert und die Features in der Variable X. Diese werden in den Folgenden Zeilen aufgerufen.
"""
y.describe()
"""
Von allen 6825 Spielen liegt der höchste Wert bei 82.53, wobei hier die Angabe in Millionen gemacht wird. Der niedrigste Wert liegt bei 0.01 und der Durchschnitt bei 0.77.
"""
X.describe(include="all")
"""
"NaN" steht für "Not a Number". Ob das für die vorhersage vom Nachteil ist, wussten wir zu diesem Zeitpunkt nicht und haben es daher vorerst ignoriert.
"""
"""
**Aufrufen der ersten 5 Einträge**
"""
y.head()
X.head()
"""
Beim Aufrufen der ersten fünf Einträge fällt auf, dass die Nummerierung in der linken Spalte nicht fortlaufend ist. Das liegt daran, dass wir zu Anfang des Notebook fehlende Eintrage gelöscht haben. So war zum Beispiel in Zeile 1 ein fehlender Wert enthalten, wodurch diese gelöscht wurde.
"""
"""
**Modell entwickeln**

Das Modell wird zunächst wie in der Übung mit dem DecisionTreeRegressor erstellt.
"""
from sklearn.tree import DecisionTreeRegressor

vgd_model = DecisionTreeRegressor(random_state=1)

vgd_model.fit(X, y)
vgd_model = DecisionTreeRegressor(random_state=1)

vgd_model.fit(X, y)
"""
Mit den neuen Features kann der Code ausgeführt werden und wir beginnen damit eine Vorhersage für die ersten und letzen fünf Spiele zu machen:
"""
X.head()
y.head()
print("Die ersten 5 Spiele:")
print(vgd_model.predict(X.head()))
print("Die letzen 5 Spiele:")
print(vgd_model.predict(X.tail()))
"""
Zum Vergleich die tatsächlichen Umsätze:
"""
print("Die ersten 5 Spiele:")
print(y.head())
print("Die letzen 5 Spiele:")
print(y.tail())
"""
**Fehlerabwichung**

Auf den ersten Blick sieht es so aus, als würde das Modell in dieser Form immer die korrekten Preise vorhersagen. Mithilfe des MAE (Mean Absolute Error) soll die Fehlerabweichung ermittelt werden.

NOTE: Der MAE wird in diesem Fall nur "In-Sample" berechnet. Das Problem dahinter wird im nächsten Teil beschrieben
"""
from sklearn.metrics import mean_absolute_error

prediction = vgd_model.predict(X)
mean_absolute_error(y, prediction)
"""
Die Abweichung der Vorhersagen ist tatsächlich so gering, das sie nicht weiter beachtet bzw. durch die geringe Größe nicht angezeigt wird.
"""
"""
**Test- und Trainingsdaten**

Das Problem dieser Abweichung besteht darin, dass nur ein einziges "Sample" verwendet wird. Als Beispiel kann hier der "Publisher" verwendet werden (auch wenn er als Feature nicht verwendet wurde). Haben Spiele des selben Herstellers immer einen hohen Umsatz, lernt der Algorithmus aus diesem Muster und wird auch für zukünftige Spiele dieses Herstellers einen höheren Umsatz vorhersagen. 

Aus diesem Grund werden Datensätze in Tainings- und Testdaten aufgeteilt. Aus dem Datensatz werden einige Daten als Testdaten verwendet, um mit diesen das Trainingsmodell präziser zu gestalten und auf bisher unbekannte Daten anzuwenden.
"""
from sklearn.model_selection import train_test_split

train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=0)

vgd_model = DecisionTreeRegressor()

vgd_model.fit(train_X, train_y)

val_prediction = vgd_model.predict(val_X)
"""
Mit train_X und val_X wird die Aufteilung gezeigt. Hier ist zu sehen, dass in etwa 80% der Daten als Testdaten und 20% als Trainingsdaten verwendet werden.
"""
train_X
val_X
print(mean_absolute_error(val_y, val_prediction))
"""
Wie zu sehen ist, ist die eigentliche Fehlerabweichung deutlich höher als gedacht. Sie liegt sogar über dem durchschnittlichen Wert der Global_Sales von 0.77. Ein Grund hierfür könnte die Wahl unserer Features sein. Für ein präzises Modell müssten auch andere Spalten wie Rating oder Genre mit einbezogen werden. Wie genau ein String als Feature verwendet werden kann, wird in der Übung allerdings nicht erwähnt.

Zudem haben wir auch mehr als die Hälfte der Daten aufgrund von fehlenden Werten entfernt. Auch hier kann es bessere Methoden geben, welche ein tieferes Verständnis über Machine Learning erfordern.

"""
"""
Zum Vergleich noch einmal der MAE ohne Trainingsdaten:
"""
mean_absolute_error(y, prediction)
"""
**Modell verfeinern**

Ein weg das Modell zu verfeinern ist die tiefe des Entscheidungsbaumes zu verändern. Ist ein Modell unpräzise in den Testdaten, liefert aber gute Ergebnisse in den Trainingsdaten spricht man von 'Overfitting'. Sind auch die Trainingsdaten unpräzise, so spricht man von 'Underfitting'. Ziel ist es, die richtige aufteilung des Entscheidungsbaumes zu finden. In unserem Fall sprechen wir von Overfitting, da der MAE der Testdaten deutlich höher ist.

![decisiontree](http://i.imgur.com/2q85n9s.png)

Quelle: https://www.kaggle.com/dansbecker/underfitting-and-overfitting

"""
"""
Wie in der Übung verfeinern wir in unserem Modell die tiefe des Entscheidungsbaumes und lassen uns den MAE für unterschiedliche "Leaf Nodes" ausgeben.
"""
def get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y):
    model = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes, random_state=0)
    model.fit(train_X, train_y)
    preds_val = model.predict(val_X)
    mae = mean_absolute_error(val_y, preds_val)
    return(mae)
for max_leaf_nodes in [5, 10, 50, 100, 500]:
    my_mae = get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y)
    print("Max leaf Nodes: ", max_leaf_nodes, "\t\t", "MAE: ", my_mae)
"""
Eine "Baumtiefe" von 50 beitet hierbei den besten MAE. Mit 0.691 liegt die Abweichung zwar unter dem Durchschnitt, allerdings ist sie weitergin sehr hoch.

Dennoch wird mit dieser Tiefe jetzt erneut eine Vorhersage gemacht.
"""
vgd_model = DecisionTreeRegressor(random_state=1, max_leaf_nodes=50)

vgd_model.fit(train_X, train_y)
print(vgd_model.predict(val_X.head()))
"""
und mit den tatsächlichen Daten verglichen.
"""
print(val_y.head())
"""
Beim Vergleich wird aufgezeigt, wie ungenau die Vorhersage ist. Lediglich ein Wert (4621) kommt dem tatsächlichen ergebnis nahe. Die Fehlerabweichung bei einer Baumteife von 50 war zwar geringer, aber dennos nicht ausreichend für das Modell. Daher wird nun geguckt, ob der RandomForestDegressor eine bessere alternative bietet.
"""
from sklearn.ensemble import RandomForestRegressor

forest_model = RandomForestRegressor(random_state=1)
forest_model.fit(train_X, train_y)
vgd_preds = forest_model.predict(val_X)
print(mean_absolute_error(val_y, vgd_preds))
"""
Auch durch den RandonForestDegressor konnte der MAE nicht verbessert werden und lag sogar über dem vorherigen MAE von 0.691.

Für ein Seminar an der FH Wedel ist der MAE ein valider Wert für die Beurteilung und Bewertung der Studenten. Doch auch andere Methoden können für eine Vorhersage verwendet werden. Dazu gehört die Lineare Regression. Diese war zwar nicht Teil der Übung, gibt aber einen weiteren Wert für eine spätere Bewertung der Arbeit.
"""
"""
**Lineare Regression**
"""
from sklearn.linear_model import LinearRegression
clf = LinearRegression()
# Modell erstellen
clf.fit(train_X, train_y)
# Vorhersage der ersten und letzen 5 Daten
print(clf.predict(val_X.head()))
print(clf.predict(val_X.tail()))
# Vergleich mit den ursprünglichen Daten
print(val_y.head())
print(val_y.tail())
"""
Auch durch die lineare Regression werden keine präzisen Vorhersagen durchgeführt. Das macht deutlich, dass das Modell noch weiter ausgebaut werden muss. Ähnlich wie der MAE kann auch bei der Linearen Regression ein Wert für die Validierung bestimmt werden.
"""
clf.score(val_X, val_y)
"""
Der Score der Linearen Regression liegt hier bei 10,49%, was bedeutet das unsere Vorhersagen zu fast 90% ungenau sind. Aus diesem Grund werden in den folgenden Code Zeilen einige Methoden aus der Übung angewendet, um das Modell zu verfeinern.
"""
# Get list of categorical variables
s = (train_X.dtypes == 'object')
object_cols = list(s[s].index)

print("Categorical variables:")
print(object_cols)
# Function for comparing different approaches
def score_dataset(train_X, val_X, train_y, val_y):
    model = RandomForestRegressor(n_estimators=50, random_state=0)
    model.fit(train_X, train_y)
    preds = model.predict(val_X)
    return mean_absolute_error(val_y, preds)
drop_train_X = train_X.select_dtypes(exclude=['object'])
drop_val_X = val_X.select_dtypes(exclude=['object'])

print("MAE from Approach 1 (Drop categorical variables):")
print(score_dataset(drop_train_X, drop_val_X, train_y, val_y))
from sklearn.preprocessing import LabelEncoder

# Make copy to avoid changing original data 
label_X_train = train_X.copy()
label_X_valid = val_X.copy()

# Apply label encoder to each column with categorical data
label_encoder = LabelEncoder()
for col in object_cols:
    label_X_train[col] = label_encoder.fit_transform(train_X[col])
    label_X_valid[col] = label_encoder.transform(val_X[col])

print("MAE from Approach 2 (Label Encoding):") 
print(score_dataset(label_X_train, label_X_valid, train_y, val_y))
"""
Doch auch hier konnte der MAE nicht verbessert werden und liegt weiterhin im Bereich von 0.7.
"""
"""
**Fazit**

Die Übung von Kaggle bietet einen guten ersten Einblick in das Thema Machine Learning. Anhand eines Beispiels werden die grundlegenden Prinzipien verdeutlicht. Allerdings konnte die Übung nicht erfolgreich auf unseren Datensatz angewendet werden. Unser Modell ist sehr ungenau und ohne ein tieferes Verständnis in die Materie fällt es schwer neue Lösungsansätze zu finden. Die Möglichkeiten im Machine Learning sind so groß, dass sie kaum durch kleine Übungen abgebildet werden können. 

Damit wir besser verstehen wieso unser Modell ungeau ist, haben wir unsere Daten grafisch Dargestellt. Die grafische Darstellung war nicht Teil der von uns durchgeführten Übungen. 
"""
"""
**Grafische Ansichten**

"""
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
plt.style.use("fivethirtyeight")

# Änderung der Schrift
plt.rcParams["font.size"] = 24
plt.rcParams["figure.facecolor"] = "white"
plt.rcParams["axes.facecolor"] = "white"

# Internal ipython tool for setting figure size
from IPython.core.pylabtools import figsize
figsize(15, 12)
"""
In unserem Notebook haben wir versucht anhand von vier Features die Global Sales vorherzusagen. Um die Daten grafisch zu betrachten haben wir jeweils ein Feature mit unserem Predicted Target gegenübergestellt:
"""
plt.scatter(vgd_clean['User_Count'], vgd_clean['Global_Sales'])
plt.scatter(vgd_clean['User_Score'], vgd_clean['Global_Sales'])
plt.scatter(vgd_clean['Critic_Score'], vgd_clean['Global_Sales'])
plt.scatter(vgd_clean['Critic_Count'], vgd_clean['Global_Sales'])
"""
Hierbei ist zu erkennen, dass einige der Daten als Außreiser betrachtet werden können, da sie nicht in das allgemeine Muster der grafik passen. Um diese Ausreißer zu entfernen benutzen wir die unten aufgeführte Funktion. Hier werden alle Daten im Bereich unter 25% bzw. über 75% markiert und in den folgenden Codezeilen aus den jeweiligen Spalten entfernt.
"""
def rm_outliers(df, list_of_keys):
    df_out = df
    for key in list_of_keys:
        # Calculate first and third quartile
        first_quartile = df_out[key].describe()["25%"]
        third_quartile = df_out[key].describe()["75%"]

        # Interquartile range
        iqr = third_quartile - first_quartile

        # Remove outliers
        removed = df_out[(df_out[key] <= (first_quartile - 3 * iqr)) |
                    (df_out[key] >= (third_quartile + 3 * iqr))] 
        df_out = df_out[(df_out[key] > (first_quartile - 3 * iqr)) &
                    (df_out[key] < (third_quartile + 3 * iqr))]
    return df_out, removed
vgd_clean, rmvd_global = rm_outliers(vgd_clean, ["Global_Sales"])
vgd_clean.describe()
"""
Fast 400 der insgesamt 6825 Daten in der Spalte "Global_Sales" wurden als Außreißer identifiziert und entfernt.
"""
vgd_clean, rmvd_global = rm_outliers(vgd_clean, ["User_Count"])
vgd_clean.describe()
"""
Die Spalte "User_Count" beinhaltet anschließend sogar fast 600 Ausreißer, wohingegen bei den letzten beiden Spalte keine weitere Veränderung auftritt.
"""
vgd_clean, rmvd_global = rm_outliers(vgd_clean, ["Critic_Score"])
vgd_clean.describe()
vgd_clean, rmvd_global = rm_outliers(vgd_clean, ["Critic_Count"])
vgd_clean.describe()
"""
Insgesamt sind jetzt noch 5811 Daten vorhanden. Auf Basis dieser Daten wird nun erneut der MAE, sowie der Score der logistischen Regression ermittelt.
"""
y = vgd_clean.Global_Sales

vgd_features = ['User_Score', 'Critic_Score', 'User_Count','User_Score']

X = vgd_clean[vgd_features]
from sklearn.model_selection import train_test_split

train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=0)

vgd_model = DecisionTreeRegressor()

vgd_model.fit(train_X, train_y)

val_prediction = vgd_model.predict(val_X)
print(mean_absolute_error(val_y, val_prediction))
"""
Der MAE konnte von 0.691 auf 0.431 gesenkt werden. Das liegt unter dem Durchschnitt, aber noch immer ist die Fehlerabweichung sehr hoch.
"""
from sklearn.linear_model import LinearRegression
clf = LinearRegression()
clf.fit(train_X, train_y)
print(clf.predict(val_X.head()))
print(clf.predict(val_X.tail()))
print(val_y.head())
print(val_y.tail())
clf.score(val_X, val_y)
"""
Der Score der logistischen Regression konnte auf fast 12% Ansteigen, aber auch hier zeigt sich, dass unser Modell weiterhin ungenau ist.

Durch das Betrachten der Daten anhand von grafischen Abbildungen konnten weitere Fehlerquellen erkannt werden. Durch die Entfernung von Ausreißern konnte der MAE um fast die hälfte reduziert werden. Für die weitere Verfeinerung des Modells müssten weitaus mehr Kenntnisse im Bereich Machine Learning vorhanden sein. Die Übung an sich beitet einen guten Einstieg, reicht aber für die Bearbeitung einer Competition nicht aus.
"""