werte <- c(12, 45, 7, 31)
werte * 2[1] 24 90 14 62
werte + 1[1] 13 46 8 32
c(werte, 1) # Anhaengen ist eine eigene Funktion[1] 12 45 7 31 1
ndarray hat einen Typ für alle Elemente, eine feste Form und liegt zusammenhängend im Speicher. Damit rechnet Python so, wie R es von Haus aus tut.dtype, wird still umgewandelt: abgeschnitten, zu Text gemacht oder bei festen Ganzzahltypen übergelaufen.axis nennt die Achse, die beim Zusammenfassen verschwindet. axis=0 liefert ein Ergebnis je Spalte.Vorwissen: Python-Grundlagen für Listen und Slicing. Der Unterschied zwischen Liste und Array ist der rote Faden dieser Seite. Wer von R herkommt, erkennt in NumPy das wieder, was R mit Vektoren und Matrizen mitbringt. Tabellen mit benannten Spalten kommen danach mit pandas.
Eine Python-Liste darf beliebige Objekte enthalten und speichert deshalb für jedes Element einen Verweis. Rechnen heisst dann: Verweis folgen, Typ prüfen, Operation heraussuchen, ausführen, und das für jedes Element einzeln. Ein Array speichert alle Werte desselben Typs zusammenhängend, und die Rechnung läuft in kompiliertem Code über den ganzen Block. Bei grossen Arrays ist das um ein Vielfaches schneller; der genaue Faktor hängt vom Rechner ab und gehört deshalb nicht in eine Notiz.
Wichtiger als die Geschwindigkeit ist die Bedeutung der Operatoren: Auf einer Liste hängt + an und * wiederholt, auf einem Array rechnen beide elementweise.
| Attribut | Bedeutung | Beispiel |
|---|---|---|
dtype |
Typ aller Elemente | int64, float64, bool, <U4 (Text) |
shape |
Form als Tupel | (3, 4): drei Zeilen, vier Spalten |
ndim |
Anzahl der Achsen | 2 |
size |
Anzahl der Elemente | 12 |
nbytes |
Speicherbedarf | 12 · 8 = 96 Byte bei int64 |
Feste Ganzzahltypen wie uint8 (0 bis 255) oder int32 haben einen begrenzten Wertebereich. Wird er überschritten, laufen sie über und beginnen von vorn, ohne Fehler. Bilddaten sind häufig uint8, und genau dort passiert es.
Bei einer 3 × 4-Matrix hat Achse 0 die Länge 3 (Zeilen) und Achse 1 die Länge 4 (Spalten). a.sum(axis=0) summiert entlang der Zeilen, Achse 0 verschwindet, und übrig bleiben vier Werte, einer je Spalte. Die Zahl in axis benennt die Achse, die verschwindet, nicht die, die bleibt.
In R bedeutet die Zahl bei apply(a, 2, mean) das Gegenteil: Die 2 steht für die Spalten, die erhalten bleiben. Beide Aufrufe liefern das Spaltenmittel, mit den Ziffern 0 und 2.
NumPy vergleicht die Formen von hinten. Zwei Achsen passen, wenn sie gleich lang sind oder eine die Länge eins hat; fehlende Achsen vorne werden als Länge eins ergänzt. (3, 2) und (2,) passen, (3, 2) und (3,) nicht, (3, 2) und (3, 1) wieder.
R recycelt stattdessen den kürzeren Vektor entlang der Speicherreihenfolge, und die ist spaltenweise. Deshalb verhält sich R bei Matrizen genau umgekehrt zu NumPy: Ein Vektor mit einem Wert je Zeile passt von selbst, ein Vektor mit einem Wert je Spalte wird falsch verteilt.
Ein Slice wie a[2:5] beschreibt ein regelmässiges Muster aus Start, Ende und Schrittweite und kann deshalb auf denselben Speicher zeigen. Eine Auswahl per Bedingung oder Indexliste greift unregelmässig verteilte Elemente heraus, die neu abgelegt werden müssen. Die Regel: Slice mit Doppelpunkt ist eine View, Maske und Indexliste sind Kopien. Ob zwei Arrays Speicher teilen, prüft np.shares_memory(a, b).
| Modul | Inhalt | R-Gegenstück |
|---|---|---|
scipy.stats |
Verteilungen (norm, t, chi2), Tests |
pnorm, qt, shapiro.test, t.test |
scipy.optimize |
Minimum, Nullstellen, Kurvenanpassung | optimize, optim, uniroot, nls |
scipy.integrate |
numerische Integration | integrate |
scipy.interpolate |
Interpolation | approx, spline |
scipy.linalg |
lineare Algebra | solve, eigen, qr |
Jede Verteilung in scipy.stats hat dieselben Methoden: pdf (Dichte), cdf (Verteilungsfunktion), ppf (Quantil) und rvs (Zufallszahlen). In R entsprechen sie den Präfixen d, p, q und r. Für Tests mit vollständiger Ergebnistabelle nimmt diese Sammlung pingouin, siehe Hypothesentests.
| Aufgabe | R | Python |
|---|---|---|
| Folge erzeugen | seq(0, 1, by = 0.1), 1:10 |
np.linspace(0, 1, 11), np.arange(1, 11) |
| Matrix aus Werten | matrix(x, nrow = 3, byrow = TRUE) |
np.array(x).reshape(3, -1) |
| Spaltensummen, Zeilensummen | colSums(a), rowSums(a) |
a.sum(axis=0), a.sum(axis=1) |
| Spalten zentrieren | sweep(a, 2, colMeans(a)) |
a - a.mean(axis=0) |
| Standardabweichung | sd(x) |
x.std(ddof=1) |
| bedingt ersetzen | ifelse(x > 3, x, 0) |
np.where(x > 3, x, 0) |
| eigene Kopie | nicht nötig | a[1:3].copy() |
| Quantil der Normalverteilung | qnorm(0.975) |
stats.norm.ppf(0.975) |
| Minimum einer Funktion | optimize(f, c(-10, 10)) |
optimize.minimize_scalar(f) |
Vier Werte 12, 45, 7, 31, einmal als Python-Liste und einmal als NumPy-Array. Was bedeuten * 2 und + 1 jeweils, und wie viel Speicher belegt das Array?
| Ausdruck | Liste | Array | R | Erklärung |
|---|---|---|---|---|
* 2 |
acht Elemente, die Liste zweimal | [24 90 14 62] |
24 90 14 62 | Nur das Array rechnet elementweise, wie der R-Vektor. |
+ |
liste + [1] hängt an |
feld + 1: [13 46 8 32] |
werte + 1 rechnet, c() hängt an |
Dasselbe Zeichen, verschiedene Bedeutung. |
dtype |
int64 |
Ganze Zahlen mit 8 Byte. | ||
shape |
(4,) |
eine Achse mit vier Elementen | ||
nbytes |
32 | 4 Werte · 8 Byte, ohne Verweise dazwischen |
Wer von R kommt, erwartet von * 2 eine Verdopplung jedes Werts und bekommt sie in Python nur vom Array. Die Umwandlung mit np.array() ist deshalb der erste Schritt jeder numerischen Arbeit in Python; pandas-Spalten sind intern bereits Arrays.
Auf dem Array liefert
* 2die verdoppelten Werte 24, 90, 14 und 62, auf der Liste eine Liste mit acht Elementen. Das Array belegt 32 Byte, acht je ganzer Zahl.
Was passiert, wenn in ein Ganzzahl-Array der Wert 3.7 geschrieben wird, wenn Zahlen und Text gemischt werden, und wenn ein uint8-Array mit den Werten 200 und 250 um 100 erhöht wird?
[1] 3.7 2.0 3.0
[1] "numeric"
[1] "1" "2" "drei"
[1] 2147483647
[1] NA
[1] 2147483648
int64
[3 2 3]
float64
['1' '2' 'drei']
[44 94]
[300 350]
| Schritt | Python | R | Erklärung |
|---|---|---|---|
| 3.7 in Ganzzahlen | [3 2 3] |
3.7 2.0 3.0, Klasse numeric |
NumPy passt den Wert an den festen dtype an und schneidet ab. R passt den Vektor an den Wert an und wird Gleitkomma. |
1, 2.5, 3 |
float64 |
Eine Gleitkommazahl macht alles zu Gleitkomma. | |
| mit Text | ['1' '2' 'drei'] |
"1" "2" "drei" |
Alles wird Text, in beiden Sprachen. |
uint8 200 + 100, 250 + 100 |
[44 94] |
300 − 256 = 44, 350 − 256 = 94. Kein Fehler, keine Warnung. | |
nach astype(int64) |
[300 350] |
Mit genügend grossem Typ richtig. | |
| grösste ganze Zahl in R | 2147483647 | 32-Bit-Ganzzahl | |
plus 1L |
NA mit Warnung |
R läuft nicht über, sondern meldet einen fehlenden Wert. | |
plus 1 |
2147483648 | 1 ist in R eine Gleitkommazahl, die Summe auch. |
NumPy behandelt den dtype als Vertrag: Das Array ändert seinen Typ nicht, die Werte müssen sich anpassen. Das ist schnell und bei Ganzzahltypen gefährlich, weil Abschneiden und Überlauf keine Meldung erzeugen. R wählt die entgegengesetzte Strategie und ändert lieber den Typ. Wer Werte in ein bestehendes Array schreibt oder mit kleinen Typen rechnet, prüft zuerst dtype.
Der Wert 3.7 wurde im Ganzzahl-Array ohne Warnung zu 3 abgeschnitten, und
uint8lief bei 200 + 100 still auf 44 über. R wandelt stattdessen den ganzen Vektor in Gleitkomma um und meldet einen Ganzzahlüberlauf alsNA.
Eine 3 × 4-Matrix mit den Zahlen 1 bis 12, zeilenweise gefüllt. Gesucht sind Spaltensummen, Zeilensummen, Gesamtsumme und Spaltenmittel.
| Ergebnis | Python | R | Werte |
|---|---|---|---|
| Form | (3, 4) |
3 Zeilen, 4 Spalten | |
| je Spalte summiert | axis=0 |
colSums |
15, 18, 21, 24 |
| je Zeile summiert | axis=1 |
rowSums |
10, 26, 42 |
| alles | ohne axis |
sum |
78 |
| Spaltenmittel | mean(axis=0) |
apply(a, 2, mean) |
5, 6, 7, 8 |
Die erste Spalte enthält 1, 5, 9, Summe 15; die erste Zeile 1, 2, 3, 4, Summe 10. Vier Spaltensummen, weil Achse 0 mit ihren drei Zeilen wegfällt.
axis=0 und apply(..., 2, ...) liefern beide ein Ergebnis je Spalte, mit verschiedenen Ziffern. Die Merkregel für NumPy lautet: Die Achse in axis verschwindet. Für R: Die Ziffer in apply bleibt. Bei einem Ergebnis mit unerwarteter Länge ist .shape der erste Blick.
Die Spaltensummen der Matrix betragen 15, 18, 21 und 24, die Zeilensummen 10, 26 und 42, die Gesamtsumme 78. In NumPy steht dafür
axis=0, in RcolSumsoderapply(a, 2, ...).
Drei Beobachtungen mit zwei Merkmalen (2, 10, 4, 30, 9, 20). Jede Spalte soll um ihr Mittel zentriert werden, wie vor einer Hauptkomponentenanalyse. Danach dasselbe je Zeile.
[1] 5 20
[,1] [,2]
[1,] -3 -10
[2,] -16 25
[3,] 4 0
[,1] [,2]
[1,] -3 -10
[2,] -1 10
[3,] 4 0
[1] 0 0
[,1] [,2]
[1,] -4.0 4.0
[2,] -13.0 13.0
[3,] -5.5 5.5
[ 5. 20.]
[[ -3. -10.]
[ -1. 10.]
[ 4. 0.]]
[0. 0.]
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
[[ -4. 4. ]
[-13. 13. ]
[ -5.5 5.5]]
| Schritt | Python | R | Erklärung |
|---|---|---|---|
| Spaltenmittel | [ 5. 20.] |
5 20 | |
| je Spalte zentriert | Zeilen -3 -10, -1 10, 4 0 |
sweep: dieselbe Matrix |
(2,) passt von hinten auf (3, 2). |
| Kontrolle | [0. 0.] |
0 0 | Zentrierte Spalten haben Mittel null. |
m - colMeans(m) in R |
Spalte 1: −3, −16, 4; Spalte 2: −10, 25, 0 | R zieht 5, 20, 5, 20, 5, 20 spaltenweise ab: Die 20 landet in der ersten Spalte. Sechs ist ein Vielfaches von zwei, also keine Warnung. | |
| je Zeile, ohne Umformen | ValueError, Formen (3,2) und (3,) |
passt | NumPy vergleicht die 2 hinten mit der 3 und bricht ab. R verteilt drei Zeilenmittel spaltenweise, also richtig. |
mit keepdims=True |
Zeilen -4 4, -13 13, -5.5 5.5 |
dieselben Werte | Die Form (3, 1) passt. Zeilenmittel 6, 17, 14.5. |
Die beiden Sprachen irren in entgegengesetzte Richtungen. NumPy verweigert das Zeilenzentrieren ohne ausdrückliche Form und macht das Spaltenzentrieren richtig. R macht das Zeilenzentrieren richtig und das Spaltenzentrieren still falsch. Die falsche R-Matrix sieht plausibel aus; erst die Kontrolle colMeans(...) auf null würde es zeigen. In R ist deshalb sweep() oder scale(m, scale = FALSE) der sichere Weg.
Mit
m - m.mean(axis=0)sind beide Spalten zentriert, ihre Mittel sind null. Derselbe Ausdruck in R,m - colMeans(m), liefert ohne Warnung eine falsche Matrix, weil R spaltenweise recycelt; richtig istsweep(m, 2, colMeans(m)).
Aus den Zahlen 0 bis 9 wird ein Ausschnitt genommen und verändert, einmal als Slice, einmal als Kopie und einmal als Auswahl per Bedingung. Was passiert mit dem Original?
[ 0 1 999 3 4 5 6 7 8 9]
[ 0 1 999 3 4 5 6 7 8 9]
[ 0 1 999 3 4 5 6 7 8 9]
True False False
| Schritt | Original danach | teilt Speicher | Erklärung |
|---|---|---|---|
| Slice verändert | [ 0 1 999 3 ...] |
True |
Das Original hat sich mitverändert. |
| Kopie verändert | unverändert | False |
.copy() trennt. |
| Maske verändert | unverändert | False |
Eine Auswahl per Bedingung ist immer eine Kopie. |
| R | 0 1 2 ... 9 |
R kopiert beim Ändern; Views gibt es nicht. |
Die 999 steht an Position 2, weil der Slice bei Position 2 beginnt. Die dritte Ausgabe zeigt dieselbe Zeile wie die zweite: Weder Kopie noch Maske haben das Original berührt.
Views sparen Speicher und Zeit, weil bei grossen Arrays nichts kopiert wird. Sie sind zugleich eine Fehlerquelle der Art aus Python-Grundlagen, Beispiel 1: Zwei Namen zeigen auf dieselben Daten. Wer einen Ausschnitt verändern und das Original behalten will, ruft .copy() auf. In pandas führt dasselbe Muster zur bekannten SettingWithCopyWarning.
Das Verändern des Slices
original[2:5]setzte im Original die 999, weil der Slice eine View ist; Kopie und Maske liessen das Original unverändert. In R bleibt das Original immer unberührt.
Zehn Messwerte 98, 102, 95, 110, 104, 99, 101, 97, 120, 100. Gesucht sind einige Standardgrössen der Normal- und t-Verteilung, ein Normalitätstest mit und ohne den auffälligen Wert 120, eine Fläche unter der Dichte, das Minimum einer Funktion und die Standardabweichung.
[1] 0.975002 1.959964 2.262157
Shapiro-Wilk normality test
data: werte
W = 0.83806, p-value = 0.04182
Shapiro-Wilk normality test
data: werte[werte != 120]
W = 0.93586, p-value = 0.5391
[1] 0.9500042
[1] 3
mittel sd
102.6000 7.3967
0.975002 1.959964 2.262157
0.83806 0.04182
0.93586 0.53909
0.9500042
3.0 2.0
102.6 7.3967 7.0171
| Grösse | Python | R | Erklärung |
|---|---|---|---|
| P(Z ≤ 1.96) | 0.975002 | 0.975002 | cdf beziehungsweise pnorm |
| 97.5-%-Quantil | 1.959964 | 1.959964 | ppf beziehungsweise qnorm; die Grenze des zweiseitigen 95-%-Intervalls |
| t-Quantil, 9 Freiheitsgrade | 2.262157 | 2.262157 | grösser als 1.96, weil die t-Verteilung breitere Ränder hat, siehe Prüfverteilungen |
| Shapiro-Wilk, alle zehn | W = 0.83806, p = 0.04182 | W = 0.83806, p-value = 0.04182 |
Beide Sprachen nutzen denselben Algorithmus. p unter 0.05: Normalität wird verworfen. |
| ohne den Wert 120 | W = 0.93586, p = 0.53909 | dieselben Werte | Ein einzelner Wert entscheidet über das Testergebnis. |
| Fläche von −1.96 bis 1.96 | 0.9500042 | 0.9500042 | numerisch integriert, wie erwartet 95 % |
| Minimum von (x − 3)² + 2 | bei x = 3.0, Wert 2.0 | 3 | R sucht im angegebenen Intervall, SciPy ohne Intervall mit dem Brent-Verfahren. |
| Mittelwert | 102.6 | 102.6 | |
| Standardabweichung | std(ddof=1): 7.3967; std(): 7.0171 |
sd: 7.3967 |
NumPy teilt ohne Angabe durch n, R durch n − 1. |
SciPy liefert dieselben Zahlen wie R, nur in anderer Verpackung: Wo R eine formatierte Testausgabe druckt, gibt SciPy ein Objekt mit statistic und pvalue zurück. Zwei Dinge sind inhaltlich wichtiger als die Syntax. Erstens kippt der Wert 120 allein das Ergebnis des Normalitätstests, weshalb ein Test nie ohne Grafik gelesen wird, siehe Normalitätstests. Zweitens ist np.std() ohne ddof=1 die Populationsformel und für Stichproben um den Faktor √(9/10) zu klein.
Mit allen zehn Werten verwirft der Shapiro-Wilk-Test die Normalität (W = 0.838, p = 0.042), ohne den Wert 120 nicht mehr (p = 0.539); R und SciPy liefern identische Zahlen. Die Standardabweichung beträgt 7.3967,
np.std()ohneddof=1gibt dagegen 7.0171 aus.
Ein Array hat die Form (100, 3), ein zweites (3,). Worauf wirkt das zweite bei einer gemeinsamen Rechnung?
(100,).
Warum liefert a.sum(axis=0) bei einer 3 × 4-Matrix vier Zahlen?
axis benennt die Achse, die verschwindet.
axis=0 in NumPy dasselbe bedeutet wie die 1 in apply
apply steht für dasselbe Ergebnis die 2.
In R wird eine Matrix mit m - colMeans(m) zentriert. Es gibt keine Warnung. Ist das Ergebnis richtig?
sweep(m, 2, colMeans(m)) oder scale(m, scale = FALSE) zentrieren richtig.
teil = original[2:5] und danach teil[0] = 999. Was ist mit original?
.copy() auf.
original[original > 5] oder in R.
Ein uint8-Array mit Grauwerten 0 bis 255 wird um 100 aufgehellt. Ein Pixel mit dem Wert 200 ist danach sehr dunkel. Warum?
np.clip auf 0 bis 255 begrenzen.
np.std(werte) ergibt 7.0171, sd(werte) in R 7.3967. Welcher Wert ist für eine Stichprobe richtig?
ddof=1