我们来看看下面这个问题:数据集中有多少不同的值?我们前面已经用了那么多次的 sort 和 uniq -c 的组合,所以现在都可以准确快速地打出来了,不过它们用得太频繁,可能应该写成脚本来用。这里有些关于泰坦尼克数据的“不同值”问题,用泰坦尼克数据为例是因为它数据量较小。
有多少男性和女性乘客?
$ awk --csv '{g[$11]++}
END {for (i in g) print i, g[i]}' passengers.csv
female 463
sex 1
male 850
看起来是对的——“sex”是表头,而剩下的是男性和女性,正如预期。可用非常相似的程序(只改个域编号)来检查乘客类别,生存状态和年龄。比如,检查年龄可以发现,1313个乘客中有258个没有给出年龄。
如果用下面的程序来统计不同年龄的数量:
$ awk --csv '{g[$5]++}
END {for (i in g) print i, g[i]}' passengers.csv | sort -n
会得到类似这样的结果:
...
1 4
1 4
2 6
2 7
3 6
3 2
...
大概一半的年龄域都包含一个垃圾的空格!如果不纠正的话,就会很容易影响后续的计算。
更一般地说,排序是用来发现数据异常的强有力技术,因为它将前缀相同(但后面不同)的文本片段都放在了一起。我们看看这个例子,比如想要统计敬语,如Mr 或 Colonel。通过打印名称域的第二个单词就能快速得到一个列表,马上就能找到最明显的这些敬语:
$ awk --csv '{split($4, name, " ")
print name[2]}' passengers.csv | sort | uniq -c | sort -nr
728 Mr
229 Miss
191 Mrs
56 Master
16 Ms
7 Dr
6 Rev
...
$
当然这个程序还会在末尾产生一长串错的非敬语,但这也提示了程序哪里可以改进;比如把标点去掉,则下面的这些差异也会被去掉:
6 Rev
1 Rev.
1 Mlle.
1 Mlle
这个实验还产生了一个 Colonel 和 一个 Col,大概指的是同一个军衔。
另外有趣的是,在Ms被现代人普遍使用之前,它就已经被用了超过50年,不过我们没法知道当时它所指示的社会状况或条件。
类似的,我们可以问问在啤酒数据集中,有多少酒厂,多少种啤酒风味,多少个评级人:
{ brewery[$2]++; style[$8]++; reviewer[$7]++ }
END { print length(brewery), "breweries," length(style), "styles,"
length(reviewer), "reviewers" }
会得到:
5744 breweries, 105 styles, 33389 reviewers
函数 length 若用在数组上,则返回数组的元素个数。
代码稍微变动下,就能回答如“不同的啤酒风味受欢迎程度如何”的问题:
{ style[$8]++ }
END { for (i in style) print style[i], i }
会得到:(结合排序,并使用2.2节的只输出头尾的程序)
117586 American IPA
85977 American Double / Imperial IPA
63469 American Pale Ale (APA)
54129 Russian Imperial Stout
50705 American Double / Imperial Stout
...
686 Gose
609 Faro
466 Roggenbier
297 Kvass
241 Happoshu
如果你要做大量“选择一些域并计算其统计值”的这类操作,也许值得写一些短脚本,就像我们在第2章讨论的那样。一个脚本可以用来选择一个特定的域,而另一个脚本用来做排序和去重。