5) Deskriptive Datenanalyse

Beginnen wir wieder mit dem Import von Pandas und laden wir den uns bereits bekannten Datensatz.

In [1]:
import pandas as pd

daten = pd.read_csv("C:\\Datenfiles\\daten.csv")

daten.head(3)
Out[1]:
sex age wohnort volksmusik hardrock
0 1 50 2 2.666667 3.666667
1 1 57 1 1.000000 3.333333
2 2 66 3 2.000000 4.333333

5.1) Häufigkeitsanalyse

Anhand dieser Daten werden wir in diesem Kapitel nun erste deskriptive/explorative Auswertungen vornehmen. Beginnen wir zuerst mit Häufigkeitsanalysen.

Die Funktion value_counts() liefert uns die Häufigkeiten für die Ausprägungen der angegebenen Spalte (Variable).

pandas.Series.value_counts

In [2]:
daten['wohnort'].value_counts()
Out[2]:
3    139
1     95
2     60
Name: wohnort, dtype: int64

Damit die Sortierung aufsteigend nach den Ausprägungen der Variable (und nicht nach Häufigkeiten) erfolgt, fügen wir noch sort_index() an.

In [3]:
daten['wohnort'].value_counts().sort_index() # 'sort_index()' sortiert nach den Kategorien, nicht nach den Häufigkeiten
Out[3]:
1     95
2     60
3    139
Name: wohnort, dtype: int64

Möchten wir nach Häufigkeiten sortieren, fügen wir sort_values() an. Je nachdem, ob wir nach Häufigkeiten auf- oder absteigend sortieren möchten, schreiben wir ascending = True oder ascending = False in die Klammer (wobei True die Standardeinstellung ist, man dies also nicht explizit hinschreiben muss).

In [4]:
daten['wohnort'].value_counts().sort_values(ascending = False)
Out[4]:
3    139
1     95
2     60
Name: wohnort, dtype: int64
In [5]:
daten['wohnort'].value_counts().sort_values() # True ist standard
Out[5]:
2     60
1     95
3    139
Name: wohnort, dtype: int64

Wir können auch den relativen Anteil (d.h. Prozent) jeder einzelnen Merkmalsausprägung ermitteln. Dazu fügen wir normalize = True hinzu. Für die Umrechnung in Prozent wurde hier noch mit 100 multipliziert, die Zahlen wurden dabei zuvor aus Gründen der Übersichtlichkeit auf 4 Dezimalstellen reduziert. Außerdem werden nur die ersten 5 Zeilen angezeigt, da die Variable Alter viele Ausprägungen hat und der Output sonst zu lang wird.

In [6]:
daten['age'].value_counts(sort = False, normalize = True).head().round(4)*100
# Nach Kategoriennummer sortiert und in Prozent (relative Häufigkeiten) angegeben
Out[6]:
15    0.34
20    0.34
21    2.38
22    4.76
23    5.44
Name: age, dtype: float64

Man kann erkennen, dass bspw. nur jeweils 0,34% 15 bzw. 20 Jahre alt sind.

Interessant ist ebenfalls, die kumulierten Summen zu ermitteln. Durch hinzufügen von cumsum() erhalten wir diese. Hier sieht man, dass insgesamt 0,68% bis inkl. 20 Jahre alt sind.

Creating a Cumulative Frequency Column in a Dataframe Python

In [7]:
daten['age'].value_counts(sort = False, normalize = True).cumsum().head().round(4)*100 # kumulative Prozentwerte
Out[7]:
15     0.34
20     0.68
21     3.06
22     7.82
23    13.27
Name: age, dtype: float64
Exkurs: Variablennamen als Attribute des Dataframes

Variablen(Spalten)namen können in neueren Versionen von Pandas nicht mehr nur in der Form dataframename['variablenname'] geschrieben werden, sondern auch als Attribut des Dataframes in der Form dataframename.variablenname. Im Folgenden als Beispiel dieser Schreibweise nochmals der Befehl von vorhin, diesmal in Attributschreibweise.

In [8]:
daten.age.value_counts(sort = False, normalize = True).cumsum().head().round(4)*100
Out[8]:
15     0.34
20     0.68
21     3.06
22     7.82
23    13.27
Name: age, dtype: float64
Grafische Darstellung der Häufigkeiten

Aus den Häufigkeitsdaten können wir eine informative Grafik erstellen. Sollen die relativen und die kumulierten relativen Häufigkeiten in einem Diagramm abgebildet werden, weisen wir beides jeweils einem Objekt (unten mit grafik1 und grafik2 bezeichnet zu. Dann erstellen wir ein neues Dataframe (grafik), fügen diesem Dataframe die beiden vorhin erstellten Objekte hinzu und vergeben gleichzeitig informative Spaltennamen.

In [9]:
grafik1 = daten['age'].value_counts(sort = False, normalize = True).cumsum()*100
grafik2 = daten['age'].value_counts(sort = False, normalize = True)*100
grafik = pd.DataFrame()
grafik ['kumulierte Häufigkeiten'] = grafik1
grafik ['Häufigkeiten'] = grafik2

Den Inhalt des Dataframes veranschaulichen wir nun mit plot.line() als Liniendiagramm. Wie im Output ersichtlich, werden beide Häufigkeiten in der Grafik abgebildet.

In [10]:
ax = grafik.plot.line(rot = 0)
# 'rot' würde eine Drehung (in Grad) der X-Achsen Beschriftung ermöglichen; in diesem Fall erfolgt keine Drehung

"Einfacher" (d.h. mit nur einer Codezeile, welche dafür aber etwas länger ist...) geht es mit folgender direkten Methode:

In [11]:
grafikneu = pd.DataFrame({'kum. H.' : daten['age'].value_counts(sort = False, normalize = True).cumsum()*100,
                     'H.' : daten['age'].value_counts(sort = False, normalize = True)*100})

Wir erhalten das gleiche Ergebnis wie vorhin.

In [12]:
ax = grafikneu.plot.line(rot = 0)

Grafiken können natürlich auch gespeichert werden (mehr dazu in Kapitel 11 bzw. unter untenstehendem Link). Dazu müssen wir die Grafik mit ax.get_figure() zuerst einem neuen Objekt (fig) zuweisen.

savefig()

In [13]:
fig = ax.get_figure()
In [14]:
fig
Out[14]:

Nun können wir die Grafik mit savefig() an einem selbst einzugebenden Pfad speichern (ax.savefig hätte nicht funktioniert), z.B. als PNG Datei mit einer Auflösung von 150 dpi - dies kann frei bestimmt werden, je größer der Wert, desto besser die Auflösung/Qualität, desto höher allerdings auch der Speicherplatzbedarf).

In [15]:
fig.savefig('C:\\Datenfiles\\test.png', dpi=150)

Durch subplots = True kann erreicht werden, dass die beiden Linien jeweils in eigenen Digrammen angezeigt werden. Die Legendenbeschriftung wird dabei von Pandas jeweils dort platziert, wo am ehesten Platz dafür vorhanden ist.

pandas.DataFrame.plot.line

In [16]:
ax = grafik.plot.line(rot = 0, subplots = True)

5.2) Weitere deskriptive / explorative Analysen

Nach den Ausführungen zur Häufigkeitsanalyse geht es nun mit div. Lage- und Streuungsmaßen weiter. Den Anfang macht der arithmetische Mittelwert, den wir mit der Funktion mean() erhalten. Wie alle in weiterer Folge vorgestellten Maße lässt sich auch diese Funktion sowohl auf das ganze Dataframe (also alle Variablen) wie auch auf einzelne Spalten anwenden.

In [17]:
daten.mean() # Mittelwert für alle Variablen im Dataframe, nicht gerundet
Out[17]:
sex            1.435374
age           38.517007
wohnort        2.149660
volksmusik     3.769274
hardrock       2.978458
dtype: float64

Wir können uns die Mittelwerte mit der Funktion round() auch gerundet - bspw. auf 2 Dezimalstellen - ausgeben lassen.

pandas.DataFrame.round

In [18]:
daten.mean().round(2)
Out[18]:
sex            1.44
age           38.52
wohnort        2.15
volksmusik     3.77
hardrock       2.98
dtype: float64

In obiger Ausgabe sind auch nominale und ordinale Variblen enthalten. Sinn macht, den arithmetischen Mittelwert für metrische Variablen zu berechnen. Wie können wir uns den Mittelwert einer Variable (Spalte) ausgeben lassen, bspw. des Alters? Einfach...

In [19]:
daten.age.mean() # oder: daten['age'].mean()
Out[19]:
38.51700680272109

Schöner - bzw. für manche Zwecke der Darstellung in Texten brauchbarer - wäre aber ein gerundeter Wert.

In [20]:
daten.age.mean().round(2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-20-4f3e6a9a7a86> in <module>
----> 1 daten.age.mean().round(2)

AttributeError: 'float' object has no attribute 'round'

Offenbar funktioniert die Funktion round() für einzelne Variablen nicht; zumindest nicht in dieser Schreibweise. Vgl. dazu auch so manche Diskussionen im Internet, bspw.: AttributeError: 'float' object has no attribute 'round'

Möglich ist folgende Schreibweise:

In [21]:
daten[['age']].mean().round(2)
Out[21]:
age    38.52
dtype: float64

Allerdings bekommen wird dann nicht nur den Mittelwert, sondern auch den dtype ausgegeben. Das ist für Texte nicht zu gebrauchen, vgl. folgendes Beispiel:

In [22]:
print("Der Mittelwert des Alters beträgt:", daten[['age']].mean().round(2), "Jahre")
Der Mittelwert des Alters beträgt: age    38.52
dtype: float64 Jahre

Folgende Schreibweise führt zum Erfolg:

In [23]:
round(daten.age.mean(), 2)
Out[23]:
38.52
In [24]:
print("Der Mittelwert des Alters beträgt:", round(daten['age'].mean(), 2), "Jahre")
Der Mittelwert des Alters beträgt: 38.52 Jahre

Es handelt sich dabei um die round() Funktion von Python ('built-in' Funktion round), nicht um die round() Funktion aus Pandas pandas.DataFrame.round

Dieses Problem der Rundung beschäftigt uns auch bei der Ausgabe der Standardabweichung, wie wir gleich sehen werden.

Die Standardabweichung erhalten wir mit std(), sie ist in der selben Einheit wie der Mittelwert, nämlich in Jahren. Fügen wir dies gleich mit einer print() Funktion ein.

pandas.DataFrame.std

In [25]:
print(round(daten.age.std(), 2),"Jahre") # Standardabweichung
13.93 Jahre

Wenn wir schon dabei sind, geben wir gleich den Mittelwert und die Standardabweichung (welche ohnehin zumeist gemeinsam interpretiert werden) in einer leicht lesbaren Form aus.

In [26]:
print("Durchschnittsalter:", round(daten.age.mean(), 2),
      "Jahre (Standardabweichung:", round(daten.age.std(), 2),"Jahre)")
Durchschnittsalter: 38.52 Jahre (Standardabweichung: 13.93 Jahre)

Für die Rundung von Mittelwert und Standardabweichung (wenn wir uns die Werte einer einzelnen Variable (Spalte) ausgeben lassen möchten) benötigen wir also die 'built-in' Funktion round() von Python, und nicht jene von Pandas.

Eine übliche Kennzahl zum Vergleich der Streuung von verschiedenen Variablen ist der Variationskoeffizient. Er berechnet sich aus (Standardabweichung/Mittelwert)*100 (und kann somit als Prozentzahl interpretiert werden).

Sehen wir uns nachfolgend an, welche der beiden Variablen volksmusik und hardrock eine breitere Streuung der Antworten aufweist.

In [27]:
daten.head(3)
Out[27]:
sex age wohnort volksmusik hardrock
0 1 50 2 2.666667 3.666667
1 1 57 1 1.000000 3.333333
2 2 66 3 2.000000 4.333333
In [28]:
vk_vm, vk_hr = ((daten.volksmusik.std())/(daten.volksmusik.mean())*100,
                (daten.hardrock.std())/(daten.hardrock.mean())*100)
In [29]:
print("Die Variable 'Volksmusik' weist einen Variationskoeffizienten von", vk_vm,
      "auf, die Variable 'Hardrock' einen Variationskoeffizienten von", vk_hr)

# Man könnte die Variationskoeffizienten auch noch runden (vgl. Python 'built-in' Funktion 'round()',
# aber dann wäre die Formel noch länger und unübersichtlicher
Die Variable 'Volksmusik' weist einen Variationskoeffizienten von 29.904393232025267 auf, die Variable 'Hardrock' einen Variationskoeffizienten von 36.047401216687

Die breitere Streuung um den Mittelwert weist also die Variable hardrock auf.

Sehen wir uns in weiterer Folge nun noch andere Lage- und Streuunsmaße an.

Den Standardfehler des Mittelwerts erhalten wir mit der Funktion sem(). Ab hier funktioniert übrigens die round() Funktion aus Pandas wieder. Diese ist in der Handhabung einfacher und wird daher von mir bevorzugt, wann immer möglich.

pandas.DataFrame.sem

In [30]:
daten['age'].sem().round(3) # Standardfehler des Mittelwerts
Out[30]:
0.812

Den Median, ein gegenüber Ausreißern robustes Lagemaß (der mittlere Wert einer geordneten Zahlenreihe), erhalten wir mit median(). Er liegt in diesem Fall um rund 2,5 Jahre unter dem Mittelwert, was auf eine leicht rechtsschiefe Verteilung des Alters schließen lässt.

In [31]:
daten['age'].median() # Median
Out[31]:
36.0

Testen wir die Schiefe nach dieser Behauptung gleich mit skew().

pandas.DataFrame.skew

In [32]:
daten['age'].skew().round(3) # Schiefe (in diesem Fall leicht rechtsschief)
Out[32]:
0.661

Der Modalwert (bzw. Modus) ist der häufigste Wert einer Variable; es können auch mehrere Werte gleich häufig vorkommen, dann gibt es mehrere Modalwerte. In diesem Beispiel erhalten wir nach Anwendung der Funktion mode() das Ergebnis, dass ein Alter von 25 sowie von 26 Jahren jeweils am häufigsten vorkommt - es gibt 2 Modalwerte.

pandas.DataFrame.mode

In [33]:
daten['age'].mode() # Modalwert (2 in diesem Fall)
Out[33]:
0    25
1    26
dtype: int64

Überprüfen wir dieses Ergebnis mit bereits bekannter Methode, nämlich der Funktion value_counts(), so sehen wir, das tatsächlich 25 Jahre und 26 Jahre jeweils gleich oft am häufigsten vorkommt, nämlich je 17 Mal.

In [34]:
daten['age'].value_counts().sort_values(ascending = False).head(10) # output mit 'head()' eingeschränkt, da sonst zu lang
Out[34]:
25    17
26    17
23    16
22    14
24    12
55    11
36    11
27    11
50    10
35    10
Name: age, dtype: int64

Den kleinsten bzw. den größten Wert einer Variable liefern uns min() bzw. max().

pandas.DataFrame.min

In [35]:
daten['age'].min() # Minimaler Wert
Out[35]:
15
In [36]:
daten['age'].max() # Maximaler Wert
Out[36]:
92

Mit quantile() können wir uns beliebige, also selbst definierbare, Quantile einer Variable ausgeben lassen. In folgenden Beispiel sehen wir uns die Dezile für die 3 metrischen Variablen im Datensatz an. Man beachte: Wird mehr als eine Variable des Dataframes ausgewählt, müssen die Variablen in doppelter eckiger Klammer angeführt werden!

pandas.DataFrame.quantile

Spezielle Quantile

In [37]:
daten[['age', 'volksmusik', 'hardrock']].quantile([0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]) # Quantile, beliebige einstellbar
# Durch die Angabe von '0' und '1' erhält man den Minimal- bzw. Maximalwert
Out[37]:
age volksmusik hardrock
0.0 15.0 1.000000 1.000000
0.1 23.0 2.000000 1.333333
0.2 25.0 3.000000 2.000000
0.3 27.0 3.333333 2.333333
0.4 31.0 3.666667 2.666667
0.5 36.0 4.000000 3.000000
0.6 41.0 4.333333 3.333333
0.7 48.0 4.666667 3.666667
0.8 51.4 5.000000 4.000000
0.9 57.0 5.000000 4.333333
1.0 92.0 5.000000 5.000000

Die Funktion describe() schließlich liefert uns auf einen Schlag gleich mehrere gebräuchliche Kennzahlen, entweder für alle Variablen im Datensatz oder für ausgewählte Variablen.

pandas.DataFrame.describe

In [38]:
daten.describe()
Out[38]:
sex age wohnort volksmusik hardrock
count 294.000000 294.000000 294.00000 294.000000 294.000000
mean 1.435374 38.517007 2.14966 3.769274 2.978458
std 0.496651 13.930032 0.88100 1.127179 1.073657
min 1.000000 15.000000 1.00000 1.000000 1.000000
25% 1.000000 26.000000 1.00000 3.000000 2.333333
50% 1.000000 36.000000 2.00000 4.000000 3.000000
75% 2.000000 50.000000 3.00000 4.666667 3.666667
max 2.000000 92.000000 3.00000 5.000000 5.000000

Solche tabellarischen Übersichten können wir uns auch gerundet ausgeben lassen; das ist ev. übersichtlicher.

In [41]:
daten.describe().round(2)
Out[41]:
sex age wohnort volksmusik hardrock
count 294.00 294.00 294.00 294.00 294.00
mean 1.44 38.52 2.15 3.77 2.98
std 0.50 13.93 0.88 1.13 1.07
min 1.00 15.00 1.00 1.00 1.00
25% 1.00 26.00 1.00 3.00 2.33
50% 1.00 36.00 2.00 4.00 3.00
75% 2.00 50.00 3.00 4.67 3.67
max 2.00 92.00 3.00 5.00 5.00

Im folgenden Beispiel wählen wir nur zwei metrische Variablen aus. Ausserdem wollen wir nicht alle standardmäßig ausgegebenen Kennzahlen, sondern nur die Anzahl (count), den Mittelwert (mean), die Standardabweichung (std) sowie den Median (50%). Diese Auswahl übergeben wir mit loc (wieder in doppelter eckiger Klammer, da wir mehrere Zeilen auswählen).

pandas.DataFrame.loc

In [42]:
daten[['age', 'hardrock']].describe().loc[['count', 'mean', 'std', '50%']] # 50% Quantil = Median
Out[42]:
age hardrock
count 294.000000 294.000000
mean 38.517007 2.978458
std 13.930032 1.073657
50% 36.000000 3.000000

Die Art der Darstellung kann mit unstack() auch geändert werden. Die Variablen sind nun nicht mehr horizontal nebeneinander sondern vertikal untereinander gelistet.

In [43]:
daten.describe().loc[['mean', 'std']].unstack().round(2)
Out[43]:
sex         mean     1.44
            std      0.50
age         mean    38.52
            std     13.93
wohnort     mean     2.15
            std      0.88
volksmusik  mean     3.77
            std      1.13
hardrock    mean     2.98
            std      1.07
dtype: float64
M-Estimator nach Huber

Bei den M-Schätzern handelt es sich um eine etwas fortgeschrittenere Methode; da sie aber gut zum Thema dieses Kapitels passen sei hier kurz gezeigt, wie man den M-Schätzer nach Huber erhalten kann.

M-Schätzer sind im Vergleich zum arithmetischen Mittelwert robuster gegenüber Ausreißern, weil sie diese bei Berechnung des Mittelwerts geringer gewichten. Ein bekannter M-Schätzer ist jener von Huber, bei dem man einen Bereich definieren kann, innerhalb dessen sämtliche Werte voll gewichtet werden. Außerhalb dieses Bereichs werden Werte mit zunehmender Entfernung immer geringer gewichtet (im Gegensatz zu manchen anderen M-Schätzern aber nie mit 0).

Die Berechnung von M-Schätzern ist mit Pandas nicht möglich, wir greifen dazu auf eine Funktion des Pakets Statsmodels zurück.

statsmodels.robust.scale.Huber

M-Schätzer

Weight Functions

Für (einigermaßen) normalverteilte Daten zeigt folgende Tabelle eine Übersicht gebräuchlicher Tuningkonstanten k (bzw. c auf Englisch) sowie den Bereich der Daten, der voll gewichtet wird. Kleinere Tuningkonstanten gewichten somit mehr Daten geringer als größere Tuningkonstanten. In der dritten Spalten stehen die üblichen Bezeichnungen (Hubers Proposal 2).

Tuningkonstante c voll gewichteter Bereich Bezeichnung
1.960 ~95% H19
1.645 ~90% H16
1.282 ~80% H12
0.842 ~60% H8
In [44]:
import statsmodels.api as sm

sm.robust.scale.Huber(c = 1.2)(daten['age']) # c = die Tuningkonstante, vgl. obige Tabelle
Out[44]:
(array(37.68428874), array(15.33869259))

Die Ausgabe oben zeigt als erste Zahl den geschätzten Mittelwert nach Huber und als zweite Zahl die dazugehörige Streuung. Diese beiden Werte dienen als robuste Alternative zum arithmetischen Mittelwert und dessen Standardabweichung. Die Grafik unten dient nur dazu, die div. Mittelwerte (Mittelwert, Median, M-Schätzer) und deren Streuungen zu verorten.

In [45]:
ax = daten.age.plot.hist(bins = 20) # Histogramm mit (max.) 20 Balken

Wenn wir schon bei robusten Lage- und Streuungsmaßen sind, rufen wir uns nochmal den Median in Erinnerung. Auch dieser ist ein robustes Lagemaß.

In [46]:
daten.age.median() 
Out[46]:
36.0

Dazu liegt mit der 'median absolute deviation' auch ein entsprechendes Streuungsmaß vor, welches auch mit der Funktion mad() ausgegben werden kann.

pandas.DataFrame.mad

In [52]:
daten.age.mad()
Out[52]:
11.80832060715442

Analog zum Variationskoeffizienten, welcher den arithmetischen Mittelwert und die dazugehörige Standardabweichung verwendet, kann man als robustes Äquivalent auch (median absolute deviation)/(median)*100 berechnen.

In [48]:
robuster_vk = ((daten.age.mad())/(daten.age.median()))*100
In [49]:
normaler_vk = ((daten.age.std())/(daten.age.mean()))*100
In [56]:
print("Robuster Variationskoeffizient:", round(robuster_vk, 2), "% vs. normaler Variationskoeffizient:", round(normaler_vk, 2), "%")
Robuster Variationskoeffizient: 32.8 % vs. normaler Variationskoeffizient: 36.17 %