我有几个巨大的制表符分隔文件@~2.1 TB每个,~8.5 K行,~39.3M列。第一列是可变长度的所有字符串(ID),其余是超过零的三个位置的非负固定精度小数(即,ID后面的每个列长度为5个字符0.000)。
在具有256GB RAM的Linux机器中转置文件的最有效方法是什么?最终,在转置后,我想将文件分块为500K行,以便我可以开始处理它。硬盘没问题,有70TB的可用空间。
这是我能想到的(每个文件大约需要2.5天)。问题是每个文件的行数和列数都略有不同。我不想在每次运行时修改脚本。或者,我可以制作一个专门的C程序,但我不愿意这样做。
#!/bin/bash
i=$1
mkdir temp-$i
cd temp-$i
echo "Splitting $i"
split -dl 1 -a 4 ../$i
echo "Transposing all lines"
for a in x???? ; do
cat $a | sed 's/\t/\n/g' > $a.txt
mv $a.txt $a
done
echo "Joining all columns"
# Here's where it gets really ugly:
paste x0000 x0001 ... x0999 > a.txt
paste x1000 x1001 ... x1999 > b.txt
paste x2000 x2001 ... x2999 > c.txt
paste x3000 x3001 ... x3999 > d.txt
paste x4000 x4001 ... x4999 > e.txt
paste x5000 x5001 ... x5999 > f.txt
paste x6000 x6001 ... x6999 > g.txt
paste x7000 x7001 ... x7999 > h.txt
paste x8000 x8001 ... x8499 > i.txt
paste ../colinfo-$i.txt a.txt b.txt c.txt d.txt e.txt f.txt g.txt h.txt i.txt > ../tr-$i.txt
cd ../
rm -Rf temp-$i
答案 0 :(得分:2)
粘贴操作会影响您的性能。如何创建尽可能多的文件,然后使用单个传递输入文件,将数据写入列文件。 I. e。:
输入文件:
ID data1 data2 data3 .... data5000
94239 0.001 0.002 0.003 .... 5.000
43244 0.011 0.012 0.013 .... 5.010
输出文件:
COL0:
ID 94239 43244
COL1:
data1 0.001 0.011
......这里有另外4999个文件......
col5000:
data5000 5.000 5.010
可以使用此perl程序完成拆分列:
#!perl -n
use strict;
use warnings;
use File::Path 'make_path';
$INPUT_RECORD_SEPARATOR = "\t";
my $colno = 0;
my $maxcol = 0;
while(my $col = <STDIN>) {
$colno = 0 if $col =~ s/\n//;
$colno++;
my $path = join '/', $colno =~ /(\d{3})/g;
if($colno > $maxcol) {
make_path $path;
$maxcol = $colno;
}
open my $OUT, '>>', "$path/col.tsv";
print $OUT "$col\t";
close $OUT;
}
(未测试!)
然后在最后将这些文件连接在一起:
cat col0 col1 ... col5000 > newfile.tsv
(可能需要xargs
。)
答案 1 :(得分:1)
https://gitlab.com/ole.tange/tangetools/raw/master/transpose/transpose
cat table.csv | transpose -d , > transposed.csv
transpose -d '\t' table.tsv > transposed.tsv
这是一个通用工具,您可以在其中提供分隔符和要使用的缓冲区大小。
它的工作原理是将table.csv切成10M块(可以用-b
调整,并且应该是FreeMem / NumberOfCores / 10的顺序)。然后将它们并行转换(每个CPU内核一个)。最后,将转置的块粘贴在一起。
它假设每一行都是一个完整的CSV记录(即引用\ n将不起作用)。
速度:3 GB CSV文件1000000x300表格的2.5分钟。
速度:3 GB CSV文件300x1000000表5分钟。
答案 2 :(得分:1)
您的文件大小非常规则,因此可以寻找数据的准确位置,并在一次性IO读/写操作中获得转置结果。
它只需要计算第一行的长度,其他行都是5字节的数字。
Input File example:
ID data1 data2 data3 data4 data5 data6 data7 ...
78028 0.185 0.146 0.910 0.458 0.223 0.853 0.215 ...
76877 0.049 0.486 0.313 0.777 0.599 0.197 0.676 ...
81636 0.055 0.640 0.081 0.477 0.713 0.866 0.308 ...
数据'0.049'(目标行= 2,col = 3)的文件偏移量为:length(标题行)+ length(eachdataline)*(col-2)+ row *(5 + 1)
以下python代码适用于我的测试数据
#encoding=utf-8
import sys
from sys import argv
import os
import time
start = time.time()
def elapsed():
return time.time() - start
def transpose(filepath):
# calculate offset for header line
header_item_offsets = []
org_rowcnt, org_colcnt = 0, 0
with open(filepath) as infile:
for line in infile:
if org_rowcnt==0:
headeritems = line[:-1].split('\t') # remove \n, then split by \t
org_colcnt = len(headeritems)
offset = 0
offset_headerline = len(line) # + 1 #windows end with \r\n
offset_dataline = org_colcnt*6 # +1 # windows end with \r\n
for item in headeritems:
header_item_offsets.append(offset)
offset += len(item)+1
# append one more offset for the last item
header_item_offsets.append(offset)
org_rowcnt += 1
with open(filepath,'rb') as infile, open( filepath+'_transed.txt','w') as outfile:
# transpose
for row in xrange(org_colcnt):
line = []
for col in xrange(org_rowcnt):
if col==0:
# data from header line
offset = header_item_offsets[row]
readsize = header_item_offsets[row+1] - header_item_offsets[row] - 1
else:
# data from data line offset = header_offset + eachline_offset*(col-1) + row*6
offset = offset_headerline + offset_dataline*(col-1) + row*6
readsize = 5
infile.seek(offset)
line.append( infile.read(readsize) )
outfile.write( '\t'.join(line) + '\n' )
if row%10000==0:
print "Generating row %d" % row
if __name__=="__main__":
script, filepath = argv
transpose(filepath)
print "Running Time:\t", elapsed()
答案 3 :(得分:0)
你正在写每个字节4次:split,sed,paste和final。
如果我们可以避免多次写入字节,我们将节省时间。
这个脚本一次完成拆分和sed。
粘贴和决赛一气呵成。
因此,您只需将数据写入临时空间一次。
最后它使用&lt;()bash结构:
paste <(paste t{1..999}) <(paste t{1000..2000})
这将寻求很多,所以如果你可以改变磁盘的预读,那么这将是一个非常好的主意。 (blockdev --setra和--setfra)
脚本:
#!/usr/bin/perl
use English;
while(<>) {
s/,/\n/g;
open(OUT,">","t$NR") or die;
print OUT $_;
close OUT;
}
my $nfiles = $NR;
@cmd = ("paste ");
my $last = 0;
for my $n (1..$nfiles) {
if(not $n % 1000) {
push @cmd, "<(paste t{".($last+1)."..".$n."})";
$last = $n;
}
}
if(not $last == $nfiles) {
push @cmd, "<(paste t{".($last+1)."..".$nfiles."})";
}
system "bash -c '@cmd'";
致电:
cat table | mysplit.pl | split -C 500000; rm t*
答案 4 :(得分:0)
如果到目前为止没有一个解决方案足够快,我相信这将是。
我们知道每一行都是39.3M *(5 + 1)+一点。因此,找到每一行的确切起始位置相对容易。这将花费大约8.5k寻求+读取。
鉴于8.5k起始位置我们做:
for s (startpositions) {
push r, record.init(s)
}
nrows = len(startpositions)
while (not finished) {
for row (1..nrows-1) {
print r[row].get(),"\t"
}
print r[nrows].get(),"\n"
}
class record:
init(position) {
pos = position
}
get() {
if values.empty and not end_of_line {
seek pos
buffer += read(1MB)
pos += 1MB
if buffer =~ s/\n.*// {
end_of_line = true;
}
values = split /\t/, buffer
buffer = pop values
}
return pop values
}
这样我们每1 MB只进行1次搜索,我们只读取一次文件,没有临时文件。
1 MB /记录应该总共缓存8.5 GB,因此在256 GB计算机上增加1 MB到20 MB应该是安全的,每20 MB数据提供1次搜索。
答案 5 :(得分:0)
如果您允许像mzedeler建议的临时文件,并希望保持这对我有用:
inFile="bigTable.csv"
sep=","
outFile="bigTable.transposed.csv"
TMPWD=`mktemp -d --tmpdir=$TMPDIR transpose.XXXXXXXX`
prefix=$TMPWD/col
while read line; do
cat \
| mawk -vFS="$sep" -f <(
#generate AWK-code and print first entries to the temporary files
echo $line \
| mawk -vFS="$sep" -vprefix=$prefix '
{
printf("{")
endCmd="END {"
for(i=1;i<=NF;i+=1){
file=sprintf(prefix"%05d",i)
printf("%s",$i) > file
printf("printf(\"%s%%s\",$%s) >> \"%s\"; ",FS,i,file)
endCmd= endCmd""sprintf("printf(\"\\n\") >> \"%s\"; ",file)
}
endCmd=endCmd"}"
printf("} ")
print endCmd
}
'
)
done < <( cat $inFile )
cat ${prefix}* > $outFile
rm -rf $TMPWD
不可否认,我只有12x25M,但有了这个,它在大约2分钟内完成。我试图通过包含代码生成步骤来避免AWK中的for循环。内部代码位于以下表单:
{printf(",%s",$1) >> "tmp/col00001"; ... printf(",%s",$N) >> "tmp/col0000N"; }
END {printf("\n") >> "tmp/col00001"; ... printf("\n") >> "tmp/col0000N" }