如何根据同一数据文件中另一列中的信息累积列中的一些值?

如何根据同一数据文件中另一列中的信息累积列中的一些值?

我有一个数据文件,如下所示:

c1.11   SNP1    -6.73098    0.764833    Chr1:1
c1.21   SNP2    -4.871  0.00393002  Chr1:101
c1.12   SNP3    -0.766822   0.0891227   Chr1:201
c1.22   SNP4    11.7499 0.141861    Chr1:301
c2.11   SNP5    8.38008 0.741379    Chr1:401
c2.21   SNP6    -0.86974    0.00824037  Chr1:501
c2.12   SNP7    -0.181898   0.00494422  Chr1:601
c2.22   SNP8    -7.32856    0.245436    Chr1:701
c3.11   SNP9    -12.0418    0.369929    Chr1:801
c3.12   SNP10   11.2833 0.357378    Chr1:901
c3.22   SNP11   -0.0308993  0.270918    Chr1:1001
c4.121  SNP12   1.51586 0.0770791   Chr1:1101
c4.122  SNP13   0.118888    0.0742901   Chr1:1201

我想要做的是累积第三列中的值,其中与第一列属于同一组。例如,前 4 个值属于组 c1,而接下来的 4 个值属于组 c2 和 ...。所以新的输出应该是这样的:

 Output :
c1 -0.61778
c2  0.00845
c3 -0.7875
c4 1.6347

请问有什么建议吗? (请考虑真实数据是巨大的。

答案1

GNU 数据混合(使用 替换句点为空格后sed

sed 's/\./ /' data | datamash -W groupby 1 sum 4
c1      -0.618902
c2      -0.000118
c3      -0.7893993
c4      1.634748

答案2

perl解决方案,假设输入文件按 c1、c2 等排序。因此,不需要保存在哈希/数组中

$ perl -lane '
$F[0] =~ s/\..*//;
if($F[0] ne $p && $. > 1)
{
    print "$p $sum";
    $sum = 0;
}
$sum += $F[2];
$p = $F[0];
END { print "$p $sum" }' ip.txt
c1 -0.618902
c2 -0.000118000000001395
c3 -0.7893993
c4 1.634748
  • -la从输入中去除换行符并在打印时添加,在空格上分割输入行并保存到@F数组
  • $F[0] =~ s/\..*//.删除第一个字段中的所有字符
  • if($F[0] ne $p && $. > 1)如果输入行号不是第一行并且第一个字段与前一个字段不同
    • 打印字段名和累计和,清除sum变量
  • 最后,再次打印以说明最后的条目


另一种方法是不拆分输入行并使用正则表达式提取所需的键和值:

$ perl -lne '
($k, $v) = /^([^.]+)(?:\S+\s+){2}(\S+)/;
if($k ne $p && $. > 1)
{
    print "$p $sum";
    $sum = 0;
}
$sum += $v;
$p = $k;
END { print "$p $sum" }' ip.txt
c1 -0.618902
c2 -0.000118000000001395
c3 -0.7893993
c4 1.634748

答案3

使用 GNU awk

awk '{grp = gensub("^([^.]+).*", "\\1", 1, $1); \
              arr[grp]+=$3} END {for (i in arr) print i, arr[i]}' file.txt
  • gensub("^([^.]+).*", "\\1", 1, $1)从第一个字段获取第一个之前的部分.,我们将其存储为变量grp

  • arr[grp]+=$3生成键为 的数组grp,值从每行的第三列开始累加

  • 段中的块END将迭代数组元素,并以所需的格式打印键值

为了获得一致的输入,使用 POSIX awk

awk '{sub("\\..*", "", $1); arr[$1]+=$3} END {for (i in arr) print i, arr[i]}'
  • sub("\\..*", "", $1)修改第一个字段以截断后面的部分.,并arr使用键作为(修改后的)第一个字段创建数组

例子:

% cat file.txt
c1.11   SNP1    -6.73098    0.764833    Chr1:1
c1.21   SNP2    -4.871  0.00393002  Chr1:101
c1.12   SNP3    -0.766822   0.0891227   Chr1:201
c1.22   SNP4    11.7499 0.141861    Chr1:301
c2.11   SNP5    8.38008 0.741379    Chr1:401
c2.21   SNP6    -0.86974    0.00824037  Chr1:501
c2.12   SNP7    -0.181898   0.00494422  Chr1:601
c2.22   SNP8    -7.32856    0.245436    Chr1:701
c3.11   SNP9    -12.0418    0.369929    Chr1:801
c3.12   SNP10   11.2833 0.357378    Chr1:901
c3.22   SNP11   -0.0308993  0.270918    Chr1:1001
c4.121  SNP12   1.51586 0.0770791   Chr1:1101
c4.122  SNP13   0.118888    0.0742901   Chr1:1201

% awk '{grp = gensub("^([^.]+).*", "\\1", 1, $1); arr[grp]+=$3} END {for (i in arr) print i, arr[i]}' file.txt
c1 -0.618902
c2 -0.000118
c3 -0.789399
c4 1.63475

% awk '{sub("\\..*", "", $1); arr[$1]+=$3} END {for (i in arr) print i, arr[i]}' file.txt
c1 -0.618902
c2 -0.000118
c3 -0.789399
c4 1.63475

答案4

这是我的解决方案,尝试一下,让我知道它是否有效。

#!/bin/bash


awk '
BEGIN{group="c1"
sum=0}
{
    if(substr($1,1,2)==group) 
    {sum+=$3
    print group " " sum} 
    else {
        group=substr($1,1,2)
        sum=$3
        print group " " sum}
    }'  file.txt > tmp.txt





awk 'BEGIN{group="c1"}
     $1!=group {print group " " sum
     group=$1} {sum=$2}
     END{print $1 " " $2}'  tmp.txt >finalResult.txt

rm tmp.txt

结果应该出现在 FinalResult.txt 中。您可以将其复制到 bash 脚本中并进行测试。

相关内容