Eintrag

Zusammenfassung des Kaggle-„Pandas“-Kurses (1) – Lektion 1–3

Praxisleitfaden zu Pandas: Daten einlesen/schreiben, DataFrame & Series, Indexing mit loc/iloc, Filter, Zuweisungen, describe(), map/apply. Zusammenfassung der Kaggle‑Lektionen 1–3.

Zusammenfassung des Kaggle-„Pandas“-Kurses (1) – Lektion 1–3

Hier fasse ich das zusammen, was ich im Pandas-Kurs von Kaggle gelernt habe.
Da der Umfang recht groß ist, habe ich es in zwei Teile aufgeteilt.

Zertifikat über den Abschluss

Lektion 1. Erstellen, Lesen und Schreiben

Pandas importieren

1
import pandas as pd

Pandas hat zwei zentrale Objekttypen: DataFrame und Series.

DataFrame

Ein DataFrame kann man sich als Tabelle bzw. Matrix vorstellen. Er besteht aus einer Matrix unabhängiger Einträge, wobei jeder Eintrag einen bestimmten Wert besitzt und genau einer Zeile bzw. einem Datensatz sowie genau einer Spalte zugeordnet ist.

1
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
 YesNo
050131
1212

Einträge in einem DataFrame müssen nicht unbedingt numerisch sein. Im folgenden Beispiel enthält der DataFrame Zeichenketten (von Nutzerinnen und Nutzern hinterlassene Rezensionen).

1
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
 BobSue
0I liked it.Pretty good.
1It was awful.Bland.

Ein DataFrame wird mit dem Konstruktor pd.DataFrame() erzeugt und dabei mit Python-Dict-Syntax deklariert: Die Keys sind die Spaltennamen, die Values Listen mit den einzutragenden Werten. Das ist die Standardmethode, einen neuen DataFrame anzulegen.

Bei der Deklaration werden Spaltenlabels als Spaltennamen angegeben; Zeilenlabels werden, falls nicht spezifiziert, automatisch als 0, 1, 2, … vergeben. Bei Bedarf kann man die Zeilenlabels manuell festlegen. Die Liste der Zeilenlabels heißt beim DataFrame Index und kann über den Konstruktorparameter index gesetzt werden.

1
2
3
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 
              'Sue': ['Pretty good.', 'Bland.']},
             index=['Product A', 'Product B'])
 BobSue
Product AI liked it.Pretty good.
Product BIt was awful.Bland.

Series

Eine Series ist eine Folge (sequence) von Datenwerten bzw. ein Vektor.

1
pd.Series([1, 2, 3, 4, 5])

Eine Series entspricht im Wesentlichen einer einzelnen Spalte eines DataFrames. Entsprechend kann man auch hier einen Index setzen; statt eines „Spaltennamens“ hat sie jedoch lediglich einen einfachen name.

1
pd.Series([30, 35, 40], index=['12015 Sales', '12016 Sales', '12017 Sales'], name='Product A')
1
2
3
4
12015 Sales    30
12016 Sales    35
12017 Sales    40
Name: Product A, dtype: int64

Series und DataFrames sind eng miteinander verwandt. Man kann sich einen DataFrame hilfreich als Kollektion von Series denken.

Daten aus Dateien einlesen

In vielen Fällen schreibt man Daten nicht selbst, sondern übernimmt sie aus bestehenden Quellen. Daten können in verschiedenen Formaten vorliegen; das grundlegendste ist CSV. Eine CSV-Datei sieht typischerweise so aus:

Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11

CSV ist also eine Tabelle, in der Werte durch Kommas (comma) getrennt sind – daher „Comma-Separated Values“, CSV.

CSV-Daten liest man mit pd.read_csv() in einen DataFrame ein.

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv")

Mit dem Attribut shape lässt sich die Form des DataFrames abfragen.

1
product_reviews.shape
1
(129971, 14)

Die obige Ausgabe bedeutet: 129971 Datensätze (Zeilen) und 14 Spalten.

Mit der Methode head() sieht man die ersten fünf Zeilen.

1
product_reviews.head()

Die Funktion pd.read_csv() besitzt über 30 Parameter. Enthält die CSV-Datei z. B. bereits einen eigenen Index, kann man mit index_col angeben, dass diese Spalte als Index verwendet wird, statt dass Pandas automatisch indiziert.

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv", index_col=0)

Daten in Dateien schreiben

Mit der Methode to_csv() kann man einen DataFrame als CSV exportieren. Beispiel:

1
product_reviews.to_csv("../output/product-reviews/example-data.csv")

Lektion 2. Indexing, Selecting & Assigning

Das Auswählen bestimmter Werte aus einem Pandas-DataFrame oder einer Series ist Schritt in fast jeder Datenaufgabe. Es ist daher wichtig, früh effizient auswählen zu lernen.

Python-native Zugriffe

Native Python-Objekte bieten gute Indexing-Mechanismen, und Pandas stellt dieselben Mechanismen bereit.

Objekteigenschaften

In Python greift man auf den Wert einer Objekteigenschaft über deren Namen zu. Hat z. B. das Objekt example_obj ein Attribut title, kann man example_obj.title schreiben. Für DataFrame-Spalten gilt dasselbe.

1
reviews.country
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Dict-Indexierung

Beim Dict-Typ greift man mit dem Indexoperator ([]) auf Werte zu. Für DataFrame-Spalten gilt das ebenso.

1
reviews['country']
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Beide Varianten – Attributzugriff und Dict-Indexierung – sind gültig. Der Dict-Stil hat den Vorteil, dass er auch Spaltennamen mit Leerzeichen oder anderen reservierten Zeichen unterstützt (z. B. geht reviews['country providence'], während reviews.country providence nicht funktioniert).

Auch innerhalb der so ausgewählten Series kann man wiederum mit dem Indexoperator auf Einzelwerte zugreifen.

1
reviews['country'][0]
1
'Italy'

Pandas-spezifische Zugriffe

Neben den oben beschriebenen Zugriffen bietet Pandas die speziellen Zugriffsattribute loc und iloc.

Positionsbasiertes Auswählen

Mit iloc führt man eine positionsbasierte Auswahl (index-based selection) durch. Man wählt anhand der ganzzahligen Position im Datenraster.

Beispielsweise wählt Folgendes die erste Zeile:

1
reviews.iloc[0]
1
2
3
4
5
6
country                                                    Italy
description    Aromas include tropical fruit, broom, brimston...
                                     ...                        
variety                                              White Blend
winery                                                   Nicosia
Name: 0, Length: 13, dtype: object

Im Gegensatz zum Python-Stil (zuerst Spalte, dann Zeile) wählt iloc zuerst Zeilen, dann Spalten. Die erste Spalte erhält man so:

1
reviews.iloc[:, 0]
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Hier wählt : alle Zeilen, danach wird die erste Spalte ausgewählt. Möchte man z. B. die zweite (1) und dritte (2) Zeile der ersten Spalte, schreibt man:

1
reviews.iloc[1:3, 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

Oder man übergibt eine Liste:

1
reviews.iloc[[1, 2], 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

Mit negativen Indizes kann man vom Ende aus wählen, z. B. die letzten fünf Zeilen:

1
reviews.iloc[-5:]

Labelbasierte Auswahl

Mit loc führt man eine labelbasierte Auswahl (label-based selection) durch. Hier wird nicht die Position, sondern der Wert des Index verwendet.

Beispiel: Der Eintrag in der Spalte ‘country’ der Zeile mit Index 0:

1
reviews.loc[0, 'country']
1
'Italy'

iloc ignoriert den DataFrame-Index und behandelt die Daten als reine Matrix; Zugriffe erfolgen positionsbasiert. loc nutzt hingegen die Indexinformationen. Da Indizes oft semantisch bedeutsam sind, ist loc in vielen Fällen intuitiver.

Unterschied der Bereichsspezifikation bei iloc und loc

iloc verwendet die Standard-Indexierung aus der Python-Standardbibliothek: 0:10 bedeutet das halboffene Intervall 0 bis strikt kleiner 10, also 0,...,9.

loc interpretiert Bereiche als geschlossen: 0:10 bedeutet 0 bis 10 einschließlich, also 0,...,10.

Der Grund: loc kann neben ganzen Zahlen auch andere Standard-Datentypen als Index verwenden. Hat man z. B. Indexwerte Apples, ..., Potatoes, ... und möchte alle Kulturen von ‘Apples’ bis ‘Potatoes’ in alphabetischer Reihenfolge auswählen, ist es wesentlich intuitiver, df.loc['Apples':'Potatoes'] zu schreiben als über einen „künstlichen“ oberen Schrankenwert wie ‘Potatoet’ zu gehen. Für Nicht-Integer-Indizes ist die geschlossene Intervallschreibweise meist natürlicher, daher folgt loc dieser Konvention.

Abgesehen davon verhalten sich beide ansonsten gleich.

Persönlich verwende ich bei aufsteigend sortierten Integer-Indizes und Bereichsauswahlen mit : oft iloc, um Verwechslungen wegen der unterschiedlichen Bereichssemantik zu vermeiden; in anderen Fällen bevorzuge ich das intuitivere loc.

Mit dem Index arbeiten

Man kann den Index nach Bedarf anpassen. Mit set_index() lässt sich z. B. eine bestimmte Spalte als neuer Index setzen:

1
reviews.set_index("title")

Bedingte Auswahl

Bisher haben wir über strukturelle Eigenschaften des DataFrames selektiert. Darüber hinaus kann man Daten nach komplexeren Bedingungen filtern.

Beispiel: Aus einem DataFrame mit Weininformationen sollen nur die italienischen Weine mit einer Bewertung ab 90 Punkten ausgewählt werden.

1
reviews.country == 'Italy'

Die Bedingung ergibt eine Series aus True/False-Werten.

1
2
3
4
5
6
0          True
1         False
          ...  
129969    False
129970    False
Name: country, Length: 129971, dtype: bool

loc ist zwar labelbasiert, akzeptiert aber auch boolesche Arrays oder sortierbare boolesche Series. So selektiert man die italienischen Weine:

1
reviews.loc[reviews.country == 'Italy']

Mehrere Bedingungen lassen sich mit & bzw. | verknüpfen. Italienisch und mindestens 90 Punkte:

1
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]

Italienisch oder mindestens 90 Punkte:

1
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]

Pandas bringt zudem einige eingebaute Selektoren mit, u. a. isin und isnull/notnull.

isin prüft, ob ein Wert in einer Liste enthalten ist, und liefert eine boolesche Maske, mit der man filtern kann. Beispiel: italienische oder französische Weine:

1
reviews.loc[reviews.country.isin(['Italy', 'France'])]

isna/notna filtern nach fehlenden (NaN) bzw. nicht fehlenden Werten. Beispiel: nur Weine mit vorhandenem Preis:

1
reviews.loc[reviews.price.notna()]

Anmerkung: Im ursprünglichen Kaggle-Kurs nicht erwähnt, aber iloc akzeptiert ebenfalls boolesche Arrays. Im Unterschied zu loc werden jedoch nur Arrays, nicht Series unterstützt, wodurch sich obige Muster weniger bequem anwenden lassen.

Zuweisungen

Man kann in einem DataFrame neue Daten zuweisen oder bestehende überschreiben.

1
2
reviews['critic'] = 'everyone'
reviews['critic']
1
2
3
4
5
6
0         everyone
1         everyone
            ...   
129969    everyone
129970    everyone
Name: critic, Length: 129971, dtype: object
1
2
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
1
2
3
4
5
6
0         129971
1         129970
           ...  
129969         2
129970         1
Name: index_backwards, Length: 129971, dtype: int64

Lektion 3. Summary Functions and Maps

Überblick über die Daten

Die Methode describe() liefert einen hochrangigen Überblick über eine gegebene Spalte.

1
reviews.points.describe()
1
2
3
4
5
6
count    129971.000000
mean         88.447138
             ...      
75%          91.000000
max         100.000000
Name: points, Length: 8, dtype: float64

Die Ausgabe von describe() hängt vom Datentyp ab. Für Strings (nicht-numerische Daten) sieht sie z. B. so aus:

1
reviews.taster_name.describe()
1
2
3
4
5
count         103727
unique            19
top       Roger Voss
freq           25514
Name: taster_name, dtype: object

Man kann auch gezielt einzelne Statistiken abfragen.

1
reviews.points.mean()
1
88.44713820775404
1
reviews.taster_name.unique()
1
2
3
4
5
6
7
array(['Kerin O’Keefe', 'Roger Voss', 'Paul Gregutt',
       'Alexander Peartree', 'Michael Schachner', 'Anna Lee C. Iijima',
       'Virginie Boone', 'Matt Kettmann', nan, 'Sean P. Sullivan',
       'Jim Gordon', 'Joe Czerwinski', 'Anne Krebiehl\xa0MW',
       'Lauren Buzzeo', 'Mike DeSimone', 'Jeff Jenssen',
       'Susan Kostrzewa', 'Carrie Dykes', 'Fiona Adams',
       'Christina Pickard'], dtype=object)

Möchte man wissen, wie oft jeder eindeutige Wert in einer Spalte vorkommt, verwendet man value_counts().

1
reviews.taster_name.value_counts()
1
2
3
4
5
6
Roger Voss           25514
Michael Schachner    15134
                     ...  
Fiona Adams             27
Christina Pickard        6
Name: taster_name, Length: 19, dtype: int64

Abbildungen (Maps)

Eine Abbildung (map) ist ein Begriff aus der Mathematik und bezeichnet eine Funktion, die eine Menge auf eine andere abbildet. In der Data Science müssen Daten oft in andere Repräsentationen transformiert werden; dafür nutzt man Abbildungen – entsprechend sind sie sehr wichtig.

Zwei Methoden werden besonders häufig verwendet.

Die Methode Series.map() nimmt eine Funktion, die jeweils einen einzelnen Wert auf einen anderen Einzelwert abbildet, und wendet sie auf alle Werte der Series an; zurück kommt eine neue Series. Beispiel: Von allen Weinbewertungen den Mittelwert subtrahieren, um Abweichungen zu erhalten.

1
2
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

Mit DataFrame.apply() wendet man eine benutzerdefinierte Funktion auf jede Zeile an, um den gesamten DataFrame zu transformieren.

1
2
3
4
5
def remean_points(row):
    row.points = row.points - review_points_mean
    return row

reviews.apply(remean_points, axis='columns')

Mit apply() und axis='index' kann man statt zeilenweise auch spaltenweise arbeiten.

Series.map() und DataFrame.apply() liefern jeweils neue, transformierte Objekte zurück und verändern die Originaldaten nicht.

MethodeSeries.map()DataFrame.apply()
ZielSeriesDataFrame
AnwendungseinheitAnwendung auf Einzelwerte
(interpretiert man eine Series als Spaltenvektor, entspricht das einer zeilenweisen Anwendung)
standardmäßig zeilenweise
optional spaltenweise mit axis='index'

Hinweis: Es gibt auch Series.apply() und DataFrame.map().

  • Series.apply():
    • by_row='compat' (Standard): verhält sich wie Series.map()
    • by_row=False: übergibt die gesamte Series als ein Argument (ähnlich DataFrame.apply() mit axis='index')
  • DataFrame.map(): wendet eine Funktion auf Einzelwerte im DataFrame an (ähnlich Series.map(), nur auf DataFrames)

Pandas unterstützt viele gängige Abbildungen bereits direkt. Das obige Beispiel lässt sich z. B. deutlich einfacher schreiben; Pandas erkennt die Absicht und führt es korrekt aus.

1
2
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

Außerdem unterstützt Pandas auch Operationen zwischen Series gleicher Länge. Im Weinbeispiel kann man z. B. Herkunftsland und Region einfach als Strings konkatenieren:

1
reviews.country + " - " + reviews.region_1
1
2
3
4
5
6
0            Italy - Etna
1                     NaN
               ...       
129969    France - Alsace
129970    France - Alsace
Length: 129971, dtype: object

Diese Operationen sind durch eingebaute Optimierungen meist schneller als map() oder apply(). Pandas unterstützt dieses Verhalten für alle Standardoperatoren (>, <, == usw.). map() und apply() bleiben dennoch wichtig, da sie flexibler sind und komplexere Aufgaben erlauben.

Dieser Eintrag ist vom Autor unter CC BY-NC 4.0 lizensiert.