循环应用函数在数据帧列表上

时间:2018-05-18 21:35:26

标签: r for-loop dataframe save subset

我查看了各种类似问题的Overflow页面(有些链接)但是找不到任何似乎有助于完成这项复杂任务的内容。

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

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

1)我想将rollmean功能限制在除第1(或前几个)之外的所有列,以便列(' info'不平均。 我还想将此列添加回输出数据框。

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循环但是如果有人可以使用嵌套适用这个工作,我&# 39;我失望了!

图片是理想的输出:Top是单输入数据框,带有彩色框,显示所有列的滚动平均值,在每列上进行迭代;底部是理想的输出,颜色反映了上面每个彩色窗口的输出位置 Top is single input dataframe with colored boxes demonstrating a rolling average across all columns, to be iterated over each column; bottom is ideal output with colors reflecting the location of output for each colored window above

2 个答案:

答案 0 :(得分:3)

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

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

一栏

如果你不喜欢减少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)] <- 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

答案 1 :(得分:1)

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

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

partial=TRUE

替代版本的make_new

上面显示的第一个make_new版本的替代版本是以下第二个版本。在第二个版本而不是定义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)) ,我们只使用普通mean3,但在mean中指定宽度为w向量,使{{1}等于c(1,1,3,3,...,3)。因此,它只取得前两个输入组件的最后一个元素的平均值和其余三个最后一个元素的平均值。请注意,现在我们明确指定了宽度,我们不再需要指定rollapplyr

w

注意

通常在编写R并操纵一组对象时,会将对象存储在列表中,而不是将它们放在全局环境中。我们可以像这样创建这样的列表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)) } 创建包含新版本的第二个列表L。这两个版本的lapply都适用于此。

L2