如何用Python在笔记本电脑上分析100GB数据?
许多组织都试图收集和利用尽可能多的数据,以改进他们如何经营业务、增加收入或如何影响周围的世界。因此,数据科学家面对50GB甚至500GB大小的数据集的情况变得越来越普遍。
现在,这些数据集使用起来有点…不舒服。它们小到可以装进你日常使用的笔记本电脑的硬盘,但大到可以装进内存。因此,它们已经很难打开和检查,更不用说探索或分析了。
在处理这样的数据集时,通常采用3种策略。第一种是对数据进行子抽样。这里的缺点是显而易见的:一个人可能会因为不看相关部分而错过关键的洞见,或者更糟的是,不看全部内容可能会曲解故事和它所讲述的数据。下一个策略是使用分布式计算。虽然在某些情况下这是一种有效的方法,但是它带来管理和维护集群的巨大开销。想象一下,必须为一个刚好超出RAM范围的数据集设置一个集群,比如在30 – 50gb范围内。对我来说,这似乎太过分了。另一种选择是,可以租用一个强大的云实例,该实例的内存与处理相关数据所需的内存一样多。例如,AWS提供了具有tb内存的实例。在这种情况下,您仍然需要管理云数据桶,等待每次实例启动时从桶到实例的数据传输,处理将数据放到云上所带来的遵从性问题,以及处理在远程机器上工作所带来的所有不便。更不用说成本了,虽然开始时很低,但随着时间的推移,成本会逐渐增加。
在本文中,我将向您展示一种新的方法:一种更快速、更安全、更全面、更方便的方法,可以使用几乎任意大小的数据进行数据科学研究,只要它能适合您的笔记本电脑、台式机或服务器的硬盘驱动器即可。
Vaex
Vaex是一个开源的DataFrame库,它可以在与硬盘大小相同的表格数据集上进行可视化、探索、分析甚至机器学习。为此,Vaex采用了内存映射、高效的外核算法和延迟计算等概念。所有这些都封装在一个熟悉的类似pandas的API中,因此任何人都可以立即开始。
亿辆出租车的分析
为了说明这一概念,让我们对一个数据集进行简单的探索性数据分析,该数据集非常大,无法容纳典型的笔记本电脑的RAM。在本文中,我们将使用纽约市出租车数据集,该数据集包含了标志性的黄色出租车在2009年至2015年间超过10亿次出租车运行的信息。数据可从本网站下载,并以CSV格式提供。完整的分析可以在这个Jupyter notebook中单独查看。
清扫街道
第一步是将数据转换为内存映射文件格式,如Apache Arrow、Apache Parquet或HDF5。在这里可以找到如何将CSV数据转换为HDF5的示例。一旦数据是内存映射格式,使用Vaex打开它是瞬间的(0.052秒!),尽管磁盘上的容量超过100GB:
使用Vaex打开内存映射文件是即时的(0.052秒!),即使它们超过100GB大。
为什么这么快?当您使用Vaex打开内存映射文件时,实际上没有数据读取。Vaex只读取文件元数据,比如磁盘上数据的位置、数据结构(行数、列数、列名和类型)、文件描述等等。那么,如果我们想要检查或与数据交互呢?打开一个数据集的结果是一个标准的数据aframe和检查它是一样快,因为它是琐碎的:
纽约市黄色出租车数据预览
再次注意,单元执行时间非常短。这是因为显示Vaex DataFrame或列只需要从磁盘读取前5行和后5行。这就引出了另一个重要的问题:Vaex只会在必要时遍历整个数据集,而且它会尽可能少地传递数据。
无论如何,让我们首先从极端异常值或错误的数据输入中清理这个数据集开始。一种好的开始方法是使用describe方法获得数据的高级概览,该方法显示了样本的数量、缺失值的数量和每个列的数据类型。如果列的数据类型是numerical,则还将显示平均值、标准偏差以及最小值和最大值。所有这些统计数据都是通过一次数据传递计算的。
使用“describe”方法获得数据帧的高级概述。注意,DataFrame包含18列,但在此屏幕截图中只有前7列可见。
describe方法很好地说明了Vaex的能力和效率:所有这些统计数据都是在我的MacBook Pro(15英寸,2018年,2.6GHz Intel Core i7, 32GB RAM)上3分钟内计算出来的。其他库或方法需要分布式计算或超过100GB的云实例才能完成相同的计算。有了Vaex,你所需要的只是数据,和你的笔记本电脑只有几GB的内存。
查看description的输出,很容易注意到数据包含一些严重的异常值。首先,让我们从检查取货地点开始。删除异常值的最简单方法是简单地绘制出上下车的位置,并直观地定义我们希望重点分析的纽约市的区域。由于我们使用的是如此大的数据集,直方图是最有效的可视化方法。用Vaex创建和显示柱状图和热图是如此的快,这样的图可以是交互式的!
df.plot_widget(df.pickup_longitude,
df.pickup_latitude,
shape=512,
limits='minmax',
f='log1p',
colormap='plasma')
一旦我们交互式地决定我们想要关注纽约市的哪个区域,我们可以简单地创建一个过滤后的数据aframe:
上面代码块最酷的地方是它需要的内存可以忽略不计!过滤Vaex数据帧时,不会生成数据的副本。相反,只创建对原始对象的引用,并在其上应用二进制掩码。掩码选择显示哪些行并用于将来的计算。这为我们节省了100GB的RAM,如果要复制数据,就像今天许多标准的数据科学工具所做的那样。
现在,让我们检查一下乘客计数栏。在一次出租车行程中记录的乘客人数最多为255人,这似乎有点极端。让我们计算一下每一位乘客的运行次数。这是很容易做到的value_counts方法:
应用于10亿行的“value_counts”方法只需要~20秒!
从上图中我们可以看出,乘客超过6人的运行很可能是罕见的异常值,或者只是数据输入错误。也有大量的运行是0名乘客。既然现在我们还不知道这些数据是否合法,那就让我们把它们过滤掉吧。
让我们做一个关于运行距离的类似练习。由于这是一个连续变量,我们可以绘制行程的分布。看最小值(负数!)和最大(比火星更远!)的距离,让我们用一个更合理的范围来绘制直方图。
纽约出租车数据集的行程距离直方图。
从上面的图表我们可以看出,运行次数随着距离的增加而减少。在大约100英里的距离上,分布有一个很大的下降。目前,我们将使用此作为截止点,以消除基于行程距离的极端异常值:
在运行距离列中存在极端异常值,可以作为调查出租车运行时长和平均速度的动机。这些特性在数据集中不易获得,但计算起来却很简单:
上面的代码块需要零内存,不需要时间执行!这是因为代码导致创建虚拟列。这些列仅包含数学表达式,并且仅在需要时计算。否则,虚拟列的行为与任何其他常规列一样。请注意,其他标准库在相同的操作中需要10GB的RAM。
好吧,我们来绘制运行时间的分布图:
纽约10亿多次出租车运营持续时间的直方图。
从上面的图中我们可以看到95%的出租车旅行都不到30分钟就能到达目的地,尽管有些旅程可以花费4到5个小时。你能想象在纽约被困在出租车里超过3个小时吗?不管怎样,让我们开诚布公,考虑一下总共持续不到3小时的所有行程:
现在让我们调查出租车的平均速度,同时也为数据限制选择一个合理的范围:
平均出租车速度的分布。
根据分布趋平的地方,我们可以推断出合理的出租车平均速度在每小时1到60英里之间,因此我们可以更新过滤后的DataFrame:
让我们把注意力转移到出租车运营成本上。从describe方法的输出中,我们可以看到fare_amount、total_amount和tip_amount列中有一些异常值。首先,这些列中的任何值都不应为负。与此相反,这些数字表明,一些幸运的司机几乎成了百万富翁。让我们看看这些量的分布,但在一个相对合理的范围内:
纽约10亿多个出租车出行的票价、总金额和小费的分布情况。在笔记本电脑上创建这些图只用了31秒!
我们看到上面的三个分布都有相当长的尾部。尾部的一些值可能是正确的,而其他值可能是错误的数据输入。无论如何,现在我们还是保守一点,只考虑票价、总票价和小费低于200美元的乘车。我们还要求票价金额、总金额值大于0美元。
最后,在对数据进行了所有的初始清理之后,让我们看看还有多少出租车行程可供我们分析:
我们还有超过11亿次的行程!这些数据足以让我们对出租车行程有一些有价值的见解。
坐进驾驶座
假设我们是一个未来的出租车司机,或出租车公司的经理,并有兴趣使用这个数据集来学习如何最大限度地提高我们的利润,降低我们的成本,或者只是改善我们的工作生活。
让我们先找出平均来说能带来最好收益的接送乘客的地点。天真地说,我们可以画出一张接客地点的热图,用平均票价编码,然后看看热点地区。然而,出租车司机自己也有成本。例如,他们得付燃料费。因此,把乘客带到很远的地方可能会导致更大的票价,但这也意味着更大的油耗和时间损失。此外,要从那个偏远的地方找到一个乘客去市中心的某个地方可能不那么容易,因此没有乘客的情况下开车回去可能会很昂贵。一种解释方法是用车费和行程距离之比的平均值对热图进行颜色编码。让我们考虑这两种方法:
纽约市彩色热图编码:平均票价金额(左)和票价金额与行程的平均比率。
在幼稚的情况下,当我们只关心为提供的服务获得最大票价时从纽约机场、以及范怀克(Van Wyck)高速公路和长岛(Long Island)高速公路等主要道路上搭载乘客的最佳区域是纽约机场。当我们考虑行程的距离时,我们得到的图像略有不同。范怀克(Van Wyck)高速公路、长岛(Long Island)高速公路大道以及机场仍然是搭载乘客的好地方,但它们在地图上的重要性要低得多。然而,在哈德逊河的西侧出现了一些新的热点地区,看起来利润颇丰。
出租车司机是一份相当灵活的工作。为了更好地利用这种灵活性,除了知道开车应该躲在什么地方之外,知道什么时候开车最赚钱也是很有用的。为了回答这个问题,让我们制作一个图表,显示每天和每小时的平均票价与旅行距离之比:
票价与每周每天行程距离和每天小时的平均比率。
上面的数字是合理的:最好的收入发生在高峰时间,特别是中午,在工作日。作为一名出租车司机,我们收入的一部分给了出租车公司,所以我们可能会对哪一天、哪段时间顾客给的小费最多感兴趣。让我们制作一个类似的图表,这次显示的是平均小费比例:
每周每天和每天小时的平均小费百分比。
上面的情节很有趣。它告诉我们,乘客在早上7点到10点之间和在本周早些时候的晚上给出租车司机小费。如果你在凌晨3点或4点接乘客,不要指望会有丰厚的小费。结合上两个部分的分析,早上8点到10点是上班的好时间:一个人每英里可以得到一个好的车费和一个好的小费。
发动引擎!
在本文的前一部分中,我们简要介绍了trip_distance列,在从异常值中清除它的同时,我们保留了所有小于100英里的行程值。这仍然是一个相当大的临界值,尤其是考虑到Yellow Taxi公司主要在曼哈顿运营。trip_distance列描述出租车从上客点到下客点的距离。然而,人们经常可以选择不同的路线,在两个确切的接送地点之间有不同的距离,例如为了避免交通堵塞或道路工程。因此,作为trip_distance列的一个对应项,让我们计算接送位置之间可能的最短距离,我们称之为arc_distance:
对于用numpy编写的复杂表达式,vaex可以在Numba、Pythran甚至CUDA(如果你有NVIDIA GPU的话)的帮助下使用即时编译来极大地提高你的计算速度。
弧长计算公式涉及面广,包含了大量的三角函数和算法,特别是在处理大型数据集时,计算量大。如果表达式或函数只使用来自Numpy包的Python操作和方法编写,Vaex将使用机器的所有核心并行计算它。除此之外,通过使用Pythran(或通过C++加速)(通过使用C语言)加速,可以支持实时编译,提供更好的性能。如果您碰巧有一个NVIDIA图形卡,您可以通过jit_CUDA方法使用CUDA来获得更快的性能。
不管怎样,让我们来绘制行程距离和弧距离的分布:
左:行程距离和弧距离的比较。右:弧距<100米的行程分布。
有趣的是,arc_distance从未超过21英里,但出租车实际行驶的距离可能是它的5倍。事实上,在数百万次的出租车行程中,落客点距离接客点只有100米(0.06英里)!
多年来的黄色出租车
我们今天使用的数据集跨越7年。看看在那段时间里,人们对某些东西的兴趣是如何演变的,这可能会很有趣。使用Vaex,我们可以快速执行核心分组和聚合操作。让我们来探讨7年来票价和行程是如何演变的:
对于一个超过10亿个样本的Vaex数据帧,在笔记本电脑上使用四核处理器进行8个聚合的分组操作只需不到2分钟。
在上面的单元格块中,我们执行分组操作,然后执行8个聚合,其中2个位于虚拟列上。上面的单元块在我的笔记本电脑上执行不到2分钟。考虑到我们使用的数据包含超过10亿个样本,这是相当令人印象深刻的。不管怎样,让我们看看结果。以下是多年来乘坐出租车的费用是如何演变的:
平均票价和总金额,以及乘客每年支付的小费百分比。
我们看到,随着时间的流逝,出租车费和小费都在上涨。现在让我们看看出租车的平均行驶距离和arc_distance,出租车是以年为单位行驶的:
出租车每年的平均行程和弧距。
上图显示,出行距离和弧线距离都有小幅增加,这意味着,平均而言,人们每年的出行都会稍微远一点。
给我看看钱的方面
在我们的旅程结束之前,让我们再停一站,调查一下乘客如何支付乘车费用的。数据集包含付款类型列,因此让我们看看它包含的值:
从数据集文档中,我们可以看到此列只有6个有效条目:
1=信用卡支付
2=现金支付
3=不收费
4=争议
5=未知
6=无效行程
因此,我们可以简单地将payment_type列中的条目映射为整数:
现在,我们可以按每年的数据分组,看看纽约人在出租车租赁支付方面的习惯是如何改变的:
每年付款方式
我们看到,随着时间的推移,信用卡支付慢慢变得比现金支付更频繁。我们真的生活在一个数字时代!注意,在上面的代码块中,一旦我们聚合了数据,小的Vaex数据帧可以很容易地转换为Pandas数据帧,我们可以方便地将其传递给Seaborn。
最后,让我们通过绘制现金支付与信用卡支付的比率来确定支付方式是取决于一天中的时间还是一周中的某一天。为此,我们将首先创建一个过滤器,它只选择用现金或卡支付的乘车。下一步是我最喜欢的Vaex特性之一:带有选择的聚合。其他库要求对以后合并为一个支付方法的每个单独筛选的数据帧进行聚合。另一方面,使用Vaex,我们可以通过在聚合函数中提供选择来一步完成此操作。这非常方便,只需要一次传递数据,就可以获得更好的性能。在此之后,我们只需以标准方式绘制结果数据帧:
在一周的某一时间和某一天,现金对卡支付的一部分。
看上面的图表,我们可以发现一个类似的模式,显示小费百分比作为一周中的一天和一天中的时间的函数。从这两个图中,数据表明,用卡支付的乘客往往比用现金支付的乘客小费更多。为了弄清这是否真的是这样,我想请你试着去弄清楚,因为现在你已经掌握了知识、工具和数据!你也可以看看这个Jupyter notebook来获得一些额外的提示。
加入阿里云钉钉群享福利:每周技术直播,定期群内有奖活动、大咖问答
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。