Einführung in die Datenstrukturen von pandas#
Um mit pandas zu beginnen, solltet ihr euch zunächst mit den beiden wichtigsten Datenstrukturen vertraut machen: Series und DataFrame.
Series#
Eine Serie ist ein eindimensionales Array-ähnliches Objekt, das eine Folge von Werten (von ähnlichen Typen wie die NumPy-Typen) und ein zugehöriges Array von Datenbeschriftungen, genannt Index, enthält. Die einfachste Serie wird nur aus einem Array von Daten gebildet:
[1]:
import numpy as np
import pandas as pd
[2]:
s = pd.Series(np.random.randn(7))
s
[2]:
0 -0.118469
1 0.269329
2 1.111124
3 -0.374502
4 -0.933926
5 0.225624
6 1.484980
dtype: float64
Die Zeichenkettendarstellung einer interaktiv angezeigten Reihe zeigt den Index auf der linken Seite und die Werte auf der rechten Seite. Da wir keinen Index für die Daten angegeben haben, wird ein Standardindex erstellt, der aus den ganzen Zahlen 0
bis N - 1
besteht (wobei N
die Anzahl (Length) der Daten ist). Ihr könnt die Array-Darstellung und das Index-Objekt der Reihe über ihre pandas.Series.array-
bzw. pandas.Series.index-Attribute erhalten:
[3]:
s.array
[3]:
<PandasArray>
[-0.1184689187248224, 0.2693294618355989, 1.1111240611604554,
-0.3745017853376782, -0.9339257383638273, 0.22562383495880484,
1.4849796204202976]
Length: 7, dtype: float64
[4]:
s.index
[4]:
RangeIndex(start=0, stop=7, step=1)
Oft werdet ihr einen Index erstellen wollen, der jeden Datenpunkt mit einer Bezeichnung kennzeichnet:
[5]:
idx = pd.date_range("2022-01-31", periods=7)
s2 = pd.Series(np.random.randn(7), index=idx)
[6]:
s2
[6]:
2022-01-31 0.655781
2022-02-01 0.102734
2022-02-02 2.468739
2022-02-03 -0.911366
2022-02-04 -0.466354
2022-02-05 -0.062434
2022-02-06 -0.777514
Freq: D, dtype: float64
Siehe auch:
Im Vergleich zu NumPy-Arrays könnt ihr Label im Index verwenden, wenn ihr einzelne Werte oder eine Gruppe von Werten auswählen wollt:
[7]:
s2['2022-02-02']
[7]:
2.4687390238898836
[8]:
s2[['2022-02-02', '2022-02-03', '2022-02-04']]
[8]:
2022-02-02 2.468739
2022-02-03 -0.911366
2022-02-04 -0.466354
dtype: float64
Hier wird ['2022-02-02', '2022-02-03', '2022-02-04']
als eine Liste von Indizes interpretiert, auch wenn sie Strings anstelle von ganzen Zahlen enthält.
Bei der Verwendung von NumPy-Funktionen oder NumPy-ähnlichen Operationen, wie z. B. dem Filtern mit einem booleschen Array, der skalaren Multiplikation oder der Anwendung mathematischer Funktionen, bleibt die Verknüpfung zwischen Index und Wert erhalten:
[9]:
s2[s2 > 0]
[9]:
2022-01-31 0.655781
2022-02-01 0.102734
2022-02-02 2.468739
Freq: D, dtype: float64
[10]:
s2 ** 2
[10]:
2022-01-31 0.430049
2022-02-01 0.010554
2022-02-02 6.094672
2022-02-03 0.830587
2022-02-04 0.217486
2022-02-05 0.003898
2022-02-06 0.604529
Freq: D, dtype: float64
[11]:
np.exp(s2)
[11]:
2022-01-31 1.926647
2022-02-01 1.108196
2022-02-02 11.807548
2022-02-03 0.401975
2022-02-04 0.627285
2022-02-05 0.939475
2022-02-06 0.459547
Freq: D, dtype: float64
Ihr könnt euch eine Serie auch als ein ordered dict mit fester Länge vorstellen, da sie eine Zuordnung von Indexwerten zu Datenwerten darstellt. Sie kann in vielen Kontexten verwendet werden, in denen man ein dict verwenden könnte:
[12]:
'2022-02-02' in s2
[12]:
True
[13]:
'2022-02-09' in s2
[13]:
False
Fehlende Daten#
NA
und null
werde ich synonym verwenden, um auf fehlende Daten hinzuweisen. Die Funktionen isna
und notna
in pandas sollten verwendet werden, um fehlende Daten zu erkennen:
[14]:
pd.isna(s2)
[14]:
2022-01-31 False
2022-02-01 False
2022-02-02 False
2022-02-03 False
2022-02-04 False
2022-02-05 False
2022-02-06 False
Freq: D, dtype: bool
[15]:
pd.notna(s2)
[15]:
2022-01-31 True
2022-02-01 True
2022-02-02 True
2022-02-03 True
2022-02-04 True
2022-02-05 True
2022-02-06 True
Freq: D, dtype: bool
Series hat diese auch als Instanzmethoden:
[16]:
s2.isna()
[16]:
2022-01-31 False
2022-02-01 False
2022-02-02 False
2022-02-03 False
2022-02-04 False
2022-02-05 False
2022-02-06 False
Freq: D, dtype: bool
Der Umgang mit fehlenden Daten wird im Abschnitt Verwalten fehlender Daten mit pandas ausführlicher behandelt.
Eine für viele Anwendungen nützliche Funktion von Series ist die automatische Ausrichtung nach Indexbezeichnungen bei arithmetischen Operationen:
[17]:
idx = pd.date_range("2022-02-07", periods=7)
s3 = pd.Series(np.random.randn(7), index=idx)
[18]:
s2, s3
[18]:
(2022-01-31 0.655781
2022-02-01 0.102734
2022-02-02 2.468739
2022-02-03 -0.911366
2022-02-04 -0.466354
2022-02-05 -0.062434
2022-02-06 -0.777514
Freq: D, dtype: float64,
2022-02-07 0.576941
2022-02-08 -0.298398
2022-02-09 -0.218974
2022-02-10 -0.671887
2022-02-11 -0.980644
2022-02-12 1.078790
2022-02-13 -0.477944
Freq: D, dtype: float64)
[19]:
s2 + s3
[19]:
2022-01-31 NaN
2022-02-01 NaN
2022-02-02 NaN
2022-02-03 NaN
2022-02-04 NaN
2022-02-05 NaN
2022-02-06 NaN
2022-02-07 NaN
2022-02-08 NaN
2022-02-09 NaN
2022-02-10 NaN
2022-02-11 NaN
2022-02-12 NaN
2022-02-13 NaN
Freq: D, dtype: float64
Wenn ihr Erfahrung mit SQL habt, ähnelt dies einem JOIN-Vorgang.
Sowohl das Series-Objekt selbst als auch sein Index haben ein name
-Attribut, das sich in andere Bereiche der pandas-Funktionalität integrieren lässt:
[20]:
s3.name = 'floats'
s3.index.name = 'date'
s3
[20]:
date
2022-02-07 0.576941
2022-02-08 -0.298398
2022-02-09 -0.218974
2022-02-10 -0.671887
2022-02-11 -0.980644
2022-02-12 1.078790
2022-02-13 -0.477944
Freq: D, Name: floats, dtype: float64
DataFrame#
Ein DataFrame stellt eine rechteckige Datentabelle dar und enthält eine geordnete, benannte Sammlung von Spalten, von denen jede einen anderen Werttyp haben kann. Der DataFrame hat sowohl einen Zeilen- als auch einen Spaltenindex.
Bemerkung:
Ein DataFrame ist zwar zweidimensional, ihr könnt ihn aber auch verwenden, um mit join, combine und Reshaping höherdimensionale Daten in einem Tabellenformat mit hierarchischer Indizierung darzustellen.
[21]:
data = {'Code': ['U+0000', 'U+0001', 'U+0002', 'U+0003', 'U+0004', 'U+0005'],
'Decimal': [0, 1, 2, 3, 4, 5],
'Octal': ['001', '002', '003', '004', '004', '005'],
'Key': ['NUL', 'Ctrl-A', 'Ctrl-B', 'Ctrl-C', 'Ctrl-D', 'Ctrl-E']}
df = pd.DataFrame(data)
df
[21]:
Code | Decimal | Octal | Key | |
---|---|---|---|---|
0 | U+0000 | 0 | 001 | NUL |
1 | U+0001 | 1 | 002 | Ctrl-A |
2 | U+0002 | 2 | 003 | Ctrl-B |
3 | U+0003 | 3 | 004 | Ctrl-C |
4 | U+0004 | 4 | 004 | Ctrl-D |
5 | U+0005 | 5 | 005 | Ctrl-E |
Bei großen DataFrames wählt die head
-Methode nur die ersten fünf Zeilen aus:
[22]:
df.head()
[22]:
Code | Decimal | Octal | Key | |
---|---|---|---|---|
0 | U+0000 | 0 | 001 | NUL |
1 | U+0001 | 1 | 002 | Ctrl-A |
2 | U+0002 | 2 | 003 | Ctrl-B |
3 | U+0003 | 3 | 004 | Ctrl-C |
4 | U+0004 | 4 | 004 | Ctrl-D |
Ihr könnt auch die Reihenfolge der Spalten angeben:
[23]:
pd.DataFrame(data, columns=['Code', 'Key'])
[23]:
Code | Key | |
---|---|---|
0 | U+0000 | NUL |
1 | U+0001 | Ctrl-A |
2 | U+0002 | Ctrl-B |
3 | U+0003 | Ctrl-C |
4 | U+0004 | Ctrl-D |
5 | U+0005 | Ctrl-E |
Wenn ihr eine Spalte übergeben wollt, die nicht im Dict enthalten ist, wird sie ohne Werte im Ergebnis erscheinen:
[24]:
df2 = pd.DataFrame(data, columns=['Code', 'Decimal', 'Octal', 'Description', 'Key'])
df2
[24]:
Code | Decimal | Octal | Description | Key | |
---|---|---|---|---|---|
0 | U+0000 | 0 | 001 | NaN | NUL |
1 | U+0001 | 1 | 002 | NaN | Ctrl-A |
2 | U+0002 | 2 | 003 | NaN | Ctrl-B |
3 | U+0003 | 3 | 004 | NaN | Ctrl-C |
4 | U+0004 | 4 | 004 | NaN | Ctrl-D |
5 | U+0005 | 5 | 005 | NaN | Ctrl-E |
Ihr könnt eine Spalte in einem DataFrame mit einer Dict-ähnlichen Notation abrufen:
[25]:
df['Code']
[25]:
0 U+0000
1 U+0001
2 U+0002
3 U+0003
4 U+0004
5 U+0005
Name: Code, dtype: object
So könnt ihr auch eine Spalte zum Index machen:
[26]:
df2 = pd.DataFrame(data,
columns=['Decimal', 'Octal', 'Description', 'Key'],
index=df['Code'])
df2
[26]:
Decimal | Octal | Description | Key | |
---|---|---|---|---|
Code | ||||
U+0000 | 0 | 001 | NaN | NUL |
U+0001 | 1 | 002 | NaN | Ctrl-A |
U+0002 | 2 | 003 | NaN | Ctrl-B |
U+0003 | 3 | 004 | NaN | Ctrl-C |
U+0004 | 4 | 004 | NaN | Ctrl-D |
U+0005 | 5 | 005 | NaN | Ctrl-E |
Zeilen können nach Position oder Name mit dem pandas.DataFrame.loc-Attribut abgerufen werden:
[27]:
df2.loc['U+0001']
[27]:
Decimal 1
Octal 002
Description NaN
Key Ctrl-A
Name: U+0001, dtype: object
Spaltenwerte können durch Zuweisung geändert werden. Zum Beispiel könnte der leeren Spalte Description ein Einzelwert oder ein Array von Werten zugewiesen werden:
[28]:
df2['Description'] = [
'Null character',
'Start of Heading',
'Start of Text',
'End-of-text character',
'End-of-transmission character',
'Enquiry character'
]
df2
[28]:
Decimal | Octal | Description | Key | |
---|---|---|---|---|
Code | ||||
U+0000 | 0 | 001 | Null character | NUL |
U+0001 | 1 | 002 | Start of Heading | Ctrl-A |
U+0002 | 2 | 003 | Start of Text | Ctrl-B |
U+0003 | 3 | 004 | End-of-text character | Ctrl-C |
U+0004 | 4 | 004 | End-of-transmission character | Ctrl-D |
U+0005 | 5 | 005 | Enquiry character | Ctrl-E |
Das Zuweisen einer nicht existierenden Spalte erzeugt eine neue Spalte.
Mit pandas.DataFrame.drop können Spalten entfernt und mit pandas.DataFrame.columns
angezeigt werden:
[29]:
df3 = df2.drop(columns=['Decimal', 'Octal'])
[30]:
df2.columns
[30]:
Index(['Decimal', 'Octal', 'Description', 'Key'], dtype='object')
[31]:
df3.columns
[31]:
Index(['Description', 'Key'], dtype='object')
Eine weitere gängige Form von Daten sind verschachtelte Dict von Dicts:
[32]:
u = {
'U+0006': {'Decimal': '6', 'Octal': '006', 'Description': 'Acknowledge character', 'Key': 'Ctrl-F'},
'U+0007': {'Decimal': '7', 'Octal': '007', 'Description': 'Bell character', 'Key': 'Ctrl-G'},
}
df4 = pd.DataFrame(u)
df4
[32]:
U+0006 | U+0007 | |
---|---|---|
Decimal | 6 | 7 |
Octal | 006 | 007 |
Description | Acknowledge character | Bell character |
Key | Ctrl-F | Ctrl-G |
Ihr könnt den DataFrame transponieren, d.h. die Zeilen und Spalten vertauschen, mit einer ähnlichen Syntax wie bei einem NumPy-Array:
[33]:
df4.T
[33]:
Decimal | Octal | Description | Key | |
---|---|---|---|---|
U+0006 | 6 | 006 | Acknowledge character | Ctrl-F |
U+0007 | 7 | 007 | Bell character | Ctrl-G |
Warnung:
Beachtet, dass beim Transponieren die Datentypen der Spalten verworfen werden, wenn die Spalten nicht alle denselben Datentyp haben, so dass beim Transponieren und anschließenden Zurücktransponieren die vorherigen Typinformationen verloren gehen können. Die Spalten werden in diesem Fall zu Arrays aus reinen Python-Objekten.
Die Schlüssel in den inneren Dicts werden kombiniert, um den Index im Ergebnis zu bilden. Dies ist nicht der Fall, wenn ein expliziter Index angegeben wird:
[34]:
df5 = pd.DataFrame(u, index=['Decimal', 'Octal', 'Key'])
df5
[34]:
U+0006 | U+0007 | |
---|---|---|
Decimal | 6 | 7 |
Octal | 006 | 007 |
Key | Ctrl-F | Ctrl-G |