将具有不同列的大数据文件合并为一个大文件

问题描述 投票:0回答:2

我有N个制表符分隔的文件。每个文件都有一个标题行,其中包含各列的名称。有些列对所有文件都是公用的,但有些列是唯一的。

我想将所有文件组合成一个包含所有相关标头的大文件。

示例:

> cat file1.dat
a b c
5 7 2
3 9 1

> cat file2.dat
a b e f
2 9 8 3
2 8 3 3
1 0 3 2

> cat file3.dat
a c d g
1 1 5 2

> merge file*.dat
a b c d e f g
5 7 2 - - - -
3 9 1 - - - -
2 9 - - 8 3 -
2 8 - - 3 3 -
1 0 - - 3 2 -
1 - 1 5 - - 2

-可以用任何东西代替,例如NA

Caveat:文件太大,以至于我无法将所有文件同时加载到内存中。

我在R中有一个解决方案,使用

write.table(do.call(plyr:::rbind.fill, 
            Map(function(filename) 
                    read.table(filename, header=1, check.names=0), 
                filename=list.files('.'))), 
    'merged.dat', quote=FALSE, sep='\t', row.names=FALSE)

但是当数据太大时,这会因内存错误而失败。

实现此目标的最佳方法是什么?

我认为最好的方法是首先循环浏览所有文件以收集列名,然后循环浏览文件以将它们设置为正确的格式,然后在遇到它们时将它们写入光盘。但是,也许已经有一些执行此操作的代码了吗?

bash dataframe bigdata multiple-columns cat
2个回答
0
投票

这里是我(OP)到目前为止提出的解决方案。不幸的是,它和时间一样慢。我希望有人能够提出更好的建议。

R代码:

library(parallel)
library(parallelMap)
library(filelock)

# specify the directory containing the files we want to merge
# as well as the output file 
args <- commandArgs(TRUE)
directory <- if (length(args)>0) args[1] else 'filenames'
output_fname <- paste0(directory, '.dat')

# list the .dat files we want to merge 
filenames <- file.path(directory, list.files(directory))
filenames <- filenames[grep('.dat', filenames)]

# a function to read the column names 
get_col_names <- function(filename) 
    colnames(read.table(filename, header=T, check.names=0, nrow=1))

# grab all the headers of all the files and merge them 
col_names <- get_col_names(filenames[1])
for (simulation in filenames) {
    col_names <- union(col_names, get_col_names(simulation))
}

# put those column names into a blank data frame 
name_DF <- data.frame(matrix(ncol = length(col_names), nrow = 0))
colnames(name_DF) <- col_names

# read in the first data file and merge with the blank data frame 
# it will have NAs in any columns it didn't have before 
DF <- read.table(filenames[1], header=1, check.names=0)
DF <- plyr:::rbind.fill(name_DF, DF)

# write that data frame to the disk 
write.table(DF, output_fname, quote=F, col.names=TRUE,
    row.names=F, sep='\t')

# now repeat for every file we have, locking the file when we want to save 
parallelStartMulticore(max(1, min(detectCores(), 62)))
success <- parallelMap(function(ii) {
    print(filenames[ii])
    DF <- read.table(filenames[ii], header=1, check.names=0)
    DF <- plyr:::rbind.fill(name_DF, DF)
    lck <- lock(output_fname, timeout=Inf)
    write.table(DF, output_fname, quote=F, append=TRUE, col.names=F,
        row.names=F, sep='\t')
    unlock(lck)
}, ii=2:length(filenames))

# and we're done 
print(all(unlist(success)))

0
投票

这可以通过GNU awk脚本轻松完成。这个想法是先读取每个文件的标题,然后再次处理文件。为此,我们需要GNU awk,因为它具有nextfile函数,该函数使我们无法完全读取GB的数据。我们还将使用awk将文件重新添加到参数列表:

BEGIN { s="-" }                # define symbol
BEGIN { f=ARGC-1 }             # get total number of files
f { for(i=1;i<=NF;++i) h[$i]   # read headers in associative array h[key]
    ARGV[ARGC++] = FILENAME    # add file at end of argument list
    if (--f == 0) n=asorti(h)  # sort header into h[idx] = key
    for(i=1;i<=n;++i) printf "%s" (i==n?ORS:OFS), h[i]       # print header
    nextfile                   # end of processing headers
}           
# Start of processing the files
(FNR==1) { delete a; for(i=1;i<=NF;++i) a[$i]=i; next } # read header
{ for(i=1;i<=n;++i) printf "%s" (i==n?ORS:OFS) , (h[i] in a ? $(a[h[i]]) : s) }

如果将以上内容存储在文件merge.awk中,则可以使用命令:

awk -f merge.awk f1 f2 f3 f4 ... fx
© www.soinside.com 2019 - 2024. All rights reserved.