我有两个大型制表符分隔文件(>10GB),我知道当它们排序时,它们的内容是相同的。
但是,我对行的顺序和交换的行的索引感兴趣,当它们共享相同的“键”时(此处的键定义为基于Source
和Location
列分组的行)。
换句话说,只有当这两个文件之间的行来自同一组时(即,当它们共享相同的源和位置时),才应相互比较。
例如,在下面的示例中,第 4、5、6 行file1.tsv
应与来自的第 4、5、6 行进行比较file2.tsv
注意:文件是普通的 TSV。仅在此处添加额外的空格以使列居中和右对齐以获得更好的可见性。这些空格不是原始文件的一部分
文件1.tsv
Identifier Position Source Location
AY1:2301 87 ch1 14
BC1U:4010 105 ch1 14
AC44:1230 90 ch1 15
AJC:93410 83 ch1 16
ABYY:0001 101 ch1 16
ABC:01 42 ch1 16
HH:A9CX 413 ch1 17
LK:9310 2 ch1 17
JFNE:3410 132 ch1 18
MKASDL:11 14 ch1 18
MKDFA:9401 18 ch1 18
MKASDL1:011 184 ch2 50
LKOC:AMC02 18 ch2 50
POI:1100 900 ch2 53
MCJE:09HA 11 ch2 53
ABYCI:1123 15 ch2 53
MNKA:410 1 ch2 53
文件2.tsv
Identifier Position Source Location
AY1:2301 87 ch1 14
BC1U:4010 105 ch1 14
AC44:1230 90 ch1 15
ABC:01 42 ch1 16
ABYY:0001 101 ch1 16
AJC:93410 83 ch1 16
HH:A9CX 413 ch1 17
LK:9310 2 ch1 17
MKASDL:11 14 ch1 18
JFNE:3410 132 ch1 18
MKDFA:9401 18 ch1 18
MKASDL1:011 184 ch2 50
LKOC:AMC02 18 ch2 50
MNKA:410 1 ch2 53
POI:1100 900 ch2 53
ABYCI:1123 15 ch2 53
MCJE:09HA 11 ch2 53
我想做一些类似于“diff”的事情,但在“组”级别(其中仅当行共享相同的Source
和时才比较行Location
)
我想提取原来的当行的顺序在同一“源/位置”内“交换”时,“行号”团体“(或键)。
整行的内容应该匹配。
但我不知道该怎么做。我只能想到编写一个 for 循环,当我的原始数据集有数百万行时,这将是极其低效的。
预期结果:
Group_Source:Location df1.index df2.index
ch1:16 4 6
ch1:16 6 4
ch1:18 9 10
ch1:18 10 9
ch2:53 14 15
ch2:53 15 17
ch2:53 17 14
假设:
- 两个数据框具有相同的行数
- 两个数据帧是相同的(仅交换行的顺序,因此如果两者都按源排序,然后按位置排序,然后按位置排序,然后按标识符排序,那么它们将完全相同)
- “交换”的行在所有列的内容方面始终完全匹配
答案1
由于输入文件的大小,这是我可能会使用的罕见情况之一,getline
因此我们一次只在内存中保存几行而不是> 10G:
$ cat tst.awk
BEGIN {
OFS = "\t"
print "Group_Source:Location", "df1.index", "df2.index"
}
NR != FNR { exit }
{ srcLoc = $3 ":" $4 }
srcLoc != prevSrcLoc {
if ( NR > 1 ) {
diff()
}
prevSrcLoc = srcLoc
}
{
file1[$1,$2] = FNR - 1
if ( (getline < ARGV[2]) > 0 ) {
file2[$1,$2] = FNR - 1
}
}
END { diff() }
function diff( idPos) {
for ( idPos in file1 ) {
if ( file1[idPos] != file2[idPos] ) {
print prevSrcLoc, file1[idPos], file2[idPos]
}
}
delete file1
delete file2
}
$ awk -f tst.awk file1.tsv file2.tsv
Group_Source:Location df1.index df2.index
ch1:16 6 4
ch1:16 4 6
ch1:18 10 9
ch1:18 9 10
ch2:53 17 14
ch2:53 15 17
ch2:53 14 15
欲了解更多信息getline
,请阅读http://awk.freeshell.org/AllAboutGetline。
即使输入中重复了Identifier
and/or ,上面的代码也会起作用,因为它比较了 2 个文件之间的所有 4 个字段。Position
它确实假设两个文件之间的源值和位置值的顺序相同,如示例输入所示。
答案2
这在awk
.例如:
$ awk '{
if(FNR==1){
next
}
else if(FNR==NR){
a[$1]=FNR-1;
}
else if ( a[$1] != FNR-1 ){
print $3":"$4, FNR-1, a[$1]
}
}' file1.tsv file2.tsv
ch1:16 4 6
ch1:16 6 4
ch1:18 9 10
ch1:18 10 9
ch2:53 14 17
ch2:53 15 14
ch2:53 17 15
解释
if(FNR==1){ next }
:FNR
保存当前正在读取的文件的行号(记录号)。因此,如果这是任一输入文件的第一行,请跳过它,因为我们不想处理标头。else if(FNR==NR){ ... }
:NR
保存当前输入行号,无论正在读取哪个文件。因此,如果FNR
等于NR
,则意味着我们正在读取第一个文件。a[$1]=FNR-1
:因此,如果这是第一个文件,则将第一个字段作为索引(键)存储在关联数组中,其值将是当前文件的行号 (FNR
),但减一,因为我们不想计算标头。else if ( a[$1] != FNR-1 ){
:这else if
与前一个文件相关,因此只有在FNR
不等于 时我们才会输入这个文件NR
,因此只有当我们正在读取第二个文件时。所以,如果我们正在读取第二个文件和该行第一个字段存储在数组中的值a
不等于当前文件的行号减一,那么我们要打印。print $3":"$4, FNR-1, a[$1]
:所以我们打印第三个字段,a:
和第四个字段,然后FNR减一以及存储在a
第一个字段的数组中的值。
最后,要使用填充和标题漂亮地打印它,请使用:
$ awk 'BEGIN{
printf "%-26s%-12s%-12s\n", \
"Group_Source:Location","df1.index","df2.index"
}
{
if(FNR==1){ next }
else if(FNR==NR){ a[$1]=FNR-1 }
else if ( a[$1] != FNR-1){
printf "%-26s%-12s%-12s\n", $3":"$4, FNR-1, a[$1]
}
}' file1.tsv file2.tsv
Group_Source:Location df1.index df2.index
ch1:16 4 6
ch1:16 6 4
ch1:18 9 10
ch1:18 10 9
ch2:53 14 17
ch2:53 15 14
ch2:53 17 15
重要的:这种方法要求您在内存中为第一个文件的每一行(除了标题)保留少量数据。对于大文件来说,这可能是一个问题,尽管在大多数可能执行此类操作的计算机上可能不是这样。如果这是一个问题,我建议艾德的回答这应该会明显更快并且没有任何内存问题。