Применить функцию к каждой строке матрицы или фрейма данных


130

Предположим, у меня есть матрица размером 2 и функция, которая принимает 2-вектор в качестве одного из своих аргументов. Я хотел бы применить функцию к каждой строке матрицы и получить n-вектор. Как это сделать в R?

Например, я хотел бы вычислить плотность стандартного двухмерного нормального распределения по трем точкам:

bivariate.density(x = c(0, 0), mu = c(0, 0), sigma = c(1, 1), rho = 0){
    exp(-1/(2*(1-rho^2))*(x[1]^2/sigma[1]^2+x[2]^2/sigma[2]^2-2*rho*x[1]*x[2]/(sigma[1]*sigma[2]))) * 1/(2*pi*sigma[1]*sigma[2]*sqrt(1-rho^2))
}

out <- rbind(c(1, 2), c(3, 4), c(5, 6))

Как применить функцию к каждой строке out?

Как передать в функцию значения для других аргументов, помимо точек, указанным вами способом?

Ответы:


181

Вы просто используете apply()функцию:

R> M <- matrix(1:6, nrow=3, byrow=TRUE)
R> M
     [,1] [,2]
[1,]    1    2
[2,]    3    4
[3,]    5    6
R> apply(M, 1, function(x) 2*x[1]+x[2])
[1]  4 10 16
R> 

Это берет матрицу и применяет (глупую) функцию к каждой строке. Вы передаете функции дополнительные аргументы как четвертый, пятый, ... аргументы функции apply().


Спасибо! Что, если строки матрицы не являются первым аргументом функции? Как указать, какому аргументу функции назначена каждая строка матрицы?
Тим

Прочтите справку для apply()- он просматривается по строкам (когда второй аргумент равен 1, иначе по столбцам), а текущая строка (или столбец) всегда является первым аргументом. Вот как все определяется.
Дирк Эддельбюттель

@Tim: если вы используете внутреннюю функцию R и строка не является первым аргументом, сделайте так, как это сделал Дирк, и создайте свою собственную пользовательскую функцию, где строка является первым аргументом.
Joris Meys,

3
Пакет plyr предоставляет широкий спектр этих прикладных функций. Он также предоставляет больше функциональных возможностей, включая параллельную обработку.
Paul Hiemstra

6
@ cryptic0 этот ответ запоздал, но для гуглеров второй аргумент в apply - это MARGINаргумент. Здесь это означает применение функции к строкам (первое измерение в dim(M)). Если бы было 2, он применил бы функцию к столбцам.
De Novo

17

Если вы хотите применить общие функции, такие как сумма или среднее значение, вам следует использовать rowSumsили, rowMeansпоскольку они быстрее, чем apply(data, 1, sum)подход. В противном случае придерживайтесь apply(data, 1, fun). Вы можете передать дополнительные аргументы после аргумента FUN (как уже предложил Дирк):

set.seed(1)
m <- matrix(round(runif(20, 1, 5)), ncol=4)
diag(m) <- NA
m
     [,1] [,2] [,3] [,4]
[1,]   NA    5    2    3
[2,]    2   NA    2    4
[3,]    3    4   NA    5
[4,]    5    4    3   NA
[5,]    2    1    4    4

Тогда вы можете сделать что-то вроде этого:

apply(m, 1, quantile, probs=c(.25,.5, .75), na.rm=TRUE)
    [,1] [,2] [,3] [,4] [,5]
25%  2.5    2  3.5  3.5 1.75
50%  3.0    2  4.0  4.0 3.00
75%  4.0    3  4.5  4.5 4.00

15

Вот краткий пример применения функции к каждой строке матрицы. (Здесь примененная функция нормализует каждую строку до 1.)

Примечание: В результате от должны apply()было быть транспонированным с помощью , t()чтобы получить тот же формат, что и входной матрица A.

A <- matrix(c(
  0, 1, 1, 2,
  0, 0, 1, 3,
  0, 0, 1, 3
), nrow = 3, byrow = TRUE)

t(apply(A, 1, function(x) x / sum(x) ))

Результат:

     [,1] [,2] [,3] [,4]
[1,]    0 0.25 0.25 0.50
[2,]    0 0.00 0.25 0.75
[3,]    0 0.00 0.25 0.75

6

Первым шагом будет создание функционального объекта, а затем его применение. Если вам нужен объект матрицы с таким же количеством строк, вы можете предварительно определить его и использовать форму object [], как показано (в противном случае возвращаемое значение будет упрощено до вектора):

bvnormdens <- function(x=c(0,0),mu=c(0,0), sigma=c(1,1), rho=0){
     exp(-1/(2*(1-rho^2))*(x[1]^2/sigma[1]^2+
                           x[2]^2/sigma[2]^2-
                           2*rho*x[1]*x[2]/(sigma[1]*sigma[2]))) * 
     1/(2*pi*sigma[1]*sigma[2]*sqrt(1-rho^2))
     }
 out=rbind(c(1,2),c(3,4),c(5,6));

 bvout<-matrix(NA, ncol=1, nrow=3)
 bvout[] <-apply(out, 1, bvnormdens)
 bvout
             [,1]
[1,] 1.306423e-02
[2,] 5.931153e-07
[3,] 9.033134e-15

Если вы хотите использовать параметры, отличные от параметров по умолчанию, вызов должен включать именованные аргументы после функции:

bvout[] <-apply(out, 1, FUN=bvnormdens, mu=c(-1,1), rho=0.6)

apply () также можно использовать с массивами более высокой размерности, а аргумент MARGIN может быть вектором или одним целым числом.


4

Apply хорошо справляется со своей задачей, но довольно медленно. Может быть полезно использовать sapply и vapply. Также может быть полезен построчный метод dplyr. Давайте посмотрим на примере, как сделать построчное произведение любого фрейма данных.

a = data.frame(t(iris[1:10,1:3]))
vapply(a, prod, 0)
sapply(a, prod)

Обратите внимание, что присвоение переменной перед использованием vapply / sapply / apply является хорошей практикой, так как это значительно сокращает время. Посмотрим результаты микробенчмарка

a = data.frame(t(iris[1:10,1:3]))
b = iris[1:10,1:3]
microbenchmark::microbenchmark(
    apply(b, 1 , prod),
    vapply(a, prod, 0),
    sapply(a, prod) , 
    apply(iris[1:10,1:3], 1 , prod),
    vapply(data.frame(t(iris[1:10,1:3])), prod, 0),
    sapply(data.frame(t(iris[1:10,1:3])), prod) ,
    b %>%  rowwise() %>%
        summarise(p = prod(Sepal.Length,Sepal.Width,Petal.Length))
)

Внимательно посмотрите, как используется t ()


Было бы более справедливо сравнить семейство приложений, если бы вы использовали b <- t(iris[1:10, 1:3])и apply(b, 2 prod).
DaSpeeg

2

Другой подход, если вы хотите использовать изменяющуюся часть набора данных вместо одного значения, - это использовать rollapply(data, width, FUN, ...). Использование вектора ширины позволяет применить функцию к изменяющемуся окну набора данных. Я использовал это для создания процедуры адаптивной фильтрации, хотя она не очень эффективна.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.