Pandas

Hier wollen wir nun den Iris Datensatz mit Hilfe von pandas Untersuchen. Dieser Datensatz eignet sich hervorragend um erste Schritte in Pandas zu machen.

# load all modules
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
plt.style.use('ggplot')

import seaborn as sns

Für viele Python-Bibliotheken können wir die Version mit __version__ auslesen.

print(np.__version__)
print(pd.__version__)
print(matplotlib.__version__)
print(sns.__version__)
1.21.2
1.3.3
3.4.3
0.11.2

Python eignet sich hervorragend für die Datenanalyse. Für einen schnellen Start greifen wir auf Datensätze zurück, welche in der Literatur oft besprochen und mit seaborn, sk-learn oder pytorch ausgeliefert werden.

# get all datasets
sns.get_dataset_names()
['anagrams',
 'anscombe',
 'attention',
 'brain_networks',
 'car_crashes',
 'diamonds',
 'dots',
 'exercise',
 'flights',
 'fmri',
 'gammas',
 'geyser',
 'iris',
 'mpg',
 'penguins',
 'planets',
 'tips',
 'titanic']

Iris Datensatz

Einer der bekanntesten Datensätze ist wohl der Iris Datensatz. Oft wird dieser verwendet um Methoden aus dem maschinellen Lernen einzuführen.

'iris' in sns.get_dataset_names()
True

Pandas Grundfunktionen

seaborn gibt die eingebauten Datensätze schon als pandas DataFrame aus. Wir sehen wie eng die Bibliotheken numpy, matplotlib, seaborn und pandas miteinander verzahnt sind.

Wir könne also den Iris Datensatz laden.

iris_df = sns.load_dataset("iris")

Es handelt sich um einen neuen Datentyp welcher mit der pandas Bibliothek ausgeliefert wird.

type(iris_df)
pandas.core.frame.DataFrame

Oft ist es hilfreich die größer des DataFrames zu untersuchen.

iris_df.shape
(150, 5)

Eine erste Übersicht bekommen wir mit:

iris_df
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
... ... ... ... ... ...
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica

150 rows × 5 columns

Mit head werden die ersten 5 Zeilen eines Datensatzes ausgegeben.

iris_df.head()
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa

Die Anzahl der Zeilen lassen mit einem Argument steuern.

iris_df.head(10)
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
5 5.4 3.9 1.7 0.4 setosa
6 4.6 3.4 1.4 0.3 setosa
7 5.0 3.4 1.5 0.2 setosa
8 4.4 2.9 1.4 0.2 setosa
9 4.9 3.1 1.5 0.1 setosa

Mit tail() werden die letzten 5 Zeil des DataFrames ausgegeben.

iris_df.tail()
sepal_length sepal_width petal_length petal_width species
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica
iris_df.tail(10)
sepal_length sepal_width petal_length petal_width species
140 6.7 3.1 5.6 2.4 virginica
141 6.9 3.1 5.1 2.3 virginica
142 5.8 2.7 5.1 1.9 virginica
143 6.8 3.2 5.9 2.3 virginica
144 6.7 3.3 5.7 2.5 virginica
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica

Mit info() bekommt man eine erste Zusammenfassung des Datensatzes. Vor allem sehen wir hier auch die Datentypen. Zum Beispiel können nur für nummerische Datentypen gewisse statische Merkmale berechnet werden.

iris_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   sepal_length  150 non-null    float64
 1   sepal_width   150 non-null    float64
 2   petal_length  150 non-null    float64
 3   petal_width   150 non-null    float64
 4   species       150 non-null    object 
dtypes: float64(4), object(1)
memory usage: 6.0+ KB

Statitische Merkmale

Für einige Datensätze ist der Befehl describe() sehr hilfreich. Es werden gewisse statistische Eigenschaften des Datensatzes berechnet.

iris_df.describe()
sepal_length sepal_width petal_length petal_width
count 150.000000 150.000000 150.000000 150.000000
mean 5.843333 3.057333 3.758000 1.199333
std 0.828066 0.435866 1.765298 0.762238
min 4.300000 2.000000 1.000000 0.100000
25% 5.100000 2.800000 1.600000 0.300000
50% 5.800000 3.000000 4.350000 1.300000
75% 6.400000 3.300000 5.100000 1.800000
max 7.900000 4.400000 6.900000 2.500000

Solche Merkmale lassen sich auch einfach selbst mit Pandas ermitteln. Dafür selektieren wir nur jene Spalten welche nummerische Werte beinhalten.

cols=['sepal_length','sepal_width','petal_width','petal_width']

Der sample mean ist das arithmetische Mittel

\[ \bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i \]
iris_df[cols].mean()
sepal_length    5.843333
sepal_width     3.057333
petal_width     1.199333
petal_width     1.199333
dtype: float64

Das sample median ist der „Mittelpunkt“ eines geordneten Datensatzes.

\[\begin{split} \tilde{x} = \begin{cases} x[\frac{n+1}{2}] & \text{wenn n ungerade} \\ \frac{1}{2}\left(x[\frac{n}{2}]+x[\frac{n}{2}+1] \right) & \text{wenn n gerade} \end{cases} \end{split}\]
iris_df[cols].median()
sepal_length    5.8
sepal_width     3.0
petal_width     1.3
petal_width     1.3
dtype: float64

Die Stichprobenvarianz (sample variance) ist ein Maß für die Streuung, ungefähr der durchschnittliche quadrierte Abstand eines Datenpunkts vom arithmetischen Mittelwert (sample mean). Die Standardabweichung (standard deviation) ist die Quadratwurzel aus der Varianz und wird als „durchschnittlicher“ Abstand eines Datenpunkts vom Mittelwert interpretiert.

Stichprobenvarianz (sample variance)

\[ s^2 = \frac{1}{n-1} \sum(x_i-\bar{x})^2 \]

Standardabweichung (standard deviation)

\[ s = \sqrt(s) \]
iris_df[cols].var()
sepal_length    0.685694
sepal_width     0.189979
petal_width     0.581006
petal_width     0.581006
dtype: float64
iris_df[cols].std()
sepal_length    0.828066
sepal_width     0.435866
petal_width     0.762238
petal_width     0.762238
dtype: float64

Das \(p\)-te Perzentil ist die Zahl im Datensatz, bei der etwa \(𝑝%\) der Daten kleiner als diese Zahl sind. Diese Zahl wird auch als Quantil bezeichnet

iris_df[cols].quantile(.1)   # the 10th percentile
sepal_length    4.8
sepal_width     2.5
petal_width     0.2
petal_width     0.2
Name: 0.1, dtype: float64
iris_df[cols].quantile(.95)    # the 95th percentile
sepal_length    7.255
sepal_width     3.800
petal_width     2.300
petal_width     2.300
Name: 0.95, dtype: float64
iris_df[cols].quantile(.75)    # commonly known as the third quartile
sepal_length    6.4
sepal_width     3.3
petal_width     1.8
petal_width     1.8
Name: 0.75, dtype: float64
iris_df[cols].quantile(.25)    # commonly known as the first quartile
sepal_length    5.1
sepal_width     2.8
petal_width     0.3
petal_width     0.3
Name: 0.25, dtype: float64

Wenn \(Q_i\) das \(i\)-te Quartil bezeichnet, ist der innere Quartilsbereich (IQR=inner-quartile range) die Differenz zwischen dem dritten Quartil und dem ersten Quartil.

\[ IQR = Q_3-Q_1 \]
iris_df[cols].quantile(.75) - iris_df[cols].quantile(.25)
sepal_length    1.3
sepal_width     0.5
petal_width     1.5
petal_width     1.5
dtype: float64

Weitere wichtige Eigenschaften sind sicher durch das Minimum und das Maximum gegeben.

iris_df[cols].min()
sepal_length    4.3
sepal_width     2.0
petal_width     0.1
petal_width     0.1
dtype: float64
iris_df[cols].max()
sepal_length    7.9
sepal_width     4.4
petal_width     2.5
petal_width     2.5
dtype: float64

Pandas Spalten

Manchmal wollen wir aber nur eine Spalte untersuchen. Der Befehl lautet dann:

iris_df['sepal_length'].describe()
count    150.000000
mean       5.843333
std        0.828066
min        4.300000
25%        5.100000
50%        5.800000
75%        6.400000
max        7.900000
Name: sepal_length, dtype: float64

Oder eben als Einzelbefehle:

print("count():", iris_df['sepal_length'].count())
print("mean():",iris_df['sepal_length'].mean())
print("std():",iris_df['sepal_length'].std())
print("min():",iris_df['sepal_length'].min())
print("25%:",iris_df['sepal_length'].quantile(.25))
print("50%:",iris_df['sepal_length'].quantile(.50))
print("75%:",iris_df['sepal_length'].quantile(.75))
print("max():",iris_df['sepal_length'].max())
print("--------------------------------------")
print("var():",iris_df['sepal_length'].var())
print("median():", iris_df['sepal_length'].median())
count(): 150
mean(): 5.843333333333334
std(): 0.828066127977863
min(): 4.3
25%: 5.1
50%: 5.8
75%: 6.4
max(): 7.9
--------------------------------------
var(): 0.6856935123042507
median(): 5.8

Pandas groupby

Wie wir schon wissen, repräsentiert dieser Datensatz 3 verschiedene Varianten einer Blume.

iris_df['species'].unique()
array(['setosa', 'versicolor', 'virginica'], dtype=object)

Wir können für jede Variante Statistiken einzeln berechnen. Dazu nutzen wir die Methode groupby.

iris_grps = iris_df.groupby("species")
iris_grps.describe().T
species setosa versicolor virginica
sepal_length count 50.000000 50.000000 50.000000
mean 5.006000 5.936000 6.588000
std 0.352490 0.516171 0.635880
min 4.300000 4.900000 4.900000
25% 4.800000 5.600000 6.225000
50% 5.000000 5.900000 6.500000
75% 5.200000 6.300000 6.900000
max 5.800000 7.000000 7.900000
sepal_width count 50.000000 50.000000 50.000000
mean 3.428000 2.770000 2.974000
std 0.379064 0.313798 0.322497
min 2.300000 2.000000 2.200000
25% 3.200000 2.525000 2.800000
50% 3.400000 2.800000 3.000000
75% 3.675000 3.000000 3.175000
max 4.400000 3.400000 3.800000
petal_length count 50.000000 50.000000 50.000000
mean 1.462000 4.260000 5.552000
std 0.173664 0.469911 0.551895
min 1.000000 3.000000 4.500000
25% 1.400000 4.000000 5.100000
50% 1.500000 4.350000 5.550000
75% 1.575000 4.600000 5.875000
max 1.900000 5.100000 6.900000
petal_width count 50.000000 50.000000 50.000000
mean 0.246000 1.326000 2.026000
std 0.105386 0.197753 0.274650
min 0.100000 1.000000 1.400000
25% 0.200000 1.200000 1.800000
50% 0.200000 1.300000 2.000000
75% 0.300000 1.500000 2.300000
max 0.600000 1.800000 2.500000

Die gruppierten Daten können wir erneut statistisch untersuchen. Dadurch können wir Einsichten gewinnen, welche Merkmale für ein Klassifizierung geeignet sein könnten.

iris_grps.mean()
sepal_length sepal_width petal_length petal_width
species
setosa 5.006 3.428 1.462 0.246
versicolor 5.936 2.770 4.260 1.326
virginica 6.588 2.974 5.552 2.026
iris_grps.std()
sepal_length sepal_width petal_length petal_width
species
setosa 0.352490 0.379064 0.173664 0.105386
versicolor 0.516171 0.313798 0.469911 0.197753
virginica 0.635880 0.322497 0.551895 0.274650
iris_grps.quantile(.75)
sepal_length sepal_width petal_length petal_width
species
setosa 5.2 3.675 1.575 0.3
versicolor 6.3 3.000 4.600 1.5
virginica 6.9 3.175 5.875 2.3
iris_grps.quantile(.75) - iris_grps.quantile(.25)
sepal_length sepal_width petal_length petal_width
species
setosa 0.400 0.475 0.175 0.1
versicolor 0.700 0.475 0.600 0.3
virginica 0.675 0.375 0.775 0.5

Pandas Diagramme

iris_df.plot()
<AxesSubplot:>
../_images/pandas_67_1.png
iris_df.plot.area(stacked=False)
<AxesSubplot:>
../_images/pandas_68_1.png
iris_df.plot.area()
<AxesSubplot:>
../_images/pandas_69_1.png
plt.figure(figsize=(10,5))
iris_df.plot.box()
<AxesSubplot:>
<Figure size 720x360 with 0 Axes>
../_images/pandas_70_2.png
iris_df.plot.box(vert=False)
<AxesSubplot:>
../_images/pandas_71_1.png
iris_df.plot.hist(bins=20)
<AxesSubplot:ylabel='Frequency'>
../_images/pandas_72_1.png
iris_df.plot.hist(stacked=True, bins=20)
<AxesSubplot:ylabel='Frequency'>
../_images/pandas_73_1.png
from pandas.plotting import radviz
iris_df.head(1)
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
radviz(iris_df,'species')
<AxesSubplot:>
../_images/pandas_76_1.png

Scatter

name2color = {'setosa':'red','versicolor':'blue','virginica':'green'}
str(iris_df.species.unique())
"['setosa' 'versicolor' 'virginica']"
scatter1 = iris_df.plot.scatter(x = 'sepal_length', y = 'sepal_width', c=iris_df['species'].map(name2color))
../_images/pandas_80_0.png
scatter1 = iris_df.plot.scatter(x = 'petal_length', y = 'petal_width', c=iris_df['species'].map(name2color))
../_images/pandas_81_0.png
pd.plotting.scatter_matrix(iris_df, c=n2c, figsize=(15, 15), marker='o',
                                 hist_kwds={'bins': 20}, s=60, alpha=.8);
../_images/pandas_82_0.png

Es gibt noch einige weitere Möglichkeiten Plots in pandas zu erstellen. Eine Beschreibung findet man unter https://pandas.pydata.org/pandas-docs/stable/reference/plotting.html.

Fazit

Wir haben einen Datensatz mit pandas untersucht. Für viele Aufgaben in der Datenanalyse ist pandas sehr gut geeignet. Dazu zählen das Einlesen, Bereinigen, Analysieren und Plotten der Daten.

Das Plotten mit den eingebauten pandas Funktionen liefert erste Ergebnisse. Mit seaborn sind oft optische schönere Ergebnisse zu erreichen. Viele Schnittstellen sind unter seaborn einfacher zu handhaben. Auch ist das Hinzufügen von Legenden unter seaborn elegant möglich.

pandas und seaborn laden gemeinsam ein, sich spielerisch der Datenanalyse zu nähern. Einfach einen Datensatz von Internet runterladen und damit spielen.