【问题标题】:Looping apply function over list of dataframes在数据帧列表上循环应用函数
【发布时间】:2018-10-29 09:08:48
【问题描述】:

我浏览了各种带有类似问题(一些链接)的溢出页面,但没有发现任何似乎有助于完成这项复杂任务的内容。

我的工作区中有一系列数据框,我想在所有数据框上循环使用相同的函数(rollmean 或某个版本),然后将结果保存到新的数据框。

我已经编写了几行来生成所有数据帧的列表和一个 for 循环,该循环应该在每个数据帧上迭代一个 apply 语句;但是,我在尝试完成我希望完成的所有事情时遇到了问题(我的代码和一些示例数据包含在下面):

1) 我想将rollmean 函数限制为除第一列(或前几列)之外的所有列,以便不会对“信息”列进行平均。 我还想将此列添加回输出数据框。

2) 我想将输出保存为一个新的数据框(具有唯一的名称)。 我不在乎它是保存到工作区还是导出为 xlsx,因为我已经编写了批量导入代码。

3) 理想情况下,我希望生成的数据框与输入的观察数量相同,其中rollmean 会缩小您的数据。我也不希望这些成为 NA,所以我不想使用 fill = NA 这可以通过编写一个新函数来完成,将 type = "partial" 传递给 rollmean 1 在我的手中),或者通过在第 n+2 项上开始滚动平均值并将非平均第 n 项和第 n+1 项绑定到结果数据帧。任何方式都可以。 (详见图片,它说明了后者的样子)

我的代码只完成了这些事情的一部分,我无法让 for 循环一起工作,但如果我在单个数据帧上运行它们可以让部分工作。

非常感谢任何意见,因为我没有想法。

#reproducible data frames 
a = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
b = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
c = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
colnames(a) = c("info", 1:20)
colnames(b) = c("info", 1:20)
colnames(c) = c("info", 1:20)

#identify all dataframes for looping rollmean
dflist = as.list(ls()[sapply(mget(ls(), .GlobalEnv), is.data.frame)]

#for loop to create rolling average and save as new dataframe
for (j in 1:length(dflist)){
  list = as.list(ls()[sapply(mget(ls(), .GlobalEnv), is.data.frame)])
  new.names = as.character(unique(list))
  smoothed = as.data.frame(
     apply(
        X = names(list), MARGIN = 1, FUN = rollmean, k = 3, align = 'right'))
  assign(new.names[i], smoothed)
}

我也尝试了嵌套应用方法,但无法调用 rollmean/rollapply 函数similar to issue here,所以我回到 for 循环,但如果有人可以使用嵌套应用来完成这项工作,我就失败了!

图片是理想的输出:顶部是单个输入数据框,带有彩色框,显示所有列的滚动平均值,将在每一列上进行迭代;底部是理想的输出,颜色反映了上面每个彩色窗口的输出位置

【问题讨论】:

  • J Ross,这两个答案都解决了你的问题吗?

标签: r for-loop dataframe save subset


【解决方案1】:

要解决这个问题,请考虑一列,然后是一个框架(这只是一个列列表),然后是一个框架列表。

(我使用的数据在答案的底部。)

一栏

如果你不喜欢zoo::rollmean的减少,那就自己写吧:

myrollmean <- function(x, k, ..., type=c("normal","rollin","keep"), na.rm=FALSE) {
  type <- match.arg(type)
  out <- zoo::rollmean(x, k, ...)
  aug <- c()
  if (type == "rollin") {
    # effectively:
    #   c(mean(x[1]), mean(x[1:2]), ..., mean(x[1:j]))
    # for the j=k-1 elements that precede the first from rollmean,
    # when it'll become something like:
    # c(mean(x[3:5]), mean(x[4:6]), ...)
    aug <- sapply(seq_len(k-1), function(i) mean(x[seq_len(i)], na.rm=na.rm))
  } else if (type == "keep") {
    aug <- x[seq_len(k-1)]
  }
  out <- c(aug, out)
  out
}

myrollmean(1:8, k=3) # "normal", default behavior
# [1] 2 3 4 5 6 7
myrollmean(1:8, k=3, type="rollin")
# [1] 1.0 1.5 2.0 3.0 4.0 5.0 6.0 7.0
myrollmean(1:8, k=3, type="keep")
# [1] 1 2 2 3 4 5 6 7

我警告说,这个实现充其量是有点幼稚,需要修复。确保您了解当您选择 "normal" 以外的其他选项时它在做什么(这对您不起作用,我只是默认为正常的 zoo::rollmean 行为)。这个函数可以很容易地应用于其他zoo::roll*函数。

在一列数据上:

rbind(
  dflist[[1]][,2],  # for comparison
  myrollmean(dflist[[1]][,2], k=3, type="keep")
)
#          [,1]      [,2]      [,3]      [,4]       [,5]      [,6]      [,7]     [,8]     [,9]     [,10]
# [1,] 1.865352 0.4047481 0.1466527 1.7307097 0.08952618 0.6668976 1.0743669 1.511629 1.314276 0.1565303
# [2,] 1.865352 0.4047481 0.8055844 0.7607035 0.65562952 0.8290445 0.6102636 1.084298 1.300091 0.9941452

一个“框架”

lapply的简单使用,省略第一列:

str(dflist[[1]][1:4, 1:3])
# 'data.frame': 4 obs. of  3 variables:
#  $ info: num  1 2 3 4
#  $ 1   : num  1.865 0.405 0.147 1.731
#  $ 2   : num  0.745 1.243 0.674 1.59
dflist[[1]][-1] <- lapply(dflist[[1]][-1], myrollmean, k=3, type="keep")
str(dflist[[1]][1:4, 1:3])
# 'data.frame': 4 obs. of  3 variables:
#  $ info: num  1 2 3 4
#  $ 1   : num  1.865 0.405 0.806 0.761
#  $ 2   : num  0.745 1.243 0.887 1.169

(为了验证,$ 1 列与上面“一列”示例中的第二行匹配。)

“框架”列表

(我将数据重置为我在上面修改之前的状态......请参阅答案底部的“数据”代码。)

我们将之前的技术嵌套到另一个lapply

dflist2 <- lapply(dflist, function(ldf) {
  ldf[-1] <- lapply(ldf[-1], myrollmean, k=3, type="keep")
  ldf
})
str(lapply(dflist2, function(a) a[1:4, 1:3]))
# List of 3
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 1.865 0.405 0.806 0.761
#   ..$ 2   : num [1:4] 0.745 1.243 0.887 1.169
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 0.271 3.611 2.36 3.095
#   ..$ 2   : num [1:4] 0.127 0.722 0.346 0.73
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 1.278 0.346 1.202 0.822
#   ..$ 2   : num [1:4] 0.341 1.296 1.244 1.528

(同样,为了简单的验证,第一帧的$ 1 行显示的滚动均值与上面“一列”示例的第二行相同。)

PS:

  • 如果您需要跳过的不仅仅是第一列,那么在外部lapply 内,请改用ldf[-(1:n)] &lt;- lapply(ldf[-(1:n)], myrollmean, k=3, type="keep") 来跳过第一列n
  • 要使用 zoo::rollmean 以外的窗口函数,您需要更改 myrollmean 的特殊情况,但对于这个示例应该足够简单
  • 我使用炮制的str(...) 来缩短此处显示的输出。您应该验证所有数据,确保它在整个每一帧中都按照您的预期进行。

可重现的数据

set.seed(2)
a = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
b = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
c = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
colnames(a) = c("info", 1:20)
colnames(b) = c("info", 1:20)
colnames(c) = c("info", 1:20)
dflist <- list(a,b,c)

str(lapply(dflist, function(a) a[1:3, 1:4]))
# List of 3
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 1.865 0.405 0.147
#   ..$ 2   : num [1:3] 0.745 1.243 0.674
#   ..$ 3   : num [1:3] 0.356 0.689 0.833
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 0.271 3.611 3.198
#   ..$ 2   : num [1:3] 0.127 0.722 0.188
#   ..$ 3   : num [1:3] 1.99 2.74 4.78
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 1.278 0.346 1.981
#   ..$ 2   : num [1:3] 0.341 1.296 2.094
#   ..$ 3   : num [1:3] 1.1159 3.05877 0.00506

【讨论】:

    【解决方案2】:

    dfnames 下方是全球环境env 中数据框的名称——我们将其命名为env,以防您以后想更改它们的位置。请注意,ls 有一个 pattern= 参数,如果数据框名称具有不同的模式,则可以使用 dfnames &lt;- ls(pattern=whatever) 代替任何合适的正则表达式。

    现在定义make_new,它调用rollapplyr,使用新的均值函数mean3,如果输入向量的长度小于3,则返回其输入的最后一个值,否则均值。然后使用 rollappyrFUN=mean3partial=TRUE 循环名称。

    library(zoo)
    
    env <- .GlobalEnv
    dfnames <- Filter(function(x) is.data.frame(get(x, env)), ls(env))
    
    # make_new - first version
    mean3 <- function(x, k = 3) if (length(x) < k) tail(x, 1) else mean(x)
    make_new <- function(df) replace(df, -1, rollapplyr(df[-1], 3, mean3, partial = TRUE))
    
    for(nm in dfnames) env[[paste(nm, "new", sep = "_")]] <- make_new(get(nm, env))
    

    make_new 的替代版本

    上面显示的第一个版本的 make_new 的替代方案是下面的第二个版本。在第二个版本中,我们没有定义mean3,而是使用普通的mean,但在rollapplyr 中指定宽度为w向量,使得w 等于c(1, 1, 3 , 3, ..., 3)。因此,前两个输入分量只取最后一个元素的平均值,其余部分取最后一个元素的平均值。请注意,现在我们明确指定了宽度,我们不再需要指定 partial=

    # make_new -- second version
    make_new <- function(df) {
      w <- replace(rep(3, nrow(df)), 1:2, 1)
      replace(df, -1, rollapplyr(df[-1], w, mean))
    }
    

    注意

    通常在编写 R 和操作一组对象时,会将对象存储在一个列表中,而不是让它们在全局环境中松散。我们可以像这样创建这样的列表L,然后使用lapply 创建包含新版本的第二个列表L2make_new 的任一版本都可以在这里使用。

    L <- mget(dfnames, env)
    L2 <- lapply(L, make_new)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-24
      • 1970-01-01
      • 1970-01-01
      • 2019-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-17
      相关资源
      最近更新 更多