8  化整为零:对文件进行批处理

在数据处理和分析过程中,常常需要对大量文件进行操作,如批量创建、复制、移动和删除文件等。为了提高效率并避免手动处理的繁琐和易错,使用脚本进行批处理是一个极其有效的方法。在本章中,我们将探讨如何利用R语言脚本来实现文件的批处理操作。具体内容包括如何批量创建文件、复制和移动文件、删除不需要的文件,以及使用如openxlsx2等工具对Excel文件进行操作。最后,我们还将介绍如何对文件进行压缩,以便更方便地存储和传输。通过这些实用的示例,读者将能够掌握使用R语言进行高效文件管理的技巧,大大简化日常数据处理任务。

8.1 文件系统操作

文件系统操作涉及对计算机存储系统中的文件和目录进行管理和操控,包括创建、删除、复制、移动和重命名文件和目录,以及获取文件的属性信息和列出目录内容等。文件系统操作是数据管理和处理过程中不可或缺的一部分,通过有效地组织和维护文件,可以提高工作效率和数据处理的可靠性。

在R语言中,fs包提供了一组功能强大且易于使用的函数,用于执行各种文件系统操作。fs包的设计注重简洁性和一致性,使得用户可以更直观和高效地进行文件和目录管理。其核心功能包括:

  • 创建目录: dir_create函数可以方便地创建新目录。这在组织和分类数据文件时非常有用。

  • 创建文件: 使用 file_create 函数可以创建新的文件。这对于初始化新的数据文件或日志文件非常实用。

  • 复制文件: file_copy 函数用于复制文件,将文件从一个位置复制到另一个位置。这在备份文件或将文件分发到不同目录时非常有用。

  • 移动或重命名文件: file_move 函数用于移动文件或重命名文件。移动文件可以改变文件的存储位置,而重命名文件可以使文件名更加有意义或符合命名规范。

  • 删除文件或目录: file_delete/dir_delete 函数用于删除不需要的文件或目录,帮助用户清理和管理存储空间。

  • 列出目录内容: 使用 dir_ls 函数可以列出目录中的所有文件和子目录。这对于检查目录内容或批量处理文件时非常有用。

  • 获取文件信息: file_info函数提供文件的详细信息,如文件大小、创建时间、修改时间等。这些信息在文件管理和分析时非常重要。

  • 检查文件或目录是否存在: 使用 file_exists/dir_exists 函数可以方便地检查文件或目录是否存在,帮助用户避免文件操作中的错误。

  • 文件路径操作: fs包还提供了处理文件路径的功能,包括路径组合、路径拆分和获取路径组件等。这些功能使得文件路径操作更加灵活和便捷。

通过使用 fs包,R语言用户可以高效地执行各种文件系统操作,从而简化文件管理任务,提升数据处理的效率和可靠性。fs包的函数设计简洁一致,使得文件和目录的创建、复制、移动、删除等操作变得直观和容易实现。无论是进行简单的文件操作还是复杂的批量处理,fs 包都提供了强大的支持,使用户能够更加专注于数据分析和处理本身。本介绍只提及了部分fs包的功能,更多相关信息可以参考官方文档(https://fs.r-lib.org/),我们将会在后续的实践中对其中实用的功能进行运用和讲解。

8.2 文件压缩操作

文件压缩操作是一种通过减少文件大小来优化存储和传输效率的技术。它涉及将数据重新编码以占用更少的空间,从而节省存储资源、提高传输速度并增强数据的安全性。压缩文件在数据备份、传输和归档过程中尤为重要,因为它能够显著减少文件的存储需求,并加快通过网络传输的速度。常见的压缩格式包括 zip、tar、rar、gzip、bzip2 和 7-zip 等,每种格式都有其特定的应用场景和优势。

archive 包是 R 语言中的一个强大工具,用于处理多种压缩格式的文件。这个包提供了统一的接口,可以轻松地对各种类型的压缩文件进行创建、解压和查看操作。与其他专注于单一压缩格式的包不同,archive包支持多种格式,这使其在处理不同类型的压缩文件时更加灵活和强大。archive 包的主要特点包括:

  • 多格式支持:能够处理常见的压缩格式,如 zip、tar、rar、gzip、bzip2、xz 和 7-zip,适用于不同需求的压缩和解压操作。
  • 统一接口:提供了一套统一的函数接口,使用户能够方便地对不同格式的压缩文件进行操作,而无需为每种格式学习不同的命令。
  • 高效处理:能够高效地创建和解压大文件,适用于大规模数据处理和备份任务。
  • 灵活操作:支持列出压缩文件中的内容,方便用户查看压缩包内的文件结构和信息。

通过 archive 包,用户可以轻松地进行压缩和解压操作。例如,使用 archive_write_files/archive_write_dir 函数可以创建一个新的压缩文件,将多个文件压缩到一起;使用 archive 函数可以列出压缩文件中的内容,查看其中包含的文件列表和结构;使用 archive_extract 函数可以将压缩文件解压到指定的目录,方便后续的文件处理和使用。

总的来说,archive 包为 R 用户提供了一个灵活、高效的工具,能够处理多种压缩格式的文件,简化了文件压缩和解压的工作流程,使数据存储和传输更加方便和高效。无论是在数据备份、传输还是归档过程中,archive 包都能提供强大的支持,更多相关的信息可参考官方文档(https://archive.r-lib.org/)。

值得一提的是,当我们把多个文件进行打包的时候,并不总是必须对文件进行压缩(即把文件的总容量减小),有的时候我们甚至会把文件变大,但是压缩依然有意义。我们可以把这个过程看做是把很多小物件放进一个容器中(容器可以是塑料袋、收纳盒或集装箱)。有的物件具有弹性,因此我们可以用力压,让它的体积更小;有的东西则弹性不大,只能直接放进容器中。但是无论如何,只要我们把东西归置到一起,就会便于我们管理,而且在对这一群东西进行移动的时候会更加方便。举一个通俗的例子,如果现在我们有6瓶矿泉水,一个人一双手要把这些水移动到操场将会非常困难。但是如果我们再提供一个背包,把6瓶矿泉水都放进去,尽管背包和矿泉水的容积可能没有变小,但是移动这些矿泉水就会非常便捷。因此压缩文件格式其实就是一定组织形式的容器,在大数据传输中还会使用hdf5等其他形式的打包管理工具,而且在R中也有便捷的接口可以调用(比如hdf5可以使用hdf5r包进行调用),感兴趣的读者可以根据需要自行学习和了解。

8.3 Excel文件批处理

Excel 文件批处理是指对多个Excel文件进行一系列自动化操作,以提高效率和减少手动工作,这些操作包括创建、读取、修改、复制、移动和删除Excel文件。在数据处理和分析过程中,批处理操作可以显著节省时间,尤其是当需要处理大量结构类似的文件时。

openxlsx2是一个现代化的R包,用于处理Excel文件,提供了简洁的接口和丰富的功能,可以轻松创建、读取和编辑Excel工作表,支持多种Excel文件格式,包括xlsx、xlsm和xlsb格式,使得处理Excel文件变得更加高效和灵活。openxlsx2是对openxlsx(出现得更早的Excel数据处理包)的改进,通过整合pugixml库解决了在解析XML文件方面的问题,并提供了更强大的样式管理、图表支持和数据处理功能,使得创建、编辑和修改xlsx文件变得更加简单和可靠。

openxlsx2的核心功能包括: 1. XML解析改进:通过整合pugixml库,解决了在解析XML文件方面的问题。 2. 样式管理:提供了更强大的样式管理功能,包括对字体、背景颜色、数字格式等的灵活控制。 3. 图表支持:支持原生图表的创建和编辑,使得在xlsx文件中插入图表变得更加简单。 4. 数据处理:提供了丰富的数据处理功能,包括数据验证、条件格式、小图标等,提高了数据处理的灵活性和效率。

其中,常用的函数包括:

  • wb_load:加载xlsx文件为工作簿对象。

  • wb_add_worksheet:添加工作表到工作簿。

  • wb_add_data:将数据添加到工作表中。

  • wb_save:保存工作簿为xlsx文件。

  • wb_to_df:将工作表转换为数据框。

这里我们不会浪费篇幅来介绍工具包的技术细节,感兴趣的读者可以参考官方文档(https://janmarvin.github.io/openxlsx2/)进行学习。下面我们将会直接在实践中对综合运用本章介绍的工具,从而对文件进行高效的批处理。

8.4 综合实践

在本部分,会首先生成一个数据集,然后模拟现实情况的需求,对数据进行各式各样的批处理,从而展示如何利用R语言各种工具包对各种文件进行高效的操作。

8.4.1 环境配置与数据生成

首先,我们会加载必要的工具包:

library(pacman)
p_load(fs,openxlsx2,archive,tidyfst,tidyverse)

然后,我们来生成相关的数据集:

nr_of_rows <- 1e7 # 可适当增加数据量,如改为 5e7或1e8

df <- data.table(
    Logical = sample(c(TRUE, FALSE, NA), prob = c(0.85, 0.1, 0.05), nr_of_rows, replace = TRUE),
    Integer = sample(1L:100L, nr_of_rows, replace = TRUE), # 可适当增加文件数量,如改为 1:1e3或1:1e4
    Real = sample(sample(1:10000, 20) / 100, nr_of_rows, replace = TRUE),
    Factor = as.factor(sample(labels(UScitiesD), nr_of_rows, replace = TRUE))
  )

df

#           Logical Integer  Real       Factor
#            <lgcl>   <int> <num>       <fctr>
#        1:    TRUE      75 89.32      NewYork
#        2:    TRUE      39  6.99      NewYork
#        3:    TRUE      76 62.89        Miami
#        4:    TRUE      41 41.42      Atlanta
#        5:    TRUE      36 53.85 SanFrancisco
#       ---                                   
#  9999996:    TRUE      37 66.42      Seattle
#  9999997:    TRUE      46  6.99        Miami
#  9999998:    TRUE      78 53.85      NewYork
#  9999999:   FALSE      85 62.89   LosAngeles
# 10000000:    TRUE      20 46.48      Atlanta

由于数据集生成具有一定的随机性,因此读者生成的数据框不会与上面展示的数据框完全一致,但是不影响试验的开展。经过观察,我们知道数据集一共有4列,分别是逻辑型、整数型、数值型和因子型数据。我们不妨来检测一下数据的大小:

object_size(df)
# 190.7 Mb

8.4.2 数据的保存

在这一部分,我们需要根据需求来对目标数据进行保存。我们的诉求和实现代码如下所示。 1. 在根目录下创建temp文件夹,实现代码如下:

dir_create("temp")
  1. 在temp文件夹下创建csv文件夹,把数据根据Integer分组,保存在不同的csv文件中,实现代码如下:
dir_create("temp/csv")
df %>% 
  split(by = "Integer") %>%  # 根据Integer列进行分组
  walk(\(x){ # 使用walk函数进行迭代
    fn = x %>% head(1) %>% pull(Integer)  # 根据Integer创建文件名
    fwrite(x,path("temp","csv",fn,ext = "csv")) # 写出文件
  })
  1. 在temp文件夹下创建fst文件夹,把数据根据Integer分组,保存在不同的fst文件中,实现代码如下:
dir_create("temp/fst")
df %>% 
  split(by = "Integer") %>%  # 根据Integer列进行分组
  walk(\(x){ # 使用walk函数进行迭代
    fn = x %>% head(1) %>% pull(Integer)  # 根据Integer创建文件名
    export_fst(x,path("temp","fst",fn,ext = "fst")) # 写出文件
  })

8.4.3 文件批量读取

如果我们需要对csv文件夹下的所有文件进行读取,然后合并为一个数据框,可以使用readr包的read_csv函数进行实现,操作方法如下:

dir_ls("temp/csv") -> all_csv_paths
read_csv(all_csv_paths,id = "file_path") -> all_data
all_data

# # A tibble: 10,000,000 × 5
#    file_path      Logical Integer  Real Factor      
#    <chr>          <lgl>     <dbl> <dbl> <chr>       
#  1 temp/csv/1.csv TRUE          1 54.6  Denver      
#  2 temp/csv/1.csv TRUE          1 52.2  Houston     
#  3 temp/csv/1.csv TRUE          1 35.8  SanFrancisco
#  4 temp/csv/1.csv TRUE          1 79.5  Houston     
#  5 temp/csv/1.csv TRUE          1 92.2  LosAngeles  
#  6 temp/csv/1.csv FALSE         1 53.8  Atlanta     
#  7 temp/csv/1.csv TRUE          1 78.4  Miami       
#  8 temp/csv/1.csv TRUE          1 18.2  Atlanta     
#  9 temp/csv/1.csv TRUE          1  8.83 Denver      
# 10 temp/csv/1.csv FALSE         1 49.8  Houston     
# # ℹ 9,999,990 more rows
# # ℹ Use `print(n = ...)` to see more rows

那么在返回的all_data变量中,包含了所有文件合并后的信息,其中file_path列保存了文件的路径。

不过在R中,能够最快读取csv格式文件的还是data.table包提供的fread函数,因此如果对性能有较高要求,可以这样操作:

map_dfr(all_csv_paths,fread) -> all_data2

这里使用了purrr包的map_dfr函数,会开展向量化操作,得到的结果会把数据框按照行合并到一起。在上述操作中,没有记录文件名称,如果需要记录,可以这样操作:

lapply(all_csv_paths,\(x) fread(x)[,file_path:=x]) %>% 
  rbindlist() -> all_data3

这里我们使用了基本包的lapply函数,并且利用data.table包的rbindlist函数把获得的数据框列表合并到一起。

8.4.4 文件的压缩

在本部分中,我们会对先前生成的数据进行打包压缩。由于Excel文件已经是一整个文件,因此不需要再进行压缩操作。我们首先把csv文件都打包为zip文件:

archive_write_dir(archive = "temp/csv.zip",dir = "temp/csv")

另一方面,我们把fst文件都打包为tar文件:

archive_write_dir(archive = "temp/fst.tar",dir = "temp/fst")

8.4.5 文件的移动

我们知道,文件打包后,移动会更加快。我们不妨来进行尝试,在temp中再建立一个dest文件夹,然后分别把csv文件夹和csv压缩包移动进去,并测试移动时间:

dir_create("temp/dest")
pst(file_move("temp/csv","temp/dest/csv"))
pst(file_move("temp/csv.zip","temp/dest/csv.zip"))

如果效果不明显,可以尝试增加文件数量。

8.4.6 保存为Excel文件

在这一步中,我们会把fst中的1到3号文件转存在一个Excel文件中(命名为“1-3.xlsx”),分成不同的工作簿进行保存,实现方法如下:

map(path("temp","fst",1:3,ext = "fst"),import_fst) %>% 
  write_xlsx(path("temp","1-3",ext = "xlsx"))

8.4.7 文件的删除

下面我们尝试删除temp文件夹,操作方法如下:

file_delete("temp")

8.5 小结

本章介绍了如何在R语言中利用fsarchiveopenxlsx2这些工具包,从而对文件进行批处理操作。当我们有海量文件的时候,我们就能够利用命令行对其进行移动、删除、复制等操作,并可以对多个文件进行压缩打包以便于传输、存储和管理。尽管本章没有对各个工具包的特性细节进行介绍(比如openxlsx2能够进行图片插入、表格格式设置、样式的更改等),这些内容笔者认为应该在读者需要的时候自行查询帮助文档,进而进行实现。通过本章的学习,相信读者可以了解并熟悉在R语言中如何利用脚本对多个文件进行批处理。

8.6 练习

  • 请设计一套代码,来测试各种压缩方法的特点(压缩比率、压缩时间),并对压缩后文件传输的速度进行测试。