Ottenere il conteggio di valori univoci in una colonna in bash


95

Ho file delimitati da tabulazioni con diverse colonne. Voglio contare la frequenza di occorrenza dei diversi valori in una colonna per tutti i file in una cartella e ordinarli in ordine decrescente di conteggio (prima il conteggio più alto). Come potrei farlo in un ambiente a riga di comando Linux?

Può utilizzare qualsiasi linguaggio a riga di comando comune come awk, perl, python ecc.

Risposte:


152

Per vedere un conteggio della frequenza per la colonna due (ad esempio):

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

Risultato:

  3 d
  2 r
  1 z
  1 m
  1 g
  1 b

68

Ecco un modo per farlo nella shell:

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

Questo è il genere di cose in cui Bash è bravo.


22
Il "genere" di cose ... ar ar ar! :)
John Rix

3
Una cosa un po 'unica. : P (btw. Utilizzare -d,per delimitare i campi con virgola o qualsiasi altro delimitatore).
cprn

4
Ho usato cut -f 1 -d ' '. Grazie mille. :)
Alfonso Nishikawa

8

Il sito GNU suggerisce questo simpatico script awk, che stampa sia le parole che la loro frequenza.

Possibili modifiche:

  • Puoi passare attraverso sort -nr(e invertire worde freq[word]) per vedere il risultato in ordine decrescente.
  • Se vuoi una colonna specifica, puoi omettere il ciclo for e scrivere semplicemente freq[3]++- sostituisci 3 con il numero della colonna.

Ecco qui:

 # 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]
 }

2
Ottimo script di esempio. Dimostra così tante delle capacità di awk.
David Mann

Questo script mi ​​è stato utile per determinare a quali righe in una cartella di lavoro di Excel avevo davvero bisogno di prestare attenzione :) (contenuto di Excel copiato in un file di testo, usa awk e, voilà !, posso creare un file di pattern per grep -n) .
Jubbles

6

Perl

Questo codice calcola le occorrenze di tutte le colonne e stampa un report ordinato per ciascuna di esse:

# 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"
    }
}

Salva il testo come columnvalues.pl Eseguilo
come: perl columnvalues.pl files*

Spiegazione

Nel ciclo while di primo livello:
*
Esegue il ciclo su ogni riga dei file di input combinati * Dividi la riga nell'array @Fields
* Per ogni colonna, incrementa la struttura dei dati della matrice di hash dei risultati

Nel ciclo for di primo livello:
* Esegue il ciclo sull'array dei risultati
* Stampa il numero di colonna
* Ottieni i valori utilizzati in quella colonna
* Ordina i valori in base al numero di occorrenze
* Ordinamento secondario in base al valore (ad esempio b vs g vs m vs z)
* Itera attraverso l'hash dei risultati, usando l'elenco ordinato
* Stampa il valore e il numero di ogni occorrenza

Risultati basati sui file di input di esempio forniti da @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

.csv input

Se i tuoi file di input sono .csv, cambia /\s+/in/,/

Offuscazione

In una brutta gara, Perl è particolarmente ben equipaggiato.
Questo one-liner fa lo stesso:

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*

2

Rubino (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

5
Questo è molto interessante, sia perché l'ho usato e ha funzionato, sia perché sono semplicemente stupito di quanto sia brutto il rubino .. pensavo che perl fosse cattivo!
ryansstack

A difesa di Ruby, questo potrebbe essere davvero sistemato. Ad esempio, utilizzando each_with_object, tra le altre cose. In breve, questo è scritto in modo piuttosto grossolano.
Rambatino
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.