Каков правильный способ умножения кадра данных по вектору?

Я пытаюсь умножить кадр данных df на вектор v, так что продукт является кадром данных, где строка i -th задается df[i,]*v. Я могу это сделать, например,

df <- data.frame(A=1:5, B=2:6); v <- c(0,2)
as.data.frame(t(t(df) * v))
   A  B
1  0  4
2  0  6
3  0  8
4  0 10
5  0 12

Я уверен, что должен быть более подход R-стиля (и очень простой!), Но ничего не приходит мне на ум. Я даже пробовал что-то вроде

apply(df, MARGIN=1, function(x) x*v)

но тем не as.data.frame(t(.)) конструкции, такие как as.data.frame(t(.)).
Как найти эффективный и элегантный обходной путь здесь?

Ответ 1

Это тоже работает:

data.frame(mapply('*',df,v))

В этом решении вы воспользуетесь тем фактом, что data.frame является типом list, поэтому вы можете перебирать оба элемента df и v одновременно с mapply.

К сожалению, вы ограничены тем, что вы можете выводить из mapply: как простой list, так и matrix. Если ваши данные огромны, это, вероятно, будет более эффективным:

data.frame(mapply('*',df,v,SIMPLIFY=FALSE))

Потому что он преобразует его в list, который более эффективен для преобразования в data.frame.

Ответ 2

Если вы ищете скорость и эффективность памяти - data.table для спасения:

library(data.table)
dt = data.table(df)

for (i in seq_along(dt))
  dt[, (i) := dt[[i]] * v[i]]


eddi = function(dt) { for (i in seq_along(dt)) dt[, (i) := dt[[i]] * v[i]] }
arun = function(df) { df * matrix(v, ncol=ncol(df), nrow=nrow(df), byrow=TRUE) }
nograpes = function(df) { data.frame(mapply('*',df,v,SIMPLIFY=FALSE)) }

N = 1e6
dt = data.table(A = rnorm(N), B = rnorm(N))
v = c(0,2)

microbenchmark(eddi(copy(dt)), arun(copy(dt)), nograpes(copy(dt)), times = 10)
#Unit: milliseconds
#               expr       min        lq      mean    median        uq       max neval
#     eddi(copy(dt))  23.01106  24.31192  26.47132  24.50675  28.87794  34.28403    10
#     arun(copy(dt)) 337.79885 363.72081 450.93933 433.21176 516.56839 644.70103    10
# nograpes(copy(dt))  19.44873  24.30791  36.53445  26.00760  38.09078  95.41124    10

Как указывает Арун в комментариях, можно также использовать функцию set из пакета data.table, чтобы сделать эту модификацию на data.frame в data.frame:

for (i in seq_along(df))
  set(df, j = i, value = df[[i]] * v[i])

Это, конечно, также работает для data.table и может быть значительно быстрее, если количество столбцов велико.

Ответ 3

Язык, который позволяет объединять векторы с матрицами, должен в какой-то момент принять решение о том, упорядочены ли матрицы по строкам или столбцам. Причина:

> df * v
  A  B
1 0  4
2 4  0
3 0  8
4 8  0
5 0 12

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

Или напишите приятную функцию, которая обертывает не очень R-стиль кода в нечто, что R-стильно.

Ответ 4

Что случилось с

t(apply(df, 1, function(x)x*v))

?

Ответ 5

Я думаю, что самый быстрый способ (без тестирования data.table) - data.frame(t(t(df)*v)).

Мои тесты:

testit <- function(nrow, ncol)
{
    df <- as.data.frame(matrix(rnorm(nrow*ncol),nrow=nrow,ncol=ncol))

    v <- runif(ncol)

    r1 <- data.frame(t(t(df)*v))
    r2 <- data.frame(mapply('*',df,v,SIMPLIFY=FALSE))
    r3 <- df * rep(v, each=nrow(df))

    stopifnot(identical(r1, r2) && identical(r1, r3))

    microbenchmark(data.frame(t(t(df)*v)), data.frame(mapply('*',df,v,SIMPLIFY=FALSE)), df * rep(v, each=nrow(df)))
}

результат

> set.seed(1)
> 
> testit(100,100)
Unit: milliseconds
                                             expr       min        lq    median        uq      max neval
                         data.frame(t(t(df) * v))  2.297075  2.359541  2.455778  3.804836 33.05806   100
 data.frame(mapply('*', df, v, SIMPLIFY = FALSE))  9.977436 10.401576 10.658964 11.762009 15.09721   100
                     df * rep(v, each = nrow(df)) 14.309822 14.956705 16.092469 16.516609 45.13450   100
> testit(1000,10)
Unit: microseconds
                                             expr      min       lq   median       uq      max neval
                         data.frame(t(t(df) * v))  754.844  805.062  844.431 1850.363 27955.79   100
 data.frame(mapply('*', df, v, SIMPLIFY = FALSE)) 1457.895 1497.088 1567.604 2550.090  4732.03   100
                     df * rep(v, each = nrow(df)) 5383.288 5527.817 5875.143 6628.586 32392.81   100
> testit(10,1000)
Unit: milliseconds
                                             expr       min        lq    median        uq       max neval
                         data.frame(t(t(df) * v))  17.07548  18.29418  19.91498  20.67944  57.62913   100
 data.frame(mapply('*', df, v, SIMPLIFY = FALSE))  99.90103 104.36028 108.28147 114.82012 150.05907   100
                     df * rep(v, each = nrow(df)) 112.21719 118.74359 122.51308 128.82863 164.57431   100

Ответ 6

library(purrr)

map2_dfc(df, v, '*')

эталонный тест

N = 1e6
dt = data.table(A = rnorm(N), B = rnorm(N))
v = c(0,2)

eddi = function(dt) { for (i in seq_along(dt)) dt[, (i) := dt[[i]] * v[i]]; dt }
arun = function(df) { df * matrix(v, ncol=ncol(df), nrow=nrow(df), byrow=TRUE) }
nograpes = function(df) { data.frame(mapply('*',df,v,SIMPLIFY=FALSE)) }
ryan = function(df) {map2_dfc(df, v, '*') }
library(microbenchmark)
microbenchmark(
  eddi(copy(dt))
  , arun(copy(dt))
  , nograpes(copy(dt))
  , ryan(copy(dt))
  , times = 100)


# Unit: milliseconds
# expr                     min        lq      mean    median        uq      max neval
# eddi(copy(dt))      8.367513  11.06719  24.26205  12.29132  19.35958 171.6212   100
# arun(copy(dt))     94.031272 123.79999 186.42155 148.87042 251.56241 364.2193   100
# nograpes(copy(dt))  7.910739  10.92815  27.68485  13.06058  21.39931 172.0798   100
# ryan(copy(dt))      8.154395  11.02683  29.40024  13.73845  21.77236 181.0375   100