【问题标题】:R -- loop through files and create output tableR -- 遍历文件并创建输出表
【发布时间】:2018-04-05 18:02:52
【问题描述】:

我需要从多个文件执行一系列计算,并使用所有文件的结果创建一个输出表。

我尝试列出文件夹中的所有文件并循环使用forlapply 函数,但我缺少一些东西。

这是我对一些“假”文件的简化对比:

# Create new folder -- "trials"
setwd("C:/Users/.../Desktop")
dir.create("trials")

# Create 'trial' files
setwd("C:/Users/.../Desktop/trials")
pathFiles <- "C:/Users/.../Desktop/trials"

df_1 <- data.frame(x=c(1,2,3,4,5,6,7,8,9,10))
df_1$y <- c(1,2,3,4,5,6,7,8,9,10)
df_1$z <- c(10,20,30,40,50,60,70,80,90,100)
write.table(df_1, "table1.csv", col.names = TRUE, row.names = FALSE, sep = ",")

df_2 <- data.frame(x=c(2,3,4,5,6,7,8,9,10,11))
df_2$y <- c(2,3,4,5,6,7,8,9,10,11)
df_2$z <- c(20,30,40,50,60,70,80,90,100,110)
write.table(df_2, "table2.csv", col.names = TRUE, row.names = FALSE, sep = ",")

df_3 <- data.frame(x=c(3,4,5,6,7,8,9,10,11,12))
df_3$y <- c(3,4,5,6,7,8,9,10,11,12)
df_3$z <- c(30,40,50,60,70,80,90,100,110,120)
write.table(df_3, "table3.csv", col.names = TRUE, row.names = FALSE, sep = ",")

对于这些文件中的每一个,我想提取某些信息并创建一个包含所有计算字段的输出表。

我已经尝试过 for 循环:

Final <- NULL
M <- NULL
slp <- NULL
eval <- NULL

dfs <- dir(pathFiles, "*.csv", full.names = TRUE, ignore.case = TRUE, all.files = TRUE)

for (df in dfs) {

  t <- read.csv(df, header = TRUE, sep = ",")
  x <- t$x
  y <- t$y
  z <- t$z

  lim_y <- y >= 3 & y <=6
  lim_x <- x[lim_y]
  lim_z <- z[lim_y]

  iFinal <- x[nrow(t)]
  Final <- c(Final, iFinal) # add value to the string

  iM <- mean(lim_z)
  M <- c(M, iM) # add value to the string

  p <- lm(lim_x ~ lim_z)
  iSlp <- summary(p)$coefficients[2,1]
  slp <- c(slp, iSlp) # add value to the string

  ifelse ((Slp <= 0.05 & Slp >= -0.05), ieval <- "ok", ieval <- "false") 
  eval <- c(eval, ieval) # add value to the string
}

sum_df <- data.frame(df, M, Slp, eval, Final)
write.table(sum_df, "sum_df.csv", sep = ",", row.names = FALSE, col.names = TRUE)

我之前以类似的方式使用过这个for 循环,它工作正常,但在这里没有。

使用lapply 函数我没有得到更好的结果:

dfs <- list.files(pathFiles, "^.+\\.csv", full.names = TRUE, ignore.case = TRUE, all.files = TRUE)

Final <- NULL
M <- NULL
slp <- NULL
eval <- NULL

model <- function(x){
  t <- read.csv(x, header = TRUE, sep = ",")
  x <- t$x
  y <- t$y
  z <- t$z

  lim_y <- y >= 3 & y <=6
  lim_x <- x[lim_y]
  lim_z <- z[lim_y]

  iFinal <- x[nrow(t)]
  Final <- c(Final, iFinal)

  iM <- mean(lim_z)
  M <- c(M, iM)

  p <- lm(lim_x ~ lim_z)
  iSlp <- summary(p)$coefficients[2,1]
  slp <- c(slp, iSlp)

  ifelse ((Slp <= 0.05 & Slp >= -0.05), ieval <- "ok", ieval <- "false") 
  eval <- c(eval, ieval)
}

lapply(dfs, model)

函数和输出表只适用于一个文件,所以我猜错误一定是我循环文件的方式。但我不知道我哪里出错了。

我将不胜感激。

【问题讨论】:

  • 我刚刚运行了你的 for 循环,我看到的唯一问题是 slpSlp 的案例问题。在我调整之后,循环成功完成。否则,请检查以确保您定义 pathFiles 的方式是合法目录,并且您的文件确实存在于该目录中。
  • 我按照您的建议进行了更改,但它对我不起作用。我收到以下错误消息:Error in data.frame(df, M, Slp, eval, Final) : arguments imply differing number of rows: 1, 0。所有文件都在指定的目录中,所以不确定我做错了什么。
  • 顺便说一句,很好的完全可重现的问题。它使快速尝试变得更容易。

标签: r function loops


【解决方案1】:

您引用了slpSlp,所以某处有错字。对其中一个进行全局替换可修复该错误。

您的for 循环不会对我产生错误。

您的lapply 有几点有误:

  • 一般来说,使用 *apply 函数的好处之一是它们可以在没有 side-effect 的情况下工作,这就是您在 for 循环中所做的事情,以及当您设置全局分配eval 和朋友。无需尝试从 lapply 中“向外”访问并分配给全局命名空间中的变量,当函数退出时,您对 M 和朋友的分配将被默默地丢弃。当您考虑使用这些 apply 函数(它们很棒)时,您几乎应该总是假设它们的宇宙在函数退出时完全消失,并且它们无法退出。如果你是 Trekkie,想想Remember Me (Star Trek TNG),贝弗利的宇宙只是泡沫中的东西。 (在 R 和电视节目中都可以刺穿它。)

  • 你的函数只返回eval,这只是偶然的。如果您想返回所有突出显示为“有趣”的内容,则需要明确返回它们,可能是 listdata.frame。 (不是vector,因为ieval 会将所有变量上转换为character。)

因此,不要考虑在lapply 中连接数据,而应考虑保持结果结构良好并稍后组合。试试这个:

model2 <- function(fname) {
  dat <- read.csv(fname, header = TRUE, sep = ",")
  lim_y <- dat$y >= 3 & dat$y <=6
  lim_x <- dat$x[lim_y]
  lim_z <- dat$z[lim_y]

  iFinal <- dat$x[nrow(dat)]
  iM <- mean(lim_z)

  p <- lm(lim_x ~ lim_z)
  iSlp <- summary(p)$coefficients[2,1]

  iEval <- (iSlp <= 0.05 & iSlp >= -0.05) 

  return(data.frame(
    fname = fname,
    M = iM, Slp = iSlp, Eval = iEval, Final = iFinal,
    stringsAsFactors = FALSE))
}

do.call(rbind, lapply(dfs, model2))
# Warning in summary.lm(p) :
#   essentially perfect fit: summary may be unreliable
# Warning in summary.lm(p) :
#   essentially perfect fit: summary may be unreliable
# Warning in summary.lm(p) :
#   essentially perfect fit: summary may be unreliable
#          fname  M Slp  Eval Final
# 1 ./table1.csv 45 0.1 FALSE    10
# 2 ./table2.csv 45 0.1 FALSE    11
# 3 ./table3.csv 45 0.1 FALSE    12

*apply 家族有无数种方法可以做到这一点,但我认为这是一个不错的方法。

有关*applylists 内的帧等的一些不错的阅读,请参阅:

【讨论】:

  • 太棒了!谢谢!!出于某种原因,for 循环对我不起作用,但这很好用!我做了类似的事情,但combine 位让我发疯了。无论如何,我会继续尝试for 循环;现在它只是在困扰我!再次感谢!
  • 顺便说一句,Trekkie 的提示很有启发性,我不是在开玩笑。我学到了很多,谢谢。
【解决方案2】:

我会推荐 data.table 库中的 rbindlist。

lapply 将返回一个长度文件列表, rbindlist 这个列表一起放到一个表中

library(data.table)
files <- dir(pathFiles, "*.csv", full.names = TRUE, ignore.case = TRUE, all.files = TRUE)
desiredTable <- rbindlist(
                          lapply(
                                 files,
                                 function(x){
                                 fileData <- fread(x)
                                 CalculatedData <- ...do stuff...
                                 return(CalculatedData)
                                 }
                                )
                            )

这是一个使用 do.call 的工作示例,避免使用 data.table

numFiles <- 100 #number of random files to generate

# Generate a bunch of .csv with a fileID, some letters, and some numbers and put those files in the working dir
sapply(
  1:numFiles,
  function(f){
    dataReplicates <- 12
    dataLetters <- sample(LETTERS,12)
    dataNumbers <- sample(seq(1:100),12)
    fileID <- rep(f,dataReplicates)
    fileData <- cbind(
      fileID,
      dataLetters,
      dataNumbers
    )
    write.csv(
      fileData,
      paste0(getwd(),"/",Sys.Date(),"_",f,".csv"),
      row.names = FALSE
    )
   }
  )

# Read those files back in and store the names in a vector
thoseRandFiles <- dir(
  path = getwd(),
  pattern = as.character(Sys.Date()),
  full.names = TRUE
)

#using lapply and rbind, read in each file, perform operations, and bind into a single table
desiredTable <- do.call(
      rbind,
       lapply(
       thoseRandFiles,
        function(x){
         fileData <- read.csv(x)
         fileID <- fileData$fileID[1]
         firstLetter <- as.character(fileData$dataLetters[1])
         sumNumbers <- sum(fileData$dataNumbers)
         calData <- cbind.data.frame(fileID,firstLetter,sumNumbers)
         return(calData)
         }
       )
      )

【讨论】:

  • 缺乏对 DT 对象的需求,也许只是 do.call(rbind, lapply(...))
  • @r2evans 你是对的。我几乎默认使用DT。您的建议将在 base 中实现同样的效果
  • @SubstantiaN 和@r2evans 非常感谢。虽然仍然缺少一些东西。也许是我输入查询的方式。它说“args”丢失。是我第一次使用function(x) 并且可能没有很好地构建它。我得玩一会儿。
  • @r2evans,我不想问这个问题,但您介意像运行脚本一样发布代码吗?我不断收到argument "args" is missing, with no default 错误消息,感觉我在绕圈子。我知道这是基本的,所以如果您不这样做,我会理解的。提前致谢。
  • @SubstantiaN 非常感谢。不错的方法!根据您的建议,我做了一些非常相似的事情,但分别定义了function。但是,我得到的表格显示了最后一个文件三次,而不是每个文件的一组结果。我想这与我应用combine 函数的方式有关。再次感谢;这很有帮助。
猜你喜欢
  • 2019-09-29
  • 1970-01-01
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多