在读取和绑定多个文件时将“文件名”列添加到表中

Posted

技术标签:

【中文标题】在读取和绑定多个文件时将“文件名”列添加到表中【英文标题】:Add "filename" column to table as multiple files are read and bound 【发布时间】:2018-02-28 04:47:51 【问题描述】:

我在多个目录中有大量 csv 文件,我想将它们读入 R tribble 或 data.table。我使用“list.files()”并将递归参数设置为 TRUE 创建文件名和路径列表,然后使用“lapply()”读取多个 csv 文件,然后使用“bind_rows()”将它们全部粘贴一起:

filenames <- list.files(path, full.names = TRUE, pattern = fileptrn, recursive = TRUE)
tbl <- lapply(filenames, read_csv) %>% 
  bind_rows()

这种方法效果很好。但是,我需要从每个文件名中提取一个子字符串并将其作为列添加到最终表中。我可以像这样使用“str_extract()”获得我需要的子字符串:

sites <- str_extract(filenames, "[A-Z]2-[A-Za-z0-9]3")

但是,当 lapply() 为每个文件运行 read_csv() 时,我一直不知道如何将提取的子字符串添加为列。

【问题讨论】:

【参考方案1】:

tidyverse 方法:

更新:

readr 2.0 现在内置支持在单个命令中将具有相同列的文件集读取到一个输出表中。只需将要在同一向量中读取的文件名传递给读取函数。例如读取 csv 文件:

(files <- fs::dir_ls("D:/data", glob="*.csv"))
dat <- read_csv(files, id="path")

或者使用map_dfrpurrr: 使用purrr::map_dfr() 中的.id = "source" 参数添加文件名 加载 .csv 文件的示例:

 # specify the directory, then read a list of files
  data_dir <- here("file/path")
  data_list <- fs::dir_ls(data_dir, regexp = ".csv$")

 # return a single data frame w/ purrr:map_dfr 
 my_data = data_list %>% 
    purrr::map_dfr(read_csv, .id = "source")
  
 # Alternatively, rename source from the file path to the file name
  my_data = data_list %>% 
    purrr::map_dfr(read_csv, .id = "source") %>% 
    dplyr::mutate(source = stringr::str_replace(source, "file/path", ""))
  

【讨论】:

【参考方案2】:

data.table 方法:

如果给列表命名,那么在将列表绑定在一起时可以使用这个名称添加到data.table中。

工作流程

files <- list.files( whatever... )
#read the files from the list
l <- lapply( files, fread )
#names the list using the basename from `l`
# this also is the step to manipuly the filesnamaes to whatever you like
names(l) <- basename( l )
#bind the rows from the list togetgher, putting the filenames into the colum "id"
dt <- rbindlist( dt.list, idcol = "id" )

【讨论】:

我想你的意思是:names(l) 另外,我认为这条评论有首选方法:***.com/a/45522323/7654965【参考方案3】:

我一般使用以下方法,基于 dplyr/tidyr:

data = tibble(File = files) %>%
    extract(File, "Site", "([A-Z]2-[A-Za-z0-9]3)", remove = FALSE) %>%
    mutate(Data = lapply(File, read_csv)) %>%
    unnest(Data) %>%
    select(-File)

【讨论】:

非常漂亮。谢谢你。我发现 mutate() 和 extract() 和 unnest() 有点难以理解,但效果很好!另外,“select(-File)”在做什么? @kray 在我们完成之后,它只是删除了File 列。至于发现难以遵循的工作流程:它是暂时的。一旦您习惯了 dplyr 管道流程,这将变得毫不费力。也就是说,我现在重新排列了表达式以将数据读取代码放在一起。【参考方案4】:

您只需要编写自己的函数来读取 csv 并添加所需的列,然后再组合它们。

my_read_csv <- function(x) 
  out <- read_csv(x)
  site <- str_extract(x, "[A-Z]2-[A-Za-z0-9]3")
  cbind(Site=site, out)


filenames <- list.files(path, full.names = TRUE, pattern = fileptrn, recursive = TRUE)
tbl <- lapply(filenames, my_read_csv) %>% bind_rows()

【讨论】:

或者你可以这样做:map_dfr(filenames, my_read_csv) %>% as_tibble()【参考方案5】:

你可以在这里使用purrr::map2,它的工作原理类似于mapply

filenames <- list.files(path, full.names = TRUE, pattern = fileptrn, recursive = TRUE)
sites <- str_extract(filenames, "[A-Z]2-[A-Za-z0-9]3")  # same length as filenames

library(purrr)
library(dplyr)
library(readr)
stopifnot(length(filenames)==length(sites))  # returns error if not the same length
ans <- map2(filenames, sites, ~read_csv(.x) %>% mutate(id = .y))  # .x is element in filenames, and .y is element in sites

map2的输出是一个列表,类似于lapply

如果您有purrr 的开发版本,则可以使用imap,它是map2 的包装器,带有索引

【讨论】:

【参考方案6】:

您可以基于“站点”构建一个与 tbl 长度完全相同的文件名向量,然后使用 cbind 将两者结合起来

### Get file names
filenames <- list.files(path, full.names = TRUE, pattern = fileptrn, recursive = TRUE)
sites <- str_extract(filenames, "[A-Z]2-[A-Za-z0-9]3")

### Get length of each csv
file_lengths <- unlist(lapply(lapply(filenames, read_csv), nrow))

### Repeat sites using lengths
file_names <- rep(sites,file_lengths))

### Create table
tbl <- lapply(filenames, read_csv) %>% 
  bind_rows()

### Combine file_names and tbl
tbl <- cbind(tbl, filename = file_names)

【讨论】:

以上是关于在读取和绑定多个文件时将“文件名”列添加到表中的主要内容,如果未能解决你的问题,请参考以下文章

使用 SparkSQL 读取多个 parquet 文件时将子文件夹作为列获取

插入多个文本文件

如何重新配置​​平面文件连接管理器上的列信息?

循环遍历表,同时记录不断添加到表中

根据在另一个表中找到的聚合和范围将列添加到表中

如何将包含现有行和新行的新列添加到表中?