NumPy und SciPy

NumPy
Python
R
Programmiergrundlagen
Arrays, Vektorisierung und die wichtigsten numerischen Werkzeuge.

Kernideen

  • Das 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.
  • Passt ein Wert nicht zum 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.
  • Broadcasting gleicht Formen von hinten an, nur wo eine Achse die Länge eins hat. R recycelt spaltenweise, und das zentriert eine Matrix falsch.
  • Ein Slice ist eine View auf dieselben Daten, eine Auswahl per Bedingung eine Kopie. R kennt keine Views.
  • SciPy liefert die Verfahren: Verteilungen, Tests, Integration, Optimierung. Was in R zur Grundausstattung gehört, liegt in Python hier.

Erklärung

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.

Warum eine Liste nicht genügt

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.

Die wichtigsten Eigenschaften eines Arrays

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.

Achsen

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.

Broadcasting und Recycling

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.

View oder Kopie

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).

SciPy: die Verfahren obendrauf

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.

Kurz nachgeschlagen

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)

Beispiele

Frage und Datenlage

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?

Rechnung

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
liste = [12, 45, 7, 31]
feld = np.array(liste)

print(liste * 2)
[12, 45, 7, 31, 12, 45, 7, 31]
print(feld * 2)
[24 90 14 62]
print(liste + [1])
[12, 45, 7, 31, 1]
print(feld + 1)
[13 46  8 32]
print(feld.dtype, feld.shape, feld.nbytes)
int64 (4,) 32

Output Zeile für Zeile

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

Interpretation und Ergebnissatz

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 * 2 die verdoppelten Werte 24, 90, 14 und 62, auf der Liste eine Liste mit acht Elementen. Das Array belegt 32 Byte, acht je ganzer Zahl.

Frage und Datenlage

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?

Rechnung

ganze <- c(1L, 2L, 3L)
ganze[1] <- 3.7              # der ganze Vektor wird Gleitkomma
ganze
[1] 3.7 2.0 3.0
class(ganze)
[1] "numeric"
c(1, 2, "drei")
[1] "1"    "2"    "drei"
.Machine$integer.max
[1] 2147483647
suppressWarnings(.Machine$integer.max + 1L)   # Ueberlauf wird NA
[1] NA
.Machine$integer.max + 1                      # als Gleitkommazahl kein Problem
[1] 2147483648
ganze = np.array([1, 2, 3])
print(ganze.dtype)
int64
ganze[0] = 3.7               # abgeschnitten, ohne Warnung
print(ganze)
[3 2 3]
print(np.array([1, 2.5, 3]).dtype)
float64
print(np.array([1, 2, "drei"]))
['1' '2' 'drei']
klein = np.array([200, 250], dtype=np.uint8)
print(klein + np.uint8(100))           # Ueberlauf: modulo 256
[44 94]
print(klein.astype(np.int64) + 100)    # erst umwandeln, dann rechnen
[300 350]

Output Zeile für Zeile

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.

Interpretation und Ergebnissatz

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 uint8 lief bei 200 + 100 still auf 44 über. R wandelt stattdessen den ganzen Vektor in Gleitkomma um und meldet einen Ganzzahlüberlauf als NA.

Frage und Datenlage

Eine 3 × 4-Matrix mit den Zahlen 1 bis 12, zeilenweise gefüllt. Gesucht sind Spaltensummen, Zeilensummen, Gesamtsumme und Spaltenmittel.

Rechnung

a <- matrix(1:12, nrow = 3, byrow = TRUE)
a
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12
colSums(a)
[1] 15 18 21 24
rowSums(a)
[1] 10 26 42
sum(a)
[1] 78
apply(a, 2, mean)            # 2 = die Spalten bleiben
[1] 5 6 7 8
a = np.arange(1, 13).reshape(3, 4)
print(a)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
print(a.shape)
(3, 4)
print(a.sum(axis=0))         # Achse 0 verschwindet
[15 18 21 24]
print(a.sum(axis=1))
[10 26 42]
print(a.sum())
78
print(a.mean(axis=0))
[5. 6. 7. 8.]

Output Zeile für Zeile

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.

Interpretation und Ergebnissatz

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 R colSums oder apply(a, 2, ...).

Frage und Datenlage

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.

Rechnung

m <- matrix(c(2, 10, 4, 30, 9, 20), nrow = 3, byrow = TRUE)
colMeans(m)
[1]  5 20
m - colMeans(m)              # FALSCH, und ohne Warnung
     [,1] [,2]
[1,]   -3  -10
[2,]  -16   25
[3,]    4    0
sweep(m, 2, colMeans(m))     # richtig: je Spalte abziehen
     [,1] [,2]
[1,]   -3  -10
[2,]   -1   10
[3,]    4    0
colMeans(sweep(m, 2, colMeans(m)))
[1] 0 0
m - rowMeans(m)              # je Zeile: passt in R von selbst
      [,1] [,2]
[1,]  -4.0  4.0
[2,] -13.0 13.0
[3,]  -5.5  5.5
m = np.array([[2.0, 10.0], [4.0, 30.0], [9.0, 20.0]])
print(m.mean(axis=0))
[ 5. 20.]
zentriert = m - m.mean(axis=0)          # (3, 2) minus (2,)
print(zentriert)
[[ -3. -10.]
 [ -1.  10.]
 [  4.   0.]]
print(zentriert.mean(axis=0))
[0. 0.]
try:
    m - m.mean(axis=1)                  # (3, 2) minus (3,)
except ValueError as fehler:
    print("ValueError:", fehler)
ValueError: operands could not be broadcast together with shapes (3,2) (3,) 
print(m - m.mean(axis=1, keepdims=True))   # (3, 2) minus (3, 1)
[[ -4.    4. ]
 [-13.   13. ]
 [ -5.5   5.5]]

Output Zeile für Zeile

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.

Interpretation und Ergebnissatz

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 ist sweep(m, 2, colMeans(m)).

Frage und Datenlage

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?

Rechnung

original <- 0:9
teil <- original[3:5]
teil[1] <- 999
original                     # unveraendert: R hat keine Views
 [1] 0 1 2 3 4 5 6 7 8 9
original = np.arange(10)

teil = original[2:5]          # View
teil[0] = 999
print(original)
[  0   1 999   3   4   5   6   7   8   9]
kopie = original[2:5].copy()
kopie[0] = -1
print(original)
[  0   1 999   3   4   5   6   7   8   9]
maske = original[original > 5]    # Auswahl per Bedingung: Kopie
maske[0] = -5
print(original)
[  0   1 999   3   4   5   6   7   8   9]
print(np.shares_memory(teil, original),
      np.shares_memory(maske, original),
      np.shares_memory(kopie, original))
True False False

Output Zeile für Zeile

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.

Interpretation und Ergebnissatz

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.

Frage und Datenlage

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.

Rechnung

werte <- c(98, 102, 95, 110, 104, 99, 101, 97, 120, 100)

round(c(pnorm(1.96), qnorm(0.975), qt(0.975, df = 9)), 6)
[1] 0.975002 1.959964 2.262157
shapiro.test(werte)

    Shapiro-Wilk normality test

data:  werte
W = 0.83806, p-value = 0.04182
shapiro.test(werte[werte != 120])

    Shapiro-Wilk normality test

data:  werte[werte != 120]
W = 0.93586, p-value = 0.5391
integrate(dnorm, -1.96, 1.96)$value
[1] 0.9500042
round(optimize(function(x) (x - 3)^2 + 2, interval = c(-10, 10))$minimum, 3)
[1] 3
c(mittel = mean(werte), sd = round(sd(werte), 4))
  mittel       sd 
102.6000   7.3967 
werte = np.array([98, 102, 95, 110, 104, 99, 101, 97, 120, 100])

print(round(stats.norm.cdf(1.96), 6), round(stats.norm.ppf(0.975), 6),
      round(stats.t.ppf(0.975, df=9), 6))
0.975002 1.959964 2.262157
sw = stats.shapiro(werte)
print(round(sw.statistic, 5), round(sw.pvalue, 5))
0.83806 0.04182
sw_ohne = stats.shapiro(werte[werte != 120])
print(round(sw_ohne.statistic, 5), round(sw_ohne.pvalue, 5))
0.93586 0.53909
flaeche, _ = integrate.quad(stats.norm.pdf, -1.96, 1.96)
print(round(flaeche, 7))
0.9500042
ergebnis = optimize.minimize_scalar(lambda x: (x - 3) ** 2 + 2)
print(round(ergebnis.x, 3), round(ergebnis.fun, 3))
3.0 2.0
print(werte.mean(), round(werte.std(ddof=1), 4), round(werte.std(), 4))
102.6 7.3967 7.0171

Output Zeile für Zeile

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.

Interpretation und Ergebnissatz

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() ohne ddof=1 gibt dagegen 7.0171 aus.

Verständnisfragen

Ein Array hat die Form (100, 3), ein zweites (3,). Worauf wirkt das zweite bei einer gemeinsamen Rechnung?

Auf jede der hundert Zeilen
Richtig. Die Formen werden von hinten verglichen, dort steht beide Male die 3. Typischer Fall ist das Abziehen von Spaltenmittelwerten wie in Beispiel 4.
Auf die ersten drei Zeilen
Broadcasting schneidet nichts ab, es wiederholt.
Es entsteht ein Fehler wegen unterschiedlicher Formen
Die Formen sind verträglich. Ein Fehler entstünde bei (100,).

Warum liefert a.sum(axis=0) bei einer 3 × 4-Matrix vier Zahlen?

Achse 0 ist die der Zeilen und fällt beim Summieren weg
Richtig. Übrig bleiben die vier Spalten. Die Zahl in axis benennt die Achse, die verschwindet.
Weil NumPy immer über die längere Achse summiert
Die Länge spielt keine Rolle, nur die angegebene Achse.
Weil axis=0 in NumPy dasselbe bedeutet wie die 1 in apply
In 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?

Ja, sonst hätte R gewarnt
R warnt nur, wenn die Länge nicht aufgeht. Bei drei Zeilen und zwei Spalten geht sie auf.
Nein, R recycelt spaltenweise und verteilt die Spaltenmittel über die Zeilen
Richtig, wie Beispiel 4 zeigt. sweep(m, 2, colMeans(m)) oder scale(m, scale = FALSE) zentrieren richtig.
Nur wenn die Matrix quadratisch ist
Auch dann wäre sie falsch zentriert, nur passten die Längen immer.

teil = original[2:5] und danach teil[0] = 999. Was ist mit original?

Es hat sich mitverändert
Richtig. Ein Slice liefert eine View. Wer eine unabhängige Kopie braucht, ruft .copy() auf.
Es bleibt unverändert
So wäre es bei original[original > 5] oder in R.
Es gibt einen Fehler
Die Zuweisung ist gültig, und genau das macht den Fall gefährlich.

Ein uint8-Array mit Grauwerten 0 bis 255 wird um 100 aufgehellt. Ein Pixel mit dem Wert 200 ist danach sehr dunkel. Warum?

Der Wert ist übergelaufen, 300 − 256 = 44
Richtig. Feste Ganzzahltypen rechnen modulo ihres Wertebereichs, ohne Warnung. Abhilfe: vorher in einen grösseren Typ umwandeln und danach mit np.clip auf 0 bis 255 begrenzen.
NumPy hat einen Rundungsfehler gemacht
Es wird nicht gerundet, sondern umgebrochen.
Grauwerte über 255 werden auf 0 gesetzt
Dann wäre der Wert 0, nicht 44.

np.std(werte) ergibt 7.0171, sd(werte) in R 7.3967. Welcher Wert ist für eine Stichprobe richtig?

7.3967, mit ddof=1
Richtig. NumPy teilt ohne Angabe durch n, R durch n − 1. pandas verwendet übrigens wie R n − 1.
7.0171, weil NumPy genauer rechnet
Beide rechnen exakt, nur mit verschiedenen Formeln.
Beide gleich gut
Für eine Stichprobe ist die Formel mit n − 1 die übliche erwartungstreue Schätzung der Varianz.

Verlinkte Ressourcen