【问题标题】:Replacing values in large table using conversion table使用转换表替换大表中的值
【发布时间】:2012-12-23 11:33:54
【问题描述】:

我正在尝试替换以空格分隔的大型文本文件中的值,但找不到针对此特定问题的合适答案:

假设我有一个文件“OLD_FILE”,其中包含一个标题和大约 200 万行:

COL1 COL2 COL3 COL4 COL5
rs10 7 92221824 C A 
rs1000000 12 125456933 G A 
rs10000010 4 21227772 T C 
rs10000012 4 1347325 G C 
rs10000013 4 36901464 C A 
rs10000017 4 84997149 T C 
rs1000002 3 185118462 T C 
rs10000023 4 95952929 T G 
...

我想使用一个大的(280 万行)转换表将每一行的第一个值替换为相应的值。在这个转换表中,第一列列出了我想要替换的值,第二列列出了相应的新值:

COL1_b36       COL2_b37
rs10    7_92383888
rs1000000       12_126890980
rs10000010      4_21618674
rs10000012      4_1357325
rs10000013      4_37225069
rs10000017      4_84778125
rs1000002       3_183635768
rs10000023      4_95733906
...

所需的输出将是一个文件,其中第一列中的所有值都已根据转换表进行了更改:

COL1 COL2 COL3 COL4 COL5
7_92383888 7 92221824 C A 
12_126890980 12 125456933 G A 
4_21618674 4 21227772 T C 
4_1357325 4 1347325 G C 
4_37225069 4 36901464 C A 
4_84778125 4 84997149 T C 
3_183635768 3 185118462 T C 
4_95733906 4 95952929 T G 
...

附加信息:

  • 性能是个问题(以下命令大约需要一年时间:

    同时阅读 a b;做 sed -i "s/\b$a\b/$b/g" OLD_FILE ;完成

  • 替换前需要完全匹配
  • 并非 OLD_FILE 中的每个值都可以在转换表中找到...
  • ...但是每个可以替换的值都可以在转换表中找到。

非常感谢任何帮助。

【问题讨论】:

  • 对转换表进行排序(如您事先所知)。将其拆分为您认为合理的尽可能多的小文件。然后解析输入文件,并根据第一个(或前两个,或...)字母在适当的拆分转换文件中搜索其转换。
  • 另一种方法:在 perl 中,使用哈希表,以便每个可能转换的字符串(转换文件中的第一列)都是与其转换值对应的键。如果它保存在内存中,它会真的很快。
  • 一种类似的方法是找到一种方法来唯一地“散列”conversion_file 的第一列,以便每个条目都有一个(并且只有一个)值。测试一下:一旦你有了一个散列函数,尝试每个条目并记下它的输出值。验证 ( cat | sort | uniq -c | sort -rn) 它是唯一的。一旦找到合适的哈希函数,您只需要每个哈希值 (hashvalue.txt) 包含一个包含转换字符串的文件。
  • 这些都是非常有用的建议,虽然要开始工作需要更精细一些,这就是我更喜欢 awk-oneliner 的原因。

标签: bash unix awk sed large-data


【解决方案1】:

这是使用awk的一种方式:

awk 'NR==1 { next } FNR==NR { a[$1]=$2; next } $1 in a { $1=a[$1] }1' TABLE OLD_FILE

结果:

COL1 COL2 COL3 COL4 COL5
7_92383888 7 92221824 C A
12_126890980 12 125456933 G A
4_21618674 4 21227772 T C
4_1357325 4 1347325 G C
4_37225069 4 36901464 C A
4_84778125 4 84997149 T C
3_183635768 3 185118462 T C
4_95733906 4 95952929 T G

解释,按出现顺序:

NR==1 { next }            # simply skip processing the first line (header) of
                          # the first file in the arguments list (TABLE)

FNR==NR { ... }           # This is a construct that only returns true for the
                          # first file in the arguments list (TABLE)

a[$1]=$2                  # So when we loop through the TABLE file, we add the
                          # column one to an associative array, and we assign
                          # this key the value of column two

next                      # This simply skips processing the remainder of the
                          # code by forcing awk to read the next line of input

$1 in a { ... }           # Now when awk has finished processing the TABLE file,
                          # it will begin reading the second file in the
                          # arguments list which is OLD_FILE. So this construct
                          # is a condition that returns true literally if column
                          # one exists in the array

$1=a[$1]                  # re-assign column one's value to be the value held
                          # in the array

1                         # The 1 on the end simply enables default printing. It
                          # would be like saying: $1 in a { $1=a[$1]; print $0 }'

【讨论】:

  • 谢谢!这个解决方案被证明是最快的(在普通计算机上只需几分钟)。不幸的是,我的 awk 技能非常基础,所以我不明白脚本中发生了什么。你介意简单解释一下吗?
  • @KJ_:没问题!我在上面的答案中添加了脚本如何工作的简短说明。如果我解释得不够透彻,请告诉我。干杯。
【解决方案2】:

这可能对你有用(GNU sed):

sed -r '1d;s|(\S+)\s*(\S+).*|/^\1\\>/s//\2/;t|' table | sed -f - file

【讨论】:

    【解决方案3】:

    你可以使用join:

    join -o '2.2 1.2 1.3 1.4 1.5' <(tail -n+2 file1 | sort) <(tail -n+2 file2 | sort)
    

    这会删除两个文件的标题,您可以使用head -n1 file1 将其添加回来。

    输出:

    12_126890980 12 125456933 G A
    4_21618674 4 21227772 T C
    4_1357325 4 1347325 G C
    4_37225069 4 36901464 C A
    4_84778125 4 84997149 T C
    3_183635768 3 185118462 T C
    4_95733906 4 95952929 T G
    7_92383888 7 92221824 C A
    

    【讨论】:

      【解决方案4】:

      join 的另一种方式。假设文件在第一列排序:

      head -1 OLD_FILE
      join <(tail -n+2 CONVERSION_TABLE) <(tail -n+2 OLD_FILE) | cut -f 2-6 -d' '
      

      但是对于这种大小的数据,您应该考虑使用数据库引擎。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-07-28
        • 1970-01-01
        • 2015-08-14
        • 1970-01-01
        • 2015-06-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多