批量读取数据或输出分析结果之前,脚本首先要解决两个问题:从目录中找到目标文件,以及在写出结果前准备好目录结构。
Base R 的 list.files() 和 dir.create() 分别处理这两个步骤,不需要加载额外包。
当前工作目录
相对路径从当前工作目录开始解释:
getwd()
例如当前工作目录是一个分析项目的根目录,那么:
list.files("data")
表示列出项目下 data/ 中的内容。
脚本中应尽量从稳定的项目根目录使用相对路径,而不是反复调用 setwd() 或写死某台电脑上的绝对路径。
list.files():列出目录内容
不提供参数时,list.files() 返回当前工作目录中的文件名和目录名:
list.files()
dir() 是它的简写别名:
dir()
指定目录:
list.files("data")
默认只返回名称,不包含起始目录:
list.files("data", full.names = FALSE)
批量读取时通常需要完整路径:
list.files("data", full.names = TRUE)
使用 pattern 筛选文件名
pattern 接收正则表达式,可以按扩展名或名称模式筛选。
list.files("data", pattern = "\\.csv$")
list.files("data", pattern = "\\.txt$")
正则表达式中的 . 表示任意字符,因此匹配扩展名前的点时要写成 \\.;$ 表示文件名结尾。
忽略扩展名大小写:
list.files(
"data",
pattern = "\\.csv$",
ignore.case = TRUE
)
按多个扩展名筛选:
list.files(
"data",
pattern = "\\.(csv|txt)$",
ignore.case = TRUE
)
也可以先列出全部名称,再用其他函数筛选:
files <- list.files("data")
csv_files <- grep("\\.csv$", files, value = TRUE)
能直接写进 pattern 的条件,通常不必再增加一次 grep()。
递归查找子目录
默认情况下,list.files() 只查看当前这一层。递归搜索所有子目录:
list.files(
"data",
recursive = TRUE
)
与完整路径和类型筛选组合:
csv_files <- list.files(
path = "data",
pattern = "\\.csv$",
recursive = TRUE,
full.names = TRUE,
ignore.case = TRUE
)
这是一种常见的批处理入口:返回值可以直接交给读取函数。
tables <- lapply(csv_files, read.csv)
如果子目录本身也需要包含在结果中,可以显式设置:
list.files(
"data",
recursive = TRUE,
include.dirs = TRUE
)
隐藏文件与特殊目录
默认不列出以点开头的隐藏文件。需要时使用 all.files = TRUE:
list.files(all.files = TRUE)
在支持 . 和 .. 目录项的系统上,可以用 no.. = TRUE 排除它们:
list.files(
all.files = TRUE,
no.. = TRUE
)
通常只需要普通数据文件时,保持 all.files = FALSE 即可。
使用 file.path() 构造路径
不要用字符串拼接硬写路径分隔符。file.path() 会按当前操作系统构造路径:
data_dir <- file.path("data", "raw")
output_file <- file.path("output", "tables", "summary.csv")
也可以为一组文件名批量添加目录:
files <- c("a.csv", "b.csv", "c.csv")
paths <- file.path("data", files)
list.files(full.names = TRUE) 已经完成了这一步;只有手头先有文件名时才需要再调用 file.path()。
dir.create():创建目录
在当前工作目录下创建一个目录:
dir.create("codes")
创建嵌套目录时,如果父目录还不存在,使用 recursive = TRUE:
dir.create(
file.path("output", "figures"),
recursive = TRUE
)
不设置 recursive = TRUE 时,缺少父目录会导致创建失败。
创建前检查目录
dir.exists() 用于判断目录是否已经存在:
if (!dir.exists("results")) {
dir.create("results")
}
初始化多层输出目录:
output_dirs <- c(
file.path("output", "figures"),
file.path("output", "tables"),
file.path("output", "models")
)
for (path in output_dirs) {
if (!dir.exists(path)) {
dir.create(path, recursive = TRUE)
}
}
如果目录已经存在,dir.create() 默认给出 warning。也可以直接尝试创建并关闭该 warning:
dir.create(
"results",
recursive = TRUE,
showWarnings = FALSE
)
需要区分“目录已存在”和“因为权限或路径错误而失败”时,应保留显式的 dir.exists() 检查。
批量读取文件
列出文件后,使用 lapply() 或 purrr::map() 逐个读取。
csv_files <- list.files(
"data",
pattern = "\\.csv$",
full.names = TRUE
)
tables <- lapply(csv_files, read.csv)
保留文件来源:
names(tables) <- basename(csv_files)
使用 purrr:
tables <- purrr::map(csv_files, read.csv)
如果要把结构相同的表合并,可以在确认列名与类型一致后继续处理:
combined <- do.call(rbind, tables)
一套完整的目录初始化
data_dir <- "data"
result_dir <- file.path("output", "tables")
if (!dir.exists(result_dir)) {
dir.create(result_dir, recursive = TRUE)
}
input_files <- list.files(
path = data_dir,
pattern = "\\.csv$",
recursive = TRUE,
full.names = TRUE,
ignore.case = TRUE
)
if (length(input_files) == 0) {
stop("没有找到 CSV 文件:", data_dir)
}
tables <- lapply(input_files, read.csv)
write.csv(
do.call(rbind, tables),
file.path(result_dir, "combined.csv"),
row.names = FALSE
)
这个流程把路径构造、目录创建、文件发现和批量读取连在一起。真正运行批处理前,还应确认文件顺序、表结构和空结果是否符合预期。
常见问题
只拿到文件名,后续读取失败
list.files("data") 返回的只是名称。读取时要么再次拼接 data/,要么一开始就使用 full.names = TRUE。
扩展名筛选匹配过宽
pattern = ".csv" 中的点会匹配任意字符。使用 pattern = "\\.csv$" 精确匹配扩展名。
递归结果失去层级信息
recursive = TRUE 时不要只保留 basename;不同子目录可能存在同名文件。保留相对路径或使用 full.names = TRUE。
输出前没有创建目录
write.csv()、ggsave() 等函数不会替你创建缺失的父目录。写文件之前先用 dir.create(..., recursive = TRUE) 初始化。
依赖返回顺序
list.files() 返回排序后的名称,但批处理逻辑不应把文件名顺序当作数据顺序。需要时间、批次或样本顺序时,从明确的元数据或文件名字段中解析并校验。