将函数应用于数据帧的每一行并返回数据帧列表的有效方法

时间:2014-07-10 18:45:37

标签: r

我有一个函数,它接受许多参数并返回一个数据框。我还有一个数据框,其中每一行都包含我想传递给我的函数的参数,我想将结果数据帧集存储在一个列表中。有效的方法是什么? (我假设这是一种apply方法。)

例如,假设你有(无意义的)函数

myfunc<-function(dfRow){
  return(data.frame(x=dfRow$x:dfRow$y,y=mean(dfRow$x,dfRow$y)))
}

和数据框

df<-data.frame(x=1:3,y=4:6)
df
  x y
1 1 4
2 2 5
3 3 6

你可以运行

myfunc(df[1,])
  x y
1 1 1
2 2 1
3 3 1
4 4 1

但是如何为数据框的每一行运行myfunc并将结果存储在列表中?我知道如何为此做一个基本的for循环,但我正在寻找能够更快运行的东西 - 一些矢量化的东西。

3 个答案:

答案 0 :(得分:3)

如果你想要一个答案列表,为什么不传递一个参数列表呢?首先将您的数据框拆分为一个列表,然后lapply您的函数:

listargs <- split(df,1:nrow(df))

lapply(listargs,myfunc)

$`1`
  x y
1 1 1
2 2 1
3 3 1
4 4 1

$`2`
  x y
1 2 2
2 3 2
3 4 2
4 5 2

$`3`
  x y
1 3 3
2 4 3
3 5 3
4 6 3

答案 1 :(得分:3)

你的&#34;毫无意义&#34;函数需要具有apply能够工作的一些含义。对于初学者,您无法使用$,因为apply会将每一行视为基本命名向量。

记住这一点,这里是一个重写(以及更多*意味着*的意思):

myfunc <- function(dfRow) {
  data.frame(x = dfRow[1]:dfRow[2], y = mean(c(dfRow[1], dfRow[2])))
}

甚至:

myfunc <- function(dfRow) {
  data.frame(x = dfRow["x"]:dfRow["y"], y = mean(c(dfRow["x"], dfRow["y"])))
}

这是我们从apply获得的MARGIN = 1(即按行应用函数):

apply(df, 1, myfunc)
# [[1]]
#   x   y
# 1 1 2.5
# 2 2 2.5
# 3 3 2.5
# 4 4 2.5
# 
# [[2]]
#   x   y
# 1 2 3.5
# 2 3 3.5
# 3 4 3.5
# 4 5 3.5
# 
# [[3]]
#   x   y
# 1 3 4.5
# 2 4 4.5
# 3 5 4.5
# 4 6 4.5

,不要总是快速注销for个循环。 apply 已优化,但基本上隐藏了某处的for循环。

以下是一些速度比较:

## Function to use with `apply`
myfunc <- function(dfRow) {
  data.frame(x = dfRow["y"]:dfRow["x"], y = mean(c(dfRow["x"], dfRow["y"])))
}

## Function to use with `lapply`
myfunc1<-function(dfRow){
  return(data.frame(x=dfRow$x:dfRow$y,y=mean(dfRow$x,dfRow$y)))
}

## Sample data
set.seed(1)
df <- data.frame(x = sample(100, 100, TRUE),
                 y = sample(100, 100, TRUE))

以下是评估的功能:

fun1 <- function() apply(df, 1, myfunc)

fun2a <- function() {
  listargs <- split(df,1:nrow(df))
}

fun3 <- function() {
  out <- vector("list", nrow(df))
  for (i in 1:nrow(df)) {
    out[[i]] <- data.frame(x = df$x[i]:df$y[i], y = mean(c(df$x[i], df$y[i])))
  }
  out
}

以下是结果:

microbenchmark(fun2(), fun2(), fun3(), times = 20)
# Unit: milliseconds
#    expr      min       lq   median       uq       max neval
#  fun1() 39.72704 39.99255 40.84243 43.77641  48.16284    20
#  fun2() 74.92324 79.20913 82.15130 83.12488 100.51695    20
#  fun3() 48.61772 49.59304 50.16654 56.17891  88.65290    20

答案 2 :(得分:1)

如果您愿意使用外部包,那么这里使用的是data.table

这是一个简化你的功能的版本:

require(data.table) ## 1.9.2+
fA <- function(x, y) {
  data.frame(x = x:y, y = y:x)
}
dt = as.data.table(df)
result1 = dt[, list(ans = list(fA(x, y))), by=seq_len(nrow(dt))]
#    seq_len          ans
# 1:       1 <data.frame>
# 2:       2 <data.frame>
# 3:       3 <data.frame>

我们首先创建data.table,然后使用dt在每行上汇总by=.,并在每行上传递相应的xy fA函数,并将结果包装在list中。现在只需执行result1$ans即可获得所需的结果。

如果你坚持不传递单个对象,那么你可以这样做:

require(data.table) ## 1.9.2+
fB <- function(dat) {
  data.frame(x = dat$x:dat$y, y = dat$y:dat$x)
}
dt = as.data.table(df)
result2 = dt[, list(ans = list(fB(.SD))), by=seq_len(nrow(dt))]
#    seq_len          ans
# 1:       1 <data.frame>
# 2:       2 <data.frame>
# 3:       3 <data.frame>

在这里,我们传递数据子集.SD - 一个特殊变量,它携带属于每个组的数据,而不是函数fB。再次做result2$ans应该得到你的答案。

HTH

哦,顺便说一下,在你的代码中使用空格是可以的;费用不高:)。