【问题标题】:Most efficient way to multiply a data frame by a vector将数据帧乘以向量的最有效方法
【发布时间】:2014-08-14 23:05:09
【问题描述】:

将数据框的每一列乘以向量的最有效方法是什么?

例如数据框 (df) 具有以下列 (col1, col2, col3, col4),向量 (v) 具有以下元素 (v1,v2,v3)。

我希望输出为:col2*v1, col3*v2, col4*v3

我一直在尝试df[c(2:4)] * c(v1,v2,v3),但似乎向量的元素并没有乘以每一列的每一行。

【问题讨论】:

  • 如果不查看整个操作需要多长时间以及所需的资源,您将无法找到任何有关效率的信息。查看code.google.com/p/rbenchmark 了解一些基准测试工具。

标签: r


【解决方案1】:

您可以尝试(使用 Richard Scriven 的回答中的 dfv):

df[-1] <- t(t(df[-1]) * v)
df
#   a  x  y   z
# 1 a  5 40 105
# 2 b 10 50 120
# 3 c 15 60 135

当您将矩阵乘以向量时,它会按列相乘。由于您想将行乘以向量,因此我们使用t 转置df[-1],乘以v,然后使用t 转置回来。

Map 方法相比,这种方法在基准测试方面似乎略有优势,并且与sweep 相比具有显着优势:

library(microbenchmark)
rscriven <- function(df, v) cbind(df[1], Map(`*`, df[-1], v))
josilber <- function(df, v) cbind(df[1], t(t(df[-1]) * v))
dardisco <- function(df, v) cbind(df[1], sweep(df[-1], MARGIN=2, STATS=v, FUN="*"))
df2 <- cbind(data.frame(rep("a", 1000)), matrix(rnorm(100000), nrow=1000))
v2 <- rnorm(100)
all.equal(rscriven(df2, v2), josilber(df2, v2))
# [1] TRUE
all.equal(rscriven(df2, v2), dardisco(df2, v2))
# [1] TRUE

microbenchmark(rscriven(df2, v2), josilber(df2, v2), dardisco(df2, v2))
# Unit: milliseconds
#               expr       min        lq    median        uq        max neval
#  rscriven(df2, v2)  5.276458  5.378436  5.451041  5.587644   9.470207   100
#  josilber(df2, v2)  2.545144  2.753363  3.099589  3.704077   8.955193   100
#  dardisco(df2, v2) 11.647147 12.761184 14.196678 16.581004 132.428972   100

感谢 @thelatemail 指出 Map 方法对于 100 倍大的数据帧来说要快得多:

df2 <- cbind(data.frame(rep("a", 10000)), matrix(rnorm(10000000), nrow=10000))
v2 <- rnorm(1000)
microbenchmark(rscriven(df2, v2), josilber(df2, v2), dardisco(df2, v2))
# Unit: milliseconds
#               expr       min         lq     median        uq       max neval
#  rscriven(df2, v2)  75.74051   90.20161   97.08931  115.7789  259.0855   100
#  josilber(df2, v2) 340.72774  388.17046  498.26836  514.5923  623.4020   100
#  dardisco(df2, v2) 928.81128 1041.34497 1156.39293 1271.4758 1506.0348   100

您似乎需要进行基准测试以确定哪种方法最适合您的应用程序。

【讨论】:

  • +1 成为一名优秀的萨米尔坦!!感谢分享基准测试结果!
  • 这个结果对df2 的大小非常敏感。尝试将其增加到 1000 列和 10000 行,结果会反转。
  • @thelatemail ty -- 我也添加了这些基准测试结果。
  • @josilber - 归根结底,只需几分之一秒。除非您在一段代码中运行数百次,否则您会眨眼并错过它。
  • 另一个选项是 df[-1] &lt;- lapply(seq_along(v), function(x) df[x+1] * v[x])(这似乎只比 @josilber 对 df2 样本数据的回答稍慢)
【解决方案2】:

您可以为此使用Map。这是一个例子

> ( df <- data.frame(a = letters[1:3], x = 1:3, y = 4:6, z = 7:9) )
#   a x y z
# 1 a 1 4 7
# 2 b 2 5 8
# 3 c 3 6 9    
> v <- c(5, 10, 15)
> cbind(df[1], Map(`*`, df[-1], v))
#   a  x  y   z
# 1 a  5 40 105
# 2 b 10 50 120
# 3 c 15 60 135

在这个例子中,

  • x 乘以v[1] (5)
  • y 乘以v[2] (10)
  • z 乘以v[3] (15)
  • cbind 用于将未使用的列 a 附加到我们操作的列上

【讨论】:

    【解决方案3】:

    没有那么快,但更灵活:

    sweep(df[-1], MARGIN=2, STATS=v, FUN="*")
    

    【讨论】:

      【解决方案4】:

      这里也可以使用简单的“应用”函数,按行读取:

      df[-1]= (t(apply(df[-1],1, FUN=function(x)x*v)))
      df
        a  x  y   z
      1 a  5 40 105
      2 b 10 50 120
      3 c 15 60 135
      

      【讨论】:

      • 或者,您可以在此处使用t(apply(df[-1], 1, "*", v))
      • 感谢您的提示。 '*' 可以用作函数。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多