R Language
Best practices voor R-codevectorisatie
Zoeken…
Door rijbewerkingen
De sleutel bij het vectoriseren van R-code is het verminderen of elimineren van "door rijbewerkingen" of methode-verzending van R-functies.
Dat betekent dat bij het benaderen van een probleem dat op het eerste gezicht "door rijbewerkingen" vereist, zoals het berekenen van de middelen van elke rij, men zich moet afvragen:
- Wat zijn de klassen van de gegevenssets waarmee ik te maken heb?
- Is er een bestaande gecompileerde code die dit kan bereiken zonder de noodzaak van herhaalde evaluatie van R-functies?
- Zo nee, kan ik deze bewerking dan per kolom uitvoeren?
- Tot slot, is het de moeite waard om veel tijd te besteden aan het ontwikkelen van gecompliceerde vectorcode in plaats van het uitvoeren van een eenvoudige
apply
? Met andere woorden, zijn de gegevens groot / verfijnd genoeg zodat R er niet efficiënt mee om kan gaan met behulp van een eenvoudige lus?
Afgezien van het probleem van de pre-allocatie van geheugen en het groeiende object in lussen, zullen we ons in dit voorbeeld concentreren op het mogelijk voorkomen van het apply
lussen, het verzenden van methoden of het opnieuw evalueren van R-functies binnen lussen.
Een standaard / eenvoudige manier om het gemiddelde per rij te berekenen zou zijn:
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
Maar kunnen we het beter doen? Laten we eens kijken wat hier is gebeurd:
- Eerst hebben we een
data.frame
naar eenmatrix
. (Merk op dat dit gebeurt binnen deapply
.) Dit is zowel inefficiënt als gevaarlijk. eenmatrix
kan niet meerdere kolomtypen tegelijk bevatten. Daarom zal een dergelijke conversie waarschijnlijk leiden tot verlies van informatie en soms tot misleidende resultaten (vergelijkapply(iris, 2, class)
metstr(iris)
of metsapply(iris, class)
). - Ten tweede hebben we herhaaldelijk een bewerking uitgevoerd, één keer voor elke rij. Dit betekent dat we enkele R-
nrow(mtcars)
moesten evalueren. In dit specifieke geval ismean
geen rekenkundig dure functie, dus R zou het waarschijnlijk gemakkelijk kunnen verwerken, zelfs voor een grote gegevensset, maar wat zou er gebeuren als we de standaarddeviatie per rij moeten berekenen (wat een dure vierkantswortelbewerking inhoudt) ? Dat brengt ons bij het volgende punt: - We hebben de R-functie vaak geëvalueerd, maar misschien is er al een gecompileerde versie van deze bewerking?
We zouden inderdaad gewoon kunnen doen:
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
Dit omvat geen rijbewerkingen en daarom geen herhaalde evaluatie van R-functies. Echter, we nog steeds omgerekend een data.frame
naar een matrix
. Hoewel rowMeans
een foutafhandelingsmechanisme heeft en niet wordt uitgevoerd op een gegevensset die het niet aankan, heeft het toch efficiëntiekosten.
rowMeans(iris)
Error in rowMeans(iris) : 'x' must be numeric
Maar toch, kunnen we het beter doen? We kunnen in plaats van een matrixconversie met foutafhandeling een andere methode proberen waarmee we mtcars
als vector kunnen gebruiken (omdat een data.frame
in wezen een list
en een list
een 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
Nu voor mogelijke snelheidswinst zijn we kolomnamen en foutafhandeling (inclusief NA
afhandeling) kwijtgeraakt.
Een ander voorbeeld is het berekenen van het gemiddelde per groep, met behulp van base R die we kunnen proberen
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
Toch evalueren we in principe een R-functie in een lus, maar de lus is nu verborgen in een interne C-functie (het maakt niet uit of het een C- of een R-lus is).
Kunnen we het vermijden? Nou, er is een gecompileerde functie in R genaamd rowsum
, dus we zouden kunnen doen:
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
Hoewel we ook eerst naar een matrix moesten converteren.
Op dit punt kunnen we ons afvragen of onze huidige gegevensstructuur de meest geschikte is. Is een data.frame
de beste praktijk? Of moet men gewoon overschakelen naar een matrix
datastructuur om efficiëntie te bereiken?
Per rij worden operaties steeds duurder (zelfs in matrices) als we beginnen met het evalueren van dure functies elke keer. Laten we een variantieberekening per rijvoorbeeld bekijken.
Laten we zeggen dat we een matrix 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
Men zou eenvoudig kunnen doen:
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
Aan de andere kant zou je deze operatie ook volledig kunnen vectoriseren door de variantieformule te volgen
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