Использование apply в R с дополнительным векторным аргументом

У меня есть матрица размером 10000 x 100 и вектор длиной 100. Я хотел бы применить настраиваемую функцию percentile, которая принимает аргумент вектора и скалярный аргумент, к каждому столбцу матрица такая, что на итерации j аргументы, используемые с процентилем, представляют собой столбец j матрицы и запись j вектора. Есть ли способ использовать для этого одну из функций apply?

Вот мой код. Он запускается, но не возвращает правильный результат.

percentile <- function(x, v){
  length(x[x <= v]) / length(x)
}

X <- matrix(runif(10000 * 100), nrow = 10000, ncol = 100)
y <- runif(100)
result <- apply(X, 2, percentile, v = y)

Обходной путь, который я использовал, заключался в том, чтобы просто добавить y к X и переписать функцию процентиля, как показано ниже.

X <- rbind(X, y)
percentile2 <- function(x){
  v <- x[length(x)]
  x <- x[-length(x)]
  length(x[x <= v]) / length(x)
}
result <- apply(X, 2, percentile2)

Этот код действительно возвращает правильный результат, но я бы предпочел что-нибудь более элегантное.


person tilleyand    schedule 23.08.2013    source источник
comment
Привет, добро пожаловать в SO. Поскольку вы здесь совсем новичок, вы можете прочитать about и FAQ, которые помогут вам извлечь из этого максимальную пользу. Если ответ действительно решает вашу проблему, вы можете рассмотреть проголосовать за и / или отметить его как принятый, чтобы показать, что на вопрос был дан ответ, отметив маленькую зеленую галочку рядом с подходящим ответом. Вы не обязаны это делать, но это помогает очистить сайт от вопросов, на которые нет ответов, и вознаграждает тех, кто нашел время для решения вашей проблемы.   -  person Simon O'Hanlon    schedule 24.08.2013


Ответы (2)


Если вы понимаете, что R векторизован, и знаете правильные функции, вы можете полностью избежать циклов и сделать все в одной относительно простой строке ...

 colSums(  t( t( X ) <= y ) ) / nrow( X ) 

Посредством векторизации R будет повторно использовать каждый элемент в y в каждом столбце X (по умолчанию он будет делать это по строкам, поэтому мы используем функцию транспонирования t, чтобы превратить столбцы в строки, применить логическое сравнение <= и затем снова транспонировать обратно.

Поскольку TRUE и FALSE оцениваются как 1 и 0 соответственно, мы можем использовать colSums для эффективного получения количества строк в каждом столбце, удовлетворяющего условию, а затем разделить каждый столбец на общее количество строк (помните повторное использование правило!). Это точно такой же результат ....

res1 <- apply(X2, 2, percentile2)
res2 <- colSums(  t( t( X ) <= y ) ) / nrow( X )
identical( res1 , res2 )
[1] TRUE

Очевидно, что поскольку здесь не используются циклы R, он на много быстрее (~ в 10 раз на этой маленькой матрице).

Еще лучше было бы использовать rowMeans вот так (спасибо @flodel):

     rowMeans(  t(X) <= y  ) 
person Simon O'Hanlon    schedule 23.08.2013
comment
Отлично работает! Спасибо! - person tilleyand; 23.08.2013

Я думаю, что самый простой и понятный способ - использовать цикл for:

result2 <- numeric(ncol(X))
for (i in seq_len(ncol(X))) {
  result2[i] <- sum(X[,i] <= y[i])
}
result2 <- result2 / nrow(X)

самое быстрое и самое короткое решение, которое я могу придумать:

result1 <- rowSums(t(X) <= y) / nrow(X)

В ответе SimonO101 есть объяснение, как это работает. Как я уже сказал, это быстро. Однако недостатком является то, что менее ясно, что именно здесь вычисляется, хотя вы можете решить эту проблему, поместив этот фрагмент кода в функцию с хорошо названным именем.

flodel также предлагает решение с использованием mapply, которое является apply, которое может работать с несколькими векторами. Однако, чтобы это сработало, вам сначала нужно поместить каждый из ваших столбцов или вашу матрицу в list или data.frame:

result3 <- mapply(percentile, as.data.frame(X), y)

С точки зрения скорости (см. Ниже некоторые тесты производительности) цикл for не так уж плох, и он быстрее, чем при использовании apply (по крайней мере, в этом случае). Уловка с rowSums и повторным использованием векторов выполняется быстрее, более чем в 10 раз быстрее, чем решение с использованием apply.

> X <- matrix(rnorm(10000 * 100), nrow = 10000, ncol = 100)
> y <- runif(100)
> 
> system.time({result1 <- rowSums(t(X) <= y) / nrow(X)})
   user  system elapsed 
  0.020   0.000   0.018 
> 
> system.time({
+   X2 <- rbind(X, y)
+   percentile2 <- function(x){
+     v <- x[length(x)]
+     x <- x[-length(x)]
+     length(x[x <= v]) / length(x)
+   }
+   result <- apply(X2, 2, percentile2)
+ })
   user  system elapsed 
  0.252   0.000   0.249 
> 
> 
> system.time({
+   result2 <- numeric(ncol(X))
+   for (i in seq_len(ncol(X))) {
+     result2[i] <- sum(X[,i] <= y[i])
+   }
+   result2 <- result2 / nrow(X)
+ })
   user  system elapsed 
  0.024   0.000   0.024 
>
> system.time({
+   result3 <- mapply(percentile, as.data.frame(X), y)
+ })
   user  system elapsed 
  0.076   0.000   0.073 
>
> all(result2 == result1)
[1] TRUE
> all(result2 == result)
[1] TRUE
> all(result3 == result)
[1] TRUE
person Jan van der Laan    schedule 23.08.2013
comment
+1 Я не видел, чтобы у вас был rowSums ответ среди всего прочего. Я думаю, вам следует выделить это подробнее, потому что это хороший ответ. Я оставляю свой ответ, потому что я объяснил, как это работает. - person Simon O'Hanlon; 23.08.2013
comment
@ SimonO101 Я отредактировал свой ответ. Надеюсь, теперь он выделяется больше. Я также сослался на ваш ответ для объяснения. - person Jan van der Laan; 23.08.2013