如何向制表符分隔文件的单列中的重复项添加不同的字符串以使它们唯一

如何向制表符分隔文件的单列中的重复项添加不同的字符串以使它们唯一

我有一个 4 列制表符分隔的文件,最后一列有时有重复项。这是该文件的摘录:

chr7    116038644       116039744       GeneA
chr7    116030947       116032047       GeneA
chr7    115846040       115847140       GeneA
chr7    115824610       115825710       GeneA
chr7    115801509       115802609       GeneA
chr7    115994986       115996086       GeneA
chrX    143933024       143934124       GeneB
chrX    143933119       143934219       GeneB
chrY    143933129       143933229       GeneC

对于该列中的每组重复值,我想将它们转换为类似的内容(而不真正触及该列中的非重复值):

chr7    116038644       116039744       GeneA-1
chr7    116030947       116032047       GeneA-2
chr7    115846040       115847140       GeneA-3
chr7    115824610       115825710       GeneA-4
chr7    115801509       115802609       GeneA-5
chr7    115994986       115996086       GeneA-6
chrX    143933024       143934124       GeneB-1
chrX    143933119       143934219       GeneB-2
chrY    143933129       143933229       GeneC

我如何使用awkor sedor Bashfor循环来做到这一点?

答案1

尝试这个

awk -F'\t' -v OFS='\t' '{$4=$4 "-" (++count[$4])}1' file.tsv

这会将第四个字段的每个值的出现存储在计数器数组中count(其中第四个字段的值用作“索引”),并将该计数器的预递增值附加到第四个字段,并用短跑。

上面的“简单”示例有一个缺点:它甚至会为第 4 列中仅在文件中出现一次的值添加消歧编号。为了抑制这种情况,可以使用以下双遍方法(通过将命令分成两行以\提高可读性):

 awk -F'\t' -v OFS='\t' 'NR==FNR{f[$4]++}\
      NR>FNR{if (f[$4]>1) {$4=$4 "-" (++count[$4])}; print}' file.tsv file.tsv

注意要处理的文件已注明两次作为参数,因此将被读取两次。

  • 第一次读取时(由 表示FNR,每个文件行计数器,等于NR,全局行计数器),我们只需计算第 4 列的每个不同值在文件中出现的频率,并将其存储在数组 中f
  • 第二次读取文件时,我们像“简单”方法一样执行实际的文本处理,并将出现计数器附加到第 4 列,但前提是在第一次传递中找到的出现总数大于 1。

这种方法避免了缓冲整个文件,如果文件非常大,这可能是一个优点。处理时间当然会更长,因为文件被读取了两次。

作为一般规则,很少需要使用 shell 循环进行文本处理,因为awk 例如可以以更有效的方式自行执行循环操作。

答案2

假设您的输入文件按第四列分组,如示例所示:

$ cat tst.awk
$NF != prev {
    prt()
    cnt = 0
    prev = $NF
}
{ rec[++cnt] = $0 }
END { prt() }

function prt() {
    for (i=1; i<=cnt; i++) {
        print rec[i] (cnt > 1 ? "-"i : "")
    }
}

$ awk -f tst.awk file
chr7    116038644       116039744       GeneA-1
chr7    116030947       116032047       GeneA-2
chr7    115846040       115847140       GeneA-3
chr7    115824610       115825710       GeneA-4
chr7    115801509       115802609       GeneA-5
chr7    115994986       115996086       GeneA-6
chrX    143933024       143934124       GeneB-1
chrX    143933119       143934219       GeneB-2
chrY    143933129       143933229       GeneC

答案3

这仅附加“-数字如果其值不唯一,则将其添加到指定的(目标)字段(示例中的第四个字段)。它还处理输入未按目标列排序的情况,并适用于任意数量的输入列。

由于以下 AWK 脚本需要按目标字段对输入进行排序,因此我们使用管道对原始行进行编号,按(现在)第五个字段(第一个是前置数字)对它们进行排序,将后缀附加到非-第五个字段的唯一值,将行恢复到初始排序并删除前置数字:

nl file | sort -b -t '<TAB>' -k5,5 -k1n,1n | awk -F '\t' -v OFS='\t' -v kf=5 '
  function prn () {
    for (i = 1; i <= nfl; i++) {
      if (i == kf)
        printf("%s", prc[i] ( sw || cnt[prc[i]] ? "-"++cnt[prc[i]] : ""))
      else
        printf("%s", prc[i])
      printf("%s", (i == nfl ? ORS : OFS))
    }
  }
  NR > 1 {
    sw = ($kf == prc[kf])
    prn()
  }
  {
    nfl = split($0, prc)
  }
  END {
    if (NR > 0)
      prn()
  } ' | sort -k1n,1n | cut -f 2-

这个 AWK 脚本的要点是打印以前的检查该行的kf第一个字段是否等于当前行的字段或者它的kf第一个字段是否已经出现至少一次。在这两种情况下,kf都会打印第 th 字段以及附加到该字段的次数。

确保进行调整-v kf=5(和-k5,5 sort键)以反映要消除歧义的列的实际位置。

给定此示例(您的示例,其中包含已打乱的行和添加的列)file

chr7    116038644   116039744   GeneA   foo
chrX    143933024   143934124   GeneB   foo
chr7    116030947   116032047   GeneA   foo
chr7    115824610   115825710   GeneA   foo
chrY    143933129   143933229   GeneC   foo
chr7    115994986   115996086   GeneA   foo
chrX    143933119   143934219   GeneB   foo
chr7    115801509   115802609   GeneA   foo
chr7    115846040   115847140   GeneA   foo

输出将是:

chr7    116038644   116039744   GeneA-1 foo
chrX    143933024   143934124   GeneB-1 foo
chr7    116030947   116032047   GeneA-2 foo
chr7    115824610   115825710   GeneA-3 foo
chrY    143933129   143933229   GeneC   foo
chr7    115994986   115996086   GeneA-4 foo
chrX    143933119   143934219   GeneB-2 foo
chr7    115801509   115802609   GeneA-5 foo
chr7    115846040   115847140   GeneA-6 foo

答案4

一个简单的两遍awk命令:

$ awk -F '\t' '
    BEGIN { OFS=FS }
    pass == 1 { count[$4]++; next }
    count[$4] > 1 { $4 = $4 "-" ++number[$4] }; 1' pass=1 file pass=2 file
chr7    116038644       116039744       GeneA-1
chr7    116030947       116032047       GeneA-2
chr7    115846040       115847140       GeneA-3
chr7    115824610       115825710       GeneA-4
chr7    115801509       115802609       GeneA-5
chr7    115994986       115996086       GeneA-6
chrX    143933024       143934124       GeneB-1
chrX    143933119       143934219       GeneB-2
chrY    143933129       143933229       GeneC

这假设输入文件是制表符分隔的。-F '\t'如果情况并非如此,请修改或删除。

第一次遍历该文件时,关联数组 中将count填充每个基因名称在第四列中出现的次数。

在第二次通过文件时,如果基因名称在数据集中多次出现(在第一次通过文件时),则会在基因名称末尾添加破折号和数字。

添加的数字是另一个计数器,也由基因名称作为键控,就像在counter数组中一样。

相关内容