x <- 5
length(x)[1] 1
is.vector(x)[1] TRUE
werte <- c(12, 45, 7, 31)
werte / 10[1] 1.2 4.5 0.7 3.1
werte > 20[1] FALSE TRUE FALSE TRUE
sum(werte > 20)[1] 2
mean(werte > 20)[1] 0.5
werte * c(10, 20, 30, 40)[1] 120 900 210 1240
Vorwissen: keines. Diese Seite setzt weder Statistik noch Programmiererfahrung voraus. Wer von Python kommt, findet den Vergleich in jedem Beispiel; die Python-Seite dazu ist Python-Grundlagen. Die Datenstrukturen von hier tauchen im Data Wrangling auf jeder Zeile wieder auf, und wie man daraus reproduzierbare Auswertungen macht, steht unter Reproduzierbarkeit.
Eine Tabellenkalkulation kommt weit. Sie stösst dort an ihre Grenze, wo eine Auswertung wiederholbar sein soll: Wer dieselbe Analyse mit neuen Zahlen braucht, klickt sie erneut zusammen und weiss hinterher nicht sicher, ob dieselben Schritte in derselben Reihenfolge erwischt wurden. Ein Skript beantwortet diese Frage, weil jeder Schritt darin nachlesbar ist.
R ist für genau diesen Zweck entstanden, nicht als allgemeine Programmiersprache mit angebauter Statistik. Das erklärt einige Eigenheiten, die zunächst ungewohnt wirken und später Arbeit sparen.
In vielen Sprachen ist die Grundeinheit die einzelne Zahl, und wer fünfhundert Messwerte umrechnen will, schreibt eine Schleife. In R ist die Grundeinheit die Folge von Werten, und ein einzelner Wert ist eine Folge der Länge eins. Deshalb wirkt werte / 10 auf jeden Wert, und werte > 20 liefert für jeden Wert ein TRUE oder FALSE. Summiert man diese Wahrheitswerte, zählt man die Treffer, weil TRUE beim Rechnen als 1 gilt; ihr Mittelwert ist der Anteil.
Python bringt das nicht von Haus aus mit. Eine Python-Liste rechnet nicht elementweise; das Verhalten von R entsteht erst mit einem NumPy-Array, siehe NumPy und SciPy.
Sind zwei Vektoren unterschiedlich lang, wiederholt R den kürzeren, bis er passt. Bei werte / 10 ist das der Grund, warum die eine 10 auf alle Werte angewendet wird. Geht die Länge des längeren nicht in einem Vielfachen auf, gibt R eine Warnung aus und rechnet trotzdem. Geht sie auf, rechnet R ohne jeden Hinweis, auch wenn die Absicht eine andere war.
NumPy folgt einer engeren Regel, dem Broadcasting: Formen werden von hinten verglichen und nur dort angeglichen, wo eine Achse die Länge eins hat. Sonst bricht die Rechnung ab.
Ein Vektor trägt genau einen Typ. Treffen verschiedene Typen aufeinander, wandelt R sie in den allgemeineren um, statt einen Fehler zu melden. Dieser Vorgang heisst Coercion, und die Rangfolge lautet
\[ \text{logisch} \;\rightarrow\; \text{ganze Zahl} \;\rightarrow\; \text{Gleitkommazahl} \;\rightarrow\; \text{Text} \]
Ein einziger Texteintrag in einer Spalte, etwa ein Strich für einen fehlenden Wert, verwandelt deshalb die ganze Spalte in Text. Die Kontrolle der Spaltentypen gehört direkt hinter jeden Import.
Kategoriale Merkmale liessen sich als Text speichern. Damit ginge verloren, welche Ausprägungen überhaupt möglich sind und in welcher Reihenfolge sie stehen. Der Faktor führt die Stufen (levels) als eigene Eigenschaft mit. Intern speichert er ganze Zahlen, die auf die Stufen zeigen; das ist der Grund für die bekannteste Faktor-Falle in Beispiel 4. Das Gegenstück in pandas ist pd.Categorical.
| Eigenschaft | Faktor ohne Vorgabe | Faktor mit levels |
geordneter Faktor |
|---|---|---|---|
| Stufen | aus den Daten, alphabetisch | wie vorgegeben, auch unbesetzte | wie vorgegeben |
Vergleich <, > |
nicht sinnvoll | nicht sinnvoll | erlaubt |
| typischer Fall | Gerätetyp, Standort | Umfrage mit fester Antwortliste | Bewertungsskala |
| Struktur | Inhalt | Typen | Python |
|---|---|---|---|
| Vektor | eine Folge | einer | NumPy-Array, pd.Series |
| Data Frame | gleich lange Spalten | je Spalte einer | pd.DataFrame |
| Liste | beliebige Objekte unter Namen | beliebig | Dictionary |
| Matrix | Zahlen in Zeilen und Spalten | einer | zweidimensionales NumPy-Array |
Der Data Frame ist genau genommen eine Liste gleich langer Vektoren. Deshalb funktioniert df$spalte wie bei einer Liste, und deshalb dürfen die Spalten verschiedene Typen tragen, aber nicht verschieden lang sein: Sonst ginge die Zuordnung zur Beobachtung verloren.
Beim Auswählen trennt das Komma Zeilen von Spalten: df[zeilen, spalten]. Ein leerer Platz heisst “alle”. df[df$gruppe == "A", ] wählt alle Zeilen der Gruppe A und alle Spalten; innen steht eine Bedingung, die für jede Zeile wahr oder falsch ist.
| Aufgabe | R | Python |
|---|---|---|
| Struktur ansehen | str(df) |
df.dtypes, df.info() |
| Kennzahlen je Spalte | summary(df) |
df.describe() |
| Zeilen und Spalten wählen | df[df$x > 40, c("a", "b")] |
df.loc[df["x"] > 40, ["a", "b"]] |
| Spalte anlegen | df$neu <- df$a / df$b |
df["neu"] = df["a"] / df["b"] |
| Mittel je Gruppe | tapply(df$x, df$g, mean) |
df.groupby("g")["x"].mean() |
| Stufen festlegen | factor(x, levels = c(...)) |
pd.Categorical(x, categories=[...]) |
| unbesetzte Stufen entfernen | droplevels(f) |
s.cat.remove_unused_categories() |
| Zahlen aus einem Faktor | as.numeric(as.character(f)) |
s.astype(float) |
| fehlende Werte zählen | sum(is.na(x)) |
s.isna().sum() |
| Länge prüfen | stopifnot(length(x) == n) |
assert len(x) == n |
Vier Messwerte 12, 45, 7, 31. Sie sollen durch 10 geteilt werden, es soll gezählt werden, wie viele über 20 liegen, und welcher Anteil das ist. Dazu die Multiplikation mit einem zweiten Vektor gleicher Länge.
| Ausdruck | R | Python | Erklärung |
|---|---|---|---|
| einzelner Wert | Länge 1, is.vector ist TRUE |
int, erst als Array Form (1,) |
In R ist die 5 ein Vektor, in Python eine einzelne Zahl. |
werte / 10 |
1.2 4.5 0.7 3.1 | [1.2 4.5 0.7 3.1] |
Die eine 10 wird auf alle vier Werte angewendet. |
werte > 20 |
FALSE TRUE FALSE TRUE |
[False True False True] |
Ein Wahrheitswert je Messwert. |
| Summe der Wahrheitswerte | 2 | 2 | Anzahl der Werte über 20: 45 und 31. |
| Mittel der Wahrheitswerte | 0.5 | 0.5 | Anteil der Werte über 20. |
| paarweise mal | 120 900 210 1240 | [ 120 900 210 1240] |
Erstes mit erstem, zweites mit zweitem. Kein Skalarprodukt; das wäre %*% beziehungsweise @. |
Das Muster sum(bedingung) und mean(bedingung) taucht in jeder Auswertung auf: Aus einer Bedingung wird ohne Schleife eine Anzahl oder ein Anteil. In Python gilt dasselbe, sobald die Werte in einem NumPy-Array oder einer pandas-Series stehen.
Zwei der vier Messwerte liegen über 20, das ist ein Anteil von 0.5. Die Rechnung läuft in R auf dem Vektor selbst, in Python erst nach der Umwandlung in ein NumPy-Array.
Ein Vektor mit sechs Werten 1 bis 6 wird mit einem Vektor der Länge zwei (10, 100) und einem der Länge vier (10, 100, 1000, 10000) multipliziert. Was tut R, was tut NumPy?
[1] 10 200 30 400 50 600
[1] "longer object length is not a multiple of shorter object length"
[1] 10 200 3000 40000 50 600
ValueError: operands could not be broadcast together with shapes (6,) (2,)
[10 20 30 40 50 60]
[[ 10 200]
[ 30 400]
[ 50 600]]
| Rechnung | R | Python | Erklärung |
|---|---|---|---|
| sechs mal zwei | 10 200 30 400 50 600, ohne Hinweis | ValueError, Formen (6,) und (2,) passen nicht |
R wiederholt 10, 100 dreimal. Ob das gemeint war, prüft niemand. |
| sechs mal vier | Warnung longer object length is not a multiple of shorter object length |
6 ist kein Vielfaches von 4. R warnt, rechnet aber: 10, 200, 3000, 40000, 50, 600. | |
| mal eine Zahl | 10 20 30 40 50 60 | Eine Zahl hat die Form () und passt zu jeder Form. |
|
(3, 2) mal (2,) |
Zeilen 10 200, 30 400, 50 600 |
Von hinten verglichen stimmen die 2 überein; die Zeile 10, 100 wird auf jede der drei Zeilen angewendet. |
|
stopifnot |
kein Output | Die Annahme stimmt, also läuft das Skript weiter. Stimmte sie nicht, bräche es an dieser Stelle ab. |
Der gefährliche Fall ist nicht die Warnung, sondern die Stille: Sechs mal zwei liefert ein plausibel aussehendes Ergebnis ohne jeden Hinweis. NumPy verlangt, dass die Formen ausdrücklich zusammenpassen, und gleicht nur Achsen der Länge eins an. Wer in R mit Vektoren unterschiedlicher Länge rechnet, sollte die Längen vorher mit stopifnot festschreiben.
Die Multiplikation eines Vektors der Länge sechs mit einem der Länge zwei ergibt in R ohne Hinweis
10 200 30 400 50 600; NumPy bricht mit einemValueErrorab. Bei Länge vier warnt R, rechnet aber dennoch.
Welchen Typ erhält ein Vektor aus Wahrheitswert, Zahl und Text? Und was passiert beim Einlesen einer CSV-Datei, in der ein fehlender Wert als Strich eingetragen ist (1.5, -, 2.0)?
[1] "logical"
[1] "numeric"
[1] "character"
[1] "TRUE" "3" "drei"
[1] "character"
[1] "1.5" "-" "2.0"
[1] 1.5 NA 2.0
[1] 3.5
[1] "numeric"
object
object ['1.5', '-', '2.0']
[1.5, nan, 2.0] 3.5
float64
| Schritt | R | Python | Erklärung |
|---|---|---|---|
| logisch und logisch | logical |
||
| logisch und Zahl | numeric |
TRUE wird zu 1. |
|
| dazu ein Text | character, Inhalt "TRUE" "3" "drei" |
gemischte Series: object |
Alles wird Text, auch die 3 und das TRUE. pandas hält gemischte Werte als allgemeine Objekte. |
| Spalte mit Strich | character, "1.5" "-" "2.0" |
Textspalte, ['1.5', '-', '2.0'] |
Der eine Strich macht die ganze Spalte zu Text. Rechnen ist damit nicht mehr möglich. |
| umgewandelt | 1.5 NA 2.0 |
[1.5, nan, 2.0] |
as.numeric beziehungsweise to_numeric(errors="coerce") machen aus dem Strich einen fehlenden Wert. |
| Summe | 3.5 | 3.5 | Ohne den fehlenden Wert. |
| mit Angabe beim Import | numeric |
float64 |
Der bessere Weg: dem Import sagen, was “fehlend” bedeutet. |
Coercion ist eine Regel und kein Fehler, und deshalb meldet sie sich nicht. Die Spalte sieht in der Vorschau richtig aus, und der Fehler zeigt sich erst, wenn eine Rechnung scheitert oder ein Mittelwert fehlt. Die Abhilfe gehört an den Anfang: fehlende Werte beim Import deklarieren (na.strings, na_values) und danach die Typen mit str() beziehungsweise df.dtypes kontrollieren.
Ein einziger Strich in der Spalte
wertmachte beim Einlesen die ganze Spalte zu Text. Mitna.strings = "-"beziehungsweisena_values="-"wird sie als Zahl gelesen, und die Summe der beiden gültigen Werte beträgt 3.5.
Fünf Umfrageantworten (ja, nein, ja, ja, nein) auf die Antwortliste ja, nein, weiss nicht. Dann Schulnoten 5, 3, 4 als Faktor und eine Bewertungsskala von schlecht bis ausgezeichnet.
[1] "ja" "nein" "weiss nicht"
als_faktor
ja nein weiss nicht
3 2 0
ja nein
3 2
[1] 3 1 2
[1] 5 3 4
[1] schlecht gut gut ausgezeichnet
Levels: schlecht < gut < ausgezeichnet
[1] TRUE FALSE TRUE TRUE
['ja', 'nein', 'weiss nicht']
{'ja': 3, 'nein': 2, 'weiss nicht': 0}
{'ja': 3, 'nein': 2}
[3, 4, 5] [2, 0, 1]
['schlecht', 'gut', 'gut', 'ausgezeichnet']
[True, False, True, True]
| Schritt | R | Python | Erklärung |
|---|---|---|---|
| Stufen | ja, nein, weiss nicht | dieselben | wie vorgegeben |
| Häufigkeiten mit Stufen | ja 3, nein 2, weiss nicht 0 | {'ja': 3, 'nein': 2, 'weiss nicht': 0} |
Die unbesetzte Stufe bleibt sichtbar. Für eine Umfrage ist das die vollständige Information. |
| Häufigkeiten ohne Vorgabe | ja 3, nein 2 | {'ja': 3, 'nein': 2} |
Was niemand gewählt hat, existiert nicht. |
as.numeric(noten) |
3 1 2 | Codes [2, 0, 1] |
Die internen Codes, also die Position in der sortierten Stufenliste 3, 4, 5. Die Note 5 steht an dritter Stelle. pandas zählt ab null. |
über as.character |
5 3 4 | der sichere Weg zurück zu den Werten | |
| geordnet sortiert | schlecht, gut, gut, ausgezeichnet | dieselbe Folge | Nach den Stufen, nicht alphabetisch; alphabetisch stünde ausgezeichnet vorne. |
> "schlecht" |
TRUE FALSE TRUE TRUE |
[True, False, True, True] |
Vergleiche sind nur bei geordneten Faktoren sinnvoll. |
Die Stufen sind keine Beschriftung, sondern Teil der Daten. Sie bestimmen, welche Kategorien in Tabellen erscheinen, in welcher Reihenfolge Balken in einem Diagramm stehen und welche Stufe in einem Regressionsmodell als Referenz dient, siehe multiple lineare Regression. Die Faktor-Falle mit as.numeric ist die häufigste stille Fehlerquelle: Die Rechnung läuft, nur mit den Codes 3, 1, 2 statt mit den Noten 5, 3, 4.
Mit vorgegebenen Stufen weist die Tabelle
weiss nichtmit 0 aus, ohne Vorgabe fehlt die Stufe.as.numericauf dem Notenfaktor liefert die Codes 3, 1, 2 statt der Noten 5, 3, 4; richtig istas.numeric(as.character()).
Fünf Beobachtungen mit Kennnummer, Messwert (2.5, 3.1, 4.0, 2.8, 3.6) und Gruppe (A, B, A, B, A). Struktur ansehen, Kennzahlen, Zeilen der Gruppe A auswählen, Mittelwert je Gruppe.
'data.frame': 5 obs. of 3 variables:
$ id : int 1 2 3 4 5
$ wert : num 2.5 3.1 4 2.8 3.6
$ gruppe: Factor w/ 2 levels "A","B": 1 2 1 2 1
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.5 2.8 3.1 3.2 3.6 4.0
[1] 0.604
id wert gruppe
1 1 2.5 A
3 3 4.0 A
5 5 3.6 A
A B
3.366667 2.950000
(5, 3)
id int64
wert float64
gruppe category
dtype: object
count 5.000
mean 3.200
std 0.604
min 2.500
25% 2.800
50% 3.100
75% 3.600
max 4.000
Name: wert, dtype: float64
id wert gruppe
0 1 2.5 A
2 3 4.0 A
4 5 3.6 A
gruppe
A 3.3667
B 2.9500
Name: wert, dtype: float64
| Grösse | R | Python | Erklärung |
|---|---|---|---|
| Umfang | 5 obs. of 3 variables |
(5, 3) |
Zeilen und Spalten. Die erste Kontrolle nach jedem Import. |
Typ id |
int |
int64 |
1:5 erzeugt in R ganze Zahlen. |
Typ wert |
num |
float64 |
|
Typ gruppe |
Factor w/ 2 levels "A","B": 1 2 1 2 1 |
category |
R zeigt die Stufen und die internen Codes. |
| Minimum, Maximum | 2.5, 4.0 | 2.5, 4.0 | |
| Quartile | 2.8, 3.1, 3.6 | 25 %, 50 %, 75 %: 2.8, 3.1, 3.6 | Beide rechnen die Quantile nach derselben Voreinstellung. |
| Mittelwert | 3.2 | 3.2 | |
| Standardabweichung | 0.604 | 0.604 | Nur describe zeigt sie von selbst, in R braucht es sd(). |
| Zeilen der Gruppe A | id 1, 3, 5 | Index 0, 2, 4 | Dieselben Zeilen; pandas zeigt den Index ab null, R die Zeilennamen ab eins. |
| Mittel je Gruppe | A 3.366667, B 2.950000 | A 3.3667, B 2.95 | (2.5 + 4.0 + 3.6) / 3 und (3.1 + 2.8) / 2 |
str() in R und df.dtypes in Python sind die Befehle, die nach jedem Import zuerst kommen: Sie zeigen, ob die Zeilenzahl plausibel ist und ob jede Spalte den erwarteten Typ hat. Die Kennzahlen danach verteilen sich verschieden: summary bringt Quartile und Mittelwert, describe zusätzlich Anzahl und Standardabweichung. Was diese Kennzahlen bedeuten, steht unter Lage- und Streuungsmasse.
Die fünf Messwerte reichen von 2.5 bis 4.0 bei einem Mittelwert von 3.2 und einer Standardabweichung von 0.604. Gruppe A liegt im Mittel bei 3.37, Gruppe B bei 2.95.
Ein Ergebnis mit Text, drei Zahlen und einer Anzahl soll zusammengehalten werden, das passt in keine Tabelle. Danach eine Matrix aus den Zahlen 1 bis 6 mit zwei Zeilen: in welcher Reihenfolge wird sie gefüllt?
List of 3
$ methode: chr "Beispiel"
$ werte : num [1:3] 1.2 3.4 5.6
$ n : num 3
[1] 1.2 3.4 5.6
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
[1] 2 3
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
| Schritt | R | Python | Erklärung |
|---|---|---|---|
| Ergebnis | List of 3 mit chr, num [1:3], num |
Dictionary mit drei Einträgen | Beliebige Objekte unter Namen. |
ergebnis$werte |
1.2 3.4 5.6 | [1.2, 3.4, 5.6] |
|
| Matrix, Voreinstellung | Zeilen 1 3 5 und 2 4 6 |
Zeilen [1 2 3] und [4 5 6] |
R füllt spaltenweise, NumPy zeilenweise. Derselbe Aufruf ergibt verschiedene Matrizen. |
| Dimension | 2 3 | (2, 3) |
|
| umgestellt | byrow = TRUE: 1 2 3, 4 5 6 |
order="F": 1 3 5, 2 4 6 |
Mit diesen Argumenten füllt jede Sprache wie die andere. F steht für Fortran, das wie R spaltenweise speichert. |
Die Liste ist die Struktur, in der R fast alle Ergebnisse zurückgibt: Das Resultat von t.test() oder lm() ist eine Liste mit Klassenattribut. Die Matrix-Reihenfolge ist die Stelle, an der beim Übertragen von Code zwischen den Sprachen am leichtesten ein Fehler entsteht, der keine Meldung erzeugt: Beide Matrizen sind gültig, nur ist die eine die umsortierte andere.
Die Liste hält Text, drei Messwerte und eine Anzahl unter einem Namen zusammen.
matrix(1:6, nrow = 2)füllt spaltenweise und ergibt die erste Zeile1 3 5;reshape(2, 3)in NumPy füllt zeilenweise und ergibt1 2 3.
Was liefert c(2, 4, 6) * c(10, 20, 30)?
%*%.
20 80 180
Bei den Werten 12, 45, 7, 31 ergibt mean(werte > 20) den Wert 0.5. Was sagt diese Zahl inhaltlich?
sum statt mean stehen.
c(1, 2, 3, 4, 5, 6) * c(10, 100) läuft in R ohne Warnung. Heisst das, die Rechnung ist richtig?
Eine eingelesene Spalte wert hat den Typ character, obwohl sie Zahlen enthält. Was ist die wahrscheinlichste Ursache?
na.strings beim Import.
read.csv Zahlen richtig.
noten <- factor(c(5, 3, 4)). Was gibt as.numeric(noten) zurück?
5 3 4, die ursprünglichen Werte
as.numeric(as.character(x)).
3 1 2, die internen Codes
1 2 3 in der Reihenfolge des Auftretens
Ein Skript baut eine Matrix mit matrix(werte, nrow = 2) und wird nach Python als np.array(werte).reshape(2, -1) übertragen. Worauf ist zu achten?
order="F" in NumPy oder byrow = TRUE in R stimmen sie überein. Ohne diese Angabe entsteht kein Fehler, nur eine andere Matrix.
1 3 5 gegen 1 2 3.
-1 ist in NumPy ungültig
-1 heisst “so viele wie nötig” und ist gültig.