《数据整理实践指南》一2.3 可视化

    xiaoxiao2024-04-22  9

    本节书摘来自异步社区《数据整理实践指南》一书中的第2章,第2.3节,作者【美】Q. Ethan McCallum(麦卡伦),更多章节内容可以访问云栖社区“异步社区”公众号查看

    2.3 可视化

    我觉得特别有帮助的另一种技术是根据字段值创建直方图。这对于大规模数据集尤其有用,而前面提到的简单的统计方式只能看到数据的表面。直方图是每个值在数据集中出现次数的统计,因此直方图还适用于非数值字段,而统计方法就不适用。

    举个例子,假设有个包含推荐关键字的数据集,这些关键字是使用Google、Bing或其他搜索引擎进行搜索的词,使用户能够浏览网站的网页。一个大型网站每天可以通过搜索引擎带来数百万的PV,其中搜索关键字可能有数百万,在一段时间后可能会有数十亿的唯一关键字。对于这些关键字,由于它们不是数值类型,而是字符串类型,所以不能使用传统的统计方法(如最小值、最大值或平均值)。

    幸运的是,可以使用直方图对大规模的非数值数据集进行汇总。一阶直方图计算每个关键字的来源数。但是,如果数据集中有数十亿的关键字,直方图就会非常大,可能没什么用。可以执行一次聚合操作,使用每个关键字给网站带来的来源数作为值,结果就会小得多,而且汇总信息更有用。该直方图会显示每个关键字的来源数。由于对于来源数有的差别很小,可以通过分箱操作进一步汇总(比如来源数为1~10、11~20、21~29等)。当然,具体的分箱数取决于数据。

    对于很多简单的数据集,一个命令管道就可以生成一个有用的直方图。比如,假设有个简单的文本文件(sample.txt),包含一些枚举字段(比如URLs、keywords、names和months)。为了快速生成数据直方图,可以运行如下命令。

    $ cat sample.txt | sort | uniq –c

    该命令如何工作呢?cat命令会读取文件,把内容发送给STDOUT。管道符号(|)会捕获到这些数据,把它发送给管道的下一个命令(因此使用管道符非常方便!),即sort命令,它实现期望的操作:对数据进行排序。在这个例子中,实际上并不关心数据是否有序,只是想把相同的行组合在一起,因为下一个命令uniq依赖于组合的结果。uniq命令(命名非常贴切,虽然不知道如何工作)每次只输出一条唯一的记录,如果指定-c选项,会在每行结果前面输出该行出现的次数。因此,该命令会最终显示文件中每行出现的次数:这就是直方图!

    请看示例2-1。

    示例2-1 生成字段Months的直方图

    $ cat sample.txt January January February October January March September September February $ cat sample.txt | sort February February January January January March October September September $ cat sample.txt | sort | uniq -c     2 February     3 January     1 March     1 October     2 September

    对于稍微复杂的数据集,比如t分隔的文件,只需要增加一个过滤器,提取想要的字段。提取字段的方法有多种,“最好”的方式取决于数据的特性以及筛选条件。最简单的方式可能是通过cut命令实现,尤其是对于t分隔的数据,只需要通过命令行参数就可以指定要哪个字段或哪些字段。举个例子,假设有个文件,第一个字段是names,第二个字段是ages。如果想计算每个年龄有多少人,可以执行如下代码。

    $ cat sample2.txt Joe    14 Marci   17 Jim    16 Bob    17 Liz    15 Sam    14 $ cat sample2.txt | cut -f2 | sort | uniq -c       2 14       1 15       1 16       2 17

    第一个字段表示计数,第二个字段表示年龄。从结果可以看出,14岁有2个,15岁有1个,16岁有1个,17岁有2个。

    筛选列的另一种常用方法是使用AWK(AWK的功能要复杂得多),其语法稍微有些复杂。

    $ cat sample2.txt | \      awk '{print $2}' | sort | uniq -c     2 14     1 15     1 16     2 17 和所有文本处理一样,也可以使用Perl,下面是一些例子。 $ cat sample2.txt | \      perl -ane 'print $F[1],"\n"' | sort | uniq -c     2 14     1 15     1 16     2 17 $ cat sample2.txt | \      perl -ne 'chomp; @F = split(/\t/,$_); print $F[1],"\n"' | sort | uniq -c     2 14     1 15     1 16     2 17

    对于真实的数据集(即包含大量数据点的数据集),直方图可以直观地展示数据的近似分布情况,可以便于评估。举个例子,你可能期望是个较平滑的函数,可能是直线或高斯分布(看起来像个钟形曲线),甚至可以呈指数形式衰减(长尾分布),但是应该特别重视图中的不连续点:它可能表示数据有问题。

    2.3.1 关键词竞价排名示例

    关于直方图,其中一个有用的例子是对于两个约750万关键字的数据集,预估其竞价排名值。数据是由第三方收集的,关于如何收集,我了解甚少。数据文件是逗号分隔的文本文件,包含关键字和相应的竞价排名值。示例2-2为竞价排名数据文件。

    示例2-2 竞价排名数据文件

    waco tourism, $0.99 calibre cpa, $1.99,,,,, c# courses,$2.99 ,,,,, cad computer aided dispatch, $1.49 ,,,,, cadre et album photo, $1.39 ,,,,, cabana beach apartments san marcos, $1.09,,, "chemistry books, a level", $0.99 cake decorating classes in san antonio, $1.59 ,,,,, k & company, $0.50 p&o mini cruises, $0.99 c# data grid,$1.79 ,,,,, advanced medical imaging denver, $9.99 ,,,,, canadian commercial lending, $4.99 ,,,,, cabin vacation packages, $1.89 ,,,,, c5 envelope printing, $1.69 ,,,,, mesothelioma applied research, $32.79 ,,,,, ca antivirus support, $1.29 ,,,,, "trap, toilet", $0.99 c fold towels, $1.19 ,,,,, cabin rentals wa, $0.99

    由于该数据集是CSV格式(引号括起来的字段内容包含逗号),前面给出的一些技巧这里不适用。一种快速解决的方式是把那些包含逗号的字段删掉,然后再使用前面给出的管道方式。下面通过忽略包含双引号字符的记录来实现。首先,检查一些会忽略多少条数据。

    $ cat data*.txt | grep -c '"' 5505 $ cat data*.txt | wc -l 7533789

    我们只是忽略了0.07%的记录,这不会影响统计结果。因此,通过管道实现如下。

    $ cat ppc_data_sample.txt | grep -v '"' | cut -d, -f2 | sort | uniq -c | sort -k2    1 $0.50    3 $0.99    1 $1.09    1 $1.19    1 $1.29    1 $1.39    1 $1.49    1 $1.59    1 $1.69    1 $1.79    1 $1.89    1 $1.99    1 $2.99    1 $32.79    1 $4.99    1 $9.99

    这看起来可能有些复杂,下面一步步对它进行探讨。首先,通过cat命令创建数据流。其次,通过grep命令匹配不包含引号的记录,−v选项表示删除包含双引号的记录,CSV格式会使用引号把字段内容包含分隔符(这里即逗号)的字段括起来。然后使用cut命令抽取出第二个字段(其中-d后面的逗号即表示字段分隔符)。再次,对结果记录进行排序,这样重复记录就会相邻。然后,使用uniq命令,其中-c选项会对相同行的出现次数进行计数。最后,根据第二个字段对结果输出进行排序(即竞价排名PPC)。

    在实际数据中,其输出结果很凌乱,因为PPC值差别很大(有些在逗号和美元符$之间有空格,有些没有)。如果希望输出结果更干净,可以实现一段Perl脚本来清洗数据并对它们进行聚合,这往往也是更灵活的解决方案,代码如下。

    use strict; use warnings; use Text::CSV_XS; my $csv = Text::CSV_XS->new({binary=>1}); my %count; while(<>) {   chomp;   s/\cM//;   $csv->parse($_) || next;   my $ppc = ($csv->fields())[1];   $ppc =~ s/^[ \$]+//;   $count{$ppc}++; } foreach my $ppc (sort {$a <=> $b} keys %count) {   print "$ppc\t$count{$ppc}\n"; }

    对于前面给出的样本数据集,这段代码会输出类似的结果,但多出两条之前被丢弃的$0.99记录,此外PPC值以实际值表示,而不是不同的字符串形式。

    0.50     1 0.99     5 1.09     1 1.19     1 1.29     1 1.39     1 1.49     1 1.59     1 1.69     1 1.79     1 1.89     1 1.99     1 2.99     1 4.99     1 9.99     1 32.79    1

    对于真实的数据集,输出结果如下。

    0.05     1071347 0.06     2993 0.07     3359 0.08     3876 0.09     4803 0.10     443838 0.11     28565 0.12     32335 0.13     36113 0.14     42957 0.15     50026 ... 23.97     1 24.64    1 24.82    1 25.11    1 25.93    1 26.07    1 26.51    1 27.52    1 32.79    1

    值得一提的是,如果数据已经在SQL数据库,生成直方图就非常容易。举个例子,假定有个表名为MyTable,包含前面提到的数据,数据有两个字段,Term和PPC,可以通过如下SQL语句对PPC进行聚合。

    SELECT PPC, COUNT(1) AS Terms FROM MyTable GROUP BY PPC ORDER BY PPC ASC

    不管如何生成数据,通过可视化是展示有趣的特征的最佳方式,如图2-1所示。

    对于较小的PPC值,存在非常多的关键词,随着PPC值增长,数据量呈指数级衰减(注意,纵轴是以对数方式显示的)。尽管如此,可以看到在图的中间有个很大的“断面”!在$15.00和$15.88之间几乎没有PPC值,而在$15.89到$18.00之间的值比预期的多(基于图中的曲线可以看出),这让我猜想可能是生成数据的方法把$15.00和$15.88之间的数据值提升了大约$0.89。仔细查看数据集之后,发现两点:一是确实是因为生成测试PPC值的算法导致的,二是数据生成方不知道其算法存在这个问题!通过该数据分析,我们知道要避免把PPC值在$15.89到$18.00之间的数据映射到任何一个关键字,数据生成方也知道了要修正其算法。

    关于该数据集,另一个有趣的特征是其最小值是$0.05。这可能是因为市场最低竞价值是$0.05,也可能是算法从$0.05开始预估竞价值,或者数据之前做过处理,删除了低于$0.05的竞价值,或者是由于其他原因。在这个例子中,最后发现是由于第一个原因,收集数据的市场的最低定价是5美分。实际上,如果直方图中PPC值较低的曲线放大显示(如图2-2所示),会发现另一个特征。虽然PPC值为$0.05的关键词超过100万条,却没有一条(更准确地说是少于3 000条)PPC值是$0.06到$0.09。而PPC值为$0.10的记录却不少(接近500 000),而值在$0.11以上的却很少(少于30 000)。因此,看起来市场上有两套不同的竞价方案,其依赖因素不确定。

    2.3.2 搜索来源示例

    使用直方图的另一个例子是查看搜索来源。当用户在Google搜索结果页中点击Web站点上的链接时,Google(有时)会把查询关键词和列表的“排名”(页面的第一条结果值为1,第二条结果值为2等)一起传给网站。这个信息对网站非常有价值,因为它表示对于不同关键词,该网站的内容在Google搜索结果中的排名。然而,它也可能是噪音数据。Google经常通过改变页面上的排名结果测试其算法和用户行为。结果排序还和用户的信息和行为有关,比如国家、过去的搜索和点击行为,甚至是好友推荐。因此,这种排序数据表示的是关键词/URL组合的多种排序中的一种,这通常会使人难于理解。有些人还认为Google故意把该数据弄得不可用。

    为了查看该排名数据是否有用,我查看了一个大型网站的来源数据,它从Google的来源流量很大(每天有数百万条)。和常见的标准Web服务器日志文件的原始数据不同,这些数据已经保存在数据仓库中,已经从来源页的URL中抽取相应字段,包括日期、URL、来源关键词以及每次浏览的排序。我创建了一个直方图,显示每个Rank的PV数(如图2-3所示)。

    从图2-3的直方图中可知,数据不是随机的,也不是很混乱。数据展现了一个非常清晰的模式,和期望的用户行为相关。举个例子,在排名10和排名11之间有一段很大的不连续性,同样,排名20和排名21之间也是,依此类推。这和Google的搜索结果默认每个页面展示10条结果对应。

    在结果页中(除了首页,后面会讨论),可以看出更多用户对结果的点击,第一条大于第二条,第二条大于第三条……有趣的是,更多用户点击页面中的最后几条结果,而不是中间结果。通过其他方式也发现了这个行为,因此通过查看该细粒度的直方图,可以很大程度上证明数据的有效性。

    那么,为什么这个模式对于第一个页面不适用呢?注意该数据并不表示CTR(点击率),它显示的是总浏览量。对于很多关键词,该网站的网页排在结果页中第一条的并不多,但是排在第二和第三的却很多。因此,虽然第一条结果的CTR是最高的(从其他页面中看出),但是在第一个页面中看不出来。在第三、第四以及后续的页面中,可以看出浏览量逐渐趋缓,因此PV值看起来和CTR相当。

    2.3.3 推荐分析

    到目前为止,我们已经探讨对字段值相同记录的计数构建直方图。正如我们所看到的,这在很多情况下很有用,但是对很多案例而言,这种方式粒度太细,导致无法查看有用的模式。举个例子,比如对于推荐模式的分析,可能是对用户的电影推荐,对某个产品推荐另一个产品等。在这个例子中,我将探讨文章推荐。假设有这样一个网站,内容丰富,包含数百万篇文章,涉及领域很广泛。为了帮助读者从当前文章导航到下一篇他们可能感兴趣的文章,该网站提供了一个简短的推荐列表,该列表是基于人工编辑、语义相似和/或过去的访问流量模式。

    假定数据集包含推荐组合:每条记录表示一个推荐,第一个字段表示源文章的URL,第二个字段表示目标文章的URL。

    示例2-3 推荐文件

    http://example.com/fry_an_egg.html http://example.com/boil_an_egg.html http://example.com/fry_an_egg.html http://example.com/fry_bacon.html http://example.com/boil_an_egg.html http://example.com/fry_an_egg.html http://example.com/boil_an_egg.html  http://example.com/make_devilled_eggs.html http://example.com/boil_an_egg.html  http://example.com/color_easter_eggs.html http://example.com/color_easter_eggs.html  http://example.com/boil_an_egg.html ...

    对于正在学习如何煮鸡蛋的读者,会显示关于如何煮鸡蛋和煎培根的文章;对于正在学习如何煮鸡蛋的读者,会显示如何煮鸡蛋,如何在鸡蛋上画鬼脸,以及如何为复活节彩蛋着色。

    对于一个大型网站,文件可能会变得非常大。我曾经工作过的一个网站有约330万篇文章,每篇文章有30条推荐,最后整个网站有约1亿条推荐。由于这些推荐内容是在夜间自动化生成的,要确保系统生成合理的推荐信息很重要,而且很富有挑战。手工检查统计学上大量样本会花费大量时间,因此需要依赖统计工具。比如,这些推荐的分布情况如何?是否有些文章被推荐了数千次,而有些文章从未被推荐过?

    我们可以生成柱状图,显示每篇文章被推荐了多少次。

    示例2-4 生成推荐目标直方图

    $ cat recommendation_file.txt | cut -f2 | sort | uniq –c 2 http://example.com/boil_an_egg.html 1 http://example.com/fry_bacon.html 1 http://example.com/fry_an_egg.html 1 http://example.com/make_devilled_eggs.html 1 http://example.com/color_easter_eggs.html

    “How to Boil an Egg(如何煮鸡蛋)”这篇文章被推荐了2次,而其他4篇文章只分别被推荐1次。这听起来没什么问题,但是有330万篇文章,在直方图中就需要有330万条记录,这样的直方图就会变得没什么可用性了。更糟糕的是,由于关键字是URL,无法像数值一样对它们执行分箱操作。为了更好地了解数据,可以进一步聚合,按示例创建一张直方图。示例2-5 生成推荐目标计数直方图

    $ cat recommendation_file.txt \ | cut -f2 \ | sort \ | uniq -c \ | sort -n \ | awk '{print $1}' \ | uniq –c 4 1 1 2

    从结果可知,有四篇文章被推荐了一次,一篇文章被推荐了两次。

    在3 300万条推荐的数据集上使用同样的技术(即每篇文章的前10条推荐),生成如图2-4所示的直方图。对于累积分布函数,得到结果如图2-5所示。

    注意图2-4的直方图运用重对数图尺,而图2-5的累积分布图呈线性垂直曲线。整体而言,数据集看起来很合理,曲线平滑,没有明显的不连续性。大多数文章被推荐次数很少,约300 000篇只被推荐1次。随着被推荐数的增加,文章的数量快速减少,约一半的文章被推荐数少于7。最流行的文章大约被推荐1 800次。

    可以针对不同的算法生成的推荐集运行该分析,从而帮助我们理解算法行为。举个例子,如果一个新的算法产生的分布范围更广,就可以推断有很多文章被推荐的次数和其他文章差距很大。如果分布完全不同—比如推荐次数的平均值是一个钟形分布(在这个示例中为10)—那么可以推断出文章的推荐次数分布很均匀。

    2.3.4 时间序列数据

    尽管直方图对于时间序列数据通常没什么意义,类似的可视化技术可能会很有用,尤其当数据可以通过其他维度聚合时。举个例子,Web服务器日志可以每分钟、每小时或每天聚合统计PV数,然后把统计结果作为时间序列。这对于查找数据丢失特别有用,如果处理不当,对于某些数据分析会带来严重的倾斜。

    很多网站负责人会仔细观察他们的网站流量,监测网站问题,包括市场营销活动、搜索引擎优化等。对于很多网站,由于受到和网站本身无关的一些因素的影响,流量的变化会变得非常复杂。其中一些原因,如季节性因素,某种程度上可以预测,这样当网站流量下降时也不至过于担心。尽管如此,季节性因素可能很复杂,因为它和网站类型有关,比如季节性因素对税务咨询网站的影响和园艺网站的影响会有很大差别。此外,常识并不总是正确,而且常识几乎不会有量化的指导作用。在周日流量下降30%会有意义吗?春天开学时,流量会有多少下降?对于有大量历史流量数据的网站,可以构建模型来预测(通过显式或隐式的“关系网知识”),但是对于一个新的站点或一个已有站点,如何构建模型呢?

    为了解决这个问题,我尝试使用公开可用的维基百科日志来构建一个长期、大规模的季节性模型,预测不同语言的流量。维基百科服务每天有近5亿的页面,有一半多是英语。通过自动化程序对网页按语言(站点)、时间和页面进行聚合。正如任何自动化程序一样,它有时会失败,导致某个小时或某天的统计值变低。对于季节性模型,这会带来灾难,意味着流量的下降是不真实的。

    第一次统计维基百科数据时,得到的是一些没有意义的值,比如在2011年6月7日这一天,PV值为1.06E69?这种值可以很容易知道是不真实的,可以丢弃掉。修剪掉一些异常值后,时间序列图如图2-6所示。

    你会发现,还是有一些异常值,有低的,也有高的。此外,整体形状很模糊。放大后(如图2-7所示),会发现其中一些模糊性是由于一周中每天的统计值变化。对于大多数信息类网站,在平时会比周日多出很多流量。

    由于关注的是网站流量变化的长期趋势,我选择查看每周的平均值,这样可以不受游离点以及一周中不同日期变化的影响(如图2-8所示)。

    图2-8看起来清晰多了。在过去几年,整体流量一直趋于上升,和期望的一致。图2-8中还是有一些较大的倾角和尖峰,以及一年过程中的很多变化。由于数据是每小时以文件形式保存,检测脏数据的一种方式是统计每天有多少个小时的数据。显然,每天应该有24小时的数据(可能潜在一些时间点的细微差别,这取决于维基百科如何记录日志)。

    在图2-8中添加小时数后,生成结果如图2-9所示,它显示了有很多天的数据小于24小时,还有两天的数据超过24小时(达到30小时!)。2009年5月11日有30小时的数据,5月12日有27小时的数据。进一步查看会发现很可能是因为数据重复。举个例子,2009-05-01 01这个时间点有两个文件,每个文件的PV数和00以及02这两个时间点一致。这很可能是因为系统程序在从原始Web日志生成这些文件时,复制了一些数据。

    示例2-6 维基百科2009-11-05这天英文页面的统计计数

    $ grep -P '^en\t' pagecounts-20090511-*.gz.summary pagecounts-20090511-000000.gz.summary:en 20090511 9419692 pagecounts-20090511-010000.gz.summary:en 20090511 9454193 pagecounts-20090511-010001.gz.summary:en 20090511 8297669 <== duplicate pagecounts-20090511-020000.gz.summary:en 20090511 9915606 pagecounts-20090511-030000.gz.summary:en 20090511 9855711 pagecounts-20090511-040000.gz.summary:en 20090511 9038523 pagecounts-20090511-050000.gz.summary:en 20090511 8200638 pagecounts-20090511-060000.gz.summary:en 20090511 7270928 pagecounts-20090511-060001.gz.summary:en 20090511 7271485 <== duplicate pagecounts-20090511-070000.gz.summary:en 20090511 6750575 pagecounts-20090511-070001.gz.summary:en 20090511 6752474 <== duplicate pagecounts-20090511-080000.gz.summary:en 20090511 6392987 pagecounts-20090511-090000.gz.summary:en 20090511 6581155 pagecounts-20090511-100000.gz.summary:en 20090511 6641253 pagecounts-20090511-110000.gz.summary:en 20090511 6826325 pagecounts-20090511-120000.gz.summary:en 20090511 7433542 pagecounts-20090511-130000.gz.summary:en 20090511 8560776 pagecounts-20090511-130001.gz.summary:en 20090511 8548498 <== duplicate pagecounts-20090511-140000.gz.summary:en 20090511 9911342 pagecounts-20090511-150000.gz.summary:en 20090511 9708457 pagecounts-20090511-150001.gz.summary:en 20090511 10696488<== duplicate pagecounts-20090511-160000.gz.summary:en 20090511 11218779 pagecounts-20090511-170000.gz.summary:en 20090511 11241469 pagecounts-20090511-180000.gz.summary:en 20090511 11743829 pagecounts-20090511-190000.gz.summary:en 20090511 11988334 pagecounts-20090511-190001.gz.summary:en 20090511 10823951<==duplicate pagecounts-20090511-200000.gz.summary:en 20090511 12107136 pagecounts-20090511-210000.gz.summary:en 20090511 12145627 pagecounts-20090511-220000.gz.summary:en 20090511 11178888 pagecounts-20090511-230000.gz.summary:en 20090511 10131273

    删除重复条目可以使结果图表变得更合理,但并不会真正解决流量的大波动问题,也无法估计丢失的数据量。值得一提的是,从2010-06-12到2010-06-15,每天都有24个小时的数据文件,但是在图中该时间段呈现最大的PV降幅。同样,最大的正向影响的游离点是2010年10月的前3周的数据,每天的流量PV数从2.4亿上升到3.75亿,而这个时期并没有多出文件。因此,这些现状说明了我们必须对这些时间段做更深入的分析,并提醒我们从该数据集得出结论时需要小心。

    最新回复(0)