提取两个文件之间按顺序交换的行的索引

提取两个文件之间按顺序交换的行的索引

我有两个大型制表符分隔文件(>10GB),我知道当它们排序时,它们的内容是相同的。

但是,我对行的顺序和交换的行的索引感兴趣,当它们共享相同的“键”时(此处的键定义为基于SourceLocation列分组的行)。

换句话说,只有当这两个文件之间的行来自同一组时(即,当它们共享相同的源和位置时),才应相互比较。

例如,在下面的示例中,第 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

即使输入中重复了Identifierand/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          

重要的:这种方法要求您在内存中为第一个文件的每一行(除了标题)保留少量数据。对于大文件来说,这可能是一个问题,尽管在大多数可能执行此类操作的计算机上可能不是这样。如果这是一个问题,我建议艾德的回答这应该会明显更快并且没有任何内存问题。

相关内容