R将非常大的数据表列表合并为一个data.table

如何解决R将非常大的数据表列表合并为一个data.table

我有一个非常庞大的列表,包含13个data.tables(总计〜11.1 Gb)。我有16Gb的RAM。将列表加载到内存后,我还有5 GB的RAM。

我需要将它们合并为一个data.table。由于它的data.table::rbindlist参数,我更愿意使用fill = TRUE(我的一些data.tables包含其他列没有的列-我需要用NA填充)。

问题是这需要5 GB以上的RAM才能完成,而我无法合并列表。好像我已经将数据加载到内存中了,合并的data.table不会再大了。我只需要弄清楚是否有一种方法可以完成操作,而不必将整个列表复制到内存(占用22GB的RAM)中来执行rbindlist

为了首先列出该列表,我运行的lapply如下所示:

  df <- lapply(fs::dir_ls(dir),function(file) {
     clean_data(file)
  })

我正在获取.csv文件的列表,并通过lapply将它们转换为clean data.tables,这就是我最终得到的列表的原因。

purrr::map_dfr似乎无效,也没有将lapply包装在rbindlist中。

解决方法

可能只有R方式可以做到这一点,但一种有效的方式是使用命令行(而非R)工具来实现。

设置此答案的目的:

mt1 <- mtcars[1:3,c(1,2,3)]
mt2 <- mtcars[3:4,4)]
mt3 <- mtcars[5:10,3,4)]

combined <- rbindlist(list(mt1,mt2,mt3),use.names = TRUE,fill = TRUE)
combined
#      mpg cyl  disp  hp
#  1: 21.0   6 160.0  NA
#  2: 21.0   6 160.0  NA
#  3: 22.8   4 108.0  NA
#  4: 22.8   4    NA  93
#  5: 21.4   6    NA 110
#  6: 18.7  NA 360.0 175
#  7: 18.1  NA 225.0 105
#  8: 14.3  NA 360.0 245
#  9: 24.4  NA 146.7  62
# 10: 22.8  NA 140.8  95
# 11: 19.2  NA 167.6 123

write.table(mt1,"mt1.tsv",row.names = FALSE)
write.table(mt2,"mt2.tsv",row.names = FALSE)
write.table(mt3,"mt3.tsv",row.names = FALSE)

现在我们知道数据的样子了,让我们以编程方式获取文件名:

filenames <- list.files(".",pattern = "^mt.*\\.tsv",full.names = TRUE)
filenames
# [1] "./mt1.tsv" "./mt2.tsv" "./mt3.tsv"

从这里开始,让我们从每个文件中抓取第一行(快速/有效,因为每个文件只有1行),并rbindlist,以便我们知道结果表的外观。当然,我们不需要保留任何实际值,只需保留列即可。

row1s <- rbindlist(lapply(filenames,function(fn) fread(fn,nrows = 1)),fill = TRUE)[0,]
row1s
# Empty data.table (0 rows and 4 cols): mpg,cyl,disp,hp

在此处进行演示时,请注意将0行表与原始表之一合并会呈现一致的架构。 (除非要验证一两个,否则不需要对真实数据进行此操作。)

row1s[mt1,on = intersect(names(row1s),names(mt1))]
#     mpg cyl disp hp
# 1: 21.0   6  160 NA
# 2: 21.0   6  160 NA
# 3: 22.8   4  108 NA
row1s[mt2,names(mt2))]
#     mpg cyl disp  hp
# 1: 22.8   4   NA  93
# 2: 21.4   6   NA 110

目标是以编程方式对所有文件执行此操作:

# iterate through each file: read,left-join,write
for (fn in filenames) {
  dat <- fread(fn)
  dat <- row1s[dat,names(dat))]
  fwrite(dat,file.path(dirname(fn),paste0("augm_",basename(fn))),sep = "\t")
}

newfilenames <- list.files(".",pattern = "^augm_mt.*\\.tsv$",full.names = TRUE)
newfilenames
# [1] "./augm_mt1.tsv" "./augm_mt2.tsv" "./augm_mt3.tsv"

要验证新文件看起来是否一致,请查找双\t(表示空数据,即导入时为NA

# double-\t indicates an empty field
lapply(newfilenames,readLines,n = 2)
# [[1]]
# [1] "mpg\tcyl\tdisp\thp" "21\t6\t160\t"      
# [[2]]
# [1] "mpg\tcyl\tdisp\thp" "22.8\t4\t\t93"     
# [[3]]
# [1] "mpg\tcyl\tdisp\thp" "18.7\t\t360\t175"  

现在我们有了这个,让我们转到命令提示符(在Windows,git-bash或仅在Windows的bash上,如果需要的话)。我们需要bashtailgrep中的一个。目的是希望从这些augm_mt文件之一中获得列标题,而从其他文件中都没有。

如果我们天真地连接文件,我们会看到标题行在数据中间重复出现,并且使用R,这意味着每一列都是character,可能不是您想要的:

$ cat augm_mt1.tsv augm_mt2.tsv
mpg     cyl     disp    hp
21      6       160
21      6       160
22.8    4       108
mpg     cyl     disp    hp
22.8    4               93
21.4    6               110

根据您拥有的工具以及对数据内容的信任程度,可以选择三个选项来避免这种情况。 (我建议您使用数字1,tail,因为它含糊不清。)

  1. 如果您有tail,则可以为每个文件“从第2行开始”(跳过第1行):

    $ cat augm_mt2.tsv
    mpg     cyl     disp    hp
    22.8    4               93
    21.4    6               110
    
    $ tail -n +2 augm_mt2.tsv
    22.8    4               93
    21.4    6               110
    

    如果您在多个文件上运行此命令,它倾向于在文件名前加上每组尾行(尝试一下),我们将通过添加-q选项来抑制使用连续行。

  2. 如果您知道一个或多个列名称在实际内容中从未出现过 ,则可以执行以下操作之一:

    $ grep -v mpg augm_mt2.tsv
    22.8    4               93
    21.4    6               110
    
    $ grep -v 'mpg.*cyl.*disp' augm_mt3.tsv
    18.7            360     175
    18.1            225     105
    14.3            360     245
    24.4            146.7   62
    22.8            140.8   95
    19.2            167.6   123
    
  3. 更复杂,但比数字2中的“手写正则表达式”更安全。

    $ HDR=$(head -n 1 augm_mt2.tsv)
    $ grep -F "$HDR" -v augm_mt2.tsv
    22.8    4               93
    21.4    6               110
    

    -F的意思是“固定字符串”,因此不尝试进行正则表达式匹配。这是最安全的,因为诸如列名中的句点之类的内容可能会带来潜在的风险。远程但非零。)

无论选择哪种方式,这都是将这三个文件合并为一个大文件以读回R的方式:

$ { head -n 1 augm_mt1.tsv ; tail -q -n +2 augm_*.tsv ; } > alldata_mt.tsv

head -n 1仅输出标题行,不输出数据,因此在下一条命令中更容易执行augm_*.tsv。 (否则,我们需要找到一种方法来做所有事,但要先做。)

现在我们可以通过一个命令将其读回R:

fread("alldata_mt.tsv")
#      mpg cyl  disp  hp
#  1: 21.0   6 160.0  NA
#  2: 21.0   6 160.0  NA
#  3: 22.8   4 108.0  NA
#  4: 22.8   4    NA  93
#  5: 21.4   6    NA 110
#  6: 18.7  NA 360.0 175
#  7: 18.1  NA 225.0 105
#  8: 14.3  NA 360.0 245
#  9: 24.4  NA 146.7  62
# 10: 22.8  NA 140.8  95
# 11: 19.2  NA 167.6 123

并通过此微数据进行验证:

all.equal(fread("alldata_mt.tsv"),combined)
# [1] TRUE

替代,它使中间文件没有列标题,因此我们不必在周围跳舞:

for (fn in filenames) {
  dat <- fread(fn)
  dat <- row1s[dat,sep = "\t",col.names = FALSE)
}

然后使用bash:

$ cat augm_*tsv > alldata2_mt.tsv

然后再次进入R,

fread("alldata2_mt.tsv",header = FALSE)
#       V1 V2    V3  V4
#  1: 21.0  6 160.0  NA
#  2: 21.0  6 160.0  NA
#  3: 22.8  4 108.0  NA
#  4: 22.8  4    NA  93
#  5: 21.4  6    NA 110
#  6: 18.7 NA 360.0 175
#  7: 18.1 NA 225.0 105
#  8: 14.3 NA 360.0 245
#  9: 24.4 NA 146.7  62
# 10: 22.8 NA 140.8  95
# 11: 19.2 NA 167.6 123

...,您将必须知道名称才能重新分配它们。这种方法似乎可以减少工作量,但是确实确实有可能不经意地更改列名的顺序。上面第一个在所有文件中保留列名的方法可以避免潜在的错误操作。

,

这是一种R data.table方法。在概念上与@ r2evans答案类似,但仅使用R,我们可以从将所有文件串联到单个csv中,并填充所有列来开始:

首先读取所有文件的列名,然后创建唯一名称的向量

library(data.table)
fnames = list.files(pattern = 'dt.+csv')
fcols = lapply(fnames,fread,nrows=0)
fcols = sapply(fcols,names)
fcols = unique(as.vector(fcols))

现在将每个文件的数据添加到单个csv中,并将缺少的值替换为NA。注意,可能不需要remove(fdt); gc()行,因为fdt被重新分配并且R 应该为您处理此操作。我添加它们只是为了确保可以正常释放所有不再使用的内存。

for (f in fnames) {
  fdt = fread(f)
  fdt[,(setdiff(fcols,names(fdt))) := NA]
  fwrite(fdt[,.SD,.SDcols = fcols],'all.csv',append = T)
  remove(fdt)
  gc()
}

然后我们只读取一个文件

fdt = fread('all.csv')

一些虚拟可复制数据文件

set.seed(1)
dt1 = data.table()[,(sample(letters[1:5],3)) := sample(10)]
dt2 = data.table()[,3)) := sample(10)]
dt3 = data.table()[,3)) := sample(10)]
dt4 = data.table()[,3)) := sample(10)]
fwrite(dt1,'dt1.csv')
fwrite(dt2,'dt2.csv')
fwrite(dt3,'dt3.csv')
fwrite(dt4,'dt4.csv')
remove(dt1,dt2,dt3,dt4)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-