R Language
Migliori pratiche di vettorizzazione del codice R
Ricerca…
Per operazioni di riga
La chiave per la vettorizzazione del codice R è quella di ridurre o eliminare "mediante operazioni di riga" o l'invio di metodi di funzioni R.
Ciò significa che quando ci si avvicina a un problema che a prima vista richiede "per operazioni di riga", come il calcolo delle medie di ogni riga, è necessario chiedersi:
- Quali sono le classi dei set di dati con cui ho a che fare?
- Esiste un codice compilato esistente che può ottenere ciò senza la necessità di una valutazione ripetitiva delle funzioni R?
- In caso contrario, posso fare queste operazioni per colonne invece per riga?
- Infine, vale la pena dedicare molto tempo allo sviluppo di un codice vettoriale complicato invece di eseguire semplicemente un semplice ciclo di
apply
? In altre parole, i dati sono abbastanza grandi e sofisticati che R non può gestirli efficientemente usando un semplice ciclo?
Mettendo da parte il problema di pre-allocazione della memoria e l'oggetto in crescita nei loop, ci concentreremo su questo esempio su come evitare di apply
cicli, dispatching o rivalutazione delle funzioni R all'interno dei loop.
Un modo semplice / standard per calcolare la media per riga sarebbe:
apply(mtcars, 1, mean)
Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout Valiant Duster 360
29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000
Merc 240D Merc 230 Merc 280 Merc 280C Merc 450SE Merc 450SL Merc 450SLC
24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000
Cadillac Fleetwood Lincoln Continental Chrysler Imperial Fiat 128 Honda Civic Toyota Corolla Toyota Corona
66.23273 66.05855 65.97227 19.44091 17.74227 18.81409 24.88864
Dodge Challenger AMC Javelin Camaro Z28 Pontiac Firebird Fiat X1-9 Porsche 914-2 Lotus Europa
47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027
Ford Pantera L Ferrari Dino Maserati Bora Volvo 142E
60.97182 34.50818 63.15545 26.26273
Ma possiamo fare di meglio? Vediamo cosa è successo qui:
- Innanzitutto, abbiamo convertito un
data.frame
in unamatrix
. (Nota che il suo avviene all'interno della funzioneapply
.) Questo è sia inefficiente che pericoloso. unamatrix
non può contenere più tipi di colonna alla volta. Quindi, tale conversione porterà probabilmente alla perdita di informazioni e alcune volte a risultati fuorvianti (confrontareapply(iris, 2, class)
constr(iris)
o consapply(iris, class)
). - In secondo luogo, abbiamo eseguito un'operazione ripetutamente, una volta per ogni riga. Significato, abbiamo dovuto valutare alcune volte la funzione R in
nrow(mtcars)
. In questo caso specifico,mean
non è una funzione computazionalmente costosa, quindi R potrebbe facilmente gestirlo anche per un grande set di dati, ma cosa succederebbe se dovessimo calcolare la deviazione standard per riga (che comporta un'operazione costosa di radice quadrata) ? Il che ci porta al prossimo punto: - Abbiamo valutato la funzione R molte volte, ma forse c'è già una versione compilata di questa operazione?
In effetti, potremmo semplicemente fare:
rowMeans(mtcars)
Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive Hornet Sportabout Valiant Duster 360
29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000
Merc 240D Merc 230 Merc 280 Merc 280C Merc 450SE Merc 450SL Merc 450SLC
24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000
Cadillac Fleetwood Lincoln Continental Chrysler Imperial Fiat 128 Honda Civic Toyota Corolla Toyota Corona
66.23273 66.05855 65.97227 19.44091 17.74227 18.81409 24.88864
Dodge Challenger AMC Javelin Camaro Z28 Pontiac Firebird Fiat X1-9 Porsche 914-2 Lotus Europa
47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027
Ford Pantera L Ferrari Dino Maserati Bora Volvo 142E
60.97182 34.50818 63.15545 26.26273
Ciò comporta no operazioni di riga e quindi nessuna valutazione ripetitiva delle funzioni R. Tuttavia , abbiamo comunque convertito un data.frame
in una matrix
. Sebbene rowMeans
abbia un meccanismo di gestione degli errori e non possa essere eseguito su un set di dati che non può gestire, ha comunque un costo di efficienza.
rowMeans(iris)
Error in rowMeans(iris) : 'x' must be numeric
Ma possiamo ancora fare meglio? Potremmo provare invece di una conversione della matrice con gestione degli errori, un metodo diverso che ci permetterà di usare mtcars
come vettore (perché un data.frame
è essenzialmente un list
e un list
è un vector
).
Reduce(`+`, mtcars)/ncol(mtcars)
[1] 29.90727 29.98136 23.59818 38.73955 53.66455 35.04909 59.72000 24.63455 27.23364 31.86000 31.78727 46.43091 46.50000 46.35000 66.23273 66.05855
[17] 65.97227 19.44091 17.74227 18.81409 24.88864 47.24091 46.00773 58.75273 57.37955 18.92864 24.77909 24.88027 60.97182 34.50818 63.15545 26.26273
Ora, per il possibile aumento di velocità, abbiamo perso i nomi delle colonne e la gestione degli errori (inclusa la gestione di NA
).
Un altro esempio sarebbe il calcolo medio per gruppo, usando la base R che potremmo provare
aggregate(. ~ cyl, mtcars, mean)
cyl mpg disp hp drat wt qsec vs am gear carb
1 4 26.66364 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909 0.7272727 4.090909 1.545455
2 6 19.74286 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286 0.4285714 3.857143 3.428571
3 8 15.10000 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000 0.1428571 3.285714 3.500000
Tuttavia, stiamo fondamentalmente valutando una funzione R in un ciclo, ma il ciclo è ora nascosto in una funzione C interna (poco importa se si tratta di un ciclo C o R).
Potremmo evitarlo? Bene c'è una funzione compilata in R chiamata rowsum
, quindi potremmo fare:
rowsum(mtcars[-2], mtcars$cyl)/table(mtcars$cyl)
mpg disp hp drat wt qsec vs am gear carb
4 26.66364 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909 0.7272727 4.090909 1.545455
6 19.74286 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286 0.4285714 3.857143 3.428571
8 15.10000 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000 0.1428571 3.285714 3.500000
Anche se abbiamo dovuto prima convertire anche in una matrice.
A questo punto potremmo chiederci se la nostra attuale struttura dati sia la più appropriata. Un data.frame
è la migliore pratica? O si dovrebbe semplicemente passare a una struttura dati matrix
per ottenere efficienza?
Con le operazioni di fila diventerà sempre più costoso (anche nelle matrici) mentre iniziamo a valutare ogni volta costose funzioni. Consente di considerare un calcolo della varianza per esempio di riga.
Diciamo che abbiamo una matrice m
:
set.seed(100)
m <- matrix(sample(1e2), 10)
m
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,] 8 33 39 86 71 100 81 68 89 84
[2,] 12 16 57 80 32 82 69 11 41 92
[3,] 62 91 53 13 42 31 60 70 98 79
[4,] 66 94 29 67 45 59 20 96 64 1
[5,] 36 63 76 6 10 48 85 75 99 2
[6,] 18 4 27 19 44 56 37 95 26 40
[7,] 3 24 21 25 52 51 83 28 49 17
[8,] 46 5 22 43 47 74 35 97 77 65
[9,] 55 54 78 34 50 90 30 61 14 58
[10,] 88 73 38 15 9 72 7 93 23 87
Si potrebbe semplicemente fare:
apply(m, 1, var)
[1] 871.6556 957.5111 699.2111 941.4333 1237.3333 641.8222 539.7889 759.4333 500.4889 1255.6111
D'altra parte, si potrebbe anche completamente vettorizzare questa operazione seguendo la formula della varianza
RowVar <- function(x) {
rowSums((x - rowMeans(x))^2)/(dim(x)[2] - 1)
}
RowVar(m)
[1] 871.6556 957.5111 699.2111 941.4333 1237.3333 641.8222 539.7889 759.4333 500.4889 1255.6111