Wenn du Sammlungen homogener atomarer Daten verarbeitest (z. B. alle numerisch oder alle vom Typ Zeichen), stehen dir in R die folgenden Strukturen zur Verfügung:
vector für 1-dimensionale Daten, auf die du mit einem einzelnen Index zugreifst.matrix für 2-dimensionale, rechteckige (nicht unregelmäßige) Daten.array für den allgemeinen N-dimensionalen Fall. Eine matrix ist einfach ein array mit N=2.Für Python-Programmierende: Dieser Abschnitt kommt dir vielleicht bekannt vor.
Jede Ähnlichkeit mit NumPy ist kein Zufall (aber R ist etwas älter und hat das alles in der Basissprache).
Eine Matrix ist im Grunde nur ein Vektor plus die Anzahl an Zeilen und Spalten, die ihre Form festlegt.
Mit der Funktion matrix() kannst du sie erzeugen:
# data already available
matrix(1:6, nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
# empty matrix to be filled in later
matrix(nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] NA NA NA
#> [2,] NA NA NA
Beachte, dass die Daten in spaltenweiser Reihenfolge gespeichert werden (also erst nach unten, dann nach rechts). Das ist wie bei Fortran, Matlab und Julia, unterscheidet sich aber von C/C++ und NumPy.
Die Dimensionen einer Matrix ermittelst du mit dim() für Zeilen und Spalten zusammen, oder mit nrow() bzw. ncol(), um eine einzelne Dimension zu erhalten.
Mit length() bekommst du nur die Länge des zugrunde liegenden Vektors: die Gesamtzahl der Elemente (im Beispiel oben 6).
Einige andere Funktionen liefern eine Matrix zurück, zum Beispiel str_split_fixed().
library(stringr)
str_split_fixed(c("split some words", "and some more"), " ", 3)
#> [,1] [,2] [,3]
#> [1,] "split" "some" "words"
#> [2,] "and" "some" "more"
Das funktioniert genauso wie die Indizierung von Vektoren, nur dass du Zeile(n) und Spalte(n) in dieser Reihenfolge angeben musst. Lässt du eine Dimension leer, bekommst du die ganze Zeile bzw. Spalte.
Mit derselben Matrix wie oben:
m <- matrix(1:6, nrow=2, ncol=3)
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[2, 3] # row 2, col 3
#> [1] 6
m[1:2, 3] # rows 1:2, col 3
#> [1] 5 6
m[2, ] # all of row 2
#> [1] 2 4 6
Beachte, dass eine Teilmenge mit nur einer Zeile und/oder Spalte als Vektor zurückgegeben wird, nicht als Matrix.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Diese Art der dimension reduction ist nicht immer erwünscht, besonders in einer Funktionskette, die Matrix-Eingaben erwartet.
Wenn du drop=FALSE angibst, ist sichergestellt, dass eine Matrix-Eingabe auch eine Matrix zurückgibt.
Beachte, dass eine Matrix mit nur einer Zeile oder einer Spalte nicht denselben Typ hat wie ein Vektor.
m[2, , drop = FALSE]
#> [,1] [,2] [,3]
#> [1,] 2 4 6
Die Funktion as.matrix() wandelt einen Vektor in eine einspaltige Matrix um.
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
Wie bei Vektoren kannst du Teile einer Matrix einfach ändern, indem du neue Werte zuweist.
m[, 2] <- c(10, 20)
m
#> [,1] [,2] [,3]
#> [1,] 1 10 5
#> [2,] 2 20 6
Bei Vektoren hat die Funktion c() ausgereicht, um Daten einzufügen und anzuhängen.
Matrizen haben mehr Struktur, deshalb musst du sorgfältiger angeben, was du brauchst.
Die Funktionen rbind() und cbind() hängen neue Zeile(n) bzw. Spalte(n) an die Matrix an.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m2 <- matrix(7:9, nrow = 1, ncol = 3)
rbind(m, m2)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#> [3,] 7 8 9
Zum Löschen einer Zeile oder Spalte verwendest du negative Indizes, wie bei Vektoren.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[, -2]
#> [,1] [,2]
#> [1,] 1 5
#> [2,] 2 6
Alle Vektorfunktionen funktionieren auch mit Matrizen und werden auf alle Elemente angewendet. Allerdings kann dabei die Matrixstruktur verloren gehen, und du bekommst einen Vektor zurück.
sqrt(m)
#> [,1] [,2] [,3]
#> [1,] 1.000000 1.732051 2.236068
#> [2,] 1.414214 2.000000 2.449490
sum(m)
#> [1] 21
m[m > 3]
#> [1] 4 5 6
Für Matrizen können auch die Funktionen colSums() und rowSums() nützlich sein.
In diesem Fall ist die Dimensionsreduktion beabsichtigt, die Rückgabewerte sind also Vektoren.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
Analog gibt es die Funktionen rowMeans() und colMeans() zur Berechnung von Mittelwerten.
In anderen Konzepten haben wir gesehen, dass Tidyverse-Bibliotheken Operationen auf Vektoren und Listen (purrr) oder Dataframes (dplyr) vereinfachen können.
Matrizen und Arrays liegen für Tidyverse-Eingaben jedoch größtenteils außerhalb des Rahmens (zumindest im Moment).
Die Funktion apply() gehört zu Base R und ist nach wie vor das beste Werkzeug, wenn du die Matrix-Programmierung feiner steuern willst.
Die Parameter sind, in dieser Reihenfolge:
Die Funktion rowMeans() gibt es in Base R, aber wir könnten sie auch mit apply() und mean() nachbauen.
randm <- sample(1:10, 16, replace = TRUE) |> matrix(4, 4)
randm
#> [,1] [,2] [,3] [,4]
#> [1,] 4 9 8 3
#> [2,] 6 8 5 5
#> [3,] 4 2 1 2
#> [4,] 4 2 5 4
apply(randm, 1, mean)
#> [1] 6.00 6.00 2.25 3.75
rowMeans(randm)
#> [1] 6.00 6.00 2.25 3.75
Dieser Abschnitt setzt mathematische Kenntnisse voraus, die über den Rahmen von Exercism hinausgehen.
Überspringe ihn bitte, wenn du damit nichts anfangen kannst.
R ist allerdings eine Sprache für Data Science, und viele Nutzende haben einen Hintergrund in angewandter Mathematik und Statistik. Diese knappe Zusammenfassung könnte für manche Lernende von Interesse sein.
Das moderne R ist „offensichtlich" eine Data-Science-Sprache: Sie ist darauf ausgelegt, hauptsächlich mit tabellarischen Daten zu arbeiten, und beliebt bei Fachleuten aus Statistik, Epidemiologie und Sozialwissenschaft. Der größte Konkurrent in diesem Bereich ist der wissenschaftliche Python-Stack, zu dem Pakete wie Pandas oder Polars gehören.
In früheren Jahrzehnten wurde R jedoch auch als mögliche Ingenieurssprache gesehen, die direkt mit Matlab konkurrierte.
Vielleicht überraschend: Base R unterstützt immer noch den vollen Umfang linearalgebraischer Operationen auf Vektoren und Matrizen. Es ist unklar, wie stark sie heute noch im Code der Endnutzenden vorkommen, aber solche Techniken bleiben intern sehr wichtig, um statistische Funktionen zu implementieren.
Lineare Algebra in R wird immer noch in einigen Hochschulkursen gelehrt, etwa in diesem Kurs zur Ökonometrie. Eine gute Einführung in die Mathematik (nicht das Programmieren) gibt es bei 3Blue1Brown.
Die vollständigste Diskussion der linearen Algebra bei Exercism findest du im Julia-Lehrplan (von denselben Autor:innen wie der R-Lehrplan).
Wir sind der Meinung, dass Julia (oder Python in Kontexten, in denen das Modul numpy.linalg verfügbar ist) heute eine geeignetere Sprache für ernsthafte lineare Algebra wäre als R.
Die Hauptdiagonale einer quadratischen Matrix A (N x N)) verläuft von oben links (A[1,1]) nach unten rechts (A[N,N]).
Die Funktion diag() verhält sich unterschiedlich, je nachdem, welches Argument du ihr übergibst.
Bei einem Vektor v erzeugt diag(v) eine Diagonalmatrix.
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
Bei einer quadratischen Matrix A gibt diag(A) die Elemente auf der Hauptdiagonale als Vektor zurück.
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
Bei einer einzelnen Ganzzahl k erzeugt diag(k) eine k × k-Einheitsmatrix: vielleicht nicht das, was du erwartet hättest.
Selbst ernsthafte R-Lehrbücher neigen an dieser Stelle zu Kommentaren wie „Wer hätte das gedacht".
diag(3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Um Zeilen und Spalten zu vertauschen, transponiert die Funktion t() jede Matrix.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
t(m)
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 3 4
#> [3,] 5 6
Bei einer komplexwertigen Matrix ist das Ergebnis nicht die komplexe Konjugierte.
Mit Conj(t(A)) bekommst du die konjugierte Transponierte (Adjungierte) der Matrix A.
Bei höherdimensionalen Arrays permutiert aperm() die Dimensionen.
Für die Inverse einer quadratischen Matrix A können wir solve(A) verwenden.
Manche von uns finden diese Syntax ... unerwartet.
Multipliziert man eine Matrix mit ihrer Inversen, erhält man die Einheitsmatrix (bis auf sehr kleine Rundungsfehler).
A <- matrix(sample(1:10, 9, replace = TRUE), 3, 3)
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
solve(A)
#> [,1] [,2] [,3]
#> [1,] 0.16666667 0.00000000 -0.08333333
#> [2,] -0.05747126 -0.06896552 0.14942529
#> [3,] -0.18965517 0.17241379 0.04310345
A %*% solve(A) |> round(digits = 10)
[,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Die üblichen arithmetischen Operatoren wie +, -, *, ^ arbeiten bei Matrizen elementweise.
Bei einer Dimensionsabweichung gelten die üblichen Regeln des Recyclings.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m * m
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
# same calculation, different syntax
m ^ 2
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
Die verschiedenen anderen Arten der Vektor- und Matrixmultiplikation zu erklären, würde den Rahmen sprengen. Hier aber eine ganz kurze Zusammenfassung:
%*% ist die Infix-Operator-Form von matmult() und liefert das Matrixprodukt.%o% ist die Infix-Operator-Form von outer() und liefert das äußere Produkt.crossprod() liefert das Kreuzprodukt.