【问题标题】:Bash way to compare specific columns from two different files based on an index list基于索引列表比较来自两个不同文件的特定列的 Bash 方法
【发布时间】:2020-06-21 03:16:39
【问题描述】:

我有两个 1708 行和不同列数的制表符分隔文件。我的目标是比较为所有行存储的值,但只有一些特定列。我有两个列表,其中包含要比较的列号;这里是一个例子:

  • FileA ➝ col_ind_A = [12,20,24,55]
  • FileB ➝ col_ind_B = [14,28,35,79]

在这里,文件 A 的第 12 列应该与文件 B 的第 14 列进行比较,文件 A 的第 20 列与文件 B 的第 28 列进行比较,依此类推。如果文件 A 的值为 0 而文件 B 没有,我想在该位置修改文件 C(文件 A 的副本),然后存储文件 B 的值(不是 0):

# FileA                     #FileB                     #FileC
col11 col12 col13           col13 col14 col15          col11 col12 col13
  A     C     G               A      C     G            A      C     G
  G     0     T               G      T     T            G      T     T 

我已经看到比较列通常使用 awk 完成,但我对 bash 很陌生,我不知道如何在迭代时迭代两个文件的行在 col_ind 列表上并指示我要比较的列位置。欢迎提出任何建议。

如果有任何帮助,我会展示一个 R 代码,它正是这样做的(它太慢了):

for(i in 1:1708){ #rows
  for(j in 1:31946){ #cols 
    if( fileA[i, col_ind_A[j]] == '0'  && fileA[i, col_ind_A[j]] != fileB[ i, col_ind_B[j]]){ 
      fileC[i, col_ind_A[j]] <- fileB[i, col_ind_B[j]] # write value from fileB in file C 
    }
  }
}

任何帮助都会很棒。谢谢!!

【问题讨论】:

  • 您能否分享您输入文件的示例记录。
  • 我会将文件转换为 SQLite 或其他数据库。索引相关列的速度,并要求数据库引擎处理查询。最终将最终输出重新格式化为数据文件。
  • @Armali 你是对的,我刚刚解决了这个问题,谢谢。至于我的记录样本,它们是使用 PLINK 创建的 .ped 文件,它们看起来与示例中的完全相同,但行和列更多。我希望这会有所帮助。

标签: bash awk


【解决方案1】:

执行此操作的 perl 脚本:

#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use feature qw/say/;
use List::Util qw/pairs/;

# Adjust as needed.
my @columns = (12 => 14, 20 => 28, 24 => 35, 55 => 79);

my ($filea_name, $fileb_name) = @ARGV;

@columns = pairs map { $_ - 1 } @columns;
open my $filea, '<', $filea_name;
open my $fileb, '<', $fileb_name;
$, = " "; # Or "\t" or whatever to delimit output columns
while (my $linea = <$filea>) {
    my $lineb = <$fileb> or die "Files have different line counts\n";
    chomp $linea;
    chomp $lineb;
    my @acols = split ' ', $linea;
    my @bcols = split ' ', $lineb;
    for my $p (@columns) {
        if ($acols[$$p[0]] eq "0" && $bcols[$$p[1]] ne "0") {
            $acols[$$p[0]] = $bcols[$$p[1]];
        }
    }
    say @acols;
}

(以 FileA 和 FileB 作为其命令行参数)

【讨论】:

  • 这看起来很不错,是否可以将列也添加为包含 '''12 => 14, 20 => 28, 24 => 35, 55 => 79 的 txt 文件, ...''' 因为对应的数量超过 30,000。谢谢!
【解决方案2】:

首先逐行加入文件,然后只检查您要检查的条件。

# recreate input
cat >file1 <<EOF
col11 col12 col13
A C G
G 0 T
EOF
cat >file2 <<EOF
col13 col14 col15
A C G
G T T
EOF

paste file1 file2 |
awk '{ if ($2 == 0 && $2 != $6) $2=$6; print $1, $2 ,$3}'

outputs:

col11 col12 col13
A C G
G T T

我猜for(i in 1:1708){ #rows 可能你想遍历所有列,假设两个文件中的列数相同:

paste file1 file2 |
awk '{ 
   for (i=1;i<=NF/2;++i) if ($i == 0 && $i != $(i*2)) $i = $(i*2);
   for (i=1;i<=NF/2;++i) printf "%s%s", $i, i==NF/2?ORS:OFS; 
}'

【讨论】:

  • 关于假设两个文件中的列数相同 - 查询者写道:我有两个制表符分隔的文件,共 1708 行,列数不同。
  • 我认为加入文件可能不是最好的主意,因为 fileA 有超过 180000 列,而 fileB 有超过 30000,但无论如何,谢谢,awk 位非常有用:)
【解决方案3】:

既然您要求awk 解决方案,这里有一个简单的解决方案:

awk -v col_ind_A='12 20 24 55' -v col_ind_B='14 28 35 79' '
BEGIN { OFS="\t"
        split(col_ind_A, ciA)
        split(col_ind_B, ciB)
        while (getline <"FileB" > 0 && split($0, B) && getline <"FileA" > 0)
        {
            for (i in ciA) if ($ciA[i] == 0) $ciA[i] = B[ciB[i]]
            print >"FileC"
        }
      }'

但这不会比 R 代码快。 R 代码的优化步骤可能是消除内部循环:

for (i in 1:nrow(FileA))
{
    j = which(FileA[i, col_ind_A] == 0)
    FileC[i, col_ind_A[j]] = FileB[i, col_ind_B[j]]
}

【讨论】:

  • getline &lt;"FileB" &gt; 0 应该是 (getline &lt;"FileB") &gt; 0。 FileA 同上。
  • R 代码运行完美,速度非常快。非常感谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-07
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 2015-09-14
  • 1970-01-01
相关资源
最近更新 更多