R Language
Рекомендации по векторизации кода R
Поиск…
Операциями по строке
Ключ в векторизации кода R состоит в том, чтобы уменьшить или исключить «операции с помощью ряда» или метод отправки функций R.
Это означает, что при приближении к проблеме, которая на первый взгляд требует «операций по строке», например, вычисления средств каждой строки, нужно спросить себя:
- Каковы классы наборов данных, с которыми я имею дело?
- Существует ли существующий скомпилированный код, который может достичь этого без необходимости повторной оценки функций R?
- Если нет, могу ли я выполнить эту операцию столбцами вместо строки?
- Наконец, стоит ли тратить много времени на разработку сложного векторизованную кода вместо того , чтобы просто работает просто
apply
цикл? Другими словами, являются ли данные большими / сложными, что R не может эффективно обрабатывать его с помощью простого цикла?
Отложив проблему выделения памяти и увеличив объект в цикле, в этом примере мы сосредоточимся на том, как избежать apply
циклов apply
, отправки методов или переоценки R-функций в циклах.
Стандартный / простой способ вычисления среднего числа строк:
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
Но можем ли мы сделать лучше? Давайте посмотрим, что здесь произошло:
- Сначала мы преобразовали
data.frame
вmatrix
. (Обратите внимание, что он происходит внутри функцииapply
.) Это неэффективно и опасно.matrix
не может содержать несколько типов столбцов за раз. Следовательно, такое преобразование, вероятно, приведет к потере информации и несколько раз к вводящим в заблуждение результатам (сравнитьapply(iris, 2, class)
сstr(iris)
или сsapply(iris, class)
). - Во-вторых, мы выполняли операцию повторно, один раз для каждой строки. Смысл, нам пришлось оценить некоторые R функции
nrow(mtcars)
раз. В этом конкретном случаеmean
не является дорогостоящей функцией, поэтому R может, вероятно, легко справиться с этим даже для большого набора данных, но что произойдет, если нам нужно вычислить стандартное отклонение по строке (что связано с дорогостоящей работой с квадратным корнем) ? Это подводит нас к следующему пункту: - Мы много раз оценивали функцию R, но, возможно, уже есть скомпилированная версия этой операции?
Действительно, мы могли бы просто сделать:
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
Это не связано с операциями по строке и, следовательно, повторной оценкой R-функций. Тем не менее , мы по-прежнему преобразовали data.frame
в matrix
. Хотя rowMeans
имеет механизм обработки ошибок, и он не будет работать в наборе данных, который он не может обрабатывать, он по-прежнему имеет эффективность.
rowMeans(iris)
Error in rowMeans(iris) : 'x' must be numeric
Но все-таки, можем ли мы сделать лучше? Мы могли бы попробовать вместо преобразования матрицы с обработкой ошибок, другой метод, который позволит нам использовать mtcars
в качестве вектора (поскольку data.frame
по существу является list
а list
- 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
Теперь для возможного увеличения скорости мы потеряли имена столбцов и обработку ошибок (включая обработку NA
).
Другим примером может быть вычисление среднего значения по группе, используя базу R, которую мы могли бы попробовать
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
Тем не менее, мы в основном оцениваем R-функцию в цикле, но цикл теперь скрыт во внутренней C-функции (мало что имеет в виду, является ли это C или R-петлей).
Мы могли бы избежать этого? Ну есть скомпилированная функция в R, называемая rowsum
, поэтому мы могли бы сделать:
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
Хотя нам пришлось сначала преобразовать в матрицу.
В этом вопросе мы можем задать вопрос о том, является ли наша нынешняя структура данных наиболее подходящей. Является ли data.frame
лучшей практикой? Или нужно просто переключиться на 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
Можно просто сделать:
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
С другой стороны, можно было бы полностью векторизовать эту операцию, следуя формуле дисперсии
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