Contagem pela primeira coluna, contagem distinta pela segunda coluna e saída do grupo pela primeira coluna?

4

Eu preciso de um comando Unix que leia um arquivo CSV (mais de 700 milhões de linhas) com o exemplo abaixo:

A, 10
B, 11
C, 12
A, 10
B, 12
D, 10
A, 12
C, 12

O comando contará o número de ocorrências na primeira coluna, contará o número da ocorrência distinta na coluna 2 e agrupará a saída pelas entradas na coluna um. Tal que a saída será semelhante a abaixo:

A, 3, 2
B, 2, 2
C, 2, 1
D, 1, 1 
    
por Dami Femi 27.07.2018 / 17:48

4 respostas

2

Para obter as duas primeiras colunas da saída:

$ cut -d, -f1 <file | sort | uniq -c | awk -vOFS=, '{ print $2, $1 }'
A,3
B,2
C,2
D,1

Isso extrai a primeira coluna do arquivo original, classifica-o e conta o número de entradas duplicadas. O awk no final apenas troca as colunas e insere uma vírgula entre elas.

A coluna final pode ser obtida com

$ sort -u <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }'
2
2
1
1

Isso classifica os dados originais e descarta os duplicados. Então a primeira coluna é extraída e o número de duplicatas de que é contado. O awk no final extrai apenas as contagens.

Combinando isso usando bash e paste :

$ paste -d, <( cut -d, -f1 <file | sort    | uniq -c | awk -vOFS=, '{ print $2, $1 }' ) \
            <( sort -u <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }' )
A,3,2
B,2,2
C,2,1
D,1,1

Se você pré-classificar os dados, isso pode ser um pouco menor (e acelerado consideravelmente):

$ sort -o file file

$ paste -d, <( cut -d, -f1 <file        | uniq -c | awk -vOFS=, '{ print $2, $1 }' ) \
            <( uniq <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }' )
A,3,2
B,2,2
C,2,1
D,1,1
    
por 27.07.2018 / 19:54
2

Com um pequeno script sqlite3 executando a partir da linha de comando, onde input.csv são seus dados de entrada:

sqlite3 -batch <<EOF
.mode csv

CREATE TABLE data (letter TEXT, number INTEGER);

.import input.csv data

SELECT letter, COUNT(*) AS lcount, COUNT(DISTINCT number) AS dcount
FROM data
GROUP BY letter ;
EOF

Isso funciona assim

$ bash query.sqlite 
A,3,2
B,2,2
C,2,1
D,1,1
    
por 30.07.2018 / 23:09
1

Eu queria ver se isso poderia ser resolvido usando um one-liner Perl, que consegui descobrir:

$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
    END { print "$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n" for sort keys %lcnt }' \
      file
A, 3, 2
B, 2, 2
C, 2, 1
D, 1, 1

Divisão

loop em um arquivo

Este one-liner pode parecer muito complicado, mas na verdade é bem simples quando você o divide. No coração disso está esse mecanismo em Perl:

$ perl -F, -ane '...; END { ... }' file

Isso diz ao Perl para pegar o arquivo file em e fazer um loop sobre ele e dividi-lo automaticamente usando -F, como o caractere separador, quando completo, execute o bloco END {..} uma vez e saia.

Por exemplo:

$ perl -F, -ane 'print "arg1: $F[0] arg2: $F[1]"; END { print "DONE\n" }' file
arg1: A arg2:  10
arg1: B arg2:  11
arg1: C arg2:  12
arg1: A arg2:  10
arg1: B arg2:  12
arg1: D arg2:  10
arg1: A arg2:  12
arg1: C arg2:  12
DONE

NOTA: o recurso de divisão automática do Perl coloca automaticamente as colunas em uma matriz @F , aqui estou usando os elementos 1 & 2, $F[0] & $F[1] .

Contando as coisas

A próxima coisa que precisamos fazer é contar vários bits da entrada. Para isso, vamos contar com o poder dos hashes em Perl. Nós vamos usar 2, %lcnt e %ccnt .

NOTA: Uma das coisas mais irritantes com o Perl é a mudança de notação ao definir um hash versus acessá-lo. Quando nós o acessamos, nós mudamos de %lcnt para $lcnt["A"] , mas eu divago.

$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++;
  • %lcnt - contagem de caracteres da primeira coluna
  • %ccnt - hash bidimensional contendo 2 coordenadas para acessar uma contagem da segunda coluna

NOTA: A contagem de coisas dessa maneira permite que a função exclusiva seja executada simplesmente pela maneira como estamos contando os bits.

Por exemplo, vamos examinar o conteúdo do %lcnt hash:

$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
    END { print "key: $_\n" for sort keys %lcnt }' file
key: A
key: B
key: C
key: D

Se quisermos ver o valor de cada hash:

$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
    END { print "key: $_ val: $lcnt{$_}\n" for sort keys %lcnt }' file
key: A val: 3
key: B val: 2
key: C val: 2
key: D val: 1

OBSERVAÇÃO: Aqui podemos ver que o $lcnt{$F[0]}++ fez todo o trabalho duro de contar cada caractere enquanto passamos pelo arquivo e os adicionamos a o hash %lcnt .

Este é o END

A última peça do quebra-cabeça é exibir todas as informações coletadas de uma maneira útil. Para isso, vamos usar isso no END {...} :

print "$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n" for sort keys %lcnt

Isso percorre a lista de chaves de %lcnt e imprime a seguinte linha:

$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n"

Se é difícil ver a estrutura acima, aqui é mais geral:

A, 3, 2
      ^--- (keys %{ $ccnt{$_} })  ## count of unique columns for each character ($_)
   ^------ $lcnt{$_}              ## count of each character
^--------- $_                     ## character

Isso produzirá uma linha que contém o caractere ( $_ ), o valor da contagem para esse caractere ( $lcnt{$_} ) e a contagem de valores exclusivos da segunda coluna para cada caractere.

Referências

por 28.07.2018 / 20:19
0
datamash -t, -s -g 1 count 1 countunique 2 < input.txt

Entrada

A, 10
B, 11
C, 12
A, 10
B, 12
D, 10
A, 12
C, 12

Resultado

A,3,2
B,2,2
C,2,1
D,1,1
    
por 30.07.2018 / 22:16