Comando para contar e agrupar em colunas

3

Eu tenho um arquivo assim:

2017-07-30 A
2017-07-30 B
2017-07-30 B
2017-07-30 A
2017-07-30 A
2017-07-30 C
2017-07-31 A
2017-07-31 B
2017-07-31 C
2017-07-31 B
2017-07-31 C

Cada linha representa um evento (A, B ou C) e o dia em que ocorreu. Quero contar o número de eventos por tipo para cada dia. Isso pode ser feito com sort file | uniq -c , dando saída assim:

  3     2017-07-30 A
  2     2017-07-30 B
  1     2017-07-30 C
  1     2017-07-31 A
  2     2017-07-31 B
  2     2017-07-31 C

No entanto, gostaria de ter cada tipo de evento como uma coluna:

              A    B    C
2017-07-30    3    2    1
2017-07-31    1    2    2

Existe uma ferramenta de linha de comando razoavelmente comum que pode fazer isso? Se necessário, pode-se supor que todos os tipos de eventos (A, B, C) são conhecidos antecipadamente, mas é melhor se não é necessário. Da mesma forma, pode-se supor que cada evento ocorre pelo menos uma vez por dia (ou seja, sem zeros na saída), mas aqui também é melhor se não for necessário.

    
por Viktor Dahl 09.08.2017 / 16:24

5 respostas

5

Se "razoavelmente comum" incluir datamash do GNU , então

datamash -Ws crosstab 1,2 < file

ex.

$ datamash -Ws crosstab 1,2 < file
    A   B   C
2017-07-30  3   2   1
2017-07-31  1   2   2

(infelizmente a formatação deste site não preserva as guias - a saída real é alinhada por tabulações).

    
por 09.08.2017 / 16:44
2
Solução

awk :

awk '{ d[$1]; k[$2]; a[$2,$1]++ }END{ 
       printf("%10s"," ");
       for(i in k) printf("\t%s",i); print ""; 
       for(j in d) { 
           printf("%-10s",j); 
           for(i in k) printf("\t%d",a[i,j]); print "" 
       } }' file

A saída:

            A   B   C
2017-07-30  3   2   1
2017-07-31  1   2   2
    
por 09.08.2017 / 23:07
2

Versão mais curta em que não atribuímos valores vazios a zero:

perl -lane '
   ++$h{$i[!$h{$F[0]} ? @i : -1]=$F[0]}{$F[1]}}{
   print join "\t", "\t", @h = sort keys %{ +{ map { map { $_ => 1 } keys %$_ } values %h } };
   print join "\t", $_, @{$h{$_}}{@h} for @i;
' yourfile
perl -lane '
   $i[@i]=$F[0] unless $h{$F[0]};
   ++$h{$F[0]}{$F[1]}}{
   @h = sort keys %{ +{ map { map { $_ => 1 } keys %$_ } values %h } };
   print join "\t", "\t", @h;
   for my $date ( @i ) {
      my $href = $h{$date};
      print join "\t", $date, map { $href->{$_} || 0 } @h;
   }
' yourfile

Resultados

                A       B       C
2017-07-30      3       2       1
2017-07-31      1       2       2

Estruturas de dados:

  • hash %h que tem keys as datas e valores sub-hashes cujas chaves são A, B, C, etc. e os valores correspondentes são suas respectivas contagens nessas datas específicas.
  %h = (
       2017-07-30 => {
           A => 3,
           B => 2,
           C => 1,
       },
       ...
  );
  • Matriz @i , que armazena as datas na ordem em que foram encontradas. Nós empurramos as datas para o array @i apenas quando não é visto antes do IOW, quando é visto pela primeira vez apenas. A ordem é fornecida pela posição da matriz.
  • A matriz @h tem as chaves uniquificadas depois de totalizar todas as chaves "A", "B", "C", etc. do hash %h .
por 09.08.2017 / 17:29
1

Versão antiga bash , usando matrizes.

#!/bin/bash
declare -A values letters dates
while read date letter; do
 values[$date$letter]=$(( ${values[$date$letter]} + 1 ))
 letters[$letter]=1
 dates["$date"]=1
done <file.txt
echo ' ' ${!letters[@]} | sed 's/ /\t/g'
for date in ${!dates[@]}; do
 printf "%-8s\t" $date
 for letter in ${!letters[@]}; do
  printf "%s\t" ${values[$date$letter]}
 done
 echo
done
    
por 10.08.2017 / 01:41
0

Uso: ./count.awk input.txt | column -t -n

#!/usr/bin/gawk -f

{
    dates[$1] = $1;
    events[$2] = $2;
    numbers[$1][$2]++;
}

END {
    num_dates=asort(dates);
    num_events=asort(events);

    for (i = 1; i <= num_events; i++) {
        printf " %s", events[i];
    }
    print "";

    for (i = 1; i <= num_dates; i++ ) {
        printf "%s ", dates[i];
        for (j = 1; j <= num_events; j++) {
            printf "%s ", numbers[dates[i]][events[j]];
        }
        print "";
    }
}

Teste:

Entrada (complicada para testes)

2017-07-30 A
2017-07-30 D
2017-07-29 D
2017-07-30 B
2017-07-28 E
2017-07-30 B
2017-07-30 A
2017-07-30 A
2017-07-30 C
2017-07-31 A
2017-07-31 B
2017-07-31 C
2017-07-31 B 
2017-07-31 C

Resultado

            A  B  C  D  E
2017-07-28              1  
2017-07-29           1     
2017-07-30  3  2  1  1     
2017-07-31  1  2  2        
    
por 10.08.2017 / 00:44