0
许多组织正试图收集和利用尽可能多的数据,以改进其业务运营方式、增加收入或对周围世界产生更大的影响。因此,数据科学家面对 50GB 甚至 500GB 大小的数据集的情况变得越来越普遍。
但是现在,这些数据集使用起来并不方便。它们可能小到可以装进你日常笔记本电脑的硬盘,也可能大到和 RAM 匹配。因此,它们已经很难被打开和检查,更不用说探索或分析了。
在处理这些数据集时,通常使用 3 种策略。第一个是对数据进行子抽样。它的缺点是显而易见的:可能会错过关键的部分,或者更糟的是,不看全部内容可能会对数据和它表达的事实有所曲解。还有一个策略是使用分布式计算。虽然在某些情况下这是一种有效的方法,但它会带来管理和维护集群的巨大开销。想象一下,必须为一个不在 RAM 范围内的数据集(比如在 30-50GB 范围内)设置一个集群会是什么样子的。对我来说,这似乎难以承受。或者,你可以租用一个强大的云实例,该实例具有处理相关数据所需的足够内存。例如,AWS 提供了具有兆字节 RAM 的实例。在这种情况下,你仍然需要管理云数据存储桶,在每次实例启动时等待从存储桶到实例的数据传输,处理将数据放到云上带来的遵从性问题,并处理在远程计算机上工作带来的所有不便。当然,成本就更不用说了,虽然起价很低,但随着时间的推移,成本往往会越来越高。
在本文中,我将向你展示一种新的方法:只要数据可以被存进笔记本电脑、台式机或服务器的硬盘上,那么这种方法可以让使用几乎任意大小的数据进行数据科学研究更快、更安全、更方便。
Vaex
Vaex 是一个开源的数据框架库,它可以在与硬盘大小相同的表格数据集上进行可视化、探索、分析甚至机器学习。为此,Vaex 采用了一些概念,如内存映射、高效的核心外算法和延后计算。所有这些都被一个和 pandas 类似的 API 类绑定起来,任何人都可以马上开始使用它。
十亿出租车分析
为了说明这个概念,让我们在一个数据集上做一个简单的探索性数据分析,这个数据集非常大,可以放入一个典型的笔记本电脑的 RAM 中。在这篇文章中,我们将使用纽约(NYC)出租车数据集,其中包含 2009 至 2015 年之间的超过 10 亿个标志性黄色出租车。数据可以从这个网站下载,并以 CSV 格式提供。完整的分析可以在这个 Jupyter notebook 中单独查看。
清理街道
第一步是将数据转换为内存可映射文件格式,如 Apache Arrow、Apache Parquet 或 HDF5。将 CSV 数据转换为 HDF5 的示例可以在这里找到。一旦数据是内存可映射格式,用 Vaex 打开它是即时的(0.052 秒!),尽管磁盘上的数据超过 100GB:
使用 Vaex 打开内存映射文件只需要 0.052 秒,即使它们超过 100 GB
为什么这么快?使用 Vaex 打开内存映射文件时,实际上没有数据读取。Vaex 只读取文件元数据,如磁盘上数据的位置、数据结构(行数、列数、列名和类型)、文件描述等。那么,如果我们想检查数据或与数据交互呢?打开一个数据集会生成一个标准数据框,检查它的速度是否也很快:
纽约市黄色出租车数据预览
再一次注意,单元执行时间非常短。这是因为显示 Vaex 数据帧或列只需要从磁盘读取前 5 行和后 5 行。这就引出了另一个重要的问题:Vaex 只会在必须的时候遍历整个数据集,它会尽可能少地传递数据。
无论如何,让我们首先从极端异常值或错误的数据输入中清除这个数据集。一个好的开始方法是使用 describe 方法获得数据的高层次概述,该方法显示每个列的样本数、缺少的值数和数据类型。如果列的数据类型是数字,则平均值、标准偏差以及最小值和最大值也将被显示。所有这些统计数据都是通过对数据的一次传递来计算的。
使用 describe 方法获得数据帧的高级概述。注意,数据帧包含 18 列,但在此屏幕截图中只有前 7 列可见
描述方法很好地说明了 Vaex 的功耗和效率:所有这些统计数据都是在我的 MacBook Pro(15", 2018, 2.6GHz Intel Core i7, 32GB RAM)上 3 分钟之内计算出来的。其他的库或方法需要分布式计算或 100GB 以上的云实例来执行相同的计算。有了 Vaex,你所需要的只是数据,你的笔记本电脑只需要几 GB 的内存。
从 descripe 的输出来看,很容易注意到数据中包含了一些严重的异常值。首先,让我们从检查取货地点开始。删除异常值的最简单方法是简单地绘制出上下车的位置,并直观地定义我们希望重点分析的纽约市的区域。由于我们使用的数据集太大了,直方图是最有效的可视化方法。用 Vaex 创建和显示直方图和热图是如此的快,这样的绘图可以更好地互动!
df.plot_widget(df.pickup_longitude,
df.pickup_latitude,
shape=512,
limits='minmax',
f='log1p',
colormap='plasma')
一旦我们以交互方式决定要关注纽约市的哪个区域,我们就可以简单地创建一个过滤数据框:
上面代码块最酷的地方是它需要的内存可以忽略不计!它在过滤 Vaex 数据帧时,不会生成数据的副本,相反,它只创建对原始对象的引用,并在其上应用二进制掩码。掩码选择显示哪些行并用于将来的计算。这为我们节省了 100GB 的 RAM。
现在,让我们检查一下乘客计数栏。在一次出租车行程中记录的乘客人数最多为 255 人,这似乎有点极端。让我们计算一下每一位乘客的出行次数。这很容易通过值计数方法实现:
对 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 高速公路大道以及机场仍然是接送乘客的好地方,但它们在地图上的重要性要小得多。然而,在 Hudson 河的西侧出现了一些新的热点地区,这些地区似乎可以赚到相当的利润。
开出租车是一项相当灵活的工作。为了更好地利用这种灵活性,知道什么时候开车是最有益的。为了回答这个问题,我们制作一个图表,显示每天和一天中每小时的车费与出行距离的平均比率:
一周中每天和一天中每小时的车费与出行距离的平均比率
上面的数字是有道理的:最好的收入发生在高峰时段,特别是在一周工作日的中午。作为出租车司机,我们的一小部分收入是出租车公司的,所以我们可能会对哪一天的顾客给的小费最多感兴趣。因此,让我们生成一个类似的图,这次显示平均小费百分比:
每周每天和每天小时的平均小费百分比
上面的图很有趣。它告诉我们,乘客喜欢在早上 7 点到 10 点之间和在本周早些时候的晚上给出租车司机小费。如果你在凌晨 3 点或 4 点接乘客,不要指望会有太大的小费。结合上面两个地块的分析,早上 8 点到 10 点是上班的好时间:每个人每英里可以获得不错的车费和满意的小费。
发动引擎!
在本文的前一部分中,我们简要介绍了 trip_distance 列,在从异常值中清除它的同时,我们保留了所有小于 100 英里的行程值。这仍然是一个相当大的截止值,特别是考虑到黄色出租车公司主要在曼哈顿经营。Trimih 距离列描述出租车在接到乘客和乘客下车位置之间行驶的距离。然而,为了避免交通堵塞或道路工程等原因,人们通常可能会选择不同的路线。因此,作为 trip_distance 列的对应项,让我们计算上车和下车位置之间的最短可能距离,我们称之为 arc_distance:
对于用 numpy 编写的复杂表达式,vaex 可以在 Numba、Pythran 甚至 CUDA(如果你有 NVIDIA GPU)的帮助下使用即时编译来大大加快计算速度
弧长计算公式涉及面广,包含了大量的三角函数和算法,特别是在处理大型数据集时,计算量很大。如果表达式或函数只使用来自 Numpy 包的 Python 操作和方法编写,Vaex 将使用机器的所有核心并行计算它。除此之外,VAEX 支持通过 NUBBA(使用 LLVM)或 Pythran(通过 C++加速)及时编译,从而提供更好的性能。如果你碰巧有一个 NVIDIA 图形卡,你可以通过 jit_CUDA 方法使用 CUDA 来获得更高的性能。
总之,让我们画出 trip_distance 和 arc_distance 的分布:
左:行程距离和弧距离的比较;右:弧距<100 米的行程分布。
有趣的是,arc_distance 从来没有超过 21 英里,但是出租车实际行驶的距离可以是 5 倍大。事实上,有数百万的出租车旅行,下车位置在上车地点 100 米(0.06 英里)以内!
多年来的黄色出租车公司
我们今天使用的数据集跨越了 7 年。我们可以看到,随着时间的推移,一些收益的数量是如何演变的。使用 Vaex,我们可以快速执行核心分组和聚合操作。让我们来探讨 7 年来票价和行程是如何演变的:
对于一个超过 10 亿个样本的 Vaex 数据帧,在笔记本电脑上使用四核处理器进行 8 个聚合的分组操作只需不到 2 分钟
在上面的单元块中,我们执行一个分组操作,然后是 8 个聚合,其中 2 个在虚拟列上。上面的单元块在我的笔记本电脑上执行不到 2 分钟。这相当令人印象深刻,因为我们使用的数据包含超过 10 亿个样本。不管怎样,让我们看看结果。以下是多年来驾驶出租车的费用演变过程:
平均票价和总金额,以及乘客每年支付的小费百分比
我们看到出租车价格,以及小费随着岁月的流逝而增加。现在让我们来看一下出租车的 trip_distance 和 arc_distance,出租车是以年为单位行驶的:
出租车每年旅行的平均行程和弧距。
上图显示,出行距离和弧线距离都有小幅增加,这意味着,平均而言,人们每年的出行都会稍微远一点。
给我看看钱
在我们的旅程结束之前,让我们再停一站,调查一下乘客如何支付乘车费用。数据集包含付款类型列,因此让我们看看它包含的值:
从数据集文档中,我们可以看到此列只有 6 个有效条目:
1 = credit card payment
2 = cash payment
3 = no charge
4 = dispute
5 = Unknown
6 =Voided trip
因此,我们可以简单地将 payment_type 列中的条目映射为整数:
现在,我们可以按每年的数据分组,看看纽约人在出租车租赁支付方面的习惯是如何改变的:
每年付款方式
我们看到,随着时间的推移,信用卡支付慢慢变得比现金支付更频繁。我们真的生活在一个数字时代!注意,在上面的代码块中,一旦我们聚合了数据,小的 Vaex 数据帧可以很容易地转换为 Pandas 数据帧,我们可以方便地将其传递给 Seaborn。不是想在这里重新发明轮子。
最后,让我们通过绘制现金支付与信用卡支付的比率来确定支付方式是取决于一天中的时间还是一周中的某一天。为此,我们将首先创建一个过滤器,它只选择用现金或卡支付的乘车。下一步是我最喜欢的 Vaex 特性之一:带有选择的聚合。其他库要求对以后合并为一个支付方法的每个单独筛选的数据帧进行聚合。另一方面,使用 Vaex,我们可以通过在聚合函数中提供选择来一步完成此操作。这非常方便,只需要一次传递数据,就可以获得更好的性能。在此之后,我们只需以标准方式绘制结果数据帧:
在一周的某一时间和某一天,现金和卡支付的一部分
看上面的图表,我们可以发现一个类似的模式,显示小费百分比和一周中的一天和一天中的时间相关的函数。从这两个图中,数据表明,用卡支付的乘客往往比用现金支付的乘客小费更多。事实真的是这样吗?我想请你自己试着去弄清楚,因为现在你已经掌握了知识、工具和数据!你也可以看看这个 notebook 来获得一些额外的提示。
到达目的地
我希望这篇文章是对 Vaex 的一个有用的介绍,它将帮助缓解你可能面临的一些「不舒服的数据」问题,至少当涉及到表格数据集时会对你有帮助。如果你对本文中使用的数据集感兴趣,可以直接从带 Vaex 的 S3 中使用它。查看完整的 Jupyter notebook 了解如何执行此操作。
有了 Vaex,你只需几秒钟就可以通过自己的笔记本电脑浏览超过十亿行数据,计算出各种统计数据、聚合数据,并生成信息丰富的图表。它不仅免费而且开源,我希望你会给它一个机会!
雷锋网雷锋网雷锋网
雷峰网版权文章,未经授权禁止转载。详情见转载须知。