![《架构师》2020年3月](https://wfqqreader-1252317822.image.myqcloud.com/cover/498/30269498/b_30269498.jpg)
如何用Python在笔记本上分析100GB数据?
许多组织都想尽可能多地收集和利用数据,从而改进业务、增加收入和提升影响力。因此,数据科学家们要面对50GB,甚至500GB数据集的场景变得越来越普遍。
目前,这些数据集处理起来有点麻烦。就大小而言,它们可以放进你笔记本电脑的硬盘里,但却无法装入内存。所以,仅仅打开和查看它们就很困难,更何况进一步探索和分析。
处理这样的数据集时,一般有3种策略。
第1种是对数据进行子抽样,但它有一个明显缺点:可能因忽略部分数据而错失关键信息,甚至误解数据表达的含义。
第2种是使用分布式计算。虽然在某些情况下这是一种有效的方法,但是管理和维护集群会带来巨大开销。想象一下,要为一个刚超出内存大小、大概30-50GB的数据集就建立一套集群,对我来说,这似乎有点“用力过猛”。
第3种是租用一个内存大小等同于数据集大小的强大云服务实例,例如,AWS提供了TB级内存的云服务实例。但这种情况还需要管理云数据存储空间,并且在每次实例启动时都要等待数据从存储空间传输到实例。另外还需要应对数据上云的合规性问题,以及忍受在远程机器上工作带来的不便。更别提成本,虽然开始会比较低,但随着时间推移会快速上涨。
本文向你展示一种全新方法,它更快、更安全,可以更方便、全面地对几乎任意大小的数据集进行数据科学研究,只要这个数据集能装进你的笔记本电脑、台式机或者服务器的硬盘里就行。
Vaex
Vaex是一个开源的DataFrame库,对于和你硬盘空间一样大小的表格数据集,它可以有效进行可视化、探索、分析乃至实践机器学习。
为实现这些功能,Vaex采用内存映射、高效的核外算法和延迟计算等概念。所有这些都封装为类Pandas的API,因此,任何人都能快速上手。
10亿级出租车的数据分析
为阐述这些概念,我们对一个远超出一般笔记本电脑内存大小的数据集进行简单地探索分析。
这里,我们使用New York City(NYC) Taxi数据集,它包含了标志性的黄色出租车2009年到2015年间超过十亿次的出租车行程信息。
数据从网站下载,提供CSV格式。完整分析可以单独查看这个Jupyter notebook。
0.052秒打开100G的数据集
第一步是将数据转换为内存映射文件格式,如Apache Arrow、Apache Parquet或HDF5。关于如何把CSV数据转为HDF5的例子请看这里。一旦数据存为内存映射格式,即便它的磁盘大小超过100GB,用Vaex也可以在瞬间打开它(0.052秒):
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0023_0001.jpg?sign=1738856416-dm2zMTAviVDcQ7xmg5Tf2vAMR2KkictS-0-e388c9834b08fe3e24dc92ada13d07e1)
Vaex瞬间打开内存映射文件(0.052秒)
为什么这么快?在用Vaex打开内存映射文件时,实际上并没有读取数据。Vaex只读取了文件元数据,比如数据在磁盘上的位置、数据结构(行数、列数、列名和类型)、文件描述等等。那如果想查看或者操作数据呢?
将数据结果展示在一个标准的DataFrame中进行预览,速度一样非常快。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0024_0001.jpg?sign=1738856416-rThLkWjBbYTRvO4Iyr98dLtLBbLaNBck-0-245200055e3b73bbe1650ec896128fda)
预览New York City Yellow Taxi数据
代码单元执行时间还是非常短。这是因为展示Vaex DataFrame或者某列,只需要从硬盘中读取前5行和后5行数据。这里引出另一个要点:Vaex只会在必要的时候遍历整个数据集,而且它会尽可能遍历更少的数据。
不管怎样,我们先从异常值和错误的输入值开始清理这个数据集。一种好的方式是用describe方法获取数据的高级概览,它可以展示样本数量、缺失值的数量和每列的数据类型。
如果某列的数据类型是数值,它还将展示其平均值、标准差、最小值及最大值。而所有的这些数据都是通过一次数据遍历计算的。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0024_0002.jpg?sign=1738856416-rBQKEniutPUlzBlpKJSgmmBkblLamdIt-0-b62e1b6c8102db1c27c798ab6e913b73)
使用describe方法获得DataFrame的高级概览,注意这个DataFrame包含18列数据,不过截图只展示了前7列。
describe方法很好地体现了Vaex的能力和效率:所有这些数据都是在我的MacBook Pro(15英寸、2018款、2.6GHz Intel Core i7和32G内存)上在3分钟内计算出来的。其他库或方法则需要分布式计算或超过100GB的云服务实例才能完成相同的计算,而有了Vaex你需要的只是数据以及拥有几GB内存的笔记本电脑。
查看describe的输出很容易发现这个数据包含了一些明显的异常值。
首先从检查上车点开始,去除异常值最简单的方法就是绘制出上车点和下车点位置,并且直观地确定纽约哪些地区是要分析关注的。由于要处理的数据集如此庞大,直方图是最有效的可视化方法。用Vaex创建和展示直方图及热力图相当快,而且图表还是可交互的!
df.plot_widget(df.pickup_longitude, df.pickup_latitude, shape=512, limits='minmax', f='log1p', colormap='plasma')
一旦通过交互确定纽约哪些区域是要关注的区域后,我们就可以创建一个筛选后的DataFrame:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0025_0001.jpg?sign=1738856416-GDuDcngH4Qfzb9tnTNvBGCoRqkGq3IG7-0-945e9ccbdaeb3840c63d93542badc7d9)
上述代码很酷的一点是,它只需很少内存就可以执行!在筛选Vaex DataFrame时并不会复制数据。而是只创建对原始对象的引用,并在其上应用二进制掩码。用掩码来选择哪些行将被显示以及将来用于计算。这为我们节省了100GB的内存,而像现在许多标准数据科学工具则必须得复制数据才行。
现在,来看下passenger_count这一列。单次出租车行程乘坐人数的最大值是255,这似乎有点夸张。我们来数数每次行程的乘客人数,这里用value_counts方法很容易实现:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0026_0001.jpg?sign=1738856416-QrKtCOugk3y0ivfuiETCK1S22oGFd8sv-0-5d80d3f7535aef55e3d24d3e82169994)
在10亿行数据上使用value_counts方法只需要20秒!
从上图可以看出,乘客超出6人可能是少见的异常值或者是错误的数据输入。同时还有大量乘客数为0的行程。既然不知道这些行程是否合理,就先把它们过滤掉。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0026_0002.jpg?sign=1738856416-8lgMJO7vYiy0YrObPgru0GLDjAb4wjvd-0-d73056e4aebe594ecb5fd0811c91d3b9)
再用行程距离做一个类似的练习。由于它是一个连续的变量,我们可以绘制出行程距离的分布情况。行程距离的最小值是负值,而最大值比火星都远,所以还是限制在一个合理的区间内绘制直方图。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0026_0003.jpg?sign=1738856416-ZVWn3I4mLaVlSZa82a145VfmG7czbIQc-0-86d4a8f908774b61f14f5482ce645718)
从上图中可以看到,行程数量随着距离的增加而减少。在距离大约为100英里处,分布有明显下降。现在,我们用这个作为分界点来消除行程距离的异常值。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0027_0001.jpg?sign=1738856416-OWY7UVyIHe1dMS6Udi0O5J2r2gSQ8kbp-0-afc5c8826ccaec91b329a848c922b59c)
在行程距离这一列中存在异常值,也因此有了动机查看行程耗费时间和平均速度。数据集中并没有提供这两个特征数据,但是很容易计算得出:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0027_0002.jpg?sign=1738856416-NSDpVloHdtbxx7vTHtRTgaetplKaSNdt-0-ebf8bf959462776694d0905687ef508b)
上面的代码块不需要内存也不消耗执行时间!是因为代码只会创建虚拟列(virtual columns),这些虚拟列只包含数学表达式,仅在需要时才进行计算。除此之外,虚拟列和其他常规列是一样的。注意,其他的标准库可能需要数十GB的内存才能实现相同操作。
好了,我们来绘制行程耗费时间的分布:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0027_0003.jpg?sign=1738856416-iXj8h8VANAVfU7GEspCLVQmQQdR95ozp-0-f86c04ca27b6186a60de310f9063a558)
纽约超过10亿次出租车行程耗费时间的直方图
从上图可以看到,95%的出租车行程不到30分钟就可以到达目的地,但有些行程可能花费超过4-5个小时。你能想象在纽约市被困在出租车里3个多小时的情景么?不管怎样,我们豁达点只考虑少于3小时的行程:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0028_0001.jpg?sign=1738856416-LsQ9DFMQRHzAz7pAsqvvDAiIMcBK1Htv-0-1593737bbae8f424cce9453b30df5d62)
对于出租车的平均速度,也选择一个合理的范围来查看:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0028_0002.jpg?sign=1738856416-7GihYdughRKFrQ03R01FjlNE08X8nvVG-0-27d877a4fe37820d7a96846d15f0c4aa)
出租车平均速度分布
根据分布趋平的位置,可以推断出租车合理的平均速度在每小时1到60英里之间,由此可以更新筛选后的DataFrame:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0028_0003.jpg?sign=1738856416-Qimgj9MtfHjb1D9avCyoVQG5VjlV3UPf-0-e49d2d8d79cf730b13b92ca7e1dd70d8)
把关注点切换到出租车行程的费用上,从describe方法的输出可以看到fare_amount、total_amount和tip_amount列都有一些夸张的异常值。首先,这几列都不应该出现负值。
另一方面,有些数字表示一些幸运的司机只开一次出租车就快要成为百万富翁了。让我们在合理的范围内查看这些数量的分布:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0029_0001.jpg?sign=1738856416-kOjTPeNJFuXQc7undFlkJkLJPTqKqFfw-0-02a1c28157f6fd50853c0551f68462b4)
纽约超过10亿次出租车行程的车费、总额和小费的分布。在笔记本上绘制这些图表只用了31秒!
以上三个分布都有相当长的尾部。尾部可能有一些正确的值,而其他可能都是错误的输入。不管怎样,我们保守一点只考虑fare_amount、total_amount和tip_amount低于200美元的行程。另外fare_amount、total_amount的值还要大于0。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0029_0002.jpg?sign=1738856416-BfyQI6RCccDOj3tIaoMiKXFsaZHRwND2-0-d4cd32b7608fe8c2266cd85ac84d59d5)
最终,在初步清理之后,看看还剩下多少出租车行程数据可供分析:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0029_0003.jpg?sign=1738856416-FwiQ9vX2Ru7SETCI6i7WQFUEqXnhRYec-0-078b4d47f9b93a754f56a0dbd4a960f1)
还剩下11亿的行程!这些数据足以让我们从出租车出行中获得一些有价值的见解。
坐上驾驶座
假设我们是一名出租车司机或者一家出租车公司的经理,有兴趣使用这些数据来了解如何最大化利润、最小化成本或者仅仅是改善我们的工作生活。
首先,我们找出可以带来平均最高收益的接客地点。简单讲,只需要绘制出接客地点的热力图,并用颜色标记平均价格,然后查看热点地区。但是由于出租车司机需要自行承担费用,例如燃料费。
因此,虽然将乘客载到较远的地方可能带来更高车费,但同时也意味着更多的燃料消耗和时间损失。
另外,在偏远地区找一个回市中心某地的乘客可不是那么容易,而在没有乘客的情况下回程就会很浪费。一种解决方法是用车费和行程距离之比的平均值对热力图进行颜色编码。我们来尝试一下这两种方法:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0030_0001.jpg?sign=1738856416-H0rWKEQbyKTFTB5Zd9cnIxOwWgy4bQ7V-0-31af4a8e95fdaa09995a02125ce37240)
纽约热力图,颜色编码:平均车费(左),车费与行程距离的平均比值
简单情况下,只关心所提供服务的最高车费时,纽约机场以及像范怀克高速公路(Van Wyck Expressway)和长岛高速公路(Long Island Expressway)这样的主干道是最佳的载客区。
当考虑行程距离时,会得到一张不同的图像。范怀克高速公路、长岛高速公路以及机场仍然是搭载乘客的好地方,但它们在地图上的重要性要低得多。而在哈德逊河(Hudson river)的西侧出现了一些新的热点地区,看起来利润颇丰。
作为出租车司机,实践中可以做得很灵活。要想更好地利用这种灵活性,除了知道要在哪里载客之外,了解什么时候出车最赚钱也很有用。为回答这个问题,我们绘制一个图表来展示每天和每小时的平均车费与行程距离之比。
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0031_0001.jpg?sign=1738856416-SwDEdoxtZgt14ZRL9g3PGV5S1E6zr87G-0-18b713a3b404525ee4a7f6ad0bcbab5e)
一周中每天以及一天中每小时的车费与行程的平均比值
上面的图很符合常识:高峰时段最挣钱,特别是工作日的中午。作为出租车司机,收入的一部分要交给出租车公司,所以我们可能会对哪天、哪个时段顾客给的小费最多感兴趣。绘制一个类似的图展示平均小费比例:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0031_0002.jpg?sign=1738856416-FxyvSXYUHKVRFJe6VXEZaKb7p6vTJXcI-0-4dc7c6a5703212054ee883e649424862)
一周中每天以及一天中每小时的小费比例平均值
上图很有意思,它告诉我们在一周中前几天的早上7-10点和晚上7-10点乘客给司机的小费最多。如果在凌晨3-4点接乘客,不要指望会有大额小费。
结合最后两张图的经验,早上8点到10点是最好的工作时间,司机可以得到较多的车费(每英里)和小费。
发动引擎
在本文的前半部分,我们简要地关注了trip_distance列,在给它清理异常值时,只保留了低于100英里的行程。但这个边界值仍然很大,尤其是考虑到Yellow Taxi公司主要在曼哈顿运营。
trip_distance描述出租车从上车点到下车点的行驶距离,但是在确切的两个上车点和下车点之间,通常有多条不同距离的路线可以选择,比如为了避开交通堵塞和道路施工的情况。
所以,相对于trip_distance列,我们计算一项接送位置之间可能的最短距离,命名为arc_distance:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0032_0001.jpg?sign=1738856416-WohJjlaI1S91OXK5neVHsiR0Lh4EiHQx-0-1fcd9b2ce9a818e0322f595fe74f1f48)
对于用numpy编写的复杂表达式,vaex可以借助Numba、Pythran甚至CUDA(需要NVIDIA GPU)通过即时编译来极大提高运算速度。
arc_distance的计算公式非常复杂,它包含了大量的三角函数和数学运算,在处理大型数据集时计算代价非常高。如果表达式或函数只用到了Python运算符和Numpy库的方法,Vaex会使用计算机的所有核心来并行计算。
除此之外,Vaex通过Numba(使用LLVM)和Pythran(通过C++加速)支持即时编译从而提供更好性能。如果你碰巧有NVIDIA显卡,就可以通过jit_cuda方法来运用CUDA以获取更快的性能。
我们来绘制trip_distance和arc_distance的分布:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0033_0001.jpg?sign=1738856416-VkYtBBPc8SsUThmOM115fP7FryjtUkMD-0-3d4d4328f5d5e1e72f6087339a4ce641)
左:trip_distance和arc_distance的比较;右:arc_distance<100米时trip_distance的分布。
有意思的是,arc_distance从来没有超过21英里,但出租车实际行驶距离可能是它的5倍。事实上,有数百万次的行程下客点距离上客点只有100米(0.06英里)!
过去几年的Yellow Taxis
我们今天使用数据集时间上跨越了7年。随着时间流逝,人们的兴趣如何演变可能是件有趣的事。使用Vaex可以进行快速的核外分组和聚合操作。
我们来看看7年间车费和行程距离都有什么变化:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0033_0002.jpg?sign=1738856416-AT5wC9kMrSQDMsIgq1dJcrvEW62WewTH-0-4b637ce85bd6f2ab86fed5664194d87a)
在四核处理器的笔记本电脑上,对拥有超过10亿样本的Vaex DataFrame进行8个聚合的分组操作只需不到2分钟。
在上面的代码块中,我们执行分组操作,然后执行8个聚合,其中有2个位于虚拟列上。上面的代码块在我的笔记本电脑上执行耗时不到2分钟。鉴于我们使用的数据包含超过10亿条样本,这是相当惊人的。
总之,我们来看看结果,以下是多年来乘坐出租车的费用的变化情况:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0034_0001.jpg?sign=1738856416-Z8G9vJVgBc4PELwKSA3Urn50crVg6Xd3-0-379fcbd7974ef08648c4aa750bb33c34)
每年的平均车费和总金额以及乘客所付的小费比例
可以看出随着时间流逝,出租车费和小费都在上涨。再来看看出租车每年的平均trip_disrance和arc_distance:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0034_0002.jpg?sign=1738856416-YOUEZgwjAotHvPQ1NyJjZe5Z4shuYD2C-0-14adfb3d9a39d3abcaba6a2a661682fa)
出租车每年的行程和弧距
上图显示,trip_distance和arc_distance都有一个小的增长,这意味着,平均而言,人们倾向于每年走得更远一点。
给钱吧
在旅程结束之前,我们再停一站,调查一下乘客是如何支付乘车费用的。数据集中包含了payment_type,来看看它都包含什么值:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0035_0001.jpg?sign=1738856416-FJZmCsydSMg6MiafadV8pXoZdtTjb4fR-0-3bf7900197143f274637d09a3b20f1cc)
从数据集文档中,可以看出只有6个有效条目:
1 =信用卡支付
2 =现金付款
3 =免费
4 =争议
5 =未知
6 =无效行程
由此,可以简单地把payment_type映射到整数:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0035_0002.jpg?sign=1738856416-K7bIKTdwOWDRfLm50sMTOH5vPopR19lN-0-1dbdc3e48bb95ca5c687c50c5d6832c7)
现在可以根据每年的数据进行分组,看看纽约人在支付打车费用方面的习惯是如何变化的:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0035_0003.jpg?sign=1738856416-hvf3Sxtk5eOF5teIHDGgaN2KHc3jSjrJ-0-b7f3d1a993954c8a8df37e80fb42e652)
每年的支付方式
可以发现随着时间的推移,信用卡支付逐渐变得比现金支付更加频繁。我们果然是生活在数字时代!在上面的代码块中,一旦完成数据聚合,小型的Vaex DataFrame可以轻易地转换为Pandas DataFrame,从而传递给Seaborn。不用费劲在这重新发明轮子。
最后通过绘制现金支付和信用卡支付之间的比例,来查看付款方法是否取决于当天的时间或者星期几。为此,先创建一个过滤器,筛出用现金或者信用卡的行程。
下一步是我最喜欢的Vaex的特性之一:带选择的聚合。其他库要求对每个支付方法筛选出单独的DataFrame进行聚合,然后再合并为一个。
而使用Vaex,可以在聚合函数中提供多个选择从而一步到位。这非常方便,只需进行一次数据传递,能提供更好性能。之后就可以用标准方式绘制DataFrame:
![](https://epubservercos.yuewen.com/B060CD/16585419005261606/epubprivate/OEBPS/Images/figure_0036_0001.jpg?sign=1738856416-NSmMBMGKVCXlMiC2vHtYb9x7FWlYH2bV-0-40e1cae7828072fcb657fb1e74531d42)
在给定的时间和星期中某一天,现金和信用卡支付的比例
从上图可以发现其模式非常类似于之前的一周中每天以及一天中每小时的小费比例。从这两个图推测,信用卡支付的乘客倾向于比现金支付的乘客给更多的小费。
要想知道这是不是真的,我希望你去尝试把它弄清楚,因为现在你已经拥有了知识、工具和数据!你可以从这个Jupyter notebook获取更到额外提示。
抵达目的地
我希望这篇文章是对Vaex一个有用的介绍,它会帮你缓解可能面临的一些“麻烦数据”的问题,至少在涉及表数据集时是这样的。如果你对探索本文中用到的数据集感兴趣,可以直接在S3中配合Vaex使用它,请参阅完整的Jupyter notebook了解如何实现。
有了Vaex,你可以在短短几秒内遍历超过10亿行数据,计算各种统计、聚合并产出信息图表,这一切都能在你的笔记本电脑上完成。它免费且开源,你可以尝试一下!
数据科学快乐!
Vaex官方网站:https://vaex.io/,目前官方团队提供1小时免费咨询支持。