Obtendo a contagem de valores únicos em uma coluna no bash

95

Tenho arquivos delimitados por tabulação com várias colunas. Desejo contar a frequência de ocorrência dos diferentes valores em uma coluna para todos os arquivos em uma pasta e classificá-los em ordem decrescente de contagem (contagem mais alta primeiro). Como eu faria isso em um ambiente de linha de comando do Linux?

Ele pode usar qualquer linguagem de linha de comando comum, como awk, perl, python etc.

fator
fonte

Respostas:

152

Para ver uma contagem de frequência para a coluna dois (por exemplo):

awk -F '\t' '{print $2}' * | sort | uniq -c | sort -nr

fileA.txt

z    z    a
a    b    c
w    d    e

fileB.txt

t    r    e
z    d    a
a    g    c

fileC.txt

z    r    a
v    d    c
a    m    c

Resultado:

  3 d
  2 r
  1 z
  1 m
  1 g
  1 b
Pausado até novo aviso.
fonte
68

Aqui está uma maneira de fazer isso no shell:

FIELD=2
cut -f $FIELD * | sort| uniq -c |sort -nr

Esse é o tipo de coisa em que o bash é ótimo.

Thedward
fonte
22
O "tipo" de coisa ... ar ar ar! :)
John Rix,
3
Meio que uma coisa única. : P (btw. Use -d,para delimitar campos por vírgula ou qualquer outro delimitador).
cprn
4
Eu usei cut -f 1 -d ' '. Muito obrigado. :)
Alfonso Nishikawa
8

O site GNU sugere este belo script awk, que imprime as palavras e sua frequência.

Possíveis mudanças:

  • Você pode canalizar sort -nr(e inverter worde freq[word]) para ver o resultado em ordem decrescente.
  • Se você quiser uma coluna específica, pode omitir o loop for e simplesmente escrever freq[3]++- substitua 3 pelo número da coluna.

Aqui vai:

 # wordfreq.awk --- print list of word frequencies

 {
     $0 = tolower($0)    # remove case distinctions
     # remove punctuation
     gsub(/[^[:alnum:]_[:blank:]]/, "", $0)
     for (i = 1; i <= NF; i++)
         freq[$i]++
 }

 END {
     for (word in freq)
         printf "%s\t%d\n", word, freq[word]
 }
Adam Matan
fonte
2
Ótimo exemplo de script. Isso demonstra muito da capacidade do awk.
David Mann
Este script foi útil para determinar em quais linhas de uma pasta de trabalho do Excel eu realmente precisava prestar atenção :) (copiei o conteúdo do Excel para um arquivo de texto, use awk e, voila !, posso criar um arquivo padrão para grep -n) .
Jubbles
6

Perl

Este código calcula as ocorrências de todas as colunas e imprime um relatório classificado para cada uma delas:

# columnvalues.pl
while (<>) {
    @Fields = split /\s+/;
    for $i ( 0 .. $#Fields ) {
        $result[$i]{$Fields[$i]}++
    };
}
for $j ( 0 .. $#result ) {
    print "column $j:\n";
    @values = keys %{$result[$j]};
    @sorted = sort { $result[$j]{$b} <=> $result[$j]{$a}  ||  $a cmp $b } @values;
    for $k ( @sorted ) {
        print " $k $result[$j]{$k}\n"
    }
}

Salve o texto como columnvalues.pl
Execute-o como: perl columnvalues.pl files*

Explicação

No loop while de nível superior:
* Loop sobre cada linha dos arquivos de entrada combinados
* Divida a linha na matriz @Fields
* Para cada coluna, incremente a estrutura de dados da matriz de hashes do resultado

No nível superior do loop:
* Loop sobre a matriz de resultados
* Imprime o número da coluna
* Obtenha os valores usados ​​nessa coluna
* Classifique os valores pelo número de ocorrências
* Classificação secundária com base no valor (por exemplo b vs g vs m vs z)
* Iterar através do hash de resultado, usando a lista classificada
* Imprimir o valor e o número de cada ocorrência

Resultados baseados nos arquivos de entrada de amostra fornecidos por @Dennis

column 0:
 a 3
 z 3
 t 1
 v 1
 w 1
column 1:
 d 3
 r 2
 b 1
 g 1
 m 1
 z 1
column 2:
 c 4
 a 3
 e 2

entrada .csv

Se seus arquivos de entrada forem .csv, mude /\s+/para/,/

Ofuscação

Em uma competição feia, o Perl é particularmente bem equipado.
Este one-liner faz o mesmo:

perl -lane 'for $i (0..$#F){$g[$i]{$F[$i]}++};END{for $j (0..$#g){print "$j:";for $k (sort{$g[$j]{$b}<=>$g[$j]{$a}||$a cmp $b} keys %{$g[$j]}){print " $k $g[$j]{$k}"}}}' files*
Chris Koknat
fonte
2

Ruby (1.9+)

#!/usr/bin/env ruby
Dir["*"].each do |file|
    h=Hash.new(0)
    open(file).each do |row|
        row.chomp.split("\t").each do |w|
            h[ w ] += 1
        end
    end
    h.sort{|a,b| b[1]<=>a[1] }.each{|x,y| print "#{x}:#{y}\n" }
end
kurumi
fonte
5
Isso é muito interessante, porque eu usei e funcionou, e também porque estou surpreso com o quão feio o rubi é .. Eu pensei que o perl era ruim!
ryansstack
Em defesa de Ruby, isso poderia ser realmente melhorado. Por exemplo, usando each_with_object, entre outras coisas. Resumindo, isso está escrito de maneira grosseira.
Rambatino