Obtenir le nombre de valeurs uniques dans une colonne dans bash

95

J'ai des fichiers délimités par des tabulations avec plusieurs colonnes. Je veux compter la fréquence d'apparition des différentes valeurs dans une colonne pour tous les fichiers d'un dossier et les trier par ordre décroissant de comptage (le plus grand nombre en premier). Comment puis-je accomplir cela dans un environnement de ligne de commande Linux?

Il peut utiliser n'importe quel langage de ligne de commande courant comme awk, perl, python, etc.

sfacteur
la source

Réponses:

152

Pour afficher un nombre de fréquences pour la deuxième colonne (par exemple):

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

Résultat:

  3 d
  2 r
  1 z
  1 m
  1 g
  1 b
Suspendu jusqu'à nouvel ordre.
la source
68

Voici un moyen de le faire dans le shell:

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

C'est le genre de chose pour laquelle bash est génial.

Thedward
la source
22
Le "genre" de chose ... ar ar ar! :)
John Rix
3
Un peu un truc unique. : P (btw. Utiliser -d,pour délimiter les champs par une virgule ou tout autre délimiteur).
cprn le
4
J'ai utilisé cut -f 1 -d ' '. Merci beaucoup. :)
Alfonso Nishikawa
8

Le site GNU suggère ce joli script awk, qui imprime à la fois les mots et leur fréquence.

Changements possibles:

  • Vous pouvez passer par sort -nr(et inverser wordet freq[word]) pour voir le résultat dans l'ordre décroissant.
  • Si vous voulez une colonne spécifique, vous pouvez omettre la boucle for et simplement écrire freq[3]++- remplacer 3 par le numéro de colonne.

Voici:

 # 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
la source
2
Excellent exemple de script. Cela démontre tellement la capacité de awk.
David Mann
Ce script m'a été utile pour déterminer les lignes d'un classeur Excel auxquelles je devais vraiment prêter attention :) (contenu Excel copié dans un fichier texte, utilisez awk et, voila !, je peux créer un fichier de modèle pour grep -n) .
Jubbles
6

Perl

Ce code calcule les occurrences de toutes les colonnes et imprime un rapport trié pour chacune d'elles:

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

Enregistrez le texte sous columnvalues.pl
Exécutez-le comme: perl columnvalues.pl files*

Explication

Dans la boucle while de niveau supérieur:
* Boucle sur chaque ligne des fichiers d'entrée combinés
* Fractionne la ligne dans le tableau @Fields
* Pour chaque colonne, incrémente la structure de données résultat du tableau de hachages

Dans la boucle for de niveau supérieur:
* Boucle sur le tableau de résultats
* Imprimer le numéro de colonne
* Obtenir les valeurs utilisées dans cette colonne
* Trier les valeurs par le nombre d'occurrences
* Tri secondaire basé sur la valeur (par exemple b vs g vs m vs z)
* Itérer à travers le hachage du résultat, en utilisant la liste triée
* Imprimer la valeur et le numéro de chaque occurrence

Résultats basés sur les exemples de fichiers d'entrée fournis par @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

entrée .csv

Si vos fichiers d'entrée sont .csv, passez /\s+/à/,/

Obfuscation

Dans un concours moche, Perl est particulièrement bien équipé.
Ce one-liner fait la même chose:

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
la source
2

Rubis (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
la source
5
C'est très intéressant, à la fois parce que je l'ai utilisé et cela a fonctionné, et aussi parce que je suis juste étonné de voir à quel point le rubis est moche ... Je pensais que perl était mauvais!
ryansstack
Pour la défense de Ruby, cela pourrait être vraiment amélioré. Par exemple, en utilisant each_with_object, entre autres. En bref, c'est un peu grossièrement écrit.
Rambatino