Όταν χειρίζεσαι συλλογές ομοιογενών ατομικών δεδομένων (π.χ. όλα αριθμητικά ή όλα χαρακτήρες), οι παρακάτω δομές είναι διαθέσιμες στη R:
vector για μονοδιάστατα δεδομένα, στα οποία έχεις πρόσβαση με μία μόνο θέση.matrix για διδιάστατα, ορθογώνια (όχι οδοντωτά) δεδομένα.array για τη γενική N-διάστατη περίπτωση. Το matrix δεν είναι παρά ένα array με N=2.Για προγραμματιστές Python: Αυτή η ενότητα μπορεί να σου φαίνεται γνωστή.
Οποιαδήποτε ομοιότητα με τη NumPy δεν είναι τυχαία (αλλά η R είναι λίγο παλαιότερη και τα έχει όλα αυτά στη βασική γλώσσα).
Μια μήτρα είναι στην ουσία απλώς ένα διάνυσμα, συν έναν αριθμό γραμμών και στηλών που καθορίζει το σχήμα της.
Υπάρχει μια συνάρτηση matrix() για να τις δημιουργήσεις:
# 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
Σημείωσε ότι τα δεδομένα αποθηκεύονται σε διάταξη κατά στήλες (δηλαδή κάθετα και μετά οριζόντια). Το ίδιο ισχύει για τη Fortran, τη Matlab και τη Julia, αλλά διαφέρει από τη C/C++ και τη NumPy.
Βρες τις διαστάσεις μιας μήτρας με τη dim() για γραμμές και στήλες μαζί, ή με τη nrow() ή τη ncol() για μία μόνο διάσταση.
Χρησιμοποιώντας τη length() θα πάρεις απλώς το μήκος του υποκείμενου διανύσματος: τον συνολικό αριθμό των στοιχείων (6 στο παραπάνω παράδειγμα).
Κάποιες άλλες συναρτήσεις παράγουν έξοδο υπό μορφή μήτρας, για παράδειγμα η 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"
Λειτουργεί με όλους τους ίδιους τρόπους όπως η προσπέλαση στοιχείων διανύσματος, με τη διαφορά ότι πρέπει να ορίσεις τη γραμμή ή τις γραμμές και τη στήλη ή τις στήλες, με αυτή τη σειρά. Άφησε μια διάσταση κενή για να πάρεις ολόκληρη τη γραμμή/στήλη.
Χρησιμοποιώντας την ίδια μήτρα όπως παραπάνω:
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
Σημείωσε ότι ένα υποσύνολο με μία μόνο γραμμή και/ή στήλη επιστρέφεται ως διάνυσμα, όχι ως μήτρα.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Αυτού του είδους η dimension reduction δεν είναι πάντα επιθυμητή, ειδικά σε μια αλυσίδα συναρτήσεων που περιμένει μήτρες ως είσοδο.
Ο καθορισμός drop=FALSE εξασφαλίζει ότι από είσοδο μήτρας θα πάρεις ως τιμή επιστροφής μήτρα.
Σημείωσε ότι μια μήτρα με μία μόνο γραμμή ή μία μόνο στήλη δεν είναι ίδιου τύπου με ένα διάνυσμα.
m[2, , drop = FALSE]
#> [,1] [,2] [,3]
#> [1,] 2 4 6
Η συνάρτηση as.matrix() μετατρέπει ένα διάνυσμα σε μήτρα μίας στήλης.
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
Όπως και με τα διανύσματα, μπορείς να τροποποιήσεις τμήματα μιας μήτρας απλώς εκχωρώντας νέες τιμές.
m[, 2] <- c(10, 20)
m
#> [,1] [,2] [,3]
#> [1,] 1 10 5
#> [2,] 2 20 6
Στα διανύσματα, η συνάρτηση c() αρκούσε για να εισάγεις και να προσαρτήσεις δεδομένα.
Οι μήτρες έχουν πιο πολύπλοκη δομή, οπότε χρειάζονται περισσότερη προσοχή στο να προσδιορίσεις τι απαιτείται.
Οι συναρτήσεις rbind() και cbind() συνδέουν νέες γραμμές ή στήλες με τη μήτρα.
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
Η διαγραφή μιας γραμμής ή στήλης γίνεται με αρνητικές θέσεις, όπως και στα διανύσματα.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[, -2]
#> [,1] [,2]
#> [1,] 1 5
#> [2,] 2 6
Όλες οι συναρτήσεις για διανύσματα λειτουργούν και στις μήτρες, εφαρμοζόμενες σε όλα τα στοιχεία. Ωστόσο, μπορεί να χάσουν τη δομή της μήτρας και να επιστρέψουν ένα διάνυσμα.
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
Για τις μήτρες, οι συναρτήσεις colSums() και rowSums() μπορεί επίσης να σου φανούν χρήσιμες.
Σε αυτή την περίπτωση η μείωση διάστασης είναι σκόπιμη, οπότε οι τιμές επιστροφής είναι διανύσματα.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
Παρομοίως, υπάρχουν οι συναρτήσεις rowMeans() και colMeans() για τον υπολογισμό μέσων όρων.
Σε άλλες έννοιες έχουμε δει ότι οι βιβλιοθήκες του Tidyverse μπορούν να απλοποιήσουν τις πράξεις σε διανύσματα και λίστες (purrr) ή σε dataframes (dplyr).
Ωστόσο, οι μήτρες και οι πίνακες είναι ως επί το πλείστον εκτός πεδίου για τα δεδομένα εισόδου του Tidyverse (τουλάχιστον για την ώρα).
Η συνάρτηση apply() είναι μέρος της Base R και παραμένει το καλύτερο εργαλείο για πιο λεπτομερή έλεγχο στον προγραμματισμό με μήτρες.
Οι παράμετροι, με τη σειρά, είναι:
Η συνάρτηση rowMeans() υπάρχει στη Base R, αλλά θα μπορούσαμε επίσης να την προσομοιώσουμε με τις apply() και mean().
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
Αυτή η ενότητα προϋποθέτει ένα επίπεδο μαθηματικών γνώσεων πέρα από το πεδίο του Exercism.
Παράλειψέ την αν δεν βγάζει κανένα νόημα για σένα.
Ωστόσο, η R είναι μια γλώσσα επιστήμης δεδομένων και πολλοί χρήστες έχουν υπόβαθρο στα εφαρμοσμένα μαθηματικά και τη στατιστική. Αυτή η σύντομη σύνοψη μπορεί να ενδιαφέρει κάποιους μαθητές.
Η σύγχρονη R είναι "προφανώς" μια γλώσσα επιστήμης δεδομένων: σχεδιασμένη να δουλεύει κυρίως με δεδομένα σε μορφή πίνακα και δημοφιλής σε χρήστες όπως στατιστικολόγοι, επιδημιολόγοι και κοινωνικοί επιστήμονες. Ο κυριότερος ανταγωνιστής σε αυτόν τον τομέα είναι το επιστημονικό οικοσύστημα της Python, με πακέτα όπως το Pandas ή το Polars.
Ωστόσο, σε παλαιότερες δεκαετίες θεωρούσαν τη R και ως πιθανή γλώσσα για μηχανικούς, που ανταγωνιζόταν απευθείας τη Matlab.
Ίσως παραδόξως, η Base R υποστηρίζει ακόμη μια πλήρη σειρά πράξεων γραμμικής άλγεβρας σε διανύσματα και μήτρες. Δεν είναι σαφές σε ποιο βαθμό χρησιμοποιούνται πλέον σε κώδικα τελικών χρηστών, αλλά τέτοιες τεχνικές παραμένουν πολύ σημαντικές εσωτερικά για την υλοποίηση στατιστικών συναρτήσεων.
Η γραμμική άλγεβρα στη R διδάσκεται ακόμη σε ορισμένα πανεπιστημιακά μαθήματα, όπως αυτό το μάθημα οικονομετρίας. Ένας καλός οδηγός για τα μαθηματικά (όχι για τον προγραμματισμό) υπάρχει στο 3Blue1Brown.
Η πληρέστερη συζήτηση για τη γραμμική άλγεβρα μέσα στο Exercism βρίσκεται στο πρόγραμμα σπουδών της Julia (ίδιοι συγγραφείς με το πρόγραμμα σπουδών της R).
Πιστεύουμε ότι η Julia (ή η Python σε περιβάλλοντα όπου το numpy.linalg είναι διαθέσιμο) θα ήταν πλέον πιο κατάλληλη γλώσσα για σοβαρή δουλειά στη γραμμική άλγεβρα από τη R.
Η κύρια διαγώνιος μιας τετραγωνικής (N x N)) μήτρας A εκτείνεται από πάνω αριστερά (A[1,1]) ως κάτω δεξιά (A[N,N]).
Η συνάρτηση diag() συμπεριφέρεται διαφορετικά, ανάλογα με το όρισμα που της περνάς.
Με ένα διάνυσμα v, η diag(v) δημιουργεί μια διαγώνια μήτρα.
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
Με μια τετραγωνική μήτρα A, η diag(A) επιστρέφει τα στοιχεία της κύριας διαγωνίου ως διάνυσμα.
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
Με έναν μεμονωμένο ακέραιο k, η diag(k) δημιουργεί μια μοναδιαία μήτρα k × k: ίσως όχι αυτό που θα περίμενες.
Ακόμη και τα σοβαρά εγχειρίδια της R έχουν την τάση να προσθέτουν σχόλια όπως "Άντε βγάλε άκρη" σε αυτό το σημείο.
diag(3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Για να ανταλλάξεις γραμμές και στήλες, η συνάρτηση t() υπολογίζει τον ανάστροφο κάθε μήτρας.
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
Για μια μήτρα με μιγαδικές τιμές, το αποτέλεσμα δεν είναι ο μιγαδικός συζυγής.
Χρησιμοποίησε το Conj(t(A)) για να πάρεις τον συζυγή ανάστροφο (τον προσαρτημένο) της μήτρας A.
Για πίνακες περισσότερων διαστάσεων, η aperm() αναδιατάσσει τις διαστάσεις.
Για τον αντίστροφο μιας τετραγωνικής μήτρας A μπορούμε να χρησιμοποιήσουμε τη solve(A).
Κάποιοι από εμάς βρίσκουμε αυτή τη σύνταξη ... απρόσμενη.
Ο πολλαπλασιασμός μιας μήτρας με τον αντίστροφό της δίνει τη μοναδιαία μήτρα (με την επιφύλαξη πολύ μικρών σφαλμάτων στρογγυλοποίησης).
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
Οι συνήθεις αριθμητικοί τελεστές όπως οι +, -, *, ^ δρουν στις μήτρες στοιχείο προς στοιχείο.
Αν υπάρχει αναντιστοιχία διαστάσεων, εφαρμόζονται οι συνήθεις κανόνες της ανακύκλωσης.
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
Η εξήγηση των διάφορων άλλων τύπων πολλαπλασιασμού διανυσμάτων και μητρών ξεφεύγει από το πεδίο μας, ωστόσο σε πολύ σύντομη σύνοψη:
%*% είναι η μορφή ένθετου τελεστή της matmult(), που δίνει το γινόμενο μητρών.%o% είναι η μορφή ένθετου τελεστή της outer(), που δίνει το εξωτερικό γινόμενο.crossprod() δίνει το διανυσματικό γινόμενο.