R Language
Bonnes pratiques de vectorisation du code R
Recherche…
Opérations par ligne
La clé de la vectorisation du code R consiste à réduire ou à éliminer les opérations "par ligne" ou la distribution des méthodes R.
Cela signifie que lorsque l'on aborde un problème qui, à première vue, nécessite des "opérations en ligne", comme le calcul des moyennes de chaque ligne, il faut se demander:
- Quelles sont les classes d'ensembles de données que je traite?
- Existe-t-il un code compilé existant qui peut y parvenir sans évaluation répétitive des fonctions R?
- Sinon, puis-je effectuer ces opérations par colonnes plutôt que par ligne?
- Enfin, est-il utile de consacrer beaucoup de temps au développement de code vectorisé compliqué au lieu de simplement exécuter une simple boucle d’
apply
? En d'autres termes, les données sont-elles suffisamment grandes / sophistiquées pour que R ne puisse pas les gérer efficacement en utilisant une simple boucle?
Mis à part le problème de pré-allocation de mémoire et l'objet croissant dans les boucles, nous allons nous concentrer dans cet exemple sur la manière d'éviter apply
boucles d' apply
, la distribution de méthodes ou la réévaluation des fonctions R dans les boucles.
Un moyen standard / facile de calculer la moyenne par ligne serait:
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
Mais pouvons-nous faire mieux? Voyons ce qui s'est passé ici:
- Tout d'abord, nous avons converti un
data.frame
en unematrix
. (Notez que cela se produit dans la fonction d’apply
.) Ceci est à la fois inefficace et dangereux. unematrix
ne peut pas contenir plusieurs types de colonnes à la fois. Par conséquent, une telle conversion entraînera probablement une perte d’informations et parfois des résultats trompeurs (comparerapply(iris, 2, class)
àstr(iris)
ou àsapply(iris, class)
). - Deuxièmement, nous avons effectué une opération de manière répétitive, une fois pour chaque ligne. Ce qui signifie que nous avons dû évaluer certains temps de la fonction R
nrow(mtcars)
. Dans ce cas précis, lamean
n'est pas une fonction coûteuse en calcul, donc R pourrait facilement la gérer même pour un ensemble de données volumineuses, mais que se passerait-il si nous devions calculer l'écart type par ligne (opération carrée coûteuse)? ? Ce qui nous amène au point suivant: - Nous avons évalué la fonction R plusieurs fois, mais peut-être existe-t-il déjà une version compilée de cette opération?
En effet, nous pourrions simplement faire:
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
Cela implique pas d'opérations ligne par ligne et donc pas d'évaluation répétitive des fonctions R. Cependant , nous avons toujours converti un data.frame
en une matrix
. Bien que rowMeans
dispose d'un mécanisme de gestion des erreurs et qu'il ne s'exécute pas sur un ensemble de données qu'il ne peut pas gérer, il a toujours un coût d'efficacité.
rowMeans(iris)
Error in rowMeans(iris) : 'x' must be numeric
Mais quand même, pouvons-nous faire mieux? Nous pourrions essayer à la place d'une conversion matricielle avec traitement d'erreur, une méthode différente qui nous permettrait d'utiliser mtcars
comme vecteur (car un data.frame
est essentiellement une list
et une 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
Maintenant, pour un gain de vitesse possible, nous avons perdu les noms de colonne et la gestion des erreurs (y compris le traitement NA
).
Un autre exemple serait de calculer la moyenne par groupe, en utilisant la base R, nous pourrions essayer
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
Cependant, nous évaluons essentiellement une fonction R dans une boucle, mais la boucle est maintenant cachée dans une fonction C interne (peu importe que ce soit une boucle C ou R).
Pouvons-nous l'éviter? Eh bien, il y a une fonction compilée dans R appelée rowsum
, par conséquent nous pourrions faire:
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
Bien que nous devions d'abord convertir en matrice.
À ce stade, nous pouvons nous demander si notre structure de données actuelle est la plus appropriée. Est-ce qu'un data.frame
est la meilleure pratique? Ou faut-il simplement passer à une structure de données matrix
pour gagner en efficacité?
Les opérations par ligne deviendront de plus en plus coûteuses (même dans les matrices) au fur et à mesure que nous commencerons à évaluer des fonctions coûteuses. Permet de considérer un calcul de variance par exemple de ligne.
Disons que nous avons une 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
On pourrait simplement faire:
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
Par contre, on pourrait aussi complètement vectoriser cette opération en suivant la formule de la variance
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