<jupyter_start><jupyter_text>Exkurs: AusreißerentfernungIn dieser Übung geht es um verschiedene Ansätze, Ausreißer in univariaten Datenreihen zu erkennen und zu verarbeiten. Ausreißer sind Beobachtungen, die nicht zu der "typischen" Verteilung der anderen Beobachtungen passen. Das können zum Beispiel extrem große oder kleine Werte sein.<jupyter_code>import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set()
df = pd.read_csv("../data/Library_Usage.csv")<jupyter_output><empty_output><jupyter_text>Wenn wir uns die nach der Größe sortierten Werte der Reihe `Total Checkouts` anschauen, so erkennt man, dass über 95% der Werte weniger als 1000 Ausleihen getätigt haben. Wir haben es hier also mit einigen wenigen extrem großen positiven Ausreißern zu tun.<jupyter_code>x = np.linspace(0, 1, len(df))
y = df['Total Checkouts'].sort_values()

sns.lineplot(x=x, y=y, estimator=None, label="checkouts")
sns.lineplot(x=x, y=1000, estimator=None, label="1000 checkouts").legend()<jupyter_output><empty_output><jupyter_text>Das gleiche Ergebniss erhalten wir, wenn wir uns die oberen **Quantile** anschauen:<jupyter_code>df['Total Checkouts'].quantile((0.9, 0.95, 0.99))<jupyter_output><empty_output><jupyter_text>99% der Beobachtungen haben weniger als 2118 Ausleihen. 95% weniger als 816 und 90% weniger als 424 Ausleihen. Den gleichen Trend, wenn auch nicht so stark, erhalten wir für die `Total Renewals`:<jupyter_code>df['Total Renewals'].quantile((0.9, 0.95, 0.99))<jupyter_output><empty_output><jupyter_text>Ausreißerentfernung IEin häufig angewandtes Verfahren in der Praxis, ist es, die oberen/ unteren `n` Prozent größten/ kleinsten Werte zu entfernen. Zuerst erstellen wir uns zwei boolsche Hilfsvektoren, die die Ausreißer identifizieren:<jupyter_code>alpha = 0.015
outlier_checkouts = df['Total Checkouts'] > df['Total Checkouts'].quantile(1-alpha)
outlier_renewals = df['Total Renewals'] > df['Total Renewals'].quantile(1-alpha)<jupyter_output><empty_output><jupyter_text>Wie viel Prozent der Beobachtungen, sind für beide Spalten, als Ausreißer markiert (Antwort: knapp 0.6 Prozent):<jupyter_code>np.mean(outlier_checkouts & outlier_renewals)<jupyter_output><empty_output><jupyter_text>Zuletzt erstellen wir einen Datensatz, indem die Ausreißer für beide Spalten entfernt sind (`~`: "not", `|`: or"):<jupyter_code>df_clean = df[~(outlier_checkouts | outlier_renewals)]
print(len(df_clean), len(df))<jupyter_output>413195 423448<jupyter_text>Die gleiche Visualisierung wie am Anfang, nur mit dem "bereinigten" Datensatz:<jupyter_code>x = np.linspace(0, 1, len(df_clean))
y = df_clean['Total Checkouts'].sort_values()

sns.lineplot(x=x, y=y, estimator=None, label="checkouts")
sns.lineplot(x=x, y=1000, estimator=None, label="1000 checkouts").legend()
df_clean.to_csv("../data/df_outlier_removed.csv")<jupyter_output><empty_output><jupyter_text>Ausreißerentfernung IIWir verwenden jetzt eine robustere Methode, um Ausreißer zu erkennen. Anstatt einfach die oberen/ unteren n-Prozent Werte zu entfernen, verwenden wir jetzt eine Formel, die auch zur Visualisierung der Ausreißer in Boxplots verwendet wird:<jupyter_code># eine Beobachtungen gilt als Ausreißer, wenn sie größer 
# als die Summe aus dem 75% Quantil und dem 1.5-fachen des Interquartilsabstands ist.

quantiles = df['Total Checkouts'].quantile((0.25, 0.75))
quantiles = quantiles.values # convert pandas Series to a numpy array
quantiles
IQR = quantiles[1] - quantiles[0]
IQR
threshold = quantiles[1] + 1.5*IQR
threshold
outlier_checkouts = df['Total Checkouts'] > threshold
outlier_checkouts.mean()<jupyter_output><empty_output><jupyter_text>Mit dieser Methode, werden fast 15% der Beobachtungen als Ausreißer markiert!<jupyter_code>df_clean = df[~outlier_checkouts]

x = np.linspace(0, 1, len(df_clean))
y = df_clean['Total Checkouts'].sort_values()

sns.lineplot(x=x, y=y, estimator=None, label="checkouts")
sns.lineplot(x=x, y=1000, estimator=None, label="1000 checkouts").legend()<jupyter_output><empty_output><jupyter_text>Ausreißerentfernung IIIAnstatt Werte nach etwas "willkürlichen" Regeln zu entfernen und damit wertvolle Informationen unberücksichtigt zu lassen, kann man auch sogenannte monotone Transformationen der Reihe durchführen, die extrem große Werte einer Reihe "stauchen", also kleiner machen. Monoton bedeutet hierbei, dass die Rangordnung der Werte dabei nicht verändert wird. Zwei solcher Kandidaten sind zum Beispiel die Logarithmus-Funktion und die Wurzel-Funktion:<jupyter_code>x = np.linspace(0, 1000, 1000)

fix, ax = plt.subplots(nrows=1, ncols=2)

sns.lineplot(x=x, y=x**0.5, estimator=None, label="root", ax = ax[0])
sns.lineplot(x=x, y=np.log(x+1), estimator=None, label="logarithm", ax = ax[1])<jupyter_output><empty_output><jupyter_text>Man erkennt, dass beide Funktionen größere Werte stärker stauchen als kleinere Werte. Die Wurzel-Funktion staucht den Wert 1000 zu ca. 32 (32*32 = 1024). Die Logarithmus-Funktion transfomiert den Wert 1000 zu ca. 7 (e^7 = 1097)<jupyter_code>sns.distplot(df['Total Checkouts'])
df['Total Checkouts Sqrt'] = df['Total Checkouts']**(0.5)
df['Total Checkouts Log'] = np.log(df['Total Checkouts']+1)

sns.distplot(df['Total Checkouts Sqrt'])
sns.distplot(df['Total Checkouts Log'])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10953729933899906, 'fraction_numerical': 0.027950897072710104, 'mean_word_length': 7.353312302839116, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '389622', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 2060, 'n_tokens_neox': 1959, 'n_words': 500, 'path': 'content/solutions.files/solutions_outlier.ipynb', 'repo_name': 'cbittner82/data-librarian', 'size': '5295'}