数据可视化之美——《纽约时报》的一天
文 / Michael Young,Nick Bilton 译 / 祝洪凯,李妹芳
你是否曾经想过《纽约时报》网站的读者会涵盖什么类型的人?我们想过。我们还在想他们倾向于在一天之中的什么时候来访问网站,使用什么工具访问以及他们都来自哪里?从他们是谁到在什么时候、以什么方式以及为什么等,所有这些问题都在我们的思考范围之内。
本文将要介绍的这个可视化项目源于在《纽约时报》研发试验室一次午餐时就这个话题开展的一次简单讨论。正如你将看到的,从非常简单的基于地理的数据集合开始,很快就深入到海量数据和潜在可视化。最终,我们创建了一个可视化用于显示每天《纽约时报》Web站点和手机站点在世界和美国的流量。
收集一些数据
为了对Web站点和手机站点24小时的流量进行可视化,我们需要创建一个可以从《纽约时报》的访问日志中抽取和清洗数据的程序。考虑到我们想要创建一个可以显示在一天内网站的访问次数的可视化并且是一个基于地理信息进行展示的可视化,我们需要的数据包括:
- 在24小时内,用户每次访问Web站点或手机站点的时间戳。
- 每个用户每次访问时所处位置的经度和纬度。
原始的访问日志包含了人们访问Web站点和手机站点的很多有用的信息(比如每个访问者使用什么浏览器);但其中有很多信息对我们而言是没有用的,因此需要把它们从日志信息中过滤掉。此外,日志中并不包括每个用户每次访问时的经纬度信息,因此这是我们在日志“清洗”过程中需要添加的信息。
《纽约时报》Web站点月独立访问读者约2000万。这意味着,在任何一天Web站点和手机站点上都有几百万次的页面浏览(或点击);这是我们准备为可视化收集的基础数据。
数据清洗
对于可视化以及其他日志数据的分析,我们只对来自人们的在Web站点和手机站点的点击数感兴趣——而不是来自网络爬虫、机器人或抓取程序。为了过滤这些不必要的数据,我们实现了一段Java代码用于识别出非人工的访问日志并将其从日志中删除。
每天Web站点原始的日志数据访问量大约有500MB~700MB(压缩格式),手机站点的访问量约80MB~100MB(压缩格式)。在数据清洗过程中,我们执行了IP到经纬度的转换,从而得到每个访问用户的精确位置。原始访问日志中已经包含了用户的IP地址,然后我们使用商业数据库把IP转换成地理位置信息。有很多公司提供GeoIP(地理位置IP)数据库,用于实现该转换。
一旦数据被清洗完毕并准确地进行了地理位置编码,只需要对数据再做最后一轮处理。由于原始的访问日志的收集、存储和清理方式,新清洗完的数据是存放在多个文件中的,需要对它们排序之后合并到一个结果文件中去,该文件将包含可视化所需的数据,即一天访问数据。
每天“清洗”后的网站日志数据被存储到360个文件中,每个文件大小约30MB~40MB(压缩格式)。由于每行中增加了一些额外的字段,如GeoIP 信息,“清洗”后的日志文件要大于原始文件。手机站点数据集小得多,因此清洗后的数据存储在一个文件中,大约70MB(压缩格式)。我们每天需要整理当天的每个清洗后的日志文件,并创建按照对Web站点和手机站点的访问时间戳以及访问者所在的经纬度排序的单个文件(Web站点和手机站点分别生成一个文件)。
Python、Map/Reduce和Hadoop
我们用Python语言创建了一个简单的map/reduce脚本,可以从清洗后的日志文件中过滤掉所有不需要的数据,并输出以逗号作为分隔符的数据,最后还会对数据进行排序。我们使用Amazon的弹性MapReduce Web服务,它允许我们在很多基于Hadoop的EC2的运行实例中运行Python实现的map/reduce。Amazon的EC2运行实例的“配置”不同(低配、中配和高配),不同的配置会提供不同的RAM、CPU核数和内存,因此我们在很多EC2实例中试验运行map/reduce代码,从而找到性价比最好的配置。数据处理需要约10~20分钟(价值几美元),具体所耗时间会依赖于机器的数量(我们从4~10台都尝试了一遍)和EC2实例的配置(我们尝试了低配和中配)。
map/reduce(Hadoop)Job的输出结果是很多有序的文件,这些文件保存在Amazon的S3桶(buckets)中。为了在可视化中把数据放到一个文件中(与前述方式相同,Web站点和手机站点分别存储,各自有一个独立文件),我们从S3下载结果文件到本地,然后按照传统的方法进行排序和归并。现在,数据已经按照期望的方式保存在一个文件中了,可视化的准备工作已经完成。
可视化的第一步
我们在可视化上做的第一个尝试是创建了一个简单的世界地图,将一天之中对《纽约时报》Web站点的每次访问用一个小的黄色圆圈表示,对移动站点的每次访问用一个小的蓝色圆圈表示。除了全球范围的视图,我们还希望创建一个聚焦(或缩放)于美国的视图。
Processing
Processing(面向设计的开源编程语言和集成开发环境)被选作可视化工具,有几个原因。首先,《纽约时报》研发小组中有些人已经有使用Processing完成小的数据可视化的项目经验,他们还拥有使用传感器作为数据收集设备进行探索的经验。此外,我们都是Casey Reas(Processing创始人)、Ben Fry和Aaron Koblin使用该工具所创造的作品的超级粉丝,我们认为Processing将会成为对海量数据进行可视化的理想工具。
对于该可视化,我们需要做的第一件事是将网站的访问用户的经纬度信息映射到Processing中的二维可视化图形中。Aaron Koblin友情提供了一些他在前一个项目中实现该功能的代码——很不错的、紧凑的Java类,可以把经纬度组转换成x、y坐标。我们需要做的就是向Java库传递数据文件中的经纬度元组,Java库就会返回x、y坐标。然后,我们把这些坐标值传给Processing的绘图API来定位《纽约时报》Web站点和手机站点的每个用户的位置。
基础层地图
创建基础层地图所需的时间会远远超过你的想象。首先,我们需要对美国和世界做出准确的表示。经过大量的数据探索后,我们最终使用加州大学洛杉矶分校的CENS组数据集,它描绘了世界上每座城市的经纬度坐标。
在使用该数据集的初始阶段,每当程序启动时,直接在Processing集成环境中进行渲染,但这个渲染花费的时间比我们期望的要多很多;因为知道该数据不会变,最后,我们创建了一个JPEG地图,向背景地图中加载一个非常小的文件。这种方式给我们节省了好几分钟的渲染时间(当解析大数据集时,这部分工作所需的时间会更长)和处理能力,并且成为所有后续的数据输出和视频的背景。
刚刚处理的数据哪去了
有了纬经度投影代码和地图轮廓,我们开始在地图上描绘交通数据图。在可视化初期,我们使用不包含重大新闻的任意一天的数据(2009年2月15日)。这一天的Web站点和手机站点的流量/访问次数和平均值一致。
我们之前已经对数据进行过清洗、排序和添加地理位置编码,包含了时间戳、Web站点和手机站点上给定一天的用户每次查看/点击时所处的纬度/经度值。现在到了创建一个Processing应用程序的时刻了,它可以扫描Web站点和手机站点的日志文件,对于用户的每次查看/点击,会在地图上描绘一个基于用户点击时所在位置而生成的点。
场景1,步骤1
Processing应用在绝大多数情况下由两部分组成:启动(setup)和循环绘制(draw)。在Processing应用的setup()函数中,你可以执行应用需要的任何工作,比如变量初始化、打开输入文件、字体加载等。循环绘制是Processing代码的根本。Processing应用中的draw()函数通常每秒钟会被调用30~60次(这是时间帧速率)。
我们第一次尝试的代码尽管存在一些问题,但能够生成一些可以在屏幕上观看的画面。可以多次运行该应用程序,查看图片中描绘的点,这些点表示《纽约时报》Web站点和手机站点一天的流量。随时间变化的流量的模式让人难以置信——画面看起来似乎是活生生的,闪烁的灯光散布在整个地球上,如图1所示。
这是伟大的第一步,但我们的代码和方法都需要做些修改。以下是需要改进的三个方面。
没有具体比例
首先,该可视化没有显示来自每个用户位置的Web站点和手机站点的流量的比例。比如,在一天的某个时刻,可能有很多Web站点和手机站点的用户是来自相同的地方,比如纽约,可以看到有非常高的流量。有时,可能有成千上万用户来自同一个地理位置。同样,假如是纽约!
在该应用程序的最初版本中,日志文件中出现的每个地理位置(一组经纬度值)在地图上都使用相同大小的点表示。为了能够表示比例,需要基于与某个位置关联的用户量来调整每个位置的可视化表示(地图上的蓝色和黄色点)。
其次,因为黄色(表示Web站点流量)和蓝色(表示手机站点流量)点大小相同,而我们(在绘制循环中)先画表示Web站点的点,再画表示手机站点的点,当两种点击类型位于同一个地理位置时,蓝色点会覆盖黄色点。这对可视化而言不是一个好的选择。
没有考虑时间
在可视化的第一阶段,我们没有考虑人们在Web站点或手机站点上每次访问或页面查看所花费的时间,只是简单地在地图上为每次访问画了一个点,在可视化的整个过程中都不再管它了。这样,就没有人会注意到在某些大城市《纽约时报》有持续较大的流量,而在一些小的偏远地区我们可能一天只有几次查看,这种表示方式会使我们错误地认为这些地区整天都有流量。
我们需要解决这个问题,并结合比例表示问题,也就是说,我们需要提出一种新的方法,可以精确地表示从任何一个位置有多少人访问该网站,以及他们在某篇文章上停留了多长时间,或者在整个网站上停留的时间。
定时拍摄
最后,我们选择将整天的数据流量创建成为一个定时拍摄视频,从而使我们能够在整个《纽约时报》公司内共享该可视化。为了解决这个问题,我们决定使用Processing的一个内置的视频库,它能够将循环绘制生成的时间帧保存到视频文件中,进而创建出很清晰的电影形式的输出。
场景1,步骤2
在第一个版本代码基础之上,我们增加了通过Processing的MovieMaker库将可视化捕获并保存到一个文件中的功能。我们还增加了应用支持,能够使一对Web站点或手机站点的每次点击的可视化都能够体现该次访问的生命周期。平均来说,Web站点和手机站点这两个站点的一次访问时间是历时3~4分钟。因此,在迭代过程中,不再是在地图上画一个点并在后面整整24小时都不管它,我们尝试慢慢地每3分钟淡出消减一个点。当然,一个独立用户不是每3分钟对Web站点或手机站点执行一次点击——日志文件中显示的很多点击都是来自同一批用户,或者是用了更长的时间浏览了网站的很多页面的用户。但是为了避免可视化的最初版本过于复杂,我们就笼统地认为每次对网站的访问都是“3分钟访问”。
对于这种简化的表示,我们需要保存一天内的每次查看/点击淡出3分钟以上的点。这意味着需要在内存中存储很多对象。对于每秒钟内Web站点和手机站点上的每次点击,我们都会在Processing应用程序中创建一个对象,它的任务是保存该点击的“生命周期”,也就是说,这个点需要在屏幕上停留多长时间(3分钟),使用这些对象来帮助我们在可视化的整个周期内对点实现淡出效果。
因此,再回过来看Processing的绘制循环。我们还是每秒钟从Web和手机站点的日志文件中读取数据,但对于每次单击,创建一个Hit(单击)对象,其初始生命周期设置为3分钟,初始不透明度是100%(这些值在迭代循环的每次绘制中不断减少)。读完日志数据后,遍历内存中Hit对象集合。对于每个Hit对象,重新描绘表示该单击的点,其透明度是基于该单击剩余的生命周期,在3分钟时间内把它淡出。当每个Hit对象达到生命周期时,把它从内存中删除,并从地图上删除相应点(即不再重新描绘它)。
因为每秒钟大约需要对400~500次点击进行可视化,这种方法意味着任何时刻都需要在内存中存储很多对象,来保存所有点击轨迹。
可视化的第二步
为了实现想要的可视化,除增加支持能够显示每个地理位置的流量比例,需要对应用程序进行优化,还需要重新思考如何收集数据。
重新回到比例问题
每秒钟显示每次点击并不能显示任何比例。在第一版的应用程序中,来自加拿大农村地区的少量的点击和来自纽约的成千上万的点击,其可视化权重是一样的。此外,从内存和处理器对可视化进行渲染的处理能力而言,每秒钟显示所有的点击代价太高。
想清楚后,我们认为答案是对每分钟每个地理位置的点击次数进行可视化,而不是每秒钟进行可视化。对于访问日志文件中的每分钟的数据,我们会累加每个地理位置的点击总数。这种方式使得可视化结果可以显示每个地理位置的流量比例,而且会极大地减少Processing应用程序的原始数据输入。但是,这种方式意味着我们需要改变数据处理和map/reduce作业。
进一步处理数据
之前用Python实现的map/reduce脚本,其目的是从原始访问日志中解析出我们需要的数据,并基于时间对数据进行排序,因此,需要做些修改。现在,该脚本需要对每分钟、每个地理位置(一组纬度/经度值)的所有点击进行计数,输出结果数据并根据访问时间进行排序。
从根本上说,map/reduce是一个编程模型,支持海量数据处理。其处理过程分成两个任务:mapping(映射)和reducing(规约)。Mapper通常是接收一些输入(在我们的例子中是日志文件),对数据做一些较小的处理,然后以键/值(key/value)对的方式输出数据。Reducer的任务是接收Mapper的输出结果数据,对数据进行归并或规约,通常生成较小的数据集。在我们的应用程序中,Mapper脚本读入原始的访问日志文件,对于每一行,以如下格式输出键/值对:
Timestamp of the access (in HH:MM format),latitude,longitude 1
在这个例子中,key是以逗号作为分隔符,包含了日志文件中每次点击的时间戳、纬度、经度,而value是1(表示一次点击计数值)。
然后,Reducer逐行读取Mapper的输出,保存每分钟每个地理位置的点击计数值。因此,它把Mapper输出的每个“key”存储到一个Python字典中,每次遇到Mapper的输出有相同的“key”,就把其在字典中的计数值增加1。Python字典看起来大概如下:
{
“12:00,40.7308,-73.9970″: 128,
“12:00,37.7791,-122.4200″: 33,
“12:00,32.7781, -96.7954″: 17,
# cut off for brevity…
“12:01,40.7308,-73.9970″: 119,
“12:01,37.7791,-122.4200″: 45,
“12:01,32.7781, -96.7954″: 27,
# …
}
一旦Reducer读取了Mapper的所有的数据输入,它对数据进行排序(基于key),然后输出排序的结果。
新的数据格式
在原始访问数据上运行完新的map/reduce脚本后,我们得到了一组更准确的数据集。这个过程不仅减少了总的数据量(Web站点的访问数据,从3000万行左右减少到300万行),而且为我们生成了每个地理位置的计数值。现在,我们需要确定比例因子。以下是新的结果数据的样本——注意时间戳、纬度、经度和(每分钟的)点击计数值。
12:00,039.948,-074.905,128
12:00,039.949,-082.057,1
12:00,039.951,-105.045,3
12:00,039.952,-074.995,1
12:00,039.952,-075.164,398
12:00,039.960,-075.270,1
12:00,039.963,-076.728,4
12:00,039.970,-075.832,2
12:00,039.970,-086.160,4
12:00,039.975,-075.048,23
可视化比例和其他可视化优化
有了新形式的数据,我们不再是每秒钟为每次点击画一个点,而是可以每分钟为每个地理位置的点击数值画一个圆圈,并根据点击数计算圆圈大小。这种方式可以生成期望的比例显示,使得可视化的读者可以轻松地区分来自加拿大农村和纽约市的不同的流量差别。
这种方式也极大地减少了应用程序需要的内存量。我们还是需要在内存中保存Web站点和手机站点的所有点击(这样我们才能消隐去时间超过3分钟的点击),但是因为我们现在保存的是每分钟每个地理位置的点击数,极大地减少了需要的Hit对象数量。对于任一分钟,来自全世界的流量通常包含2000~3500个不同的地理位置。每个位置的Hit对象必须存储在内存中;每个Hit对象生命期是3分钟,因此对于任一时刻,内存中可能有6000~12 000个对象——数量还是很多,但是已经远远小于前一版本的对象数量。
现在,需要更新Processing应用程序,从而可以实时保存每个位置在任一时刻的点击数,而且圆圈大小比例可以根据点击数调整。
使定时拍摄能够正常工作
对Processing应用程序进行升级使其能够处理新的数据格式和方法,在此之后,我们创建了一个完整的历时24小时的定时拍摄视频。我们新的代码每次能够正常运行几个小时,不存在之前遇到的内存和整体机器延时,现在是生成完整的定时拍摄视频的时候了。不再像第一次那样尝试在地图上为历时24小时定时拍摄渲染Web站点和手机站点数据,我们只使用手机站点的数据(其数据量大约是Web站点数据量的10%)。这样,我们就可以比同时渲染Web站点和手机站点数据更快地查看到结果或者发现可能存在的问题。
由于不确定应该对24小时的定时拍摄进行多大程度的收缩,我们决定测试一下,采用10分钟。该项目最激动人心的时刻之一是当我们首次尝试渲染24小时的手机站点数据时,点击Processing的运行(Run)按钮那一刻。把数据在一台MacBook Pro机上渲染成10分钟的定时拍摄视频花了约2个小时。结果生成了!
大家互相击拳祝贺后,开始观看视频。看了大约两分钟,我们意识到视频时间太长了——感觉视频太慢了!开始重新装载数据,创建一个历时接近1.5分钟的视频。经过几次尝试以及对代码和帧速率的调整,我们生成了新的视频。对较小规模的手机站点数据集进行渲染可以正常工作后,我们开始在Web站点和手机站点的混合数据集上尝试。由于数据量比之前大得多,渲染花费的时间也长很多——之前是2个小时,这次渲染花了24~36个小时,这取决于其所用的机器的性能。
半自动化
最后,我们希望能够对整个过程实现自动化,这样程序接收到输入命令后,可以执行任何一天的定时拍摄渲染。该过程现在是半自动化的,我们可以很容易为同一天渲染多个定时拍摄的视频。举个例子,我们可以针对以下任何一种情况进行渲染:
- 世界地图的Web站点和手机站点的数据。
- 美国地图的Web站点和手机站点的数据。
- 世界地图和美国地图的Web站点的数据。
- 世界地图和美国地图的手机站点数据。
每种类型的数据需要花多长时间渲染?这取决于日期以及那一天是否是重大新闻日(即是否有很大流量)。
渲染定时拍摄视频的数据计算
在Processing应用程序内,我们每秒钟捕获15帧的视频。对于每一帧,在屏幕上绘制了1 分钟的日志量。对于24小时的数据量,需要捕获1440分钟的数据。把每15分钟的数据渲染成时间长度为一秒的视频,则1440分钟的数据会生成96秒钟的视频(约1.5分钟)。
生成的视频有什么用
在纽约时报大厦28层的走廊上挂着10台监视器,播放着我们所做的一些可视化视频,包括这些流量图。其中有6台监视器自动播放本章介绍的定时拍摄视频;其他4台屏幕上显示的是《纽约时报》Web站点和手机站点当天全部流量的快照(美国和全球)。我们开始在公司内分享这些视频,并且探索更多的可视化来查看一天内可以发现哪些模式。我们还观察“重大新闻日”和“平常日”中,用户使用模式的差异。
结束语
我们从目前创建的可视化中观察到了一些有趣的模式,绝大多数如图2~图5所示。
第一个模式是手机站点的流量在美国约早上5点或6点开始暴涨,该时段人们醒来开始去上班(尤其是在东海岸)。在约8点半或9点人们到达办公室前,手机站点流量一直很大;而当人们到达办公室时,Web站点流量开始第一次大增。Web站点的流量在一整天都很大(尤其是午饭时间),下午稍有点下降,很可能是人们在下班路上,而这时手机站点的流量又开始增加。这个观察和我们开始研究前的预期相同,但是该可视化进一步证实了我们的猜想。
另一个有趣的模式是Web和手机站点的国际流量都很大,非洲、中国、印度和日本某些地区的手机站点流量也很大。
本文节选自机械工业出版社华章公司《数据可视化之美》一书。该书介绍了数据可视化的方法和思想,通过描述分析很多实例,带领读者深入洞察数据可视化之美。特此感谢华章公司与Michael Young和Nick Bilton先生授权。