Series und DataFrame, Auswahl, Aggregation und Zusammenführen.
Kernideen
Eine Series ist ein Vektor mit Index, ein DataFrame eine Sammlung solcher Spalten mit gemeinsamem Zeilenindex.
Rechnungen zwischen Series werden nach Index ausgerichtet, nicht nach Position. Benannte Vektoren in R rechnen nach Position.
.loc wählt nach Beschriftung (Grenze eingeschlossen), .iloc nach Position (Grenze ausgeschlossen). Bedingungen brauchen runde Klammern.
Zeilen- und Spaltenauswahl gehören in einen.loc-Zugriff; Kettenindizierung schreibt ins Leere.
Aggregationen lassen NaN still weg, und groupby lässt Zeilen mit fehlendem Gruppenschlüssel ebenso still fallen.
Nach jedem merge die Zeilenzahl prüfen: Ein nicht eindeutiger Schlüssel vervielfacht Zeilen und verfälscht jede Summe.
Erklärung
Vorwissen:NumPy und SciPy, denn pandas legt eine beschriftete Ebene über NumPy-Arrays, und R-Grundlagen für den Data Frame als Vergleich. Die Arbeitsschritte selbst, also filtern, umformen und zusammenfassen, stehen mit dplyr und pandas nebeneinander unter Data Wrangling.
Was pandas über NumPy legt
Ein NumPy-Array kennt nur Positionen. Für Daten braucht man Namen: Spalten heissen verbrauch und nicht 2, Zeilen gehören zu einem Datum und nicht zu Position 17. Genau das ergänzt pandas, und zwar über den Index.
Der Index ist mehr als eine Beschriftung. Bei jeder Rechnung zwischen zwei Series oder DataFrames sucht pandas zuerst die passenden Einträge nach Index und rechnet dann. Fehlt ein Eintrag auf einer Seite, entsteht NaN. Das ist der grösste konzeptionelle Unterschied zum R-Data-Frame, der keinen vergleichbaren Mechanismus hat; dort gehen Rechnungen immer nach Position. Wo keine Ausrichtung erwünscht ist, hilft reset_index(drop=True) oder das Rechnen auf .to_numpy().
Die Datenlage dieser Seite
24 wöchentliche Verbrauchsmessungen vom 5. Januar bis 23. März 2026, je eine für die Standorte Bern und Chur. Die Werte sind fest eingetragen, damit R und Python dieselben Zahlen zeigen.
Auswahl
Zugriff
arbeitet mit
obere Grenze
Beispiel
df["a"]
Spaltenname
eine Spalte als Series
df[["a", "b"]]
Liste von Spaltennamen
mehrere Spalten als DataFrame
.loc[zeilen, spalten]
Beschriftungen und Bedingungen
eingeschlossen
df.loc["b":"c", "wert"]
.iloc[zeilen, spalten]
Positionen
ausgeschlossen
df.iloc[1:3, 0]
.query("...")
Bedingung als Text
df.query("wert > 3")
Bedingungen werden mit & (und), | (oder) und ~ (nicht) verknüpft, nicht mit and und or, und jede Teilbedingung braucht runde Klammern. Der Grund ist die Rangfolge: In Python bindet & stärker als == und >. In R ist es umgekehrt, dort geht es ohne Klammern.
Warum Kettenindizierung nicht funktioniert
df[bedingung]["wert"] = 99 besteht aus zwei Zugriffen. Der erste erzeugt ein Zwischenergebnis, der zweite schreibt in dieses Zwischenergebnis, das danach verworfen wird. Bei einer Auswahl per Bedingung ist das Zwischenergebnis eine Kopie, die Zuweisung bewirkt nichts, und pandas warnt: bis Version 2 mit SettingWithCopyWarning, ab Version 3 (copy on write, dort gilt es für jedes Zwischenergebnis) mit ChainedAssignmentError. Richtig ist ein einziger Zugriff: df.loc[bedingung, "wert"] = 99. In R ist die verkettete Form df$wert[bedingung] <- 99 dagegen gültig und üblich.
Fehlende Werte
pandas lässt NaN in Aggregationen weg (skipna=True), R gibt NA zurück und zwingt mit na.rm = TRUE zur Entscheidung. Wer in Python denselben Reflex will, zählt vor jeder Aggregation mit isna().sum(). Dazu kommt eine zweite, weniger bekannte Stelle: groupby wirft Zeilen, deren Gruppenschlüssel fehlt, ohne Hinweis hinaus. Erst dropna=False behält sie als eigene Gruppe.
dplyr und pandas
dplyr
pandas
filter()
.loc[maske], .query()
select()
df[["a", "b"]]
mutate()
.assign()
arrange()
.sort_values()
group_by() mit summarise()
.groupby() mit .agg()
left_join()
.merge(how="left")
pivot_wider()
.pivot()
pivot_longer()
.melt()
n()
"size"
Methodenketten funktionieren wie die Pipe in R. Runde Klammern um den ganzen Ausdruck erlauben den Zeilenumbruch, und assign mit einer lambda-Funktion greift auf den Zwischenstand zu:
Zwei Serien mit den Beschriftungen x, y, z, die zweite in umgekehrter Reihenfolge (z = 10, y = 20, x = 30). Was ergibt die Summe, in pandas und mit benannten Vektoren in R? Und was, wenn in der zweiten Serie z fehlt?
a = pd.Series([1, 2, 3], index=["x", "y", "z"])b = pd.Series([10, 20, 30], index=["z", "y", "x"])print((a + b).to_dict()) # nach Index
{'x': 31, 'y': 22, 'z': 13}
print((a.to_numpy() + b.to_numpy()).tolist()) # nach Position
[11, 22, 33]
c = pd.Series([10, 20], index=["x", "y"])print((a + c).to_dict())
{'x': 11.0, 'y': 22.0, 'z': nan}
Output Zeile für Zeile
Rechnung
pandas
R
Erklärung
a + b
x 31, y 22, z 13
x 11, y 22, z 33
pandas addiert x zu x. R addiert Position zu Position und übernimmt die Namen von a, obwohl dort z und x zusammengezählt wurden.
nach Position
[11, 22, 33]
Über .to_numpy() rechnet pandas wie R.
ausgerichtet in R
x 31, y 22, z 13
b[names(a)] sortiert b nach den Namen von a.
z fehlt
x 11.0, y 22.0, z NaN
x 11, y 22, z 13
pandas meldet die Lücke als fehlenden Wert. R recycelt die 10 für z und warnt nur.
Die Werte in pandas werden zu Gleitkommazahlen (11.0), weil NaN eine Gleitkommazahl ist und die ganze Serie denselben Typ tragen muss.
Interpretation und Ergebnissatz
Die Ausrichtung ist der Grund, warum pandas-Code selten “danebenrechnet”, wenn zwei Tabellen verschieden sortiert sind. Sie ist zugleich der Grund für unerwartete NaN, sobald die Indizes nicht deckungsgleich sind. R vertraut auf die Reihenfolge; die Namen sind dort Beschriftung, keine Zuordnung.
pandas addiert nach Index und liefert x = 31, y = 22, z = 13, R addiert benannte Vektoren nach Position und liefert unter denselben Namen 11, 22, 33. Fehlt ein Eintrag, entsteht in pandas NaN, in R ein recycelter Wert.
Frage und Datenlage
Eine kleine Tabelle mit Index a bis d: Was liefern loc["b":"c"] und iloc[1:3]? Dann aus den 24 Messungen alle Berner Werte über 125, und die Probe, was ohne Klammern um die Teilbedingungen passiert.
Positionen 1 und 2, die 3 ist ausgeschlossen. Dasselbe Ergebnis auf verschiedenem Weg.
Bern über 125
vier Zeilen: 125.4, 130.6, 128.9, 133.2
am 12. Januar, 26. Januar, 16. Februar und 16. März
Zeilenbeschriftung
pandas 2, 6, 12, 20; R 3, 7, 13, 21
Dieselben Zeilen. pandas zählt den Index ab null, R die Zeilennamen ab eins.
über 130
2 Messungen
130.6 und 133.2
ohne Klammern
TypeError
Python rechnet zuerst "Bern" & messungen[...], und das ist sinnlos. R braucht die Klammern nicht, weil dort == stärker bindet als &.
Interpretation und Ergebnissatz
Die untere Grenze wird bei beiden Zugriffen gleich behandelt, die obere nicht. Das ist Absicht: Bei Beschriftungen wüsste man sonst nicht, welcher Eintrag der nächste wäre, den man ausschliessen soll. Für Bedingungen ist .loc der Zugriff der Wahl, weil er Zeilen und Spalten in einem Schritt nimmt.
loc["b":"c"] und iloc[1:3] liefern beide die Werte 20 und 30. In Bern lagen vier Messungen über 125, die höchste bei 133.2 am 16. März; ohne Klammern um die Teilbedingungen bricht der Ausdruck mit einem TypeError ab.
Frage und Datenlage
In einer Tabelle mit den Gruppen a, b, a soll der Wert aller Zeilen der Gruppe a auf 99 gesetzt werden, einmal mit zwei Zugriffen hintereinander und einmal mit einem einzigen .loc.
df <-data.frame(wert =c(1, 2, 3), gruppe =c("a", "b", "a"))df$wert[df$gruppe =="a"] <-99# verkettet, in R gueltigdf$wert
[1] 99 2 99
df = pd.DataFrame({"wert": [1, 2, 3], "gruppe": ["a", "b", "a"]})with warnings.catch_warnings(record=True) as meldungen: warnings.simplefilter("always") df[df["gruppe"] =="a"]["wert"] =99# zwei Zugriffeprint([type(m.message).__name__for m in meldungen])
['SettingWithCopyWarning']
print(df["wert"].tolist())
[1, 2, 3]
df.loc[df["gruppe"] =="a", "wert"] =99# ein Zugriffprint(df["wert"].tolist())
[99, 2, 99]
Output Zeile für Zeile
Variante
Warnung
Werte danach
Erklärung
zwei Zugriffe
SettingWithCopyWarning (ab pandas 3 ChainedAssignmentError)
[1, 2, 3]
Die Zuweisung traf eine Kopie, die sofort verworfen wurde. Das Original ist unverändert.
ein .loc
keine
[99, 2, 99]
Zeilen und Spalte in einem Zugriff.
R verkettet
99 2 99
In R ist df$wert[...] <- 99 eine gültige Ersetzung.
Die Warnung wird hier eingefangen und ausgegeben, weil diese Sammlung Warnungen sonst ausblendet. Im eigenen Code sollte sie nie unterdrückt werden: Sie ist der einzige Hinweis darauf, dass eine Zuweisung nichts bewirkt hat.
Interpretation und Ergebnissatz
Der Fehler ist heimtückisch, weil der Code gültig aussieht und in R genau so geschrieben würde. Bis pandas 2 hängt das Ergebnis sogar vom Fall ab, weil ein Zwischenergebnis mal eine View und mal eine Kopie ist; ab pandas 3 ist es mit copy on write eindeutig, aber eindeutig wirkungslos. Die Regel bleibt: Wer schreiben will, benutzt einen einzigen .loc-Zugriff.
Die verkettete Zuweisung liess die Werte bei 1, 2, 3 und löste eine Warnung aus; erst df.loc[bedingung, "wert"] = 99 setzte beide Zeilen der Gruppe a auf 99. In R ist die verkettete Form gültig.
Frage und Datenlage
Vier Werte 1, 2, NaN, 4: Welcher Mittelwert kommt heraus? Und eine Tabelle mit den Gruppen a, b, fehlend, a und den Werten 1 bis 4: Welche Summen je Gruppe liefert groupby?
pandas lässt den fehlenden Wert still weg: (1 + 2 + 4) / 3. R verweigert das Ergebnis.
mit ausdrücklicher Angabe
skipna=False: nan
na.rm = TRUE: 2.333333
Jede Sprache kann das Verhalten der anderen, nur mit umgekehrter Voreinstellung.
vorhandene Werte
count(): 3 von len(): 4
count zählt nur Vorhandenes, len alles.
fehlende Werte
1
1
Summen je Gruppe
a 5, b 2
a 5, b 2, NA 3
Die Zeile mit fehlender Gruppe fehlt in pandas, ohne Hinweis. dplyr führt sie als eigene Gruppe.
mit dropna=False
a 5, b 2, nan 3
Jetzt ist die Summe aller Gruppen wieder 10.
Interpretation und Ergebnissatz
Beide Stellen verlieren Daten, ohne es zu sagen. Beim Mittelwert ist das oft gewollt, beim groupby selten: Die Gruppensummen addieren sich zu 7 statt zu 10, und wer die Gesamtsumme nicht nachrechnet, bemerkt es nicht. Die Kontrolle ist einfach: Summe der Gruppen gegen Summe der Spalte, und die Anzahl fehlender Werte je Spalte vor jeder Auswertung. Mehr dazu unter Datenqualität.
pandas ergibt einen Mittelwert von 2.3333, weil der fehlende Wert still weggelassen wird; R liefert NA. Beim Gruppieren fällt in pandas die Zeile mit fehlendem Schlüssel weg, sodass sich die Summen zu 7 statt 10 addieren; erst dropna=False behält sie.
Frage und Datenlage
Anzahl, mittlerer Verbrauch und Spannweite je Standort über alle 24 Messungen, danach der mittlere Verbrauch je Monat und Standort als breite Tabelle mit einer Spalte je Standort.
size in pandas, n() in dplyr: Zeilen je Gruppe, fehlende Werte eingeschlossen. count würde nur vorhandene zählen.
Mittel
1477.4 / 12 für Bern, 1149.0 / 12 für Chur
Spannweite
Bern 133.2 − 115.8, Chur 103.1 − 88.7. Eine eigene Funktion als lambda in agg.
Monatsmittel
je vier Wochen, weil jeder Monat hier vier Messtage hat
breite Form
pivot in pandas, in R hier tapply mit zwei Gruppierungen; mit tidyr wäre es pivot_wider().
Interpretation und Ergebnissatz
groupby mit benannten Aggregationen entspricht Zeile für Zeile group_by mit summarise, nur dass pandas die Gruppen in den Index legt. Die breite Tabelle zeigt, was die Gesamtmittel verbergen: Bern bleibt über alle drei Monate nahezu gleich, Chur steigt von 94.62 auf 97.25.
Bern verbrauchte im Mittel 123.12 bei einer Spannweite von 17.4, Chur 95.75 bei 14.4, je über zwölf Messungen. Nach Monaten steigt der Verbrauch in Chur von 94.62 im Januar auf 97.25 im März, in Bern bleibt er zwischen 121.62 und 123.92.
Frage und Datenlage
Die Messungen sollen um den Kanton ergänzt werden. Die Stammdaten enthalten Bern, Chur und Sion. Eine zweite Fassung der Stammdaten führt Chur versehentlich doppelt. Was passiert mit Zeilenzahl und Gesamtverbrauch?
Jede Messung findet genau einen Kanton. Sion hat keine Messung und fällt beim Left Join weg.
nach Join mit doppeltem Chur
36
36
Jede der zwölf Churer Messungen wird mit beiden Chur-Zeilen kombiniert.
Gesamtverbrauch
2626.4 gegen 3775.4
dieselben
Der Churer Verbrauch von 1149.0 zählt doppelt.
Schlüssel eindeutig?
True, False
anyDuplicated: 3
R nennt die Position der ersten Doppelung.
Join mit Erwartung
MergeError
Fehler (TRUE)
validate="many_to_one" beziehungsweise relationship = "many-to-one" bricht ab, statt still zu vervielfachen.
Outer Join
Bern 12, Chur 12, Sion 1
Ein Outer Join fügt fehlende Schlüssel als Zeile mit NaN hinzu, vervielfacht aber nichts.
Interpretation und Ergebnissatz
Ein Join mit doppeltem Schlüssel erzeugt keinen Fehler, sondern eine grössere Tabelle, und jede Summe danach ist falsch. Die Gewohnheit, nach jedem Join die Zeilenzahl mit der vorherigen zu vergleichen, kostet eine Zeile Code. Noch besser ist es, die Erwartung gleich in den Aufruf zu schreiben: validate in pandas, relationship in dplyr. Warum Schlüssel eindeutig sein müssen, erklärt die Datenmodellierung.
Mit eindeutigen Stammdaten behält die Tabelle ihre 24 Zeilen. Ist Chur in den Stammdaten doppelt, wächst sie ohne Fehlermeldung auf 36 Zeilen, und der Gesamtverbrauch steigt von 2626.4 auf 3775.4; validate="many_to_one" hätte den Join abgebrochen.
Verständnisfragen
Warum schliesst df.loc["b":"c"] die Grenze ein, df.iloc[1:3] aber nicht?
.loc arbeitet mit Beschriftungen, .iloc mit Positionen
Richtig. Bei Beschriftungen wäre eine ausgeschlossene obere Grenze unbrauchbar, weil man den nächsten Eintrag kennen müsste.
Es ist eine Inkonsistenz ohne Grund
Der Unterschied ist beabsichtigt und folgt aus der Art der Angabe.
.loc ist die ältere Schreibweise
Beide sind aktuell und für verschiedene Zwecke gedacht.
Zwei Series mit Index x, y, z und z, y, x werden addiert. In R ergäben benannte Vektoren 11, 22, 33. Was ergibt pandas?
x 31, y 22, z 13
Richtig. pandas richtet nach Index aus und addiert x zu x. R rechnet nach Position.
Ebenfalls 11, 22, 33
Das wäre das Verhalten von NumPy-Arrays oder .to_numpy().
Einen Fehler wegen unterschiedlicher Reihenfolge
Der Fall ist vorgesehen und läuft still durch.
Warum ist df[df["a"] > 1]["b"] = 0 wirkungslos?
Die Zuweisung trifft ein Zwischenergebnis, das danach verworfen wird
Richtig. pandas warnt dabei mit SettingWithCopyWarning, ab Version 3 mit ChainedAssignmentError. Ein einziger .loc-Zugriff umgeht das.
Die Bedingung ist falsch geschrieben
Die Bedingung ist in Ordnung.
Es funktioniert nur bei numerischen Spalten
Der Datentyp spielt keine Rolle.
Die Summen aus df.groupby("gruppe")["wert"].sum() ergeben zusammen 7, die Spalte wert summiert 10. Was ist passiert?
Zeilen mit fehlendem Gruppenschlüssel wurden weggelassen
Richtig. groupby verwirft sie ohne Hinweis. dropna=False behält sie als eigene Gruppe, wie Beispiel 4 zeigt.
sum hat einen fehlenden Wert als 0 gezählt
Das würde die Summe nicht verringern.
Ein Rundungsfehler
Bei ganzen Zahlen gibt es keinen.
Nach einem merge hat die Tabelle mehr Zeilen als vorher. Was ist passiert?
Der Schlüssel war in der rechten Tabelle nicht eindeutig
Richtig. Jede Zeile links wurde mit jeder passenden Zeile rechts kombiniert. Vor dem Join lohnt der Blick auf is_unique, oder gleich validate="many_to_one".
Es wurde versehentlich ein Outer Join verwendet
Der fügt fehlende Schlüssel hinzu, vervielfacht aber keine vorhandenen Zeilen.
pandas hat die Zeilen doppelt eingelesen
Der Einlesevorgang ist nicht beteiligt.
In pandas liefert x.mean() bei einem fehlenden Wert eine Zahl, in R mean(x) den Wert NA. Welche Voreinstellung ist die vorsichtigere?
Die von R, weil sie zur Entscheidung zwingt
Richtig. Wer na.rm = TRUE schreibt, weiss, dass Werte fehlen. In pandas muss man daran denken, isna().sum() selbst aufzurufen.
Die von pandas, weil sie ein Ergebnis liefert
Ein Ergebnis ohne Hinweis auf fehlende Werte kann täuschen.
Beide sind gleichwertig
Das Ergebnis mit na.rm = TRUE ist gleich, der Weg dorthin nicht.