第2章 是我的问题还是数据的问题
数据整理实践指南
Kevin Fink
假设给你一份未知来源的数据集,如何确定数据是否有用呢?
这种情况并不少见,给了你一份数据集,却无法提供关于数据来源、如何收集、字段含义等方面的诸多信息。事实上,收到这样的数据可能再正常不过了。在很多情况下,收到的数据可能经过了很多人的处理和加工,和收集的原始数据已经差别甚大,确实也没有人知道这些数据是什么含义了。在本章中,我将一步步引导你如何理解数据、验证数据并最终把数据集转换成可用的信息。特别地,我将探讨洞察数据的特殊方式,并给出一些示例,说明自己从中学到了什么。
首先,我先简单提一下自己的背景。在过去25年里,我一直在处理很多不同的数据。我编写代码,处理加速计和水听器信号,分析水坝和其他大型架构(哈维姆德学院工程专业的本科生);分析不同种类的蝙蝠叫声(华盛顿大学电子工程专业的研究生);构建对图像声纳数据进行可视化的系统(应用物理实验室的研究助理);利用大量的Web内容构建内容过滤系统(N2H2公司的联合创始人和CTO);为门户软件设计内网搜索系统(DataChannel),并且把多个目录辅助数据集结合到可搜索的网站中(WhitePages.com的CTO)。在过去5年的时间里,我在Demand Media公司花了大部分时间使用很多不同的数据源构建广告和内容推荐系统的优化系统,对大规模数据驱动的搜索引擎优化(SEO)和搜索引擎营销(SEM)有广泛涉猎。
大多数示例涉及我在广告优化、内容推荐、SEO和SEM方面相关的工作。和其他大多数领域一样,这些领域也有自己的术语,表2-1列出了一些相关术语的定义。
2.1 理解数据结构
当收到数据集时,第一个障碍往往是无法访问。不过,暂时忽略这些问题,假设可以读取该数据集所在的物理媒介,对它进行解压或通过其他方式抽取文件,把数据转换成某种可读形式。一旦完成这些,下一步是要理解数据结构。传输数据时,往往会使用很多不同的数据结构。庆幸的是,有很多数据结构实际使用并不频繁。我将重点讨论最常见(也是最容易处理)的几种格式:表格(Columnar)、XML、JSON和Excel。
最常见的格式是表格(也就是说,数据是按行和列存储的)。列可以由制表符(tab键)、逗号或其他字符分隔,这些列可以是固定长度。行一般由换行符和/或回车符分隔。对于某些较小的数据集,数据可能是某种专门格式,比如各种Excel版本所使用的格式,但是很容易通过适当软件将其转换成更简单的文本格式。我经常收到Excel电子表格,几乎总是马上把它们导出成tab分隔的文本文件中。
CSV格式的文件是最常见的。在这些文件中,每条记录一行,各个字段用逗号分隔。有些值或所有值(特别是字段内的逗号)可能会通过引号或其他字符引起来,以和分隔符逗号区别开。最常见的情况是,如果使用逗号作为分隔符,包含逗号的字符串就通过双引号引起来。有时,会把所有的字符串都引起来,有时只把那些包含分隔符的字符串引起来。Excel会自动加载CSV文件,大多数编程语言也都提供库处理这类文件。
注意
在下面的例子中,会使用一些基本的UNIX命令:尤其是echo和cat命令。这只是为了简单说明示例数据。以$开头的行,表示在UNIX Shell终端下运行的命令。举个例子,由于制表符(\t)和空格在页面上看起来很相似,我会通过下面这种方式生成数据:
$ echo -e 'Field 1\tField 2\nRow 2\n'
该示例数据包含两行,第一行有两个字段,分隔符是\t。 我还会详细探讨绝大多数的管道,如下所示。
$ cat filename |
虽然在实践中,你可能是把文件名作为第一个命令的参数。也就是说,命令
$ cat filename | sed -e 's/cat/dog/'
和命令在功能上完全等价(而且后者更高效)。
$ sed -e 's/cat/dog/' filename
下面这行Perl代码从CSV文件中抽取第三个和第一个字段。
$ echo -e 'Column 1,"Column 2, protected","Column 3"'
Column 1,"Column 2, protected","Column 3"
$ echo -e 'Column 1,"Column 2, protected","Column 3"' | \
perl -MText::CSV -ne '
$csv = Text::CSV->new();
$csv->parse($_); print join("\t",($csv->fields())[2,0]);'
Column 3 Column 1
下面这段Perl代码可读性更好。
use Text::CSV;
while(<>) {
my $csv = Text::CSV->new();
$csv->parse($_);
my @fields = $csv->fields();
print join("\t",@fields[2,0]),"\n";
}
大多数数据并不包含\t,因此它是一个很安全、很受欢迎的分隔符。以\t 分隔的文件通常不允许在数据中包含\t,因此不要用引号或转义字符,使得\t 分隔的文件比CSV文件更容易处理。使用UNIX命令行工具可以很容易地处理以\t分隔的文件,比如perl、awk、cut、join、comm等。很多简单的可视化工具(如Excel)可以半自动化方式导入\t分隔的文件,每个字段作为一列,很方便人工处理。
以下几个简单的示例,输出以\t 分隔的字符串的第一个字段和第三个字段。cut命令只会按出现的顺序输出数据,但其他工具可以重新组织数据。以下示例分别使用cut、awk和perl输出第一个和第三个字段。
$ echo -e 'Column 1\tColumn 2\tColumn 3\n'
Column 1 Column 2 Column 3
cut:
$ echo -e 'Column 1\tColumn 2\tColumn 3\n' | \
cut -f1,3
Column 1 Column 3
awk:
$ echo -e 'Column 1\tColumn 2\tColumn 3\n' | \
awk -F"\t" -v OFS="\t" '{ print $3,$1 }'
Column 3 Column 1
perl:
$ echo -e 'Column 1\tColumn 2\tColumn 3\n' | \
perl -a -F"\t" -n -e '$OFS="\t"; print @F[2,0],"\n"'
Column 3 Column 1
在某些场合,XML是常见的数据格式。虽然XML并没有真正非常流行,但一些数据库(比如BaseX)以XML作为内部存储格式,其他很多数据库支持直接以XML格式导出数据。对于CSV,很多语言提供库,可以把它解析成本地数据格式,对数据进行分析和转换。
以下是一行Perl代码,它从XML中抽取字段。
$ echo -e '<config>\n\t<key name="key1" value="value 1">
\n\t<description>Description 1</description>
\n\t</key>\n</config>'
<config>
<key name="key1" value="value 1">
<description>Description 1</description>
</key>
</config>
$ echo '<config><key name="key1" value="value 1">
<description>Description 1</description>
</key></config>' | \
perl -MXML::Simple -e 'my $ref = XMLin(<>);
print $ref->{"key"}->{"description"}'
Description 1
下面这段Perl代码可读性更好。
use XML::Simple;
my $ref = XMLin(join('',<>));
print $ref->{"key"}->{"description"}';
虽然JSON主要是用于Web API,以在服务器和JavaScript客户端之间传输信息,但它有时也用于传输大量的数据。有很多数据库把JSON作为内部数据格式(如CouchDB)或使用了序列化的JSON格式(如MongoDB),因此从这些系统中导出的数据通常是JSON格式。
下面一行Perl代码从JSON文件中抽取一个节点信息。
$ echo '{"config": {"key1":"value 1","description":"Description 1"}}'
{"config": {"key1":"value 1","description":"Description 1"}}
$ echo '{"config": {"key1":"value 1","description":"Description 1"}}' | \
perl -MJSON::XS -e 'my $json = decode_json(<>);
print $json->{"config"}->{"description"}'
Description 1
下面这段Perl代码可读性更好。
use JSON::XS;
my $json = decode_json(join('',<>));
print $json->{"config"}->{"description"}';