R Language
Najlepsze praktyki wektoryzacji kodu R.
Szukaj…
Według operacji na wierszach
Kluczem do wektoryzacji kodu R jest zmniejszenie lub wyeliminowanie „operacji wierszowych” lub metody wywoływania funkcji R.
Oznacza to, że podchodząc do problemu, który na pierwszy rzut oka wymaga „operacji rzędowych”, takich jak obliczanie średnich dla każdego rzędu, należy zadać sobie pytanie:
- Z jakimi klasami zbiorów danych mam do czynienia?
- Czy istnieje istniejący skompilowany kod, który może to osiągnąć bez potrzeby powtarzalnej oceny funkcji R.
- Jeśli nie, czy mogę wykonać te operacje według kolumn zamiast według wiersza?
- Wreszcie, warto spędzać dużo czasu na opracowanie skomplikowanego kodu wektorowy zamiast po prostu działa prosty
apply
pętlę? Innymi słowy, czy dane są na tyle duże / wyrafinowane, że R nie jest w stanie poradzić sobie z nimi skutecznie za pomocą prostej pętli?
Odkładając na bok problem wstępnej alokacji pamięci i rosnący obiekt w pętlach, w tym przykładzie skupimy się na tym, jak uniknąć apply
pętli, wywoływania metod lub ponownej oceny funkcji R w pętlach.
Standardowym / łatwym sposobem obliczania średniej według wiersza byłoby:
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
Ale czy możemy zrobić lepiej? Zobaczmy, co się tu wydarzyło:
- Najpierw przekonwertowaliśmy
data.frame
namatrix
. (Zauważ, że zdarza się to w ramach funkcjiapply
.) Jest to zarówno nieefektywne, jak i niebezpieczne.matrix
nie może pomieścić kilku typów kolumn jednocześnie. Dlatego taka konwersja prawdopodobnie doprowadzi do utraty informacji, a czasami do mylących wyników (porównajapply(iris, 2, class)
zstr(iris)
lubsapply(iris, class)
). - Po drugie, wykonywaliśmy operację powtarzalnie, jeden raz dla każdego wiersza. Oznacza to, że musieliśmy oszacować niektóre
nrow(mtcars)
funkcji Rnrow(mtcars)
. W tym konkretnym przypadkumean
nie jest funkcją obliczeniowo kosztowną, dlatego R prawdopodobnie z łatwością poradziłby sobie z nią nawet dla dużego zbioru danych, ale co by się stało, gdybyśmy musieli obliczyć standardowe odchylenie według wiersza (co wymaga kosztownej operacji pierwiastka kwadratowego) ? Co prowadzi nas do następnego punktu: - Oceniliśmy funkcję R wiele razy, ale może jest już skompilowana wersja tej operacji?
Rzeczywiście moglibyśmy po prostu zrobić:
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
Nie wymaga to operacji wierszowych, a zatem powtarzalnej oceny funkcji R. Jednak wciąż przekształca się data.frame
do matrix
. Chociaż rowMeans
ma mechanizm obsługi błędów i nie działa na zbiorze danych, którego nie może obsłużyć, nadal ma koszt wydajności.
rowMeans(iris)
Error in rowMeans(iris) : 'x' must be numeric
Ale czy możemy to zrobić lepiej? Możemy spróbować zamiast konwersji macierzy z obsługą błędów, innej metody, która pozwoli nam używać mtcars
jako wektora (ponieważ data.frame
jest zasadniczo list
a list
jest 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
Teraz dla możliwego zwiększenia prędkości straciliśmy nazwy kolumn i obsługę błędów (w tym obsługę NA
).
Innym przykładem byłoby obliczanie średniej według grupy przy użyciu podstawy R, którą moglibyśmy spróbować
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
Wciąż oceniamy funkcję R w pętli, ale pętla jest teraz ukryta w wewnętrznej funkcji C (nie ma znaczenia, czy jest to pętla C czy R).
Czy możemy tego uniknąć? Cóż, w R jest skompilowana funkcja o nazwie rowsum
, dlatego możemy zrobić:
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
Chociaż najpierw musieliśmy przekonwertować na macierz.
W tym miejscu możemy zapytać, czy nasza obecna struktura danych jest najbardziej odpowiednia. Czy data.frame
jest najlepszą praktyką? A może należy po prostu przełączyć się na matrix
strukturę danych w celu uzyskania wydajności?
Po wierszach operacje będą coraz droższe (nawet w macierzach), ponieważ za każdym razem zaczynamy oceniać drogie funkcje. Rozważmy obliczenie wariancji według przykładu wiersza.
Powiedzmy, że mamy macierz 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
Można po prostu zrobić:
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
Z drugiej strony można również całkowicie wektoryzować tę operację, stosując formułę wariancji
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