DataFrames#
It isn’t so much what’s on the table that matters, as what’s on the chairs.
— W.S. Gilbert
Pandas ist die wichtigste Python-Bibliothek für die Arbeit mit Tabellen (DataFrames). Pandas nutzt intern Numpy
(Vektoren und Matrizen) fügt aber Tabellenkonzepte hinzu (Spaltennamen, Zeilenindizes, Aggregationsoperatoren, etc.).
Pandas enthält zwei zentrale Datentypen: Series
und DataFrame
. Series wird oft als Bürger zweiter Klasse verwendet, einfach als eine einzelne Variable (Spalte) im DataFrame. Sie kann aber auch als vektorisiertes Diktat verwendet werden, das Schlüssel (Indizes) mit Werten verknüpft. DataFrame ähnelt im Großen und Ganzen anderen DataFrames, wie sie in R oder Spark implementiert sind. Wenn Sie die einzelnen Spalten und Zeilen extrahieren, erhalten Sie diese normalerweise in Form von Reihen. Es ist also äußerst nützlich, die Grundlagen von Series zu kennen, wenn man mit Datenrahmen arbeitet. Sowohl DataFrame als auch Series enthalten einen Index, einen glorifizierten Zeilennamen, der sehr nützlich ist, um Informationen auf der Grundlage von Namen zu extrahieren oder um verschiedene Variablen in einem Datenrahmen zusammenzufassen (siehe Abschnitt Verkettung von Daten mit pd.concat
).
Pandas wird in Python meist mit der Abkürzung pd
importiert. Da man oft auch einige Funktionen aus Numpy brauch, importiert man meist beides zusammen.
# Import von NumPy und Pandas
import numpy as np
import pandas as pd
Folien#
Spalten als Series#
Pandas enthält zwei zentrale Datentypen: Series
zur Darstellung von Spalten und DataFrame
für ganze Tabellen. Wir diskutieren zuerst Series
, da sie die Spalten in DataFrame
darstellen und ihnen somit zugrunde liegen.
Eine Series
ist eine eindimensionale Spalte (oder -zeile) von Werten. In gewisser Weise ähnelt sie einer list
in Python oder einem 1-dimensionalem nbarray
in NumPy, worauf es abgebildet wird. Es fügt allerdings einen Index hinzu, mit dem man einzelne Werte Schlüssel zuweisen kann, womit man die Werte dann nachschlagen kann (Womit es auch einem dict
in Python ähnelt). Es ermöglicht also nicht nur einen positionsbezogenen Zugriff durch numerische Indizes, wie in NumPy, sondern auch einen schlüsselbasierten Zugriff.
Lassen Sie uns eine einfache Serie der Einwohner in Millionen für Europa erstellen:
pop = pd.Series( [ 83, 67, 67, 59] )
pop
0 83
1 67
2 67
3 59
dtype: int64
Die Reihe wird in zwei Spalten dargestellt. Die erste ist der Index, die zweite ist der Wert. In diesem Beispiel ist der Index im Wesentlichen nur die Zeilennummer und ist nicht sehr nützlich. Das liegt daran, dass wir keinen spezifischen Index angegeben haben und Pandas daher nur die Zeilennummer ausgewählt hat. Unter den beiden Spalten sehen Sie auch den Datentyp, in diesem Fall 64-Bit-Integer, den Standard-Datentyp für Integer in Python.
Das Problem der oben dargestellten Einwohnerzahl ist, dass wir nicht wissen, welche Zahl welchem Land entspricht. Deshalb wollen wir der Serie einen informativeren Index mit den Länderkürzeln zuweisen:
pop = pd.Series( [ 83, 67, 67, 59], index = ['de', 'fr', 'gb', 'it'])
# population, in millions
pop
de 83
fr 67
gb 67
it 59
dtype: int64
Wir können Werte extrahieren und über die entsprechenden Attribute indexieren:
pop.values
array([83, 67, 67, 59])
pop.index
Index(['de', 'fr', 'gb', 'it'], dtype='object')
Reihen unterstützen auch die gewöhnliche Mathematik, z. B. können wir Operationen durchführen wie
2 * pop
de 166
fr 134
gb 134
it 118
dtype: int64
oder Filtern wie
pop > 70
de True
fr False
gb False
it False
dtype: bool
Was eine weitere Serie mit logischen Wahrheitswerten zurück gibt.
Erstellen von DataFrames#
Die wichtigste Datenstruktur in Pandas ist der DataFrame
. Er repräsentiert einfach eine Tabelle, wie ein Tabellenblatt in Excel. Intern besteht er aus vielen Serien (für jede Spalte eine), die den gleichen Index haben.
Es gibt verschiedene Möglichkeiten DataFrame
Objekte zu erzeugen. Man kann DataFrames direkt aus Numpy Objekten erzeugen, dabei wird automatisch ein numerischer Index, wie in Numpy erzeugt.
A = np.array([[1, 2], [3, 4]])
A_DF = pd.DataFrame(A)
A_DF
0 | 1 | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
Wir können dabei aber auch neue Spaltennamen und Zeilenindexe zuweisen:
A = np.array([[1, 2], [3, 4]])
A_DF = pd.DataFrame(A, columns=["Spalte A", "Spalte B"], index=["Zeile 1", "Zeile 2"])
A_DF
Spalte A | Spalte B | |
---|---|---|
Zeile 1 | 1 | 2 |
Zeile 2 | 3 | 4 |
Häufig ist es aber intuitiver DataFrames aus Python Dictionary Objekten zu erzeugen. Dabei erzeugt man ein Objekt, wo man den Spaltennamen die Serien mit den Werten zuweist und dann ein DataFrame mit dem entsprechendem Index erstellt.
df = {'de': [ 83, 82, 79, 68], 'fr': [ 67, 59, 52, 42], 'gb': [ 67, 59, 56, 50], 'it': [ 59, 58, 55, 47]}
pop = pd.DataFrame(df, index=[2010, 1998, 1973, 1950])
pop
de | fr | gb | it | |
---|---|---|---|---|
2010 | 83 | 67 | 67 | 59 |
1998 | 82 | 59 | 59 | 58 |
1973 | 79 | 52 | 56 | 55 |
1950 | 68 | 42 | 50 | 47 |
Wenn man Daten automatisch erstellt, wählt man meistens eine zeilenweise Darstellung, wo man eine Liste mit Objekten der einzelnen Zeilen hat. Dadurch kann man neue Zeilen in einem Skript einfach hinzufügen.
df = [{'de': 83, 'fr': 67, 'gb': 67, 'it': 59, 'year': 2010},
{'de': 82, 'fr': 59, 'gb': 59, 'it': 58, 'year': 1998},
{'de': 79, 'fr': 52, 'gb': 56, 'it': 55, 'year': 1973},
{'de': 68, 'fr': 42, 'gb': 50, 'it': 47, 'year': 1950}]
pop = pd.DataFrame(df)
pop
de | fr | gb | it | year | |
---|---|---|---|---|---|
0 | 83 | 67 | 67 | 59 | 2010 |
1 | 82 | 59 | 59 | 58 | 1998 |
2 | 79 | 52 | 56 | 55 | 1973 |
3 | 68 | 42 | 50 | 47 | 1950 |
Hier muss man erst den Index aus der Spalte year
zuweisen.
pop=pop.set_index("year")
pop
de | fr | gb | it | |
---|---|---|---|---|
year | ||||
2010 | 83 | 67 | 67 | 59 |
1998 | 82 | 59 | 59 | 58 |
1973 | 79 | 52 | 56 | 55 |
1950 | 68 | 42 | 50 | 47 |
Einlesen von DataFrames aus Dateien#
Für viele praktische Anwendungen liegen Daten bereits in Tabellenform vor. Ein sehr übliches Datenformat hierfür sind CSV-Dateien. Diese können einfach mit pd.read_csv
eingelesen werden. Die Funktion nimmt den Dateinamen als erstes Argument und unterstützt viele andere Optionen weiteren Argumente. Die wichtigsten sind:
sep
: Trennzeichen der Spalten in der CSV-Datei (Standard: “,” - in Deutschland oft “;” oder “\t”).decimal
: Zeichen als Dezimaltrennzeichen erkennen (Standard: “.” - in Deutschland oft “,”).na_values
: Liste mit Werten, die als fehlende Werte interpretiert werden sollen (Standard: “NaN”).header
: Zeilennummer des Headers (Standard: 0 für erste Zeile). Kannfalse
gesetzt werden um kein Header einzulesen, wenn dieser z.B. nicht vorhanden ist.skiprows
: Anzahl der zu überspringenden Zeilen am Anfang der Datei. Sollte verwendet werden, wenn am Anfang der Datei Meta-Information stehen, die zu ignorieren sind.names
: Optionale Liste mit Spaltennamen (überschreibt die Header-Zeile).nrows
: Anzahl der zu lesenden Zeilen (Standard: alle).usecols
: Liste mit Spaltennamen, die gelesen werden sollen.
Es empfiehlt sich CSV-Dateien zuerst in einem Texteditor oder in Excel zu öffnen, um die Struktur zu verstehen. Im folgenden Beispiel lesen wir historische Daten des Deutschen Wetterdienstes ein. Die ersten Zeilen in der Datei mit den Wetterdaten sehen wie folgt aus:
Wir sehen also, dass die Datei in der ersten Zeile die Spaltennamen hat. Ab der zweiten Spalte stehen Werte. Für alle Werte sind die Spalten in der Datei durch Semikolon separiert. Pandas geht aber von der Trennung durch Komma aus, es heißt ja CSV - Comma Separated Values. Zudem ist bei deutschen CSV auch darauf zu achten, dass als Dezimaltrennzeichen, das ,
verwendet wird, im Englischen jedoch standartmäßig der .
, was auch Pandas annimmt.
Für unser Beispiel wollen wir nur die Spalten “MESS_DATUM”, “PM”, und “TMK” auswählen und nur 200 Zeilen einlesen:
wetter = pd.read_csv("../data/Wetter/warnemuende_1960.csv", sep=';', usecols=["MESS_DATUM", "PM", "TMK"], nrows=200)
wetter
MESS_DATUM | PM | TMK | |
---|---|---|---|
0 | 19470101 | 1019.3 | -2.1 |
1 | 19470103 | 1032.4 | -4.8 |
2 | 19470105 | 1031.9 | -12.9 |
3 | 19470108 | 1023.1 | -12.9 |
4 | 19470109 | 1017.5 | -9.9 |
... | ... | ... | ... |
195 | 19470719 | 1009.5 | 17.2 |
196 | 19470720 | 1008.2 | 17.4 |
197 | 19470721 | 1013.5 | 16.8 |
198 | 19470722 | 1019.8 | 18.6 |
199 | 19470723 | 1019.6 | 19.8 |
200 rows × 3 columns
Den Umfang der eingelesenen Daten können wir uns mit shape
Anzeigen lassen.
wetter.shape
(200, 3)
Mit columns
können wir die Spaltennamen anzeigen
wetter.columns
Index(['MESS_DATUM', 'PM', 'TMK'], dtype='object')
und sie auch durch Zuweisung umbenennen
wetter.columns = ["Datum", "Druck", "Temperatur"]
Daten auswählen#
Indizierung bezieht sich auf die Auswahl von Daten aus Datenrahmen und Serien basierend auf Variablennamen, logischen Bedingungen und Position. Es handelt sich um eine komplexe Aufgabe mit verschiedenen Varianten für unterschiedliche Anwendungsfälle.
Variablen auswählen#
Wir haben bereits festgestellt, dass der Hauptunterschied zwischen Numpy und Pandas ist, dass die Tabellen in Form von DataFrames Spaltennamen haben. Wir können eine einzelne Variable entweder mit ["Spaltenname"]
oder einer Abkürzung als Attribut .Spaltenname
extrahieren (Hinweis: Ersetzen Sie Spaltenname durch den Namen der relevanten Variable):
wetter["Temperatur"] # Spalte Temperatur als Serie
wetter.Temperatur # Die gleiche Spalte Temperatur als Serie
0 -2.1
1 -4.8
2 -12.9
3 -12.9
4 -9.9
...
195 17.2
196 17.4
197 16.8
198 18.6
199 19.8
Name: Temperatur, Length: 200, dtype: float64
Diese Konstrukte geben die Spalte als Serie zurück. Wenn wir lieber ein DataFrame mit einer einzelnen Spalte erhalten möchten, können wir den Variablennamen in eine Liste einpacken:
wetter[["Temperatur"]] # Spalte Temperatur als DataFrame
Temperatur | |
---|---|
0 | -2.1 |
1 | -4.8 |
2 | -12.9 |
3 | -12.9 |
4 | -9.9 |
... | ... |
195 | 17.2 |
196 | 17.4 |
197 | 16.8 |
198 | 18.6 |
199 | 19.8 |
200 rows × 1 columns
Die Attributschreibweise ist normalerweise der einfachere Weg, funktioniert jedoch nicht, wenn Sie einen indirekten Variablennamen verwenden müssen (Variablenname, der in einer anderen Variablen gespeichert ist) oder wenn der Variablenname Leerzeichen oder andere Sonderzeichen enthält. Es funktioniert auch nicht, um neue Variablen im Datenrahmen zu erstellen. Weitere Informationen finden Sie im Abschnitt Daten modifizieren.
Das vorherige Beispiel, bei dem wir eine einzelne Spalte als Datenrahmen extrahiert haben, anstelle von Series, deutet auch darauf hin, wie man mehr als eine Variable extrahiert: Verpacken Sie einfach alle erforderlichen Variablennamen in eine Liste:
vars = ["Datum", "Temperatur"]
wetter[vars]
Datum | Temperatur | |
---|---|---|
0 | 19470101 | -2.1 |
1 | 19470103 | -4.8 |
2 | 19470105 | -12.9 |
3 | 19470108 | -12.9 |
4 | 19470109 | -9.9 |
... | ... | ... |
195 | 19470719 | 17.2 |
196 | 19470720 | 17.4 |
197 | 19470721 | 16.8 |
198 | 19470722 | 18.6 |
199 | 19470723 | 19.8 |
200 rows × 2 columns
Daten filtern#
Filtern bezieht sich darauf, nur eine Teilmenge von Zeilen aus dem DataFrame basierend auf bestimmten Bedingungen zu extrahieren. Die Bedingungen sind logische Operationen, die je nach den Werten in jeder Zeile entweder wahr oder falsch sein können. Durch Filtern wird ein Unter-DataFrame erzeugt, in dem nur diejenigen Beobachtungen vorhanden sind, die den Auswahlkriterien entsprechen. Hier ist ein Beispiel:
wetter[wetter.Temperatur > 20]
Datum | Druck | Temperatur | |
---|---|---|---|
129 | 19470514 | 1011.8 | 20.6 |
146 | 19470531 | 1018.8 | 21.6 |
147 | 19470601 | 1014.0 | 20.8 |
148 | 19470602 | 1011.9 | 20.7 |
171 | 19470625 | 1016.6 | 20.8 |
174 | 19470628 | 1020.8 | 24.7 |
175 | 19470629 | 1019.0 | 27.8 |
176 | 19470630 | 1016.2 | 22.0 |
180 | 19470704 | 1013.4 | 21.7 |
Bitte beachten Sie, dass wir auf Datenvariablen in der logischen Bedingung als wetter.Temperatur
verweisen. Dabei können wir komplexere Auswahlbedingungen verwenden, zum Beispiel können wir nach sehr niedrigen oder sehr hohen Temperaturen suchen, wie folgt:
wetter[(wetter.Temperatur < 0) | (wetter.Temperatur > 30)]
Datum | Druck | Temperatur | |
---|---|---|---|
0 | 19470101 | 1019.3 | -2.1 |
1 | 19470103 | 1032.4 | -4.8 |
2 | 19470105 | 1031.9 | -12.9 |
3 | 19470108 | 1023.1 | -12.9 |
4 | 19470109 | 1017.5 | -9.9 |
... | ... | ... | ... |
65 | 19470311 | 1003.2 | -5.8 |
66 | 19470312 | 1015.8 | -7.4 |
67 | 19470313 | 1004.4 | -4.7 |
68 | 19470314 | 988.6 | -1.6 |
69 | 19470315 | 1016.6 | -3.2 |
64 rows × 3 columns
Bitte beachten Sie, dass wir den vektorisierten “oder” Operator |
verwenden, nicht das Basis-Python or
. Wir müssen auch sowohl den “kleiner als” als auch den “größer als” Teil in Klammern setzen. Weitere Informationen finden Sie in Abschnitt Numpy.
Positionales Indizieren#
Abgesehen von der Auswahl von Variablen und der Filterung nach logischen Bedingungen müssen wir gelegentlich auf Elemente nach Index oder Position zugreifen. Zum Beispiel wenn wir durch Zeilen oder Spalten iterieren wollen, wobei performantere vektorisierte Funktionen vorzuziehen sind.
Das gefilterte Objekt ist kein neuer Datenrahmen, sondern eine Ansicht des Originaldatenrahmens. Dadurch wird Speicher gespart, weil insbesondere das Kopieren großer DataFrames viel Speicher und Rechenzeit verbraucht. Allerdings können dadurch später Warnungen und Fehler entstehen, wenn Sie versuchen, die gefilterten Daten zu ändern. Wenn Sie das beabsichtigen, führen Sie eine tiefe Kopie der Daten mit der Methode .copy()
durch. Weitere Informationen finden Sie in Abschnitt Daten modifizieren.
Wir können auf die Werte der Serie auf zwei Arten zugreifen: nach Position und nach Index.
Um auf Elemente nach Position zuzugreifen, müssen wir das Attribut .iloc[]
verwenden, wobei das i für “integer” steht. Im Gegensatz zu den meisten anderen Methoden erwartet .iloc
Argumente in eckigen Klammern. Eine einzelne Zahl in eckigen Klammern gibt das Element als Element zurück. Z. B. eine einzelne Zeile aus unserem DataFrame
wetter.iloc[0] # Gibt 1. Zeile zurück
Datum 19470101.0
Druck 1019.3
Temperatur -2.1
Name: 0, dtype: float64
Wenn die Klammern eine Liste enthalten (das sieht aus wie doppelte Klammern), gibt es eine Serie zurück, die möglicherweise nur ein einzelnes Element enthält. Um also das 1. und 3. Element zu extrahieren, können wir schreiben:
wetter.iloc[[0,3]] # Gibt 1. und 3. Zeile zurück
Datum | Druck | Temperatur | |
---|---|---|---|
0 | 19470101 | 1019.3 | -2.1 |
3 | 19470108 | 1023.1 | -12.9 |
Wir können auch einzelne Werte extrahieren indem wir die Zeile und Spaltennummer angeben.
wetter.iloc[0, 0] # Gibt den Druck aus der 1. Zeile zurück
19470101
Alternativ können wir die Elemente auch nach Index extrahieren. Das ist insbesondere, dann sinnvoll, wenn man aussagekräftige Indexe verwendet. Bei unserem Beispiel empfiehlt es sich zum Beispiel die Spalte “Datum” als Index zuzuweisen. Das geht mit der Funktion set_index
wetter.set_index("Datum", inplace=True)
wetter.head()
Druck | Temperatur | |
---|---|---|
Datum | ||
19470101 | 1019.3 | -2.1 |
19470103 | 1032.4 | -4.8 |
19470105 | 1031.9 | -12.9 |
19470108 | 1023.1 | -12.9 |
19470109 | 1017.5 | -9.9 |
Jetzt können wir diesen Index nutzen, um auf Zeilen zuzugreifen. Dies funktioniert ähnlich, außer dass wir .loc[]
anstelle von .iloc[]
verwenden müssen. Die Regeln für einzelne und doppelte Klammern gelten ähnlich wie beim Zugriff über die Position.
wetter.loc[19470101] # Extrahiert die Zeile für das Datum "01.01.1947"
Druck 1019.3
Temperatur -2.1
Name: 19470101, dtype: float64
wetter.loc[[19470101, 19470103]] # Extrahiert die Zeile für das Datum "01.01.1947" und "03.01.1947"
Druck | Temperatur | |
---|---|---|
Datum | ||
19470101 | 1019.3 | -2.1 |
19470103 | 1032.4 | -4.8 |
wetter.loc[[19470101, 19470103],["Druck"]]
Druck | |
---|---|
Datum | |
19470101 | 1019.3 |
19470103 | 1032.4 |
Die Tatsache, dass es mehrere Möglichkeiten gibt, Positionsdaten abzurufen, führt bei Anfängern oft zu Verwirrung. Dies wird durch die häufige Gewohnheit, keine Indizes zu verwenden und sich einfach auf die automatischen Zeilennummern zu verlassen, nicht erleichtert. In diesem Fall liefert der positionelle Zugriff über .iloc[]
genau die gleichen Ergebnisse wie der Indexzugriff über .loc[]
, und man kann bequem den Index vergessen und das verwenden, was einfacher erscheint. Manchmal ändert sich jedoch der Index als Folge bestimmter Operationen, was zu Fehlern oder unerwarteten Ergebnissen führen kann.
Daten modifizieren#
Das Modifizieren von DataFrames kann auf ähnliche Weise wie das Extrahieren von Elementen erfolgen. Es gibt jedoch einige Ausnahmen und Einschränkungen. Lassen Sie uns dies anhand der Modifizierung des DataFrames für das Wetter demonstrieren.
Neue Spalten erstellen#
Wir können eine einzelne Serie als wetter.Temperatur
extrahieren, aber wenn wir eine neue Spalte (Variable) erstellen, müssen wir sie in Klammern angeben. Zum Beispiel:
wetter["Nullpunkt"] = 0
wetter
Druck | Temperatur | Nullpunkt | |
---|---|---|---|
Datum | |||
19470101 | 1019.3 | -2.1 | 0 |
19470103 | 1032.4 | -4.8 | 0 |
19470105 | 1031.9 | -12.9 | 0 |
19470108 | 1023.1 | -12.9 | 0 |
19470109 | 1017.5 | -9.9 | 0 |
... | ... | ... | ... |
19470719 | 1009.5 | 17.2 | 0 |
19470720 | 1008.2 | 17.4 | 0 |
19470721 | 1013.5 | 16.8 | 0 |
19470722 | 1019.8 | 18.6 | 0 |
19470723 | 1019.6 | 19.8 | 0 |
200 rows × 3 columns
Danach können wir auf die neue Variable als wetter.Nullpunkt
zugreifen.
Erstellen expliziter Kopien beim Arbeiten mit gefilterten Daten#
Ein typischer Datenwissenschafts-Workflow besteht aus a) Filtern von Daten auf relevante Fälle und b) Modifizieren des resultierenden Teilsatzes. Der erste Schritt beinhaltet oft das Entfernen von fehlenden Werten oder die Begrenzung der Analyse auf einen bestimmten interessanten Teilsatz. Es ist wichtig zu erkennen, dass das Filtern mit Pandas die interessanten Fälle nicht im Speicher kopiert, sondern möglicherweise nur eine Ansicht erstellt, d.h. denselben Speicherort im Computer-Speicher wiederverwendet, aber nur den Zugriff auf einen bestimmten Teil davon beschränkt. Pandas entscheidet in jedem Fall separat, ob eine Kopie oder eine Ansicht erstellt wird, abhängig davon, was der effizientere Ansatz ist. Dies ist eine sehr gute Idee in Bezug auf Speicherkonservierung und Vermeidung unnötiger Kopiervorgänge. Dies kann jedoch Warnungen und Fehler verursachen, wenn die gefilterten Daten später modifiziert werden. Wir demonstrieren dies am selben Datensatz.
Wählen Sie nur große Länder aus (Bevölkerung über 20 Mio):
warm = wetter[wetter.Temperatur > 22]
warm
Druck | Temperatur | Nullpunkt | |
---|---|---|---|
Datum | |||
19470628 | 1020.8 | 24.7 | 0 |
19470629 | 1019.0 | 27.8 | 0 |
Wir habe einen Teilmenge von Wetter erstellt, die nur die warmen Tage enthält. Versuchen wir jetzt eine neue Spalte zu dieser gefilterten Teilelemente zu erstellen, so gibt das eine Fehlermeldung
warm["status"] = "Warm"
/var/folders/0c/805v004947lf04d_w7xm50rc0000gn/T/ipykernel_40915/650082562.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
warm["status"] = "Warm"
Beachte die Warnung: Ein Wert wird versucht, auf einer Kopie eines Slices gesetzt zu werden…. Dies sagt dir, dass das Filtern von wetter[wetter.Temperatur > 22]
kein neues Datenrahmen erstellt hat, sondern eine Ansicht des bestehenden im Speicher, und Pandas ist unzufrieden mit dem Code, der nur einen Teil des ursprünglichen Datenrahmens modifiziert.
Schauen wir uns das Ergebnis in der Variable warm
an:
warm.head()
Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|
Datum | ||||
19470628 | 1020.8 | 24.7 | 0 | Warm |
19470629 | 1019.0 | 27.8 | 0 | Warm |
Ok, dieser wurde korrekt erweitert. Der original DataFrame wetter
hingegen nicht
wetter.head()
Druck | Temperatur | Nullpunkt | |
---|---|---|---|
Datum | |||
19470101 | 1019.3 | -2.1 | 0 |
19470103 | 1032.4 | -4.8 | 0 |
19470105 | 1031.9 | -12.9 | 0 |
19470108 | 1023.1 | -12.9 | 0 |
19470109 | 1017.5 | -9.9 | 0 |
Obwohl das Ergebnis hier korrekt erscheint, sollten Sie sich nicht auf diesen Ansatz verlassen! Es könnte funktionieren oder auch nicht, abhängig von der genauen Speicherstruktur des Datensatzes!
Glücklicherweise ist die Lösung sehr einfach. Wir müssen eine explizite Kopie mit der Methode .copy
erstellen, bevor wir mit Änderungen beginnen:
warm = wetter[wetter.Temperatur > 22].copy() # Neue Kopie
warm["status"] = "Warm"
warm.head()
Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|
Datum | ||||
19470628 | 1020.8 | 24.7 | 0 | Warm |
19470629 | 1019.0 | 27.8 | 0 | Warm |
wetter['status']='undefined'
wetter
Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|
Datum | ||||
19470101 | 1019.3 | -2.1 | 0 | undefined |
19470103 | 1032.4 | -4.8 | 0 | undefined |
19470105 | 1031.9 | -12.9 | 0 | undefined |
19470108 | 1023.1 | -12.9 | 0 | undefined |
19470109 | 1017.5 | -9.9 | 0 | undefined |
... | ... | ... | ... | ... |
19470719 | 1009.5 | 17.2 | 0 | undefined |
19470720 | 1008.2 | 17.4 | 0 | undefined |
19470721 | 1013.5 | 16.8 | 0 | undefined |
19470722 | 1019.8 | 18.6 | 0 | undefined |
19470723 | 1019.6 | 19.8 | 0 | undefined |
200 rows × 4 columns
wetter["status"][wetter.Temperatur > 22]= "warm"
/var/folders/0c/805v004947lf04d_w7xm50rc0000gn/T/ipykernel_40915/1908037705.py:1: FutureWarning: ChainedAssignmentError: behaviour will change in pandas 3.0!
You are setting values through chained assignment. Currently this works in certain cases, but when using Copy-on-Write (which will become the default behaviour in pandas 3.0) this will never work to update the original DataFrame or Series, because the intermediate object on which we are setting values will behave as a copy.
A typical example is when you are setting values in a column of a DataFrame, like:
df["col"][row_indexer] = value
Use `df.loc[row_indexer, "col"] = values` instead, to perform the assignment in a single step and ensure this keeps updating the original `df`.
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
wetter["status"][wetter.Temperatur > 22]= "warm"
/var/folders/0c/805v004947lf04d_w7xm50rc0000gn/T/ipykernel_40915/1908037705.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
wetter["status"][wetter.Temperatur > 22]= "warm"
wetter[wetter.Temperatur > 22]
Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|
Datum | ||||
19470628 | 1020.8 | 24.7 | 0 | warm |
19470629 | 1019.0 | 27.8 | 0 | warm |
Jetzt funktioniert die Modifikation ohne Warnung. Sie müssen eine explizite Kopie nicht immer durchführen. Viele verschiedene Filterungsschritte lassen sich ohne .copy
durchführen. Allerdings sollten sie auf Warnungen achten und vor Modifikationen neue Kopien erstellen.
Index anpassen#
Der Index, der an Serien und Datenrahmen angehängt ist, ist potenziell ein nützliches und informatives Werkzeug. Manchmal ist er jedoch nicht sehr nützlich. Zum Beispiel, wenn Sie Daten von der Festplatte laden, wird der Index standardmäßig auf die Zeilennummer gesetzt, die selten repräsentativ für die Zeile ist. In solchen Fällen möchte man möglicherweise den Index ändern. Wenn Sie einen neuen Index erstellen möchten, können Sie ihn einfach df.index
zuweisen. Zum Beispiel können wir einfach Ländernamen als Index unserem Datenrahmen großer Länder zuweisen:
warm.index = ["28.06.1947", "29.06.1947"]
warm
Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|
28.06.1947 | 1020.8 | 24.7 | 0 | Warm |
29.06.1947 | 1019.0 | 27.8 | 0 | Warm |
Mit der Funktion .reset_index()
kann ein Index entfernt werden. Dadurch wird eine neue Spalte mit den Indexwerten hinzugefügt und ein numerischer Index anhand der Zeilennummern erzeugt. Beachten Sie, dass .reset_index()
standardmäßig einen neuen Datenrahmen zurückgibt, anstatt ihn direkt zu ändern. Wenn Sie ihn also beibehalten möchten, müssen Sie ihn entweder neu zuweisen oder inplace=True
nutzen.
warm = warm.reset_index()
warm
index | Druck | Temperatur | Nullpunkt | status | |
---|---|---|---|---|---|
0 | 28.06.1947 | 1020.8 | 24.7 | 0 | Warm |
1 | 29.06.1947 | 1019.0 | 27.8 | 0 | Warm |
Die Methode .set_index()
haben wir schon kennen gelernt um eine Spalte in einen Index umzuwandeln. Dies entfernt die Spalte “index” aus dem Datenrahmen, da ihre Werte stattdessen im Index sein werden.
warm.set_index("index", inplace=True)