Получение количества уникальных значений в столбце в bash

98

У меня есть файлы с разделителями табуляцией и несколькими столбцами. Я хочу подсчитать частоту появления различных значений в столбце для всех файлов в папке и отсортировать их в порядке убывания количества (сначала максимальное количество). Как мне сделать это в среде командной строки Linux?

Он может использовать любой распространенный язык командной строки, такой как awk, perl, python и т. Д.

фактор
источник

Ответы:

156

Чтобы увидеть частоту для второго столбца (например):

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

Результат:

  3 d
  2 r
  1 z
  1 m
  1 g
  1 b
Приостановлено до дальнейшего уведомления.
источник
69

Вот способ сделать это в оболочке:

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

Bash хорош в этом.

Thedward
источник
23
"Что-то вроде" ... ар ар ар! :)
Джон Рикс
3
Вроде как уникальная штука. : P (кстати, используйте -d,для разделения полей запятой или любым другим разделителем).
cprn
4
Я использовал cut -f 1 -d ' '. Большое спасибо. :)
Альфонсо Нисикава
8

Сайт GNU предлагает этот красивый скрипт awk, который печатает как слова, так и их частоту.

Возможные изменения:

  • Вы можете пропустить sort -nr(и перевернуть wordи freq[word]), чтобы увидеть результат в порядке убывания.
  • Если вам нужен конкретный столбец, вы можете опустить цикл for и просто написать freq[3]++- замените 3 номером столбца.

Вот оно:

 # 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
Отличный пример сценария. Он демонстрирует так много возможностей awk.
Дэвид Манн,
Этот скрипт помог мне определить, на какие строки в книге Excel мне действительно нужно обратить внимание :) (скопировал содержимое Excel в текстовый файл, использовал awk и, вуаля !, я могу создать файл шаблона для grep -n) .
Jubbles
6

Perl

Этот код вычисляет вхождения всех столбцов и распечатывает отсортированный отчет для каждого из них:

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

Сохраните текст как columnvalues.pl
Запустите его как: perl columnvalues.pl files*

Объяснение

В цикле while верхнего уровня:
* Перебирать каждую строку объединенных входных файлов
* Разбивать строку на массив @Fields
* Для каждого столбца увеличивать структуру данных результирующего массива хэшей

В цикле for верхнего уровня:
* Перебирать массив результатов
* Распечатать номер столбца
* Получить значения, используемые в этом столбце
* Сортировка значений по количеству вхождений
* Вторичная сортировка на основе значения (например, b vs g vs m vs z)
* Перебирать хэш результата, используя отсортированный список
* Распечатать значение и номер каждого вхождения

Результаты основаны на примерах входных файлов, предоставленных @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 вход

Если ваши входные файлы - это .csv, измените /\s+/на/,/

Запутывание

В уродливом соревновании Perl особенно хорошо оборудован.
Этот однострочник делает то же самое:

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

Рубин (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
Это очень интересно, потому что я использовал его, и он работал, а также потому, что я просто поражен тем, насколько уродливым является Ruby ... Я думал, что Perl плох!
ryansstack
В защиту Рубина это действительно можно было бы улучшить. Например, используя each_with_object, среди прочего. Короче, это несколько грубо написано.
Rambatino 08