Faça uma soma com base em colunas específicas

Faça uma soma com base em colunas específicas

Tenho uma enorme pilha de dados – cerca de 30 a 40 milhões de itens de dados. Precisamos processar esses arquivos e enviá-los para outra equipe de interface.

Abaixo está o meu formato de arquivo que recebemos

c1  c2  c3  c4  c5  c6
A   B   C   D   5   s
A   B   C   D   4   s
A   B   E   F   5   s
A   B   E   F   8   S
C   D   E   F   9   S

Preciso imprimir no meu arquivo de saída todas as colunas. Como isso está relacionado ao uso de GPRS, precisamos agrupar porc1 - c4e então, se tudo estiver combinando, precisamos somar oc5e imprima tudo no arquivo de saída.

Abaixo está um exemplo de arquivo de saída.

c1  c2  c3  c4  c5  c6
A   B   C   D   9   s
A   B   E   F   13  s
C   D   E   F   9   s

Também ouvi dizer que esse fluxo de trabalho funciona muito mais rápido em Perl do que em scripts Unix.

Responder1

Outra perlsolução, semelhante à resposta do @terdon, mas com melhor formato de saída:

$ perl -alne '
    (print && next) if $. == 1;   
    $h{"@F[0..3]"}{s} += $F[4];
    $h{"@F[0..3]"}{t}  = $F[5];
    END {
        for (keys %h) {
            printf "%-4s%-4s%-4s%-4s%-4s%-4s",split(" ",$_),$h{$_}{s},$h{$_}{t};                        
            printf "\n";
        }
    }' file
c1  c2  c3  c4  c5  c6
A   B   E   F   13  S   
A   B   C   D   9   s   
C   D   E   F   9   S

Responder2

Quanto à escolha das ferramentas: normalmente, quanto mais especializada for uma ferramenta, mais rápida ela será. Portanto, tubos envolvendo tr, cut, grep, sortetc. tendem a ser mais rápidos do sedque tendem a ser mais rápidos do awkque tendem a ser mais rápidos do que perl, python, ruby. Mas é claro que isso também depende muito da tarefa. Se você leu que Perl é mais rápido, então você leu errado ou a comparação foi com um loop de shell que processa uma linha por vez (isso definitivamente será lento para arquivos com milhões de linhas).

Se sua entrada estiver em um formato em que as linhas a serem mescladas sejam consecutivas, awk é uma boa aposta (não há uma maneira sensata de realizar adições no sed).

awk -v OFS='\t' '                      # use tabs to separate output fields
    NR==1 {print; next}                # keep the first line intact
    function flush () {                # function to print a completed sum
        if (key != "") print previous, sum, more;
        sum=0
    }
    {key = $1 OFS $2 OFS $3 OFS $4}    # break out the comparison key
    key!=previous {flush()}            # if the comparison key has changed, print the accumulated sum
    {previous=key; sum+=$5; more=$6}   # save the current line
    END {flush()}                      # print the last 
'

Se as linhas não forem consecutivas, você poderá torná-las ordenando. As implementações típicas sortsão altamente otimizadas e mais rápidas do que a manipulação de estruturas de dados em linguagens de alto nível.

sort | awk …

Isso pressupõe que seus delimitadores de coluna sejam consistentes, por exemplo, sempre uma tabulação. Se não estiverem, pré-processe a entrada para torná-los assim ou use-os sort -k1,1 -k2,2 -k3,3 -k4,4para comparar esses campos específicos sem levar em consideração os delimitadores.

Responder3

Isso pode ajudar você a começar:

perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file

Não imprime a última coluna, pois você não especificou o que fazer com ela. Além disso, ele não manipula a linha do cabeçalho corretamente, mas deve ser fácil de corrigir.

Responder4

Se bem entendi, você quer algo assim:

$ perl -lane 'if($.>1){$k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5]}
              else{print "@F"}
              END{
                foreach (keys(%k)){ print "$_ $k{$_}{sum} $k{$_}{last}"}
              }' file
c1 c2 c3 c4 c5 c6
C D E F 9 S
A B E F 13 S
A B C D 9 s

Isso não manterá suas colunas alinhadas. Não sei se isso é um problema para você. No entanto, ele lidará com o cabeçalho corretamente e deverá produzir a saída necessária.

Explicação

  • perl -lane: -lremove novas linhas do final de cada string e as adiciona a cada printinstrução. O adivide cada linha de entrada em campos em espaços em branco e salva os campos no array @F. O nsignificadoleia o arquivo de entrada linha por linha e aplique o script fornecido por-e.

Aqui está a mesma linha em formato de script comentado:

#!/usr/bin/env perl

## This is the equivalent of perl -ne
## in the one-liner. It iterates through
## the input file.
while (<>) {
    
    ## This is what the -a flag does
    my @F=split(/\s+/);
    ## $. is the current line number.
    ## This simply tests whether we are on the
    ## first line or not.
    if ($.>1) {
    ## @F[0..3] is an array slice. It holds fields 1 through 4.
    ## The slice is used as a key for the hash %k and the 5th
    ## field is summed to $k{slice}{sum} while the last column is 
    ## saved as $k{slice}{last}.
    $k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5];
    }

    ## If this is the first line, print the fields.
    ## I am using print "@F" instead of a simple print 
    ## so that all lines are formatted in the same way.
    else {
    print "@F\n";
    }
}

## This is the same as the END{} block
## in the one liner. It will be run after
## the whole file has been read.

## For each of the keys of the hash %k
foreach (keys(%k)){ 
    ## Print the key ($_, a special variable in Perl),
    ## the value of $k{$key}{sum} (the summed values),
    ## and the last column.
    print "$_ $k{$_}{sum} $k{$_}{last}\n"
}


    

informação relacionada