Calcule e divida por total com AWK

5

Dado o seguinte arquivo data ...

foo     10
bar     20
oof     50
rab     20

... como eu imprimiria a coluna dois como uma porcentagem do total da coluna dois? Em outras palavras, eu quero ...

foo     10    10%
bar     20    20%
oof     50    50%
rab     20    20%

... com números menos óbvios, é claro. Posso criar um total de execução com bastante facilidade, mas não sei como posso calcular o total antes de imprimir as linhas . Eu estou fazendo isso em um arquivo awk totals.awk ...

#!/usr/bin/awk -f
BEGIN{
        runningtotal=0
}
{
        runningtotal=runningtotal+$2
        print $1 "\t" $2 "\t" runningtotal "\t" $2/runningtotal
}

Então, executando ./totals.awk data yields ...

foo     10      10      1
bar     20      30      0.666667
oof     50      80      0.625
rab     20      100     0.2

Existe uma maneira de fazer o loop duas vezes, uma vez para calcular o total e uma vez para imprimir as linhas? Isso é possível no AWK ou devo usar outros utilitários?

    
por Rip Leeb 15.12.2014 / 18:58

3 respostas

11

Para criar a tabela com uma única chamada para awk :

$ awk 'FNR==NR{s+=$2;next;} {printf "%s\t%s\t%s%%\n",$1,$2,100*$2/s}' data data
foo     10      10%
bar     20      20%
oof     50      50%
rab     20      20%

Como funciona

O arquivo data é fornecido como um argumento para awk duas vezes. Conseqüentemente, ele será lido duas vezes, a primeira vez para obter o total, que é armazenado na variável s , e o segundo para imprimir a saída. Olhando os comandos com mais detalhes:

  • FNR==NR{s+=$2;next;}

    NR é o número total de registros (linhas) que awk leu e FNR é o número de registros lidos até o momento a partir do arquivo atual. Conseqüentemente, quando FNR==NR , estamos lendo o primeiro arquivo. Quando isso acontece, a variável s é incrementada pelo valor na segunda coluna. Então, next diz awk para pular o resto dos comandos e começar de novo com o próximo registro.

    Note que não é necessário inicializar s para zero. Em awk , todas as variáveis numéricas são, por padrão, inicializadas como zero.

  • printf "%s\t%s\t%s%%\n",$1,$2,100*$2/s

    Se chegarmos a este comando, estamos processando o segundo arquivo. Isso significa que s agora contém o total da coluna 2. Portanto, imprimimos a coluna 1, a coluna 2 e a porcentagem, 100*$2/s .

Opções de formato de saída

Com printf , o controle detalhado do formato de saída é possível. O comando acima usa o especificador de formato %s , que funciona para strings, inteiros e flutuantes. Três outras opções que podem ser úteis aqui são:

  • %d formata números como números inteiros. Se o número for realmente ponto flutuante, ele será truncado para um inteiro

  • %f formata números como ponto flutuante. Também é possível especificar larguras e casas decimais como, por exemplo, %5.2f .

  • %e fornece notação exponencial. Isso seria útil se alguns números fossem excepcionalmente grandes ou pequenos.

Faça uma função de shell

Se você for usar isso mais de uma vez, é inconveniente digitar um comando longo. Em vez disso, crie uma função ou um script para burlar o comando.

Para criar uma função chamada totals , execute o comando:

$ totals() { awk 'FNR==NR{s+=$2;next;} {printf "%s\t%s\t%s%%\n",$1,$2,100*$2/s}' "$1" "$1"; }

Com essa função definida, as porcentagens de um arquivo de dados chamado data podem ser encontradas executando:

$ totals data

Para tornar a definição de totals permanent, coloque-a no arquivo ~/.bashrc .

Crie um script de shell

Se você preferir um script, crie um arquivo chamado totals.sh com o conteúdo:

#!/bin/sh
awk 'FNR==NR{s+=$2;next;} {printf "%s\t%s\t%s%%\n",$1,$2,100*$2/s}' "$1" "$1"

Para obter as porcentagens de um arquivo de dados chamado data , execute:

sh totals.sh data
    
por 15.12.2014 / 19:18
4

Awk com um arquivo aberto (para completar)

awk '{a[NR]=$0;x+=(b[NR]=$2)}END{while(++i<=NR)print a[i]"\t"100*b[i]/x"%"}' file

foo     10      10%
bar     20      20%
oof     50      50%
rab     20      20%

Isso usará mais memória que os outros, mas deverá ser mais rápido

Isto lê a linha no array a e o campo dois no array b .
Em seguida, incrementa x pelo valor no campo 2.

No final, itera de 1 para o número de registros e gera a linha correta e calcula a porcentagem.

    
por 15.12.2014 / 22:01
3

A maneira "simples" de fazer isso seria chamar awk duas vezes: uma vez para obter o total, outra hora para calcular as proporções.

$ total=$(awk 'BEGIN{ total=0 } { total=total+$2 } END{ printf total }' data)
$ awk -v total=$total '{ print $1 "\t" $2 "\t" 100*$2/total "%" }' data

Agora, tenho certeza de que alguém criará um one-liner de alguma forma ...

    
por 15.12.2014 / 19:09