在 bash 中获取列中唯一值的计数

我有几个列的制表符分隔的文件。我想为一个文件夹中的所有文件计算列中不同值出现的频率,并按计数的递减顺序对它们进行排序(首先是最高计数)。在 Linux 命令行环境中如何实现这一点?

它可以使用任何常用的命令行语言,如 awk、 perl、 python 等。

153056 次浏览

The GNU 网站 suggests this nice awk script, which prints both the words and their frequency.

可能的变化:

  • 您可以通过管道 sort -nr(并反向 wordfreq[word])查看降序结果。
  • If you want a specific column, you can omit the for loop and simply write freq[3]++ - replace 3 with the column number.

开始了:

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

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

查看第二列的频率计数(例如) :

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

Here is a way to do it in the shell:

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

This is the sort of thing bash is great at.

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*

解释

In the top-level while loop:
* Loop over each line of the combined input files
* 将这行拆分为@Fields 数组
* 对于每一列,递增结果哈希数组数据结构

在顶级 for 循环中:
* 循环遍历结果数组
* 列号
* 获取该列中使用的值
* 按出现次数对值进行排序
* 基于值的二级排序(例如 b vs g vs m vs z)
* Iterate through the result hash, using the sorted list
* 列印每次出现的数值和次数

基于@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 的装备尤其精良。
This one-liner does the same:

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*

这是一个接近线性时间的棘手问题(但可能不会更快!)通过避免 sortuniq,除了最终排序。它是基于... teewc而不是!

$ FIELD=2
$ values="$(cut -f $FIELD *)"
$ mkdir /tmp/counts
$ cd /tmp/counts
$ echo | tee -a $values
$ wc -l * | sort -nr
9 total
3 d
2 r
1 z
1 m
1 g
1 b
$