2013-08-22 2 views
16

Я пытаюсь умножить фрейм данных df на вектор v, так что продукт представляет собой кадр данных, где i-я строка задается 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(.)) требуется.
Как найти эффективный и элегантный обходной путь здесь?

+3

Почему нужно быть data.frame? Если у вас есть все числовые элементы, обычно имеет смысл использовать матрицу. –

ответ

21

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

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

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

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

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

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

+0

Это отличная строка кода, и она кажется самой эффективной. Не совсем самоочевидно в коде, но очень аккуратно, по сравнению с моим решением. +1 для дальнейшей оптимизации! – tonytonov

+0

@Arun Я думал, что ты прав, ответ Эдди, похоже, показывает, что он намного медленнее. Возможно, генерация матрицы занимает больше времени, чем вы думаете? – nograpes

7

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

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

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

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

+0

Гибкость R - это то, ради чего мы ее любим, это так. Спасибо за комментарий, я думаю, что решение будет заключаться в том, чтобы превратить это в функцию, чтобы сохранить читаемость кода. – tonytonov

3

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

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

?

+0

Кажется, что все работает нормально ... – Mayou

+0

Это возвращает матрицу вместо data.frame, поэтому это будет 'data.frame (t (apply (df, 1, function (x) x * v))), который менее кратким, чем @nograpes 'answer 'data.frame (mapply (' * ', df, v))'. – Rob

+0

* mapply * версия похоже быстрее, прохладный. – Fernando

9

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

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

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


eddi = function(dt) { for (i in seq_along(dt)) dt[, i := dt[[i]] * v[i], with = F] } 
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  median   uq  max neval 
#  eddi(copy(dt)) 17.46796 19.23358 23.53997 26.03665 30.
#  arun(copy(dt)) 1014.36108 1375.66253 1461.46489 1527.66639 1721.96316 10 
# nograpes(copy(dt)) 92.14517 109.30627 158.42780 186.32240 188.01758 10 

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

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

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

+1

+1 приятно! В документации указано, что использование 'set' с' for-loop' будет быстрее, потому что нет 'накладных расходов 'из-за' .data.table'. Однако, здесь, я не вижу, чтобы это было быстрее .. любая идея? Кроме того, 'set' может использоваться с' data.frame'. Вам не нужно конвертировать в 'data.table' (и присваивание происходит по ссылке)! – Arun

+0

Хороший вопрос о множестве, но поскольку я предполагаю, что количество столбцов невелико, я не думаю, что для цикла vs set будет иметь значение (если количество столбцов достаточно велико, чтобы это имело значение, я думаю 'data.table' уже не является хорошей структурой данных в этой точке); также в моем мире нет конверсий в 'data.table', так как все начинается с' data.table';) – eddi

+0

да. То, что я имел в виду (о * любой идее *), было: «set» медленнее * ... Я не могу объяснить, почему это медленнее ... – Arun

1

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

Мои тесты:

Результат

> 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 
+0

вы смотрите на крошечные данные (где эти различия не имеют значения, если вы не делаете петли) - смотрите, например, 'testit (100000,10)' - не очень большие и имеющие форму данные, как правило, имеют форму – eddi

+0

@eddi, интересно. Но перенос дважды по-прежнему находится в том же порядке mapply для 1e6. ряды На самом деле это примерно на 5% быстрее в моем прогоне. –

Смежные вопросы