Como acumular alguns valores em uma coluna com base nas informações de outra coluna do mesmo arquivo de dados?

1

Eu tenho um arquivo de dados parecido com:

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

O que eu quero fazer é acumular valores na terceira coluna em que pertencem ao mesmo grupo da primeira coluna. por exemplo, os primeiros 4 valores pertencem ao grupo c1, enquanto os 4 valores seguintes pertencem ao grupo c2 e .... Então, o novo resultado deve ser parecido com:

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

Alguma sugestão por favor? (por favor considere que os dados reais são enormes.

    
por zara 07.10.2016 / 22:37

4 respostas

1

perl solution, assume que o arquivo de entrada é classificado por c1, c2, etc. Portanto, salvar em hash / array não é necessário

$ 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 tira linhas novas da entrada e adiciona durante a impressão, divide a linha de entrada nos espaços e salva em @F array
  • $F[0] =~ s/\..*// exclui todos os caracteres de . para o primeiro campo
  • if($F[0] ne $p && $. > 1) se o número da linha de entrada não for de primeira linha e o primeiro campo não for o mesmo que o anterior
    • nome do campo de impressão e soma acumulada, variável de soma clara
  • No final, imprima novamente para contabilizar a última entrada


Outra maneira seria não dividir a linha de entrada e usar o regex para extrair a chave e o valor necessários:

$ 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
    
por 08.10.2016 / 07:58
3

Com datamash do GNU (após substituir o período por espaço em branco usando sed )

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

Com o 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) obtém a parte antes do primeiro . do primeiro campo, nós o armazenamos como variável grp

  • arr[grp]+=$3 gera matriz com chave como grp e valores são acumulados da terceira coluna de cada linha

  • O fragmento no segmento END itera nos elementos da matriz e imprime valores-chave no formato desejado

Para entrada consistente, com POSIX awk :

awk '{sub("\..*", "", $1); arr[$1]+=$3} END {for (i in arr) print i, arr[i]}'
  • sub("\..*", "", $1) modifica o primeiro campo para truncar a parte depois de . e a matriz arr é criada com chaves como o primeiro campo (modificado)

Exemplo:

% 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
    
por 07.10.2016 / 22:44
0

esta é a minha solução, experimente e deixe-me saber se funciona.

#!/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

E o resultado deve aparecer em finalResult.txt. Você pode copiá-lo em um script bash e testá-lo.

    
por 07.10.2016 / 23:58