相比较于 Numpy,Pandas 使用一个二维的数据结构 DataFrame 来表示表格式的数据, 可以存储混合的数据结构,同时使用 NaN 来表示缺失的数据,而不用像 Numpy 一样要手工处理缺失的数据,并且 Pandas 使用轴标签来表示行和列。
通常用于处理小数据(小于 100Mb),而且对计算机的性能要求不高,但是当我们需要处理更大的数据时(100Mb到几千Gb),计算机性能就成了问题,如果配置过低就会导致更长的运行时间,甚至因为内存不足导致运行失败。
在处理大型数据集时(100Gb到几TB),我们通常会使用像 Spark 这样的工具,但是想要充分发挥 Spark 的功能,通常需要很高的硬件配置,导致成本过高。而且与 Pandas 不同,这些工具缺少可用于高质量数据清洗、勘测和分析的特征集。
因此对于中等规模的数据,我们最好挖掘 Pandas 的潜能,而不是转而使用其他工具。那么在不升级计算机配置的前提下,我们要怎么解决内存不足的问题呢?
在这篇文章中,我们将介绍 Pandas 的内存使用情况,以及如何通过为数据框(dataframe)中的列(column)选择适当的数据类型,将数据框的内存占用量减少近 90%。
棒球比赛日志
我们将要处理的是 130 年来的大型棒球联盟比赛数据,原始数据来源于 retrosheet。
最原始的数据是 127 个独立的 CSV 文件,不过我们已经使用 csvkit 合并了这些文件,并且在第一行中为每一列添加了名字。 如果读者想亲自动手操作,可下载网站上的数据实践下:
首先让我们导入数据,看看前五行:
我们总结了一些重要的列,但是如果你想查看所有的列的指南,我们也为整个数据集创建了一个数据字典:
我们可以使用 DataFrame.info() 的方法为我们提供数据框架的更多高层次的信息,包括数据大小、类型、内存使用情况的信息。默认情况下,Pandas 会占用和数据框大小差不多的内存来节省时间。因为我们对准确度感兴趣,所以我们将 memory_usage 的参数设置为 ‘deep’,以此来获取更准确的数字。
我们可以看到,这个数据集共有 171,907 行、161 列。Pandas 已经自动检测了数据的类型:83 列数字(numeric),78 列对象(object)。对象列(object columns)主要用于存储字符串,包含混合数据类型。为了更好地了解怎样减少内存的使用量,让我们看看 Pandas 是如何将数据存储在内存中的。
数据框的内部表示
在底层,Pandas 按照数据类型将列分成不同的块(blocks)。这是 Pandas 如何存储数据框前十二列的预览。
你会注意到这些数据块不会保留对列名的引用。这是因为数据块对存储数据框中的实际值进行了优化,BlockManager class 负责维护行、列索引与实际数据块之间的映射。它像一个 API 来提供访问底层数据的接口。每当我们选择、编辑、或删除某个值时,dataframe class 会和 BlockManager class 进行交互,将我们的请求转换为函数和方法调用。
每个类型在 pandas.core.internals 模块中都有一个专门的类, Pandas 使用 ObjectBlock class 来代表包含字符串列的块,FloatBlock class 表示包含浮点型数据(float)列的块。对于表示数值(如整数和浮点数)的块,Pandas 将这些列组合在一起,并存储为 NumPy ndarry 数组。NumPy ndarry 是围绕 C array 构建的,而且它们的值被存储在连续的内存块中。由于采用这种存储方案,访问这些值的地址片段(slice)是非常快的。
因为不同的数据都是单独存储的,所以我们将检查不同类型的数据的内存使用情况。我们先来看看所有数据类型的平均内存使用情况。
可以看到,大部分的内存都被 78 个对象列占用了。我们稍后再来分析,首先看看我们是否可以提高数字列(numeric columns)的内存使用率。
了解子类型
正如前面介绍的那样,在底层,Pandas 将数值表示为 NumPy ndarrays,并将它存储在连续的内存块中。该存储模型消耗的空间较小,并允许我们快速访问这些值。因为 Pandas 中,相同类型的值会分配到相同的字节数,而 NumPy ndarray 里存储了值的数量,所以 Pandas 可以快速并准确地返回一个数值列占用的字节数。
中的许多类型包含了多个子类型,因此可以使用较少的字节数来表示每个值。例如,float 类型就包含 float16、float32、float64 等子类型。类型名称的数字部分代表 了用于表示值类型的位数。例如,我们刚刚列出的子类型就分别使用了 2、4、8、16 个字节。下表显示了最常见的 的子类型:
使用 1 个字节(或者 8 位)来存储一个值,并且可以以二进制表示 256 个值。这意味着,我们可以使用这种子类型来表示从 -128 到 127 (包括0)的值。我们可以使用 numpy.iinfo class 来验证每个整数子类型的最小值和最大值,我们来看一个例子:
我们可以在这里看到 uint(无符号整数)和 int(有符号整数)之间的区别。这两种类型具有相同的存储容量,但如果只存储正数,无符号整数显然能够让我们更高效地存储只包含正值的列。
使用子类型优化数字列
我们可以使用函数 pd.to_numeric() 来 downcast(向下转型)我们的数值类型。我们将使用 DataFrame.select_dtypes 来选择整数列,然后优化这些列包含的类型,并比较优化前后内存的使用情况。
我们可以看到,内存的使用量从 7.9Mb 降到了 1.5 Mb,减少了 80% 以上。但这对原始数据框的影响并不大,因为本身整数列就非常少。
现在,让我们来对浮点型数列做同样的事情。
可以看到,我们所有的浮点型数列都从 float64 转换成 float32,使得内存的使用量减少了 50%。
让我们创建一个原始数据框的副本,然后分配这些优化后的数字列代替原始数据,并查看现在的内存使用情况。
虽然我们大大减少了数字列的内存使用量,但是从整体来看,我们只是将数据框的内存使用量降低了 7%。内存使用量降低的主要原因是我们对对象类型(object types)进行了优化。
在动手之前,让我们仔细看一下,与数字类型相比,字符串是怎样存在 Pandas 中的。
比较数字和字符串的存储方式
对象类型代表了 Python 字符串对象的值,部分原因是 NumPy 缺少对字符串值的支持。因为 Python 是一种高级的解释语言,它不能对数值的存储方式进行细粒度控制。
这种限制使得字符串以分散的方式存储在内存里,不仅占用了更多的内存,而且访问速度较慢。对象列表中的每一个元素都是一个指针(pointer),它包含了实际值在内存中位置的“地址”。
下面的图标展示了数字值是如何存储在 NumPy 数据类型中,以及字符串如何使用 Python 内置的类型存储。
你可能已经注意到,我们的图表之前将对象类型描述成使用可变内存量。当每个指针占用一字节的内存时,每个字符的字符串值占用的内存量与 Python 中单独存储时相同。让我们使用 sys.getsizeof() 来自证明这一点:先查看单个字符串,然后查看 Pandas 系列中的项目(items)。
你可以看到,存储在 Pandas 中的字符串的大小与作为 Python 中单独字符串的大小相同。
使用分类来优化对象类型
在 0.15版引入了 Categoricals (分类)。category 类型在底层使用整数类型来表示该列的值,而不是原始值。用一个单独的字典来映射整数值和相应的原始值之间的关系。当某一列包含的数值集有限时,这种设计是很有用的。当我们将列转换为 category dtype 时,使用了最省空间的 int 子类型,来表示一列中所有的唯一值。
想要知道我们可以怎样使用这种类型来减少内存使用量。首先 ,让我们看看每一种对象类型的唯一值的数量。
可以看到,我们的数据集中一共有 17.2 万场比赛, 而唯一值的数量是非常少的。
在我们深入分析之前,我们首先选择一个对象列,当我们将其转换为 www.7881188.cn categorical type时,观察下会发生什么。我们选择了数据集中的第二列 day_of_week 来进行试验。
在上面的表格中,我们可以看到它只包含了七个唯一的值。我们将使用 .astype() 的方法将其转换为 www.wmyl20.com categorical。
如你所见,除了列的类型已经改变,这些数据看起来完全一样。我们来看看发生了什么。
在下面的代码中,我们使用 Series.cat.codes 属性来返回 category 类型用来表示每个值的整数值。
你可以看到,每个唯一值都被分配了一个整数,并且该列的底层数据类型现在是 int8。该列没有任何缺失值,如果有的话,这个 category 子类型会将缺省值设置为 -1。最后,我们来看看这个列在转换到 category 类型之前和之后的内存使用情况。
可以看到,内存使用量从原来的 9.8MB 降到了 0.16MB,相当于减少了 98%!请注意,这一列可能代表我们最好的情况之一:一个具有 172,000 个项目的列,只有 7 个唯一的值。
将所有的列都进行同样的操作,这听起来很吸引人,但使我们要注意权衡。可能出现的最大问题是无法进行数值计算。我们不能在将其转换成真正的数字类型的前提下,对这些 category 列进行计算,或者使用类似 Series.min() 和 Series.max() 的方法。
当对象列中少于 50% 的值时唯一对象时,我们应该坚持使用 category 类型。但是如果这一列中所有的值都是唯一的,那么 category 类型最终将占用更多的内存。这是因为列不仅要存储整数 category 代码,还要存储所有的原始字符串的值。你可以阅读 Pandas 文档,了解 category 类型的更多限制。
我们将编写一个循环程序,遍历每个对象列,检查其唯一值的数量是否小于 50%。如果是,那么我们就将这一列转换为 category 类型。
和之前的相比
在这种情况下,我们将所有对象列都转换为 category 类型,但是这种情况并不符合所有的数据集,因此务必确保事先进行过检查。
此外,对象列的内存使用量已经从 752MB 将至 52MB,减少了 93%。现在,我们将其与数据框的其余部分结合起来,再与我们最开始的 861MB 的内存使用量进行对比。
可以看到,我们已经取得了一些进展,但是我们还有一个地方可以优化。回到我们的类型表,里面有一个日期(datetime)类型可以用来表示数据集的第一列。
你可能记得这一列之前是作为整数型读取的,而且已经被优化为 uint32。因此,将其转换为 datetime 时,内存的占用量会增加一倍,因为 datetime 的类型是 64 位。无论如何,将其转换成 datetime 是有价值的,因为它将让时间序列分析更加容易。
我们将使用 pandas.to_datetime() 函数进行转换,并使用 format 参数让日期数据按照 YYYY-MM-DD 的格式存储。
在读取数据时选择类型
到目前为止,我们已 经 探索了减少现有数 据框内存占用的方法。首先,读入阅读数据框,然后再反复迭代节省内存的方法,这让我们可以更好地了解每次优化可以节省的内存空间。然而,正如我们前面提到那样,我们经常没有足够的内存来表示数据集中所有的值。如果一开始就不能创建数据框,那么我们该怎样使用内存节省技术呢?
幸运的是,当我们读取数据集时,我们可以制定列的最优类型。pandas.read_csv() 函数有几个不同的参数可以让我们做到这一点。dtype 参数可以是一个以(字符串)列名称作为 keys、以 NumPy 类型对象作为值的字典。
首先,我们将每列的最终类型、以及列的名字的 keys 存在一个字典中。因为日期列需要单独对待,因此我们先要删除这一列。
现在,我们可以使用字典、以及几个日期的参数,通过几行代码,以正确的类型读取日期数据。
通过优化这些列,我们设法将 pandas 中的内存使用量,从 861.6MB 降到了 104.28MB,减少了 88%。
分析棒球比赛
我们已经优化了数据,现在我们可以开始对数据进行分析了。我们来看看比赛的时间分布。
可以看到,在二十世纪二十年代之前,棒球比赛很少在周日举行,一直到下半世纪才逐渐流行起来。此外,我们也可以清楚地看到,在过去的五十年里,比赛时间的分是相对静态的。我们来看看比赛时长多年来的变化。
看起来,棒球比赛的时长自 1940 年以来就一直处于增长状态。
总结和后续步骤
我们已经了解到 Pandas 是如何存储不同类型的数据的,然后我们使用这些知识将 Pandas 里的数据框的内存使用量降低了近 90%,而这一切只需要几个简单的技巧:
将数字列 downcast 到更节省空间的类型;
将字符串转换为分类类型(categorical www.xucaizxyl.com type)。
实操 | 内存占用减少高达90%,还不用升级硬件?没错,这篇文章教你妙用Pandas轻松处理大规模数据
时间: 2024-10-12 13:53:41
实操 | 内存占用减少高达90%,还不用升级硬件?没错,这篇文章教你妙用Pandas轻松处理大规模数据的相关文章
打造网络营销利器-第1篇-TDK设置实操-网站优化进阶
网络营销如何做,才能从网络挖掘出大量的新业务?互联网时代你还停留于没业务就扩充销售队伍的思想,你就OUT了! 本系列文章将用通俗的语言介绍网站运营大牛们的惯用手法,网站SEO并没有你们想象那么难!重点是了解并坚持去做! 上一篇文章<中小企业网站优化技巧-1-网站的TDK>介绍了网站的TDK是什么(标题.描述.关键词),那么本文,将以具体产品为例介绍下TDK的实际操作. 首先,登录进网站系统后台,从左边菜单栏找到产品管理==>添加产品,点击打开添加产品的页面: 然后第一步,选择您产品对应的
chrome浏览器tab页内存占用变大,网站变慢为哪般?
问题概述: 公司做的是BS应用. 之前我们的后台服务器程序是带状态的,用ehcache存储登录状态:这两天被我改成了redis存储,应用本身不再存储登录状态. 然后自测,我在测试某个很耗时间的网页操作的时候,发现第一次请求的时候还比较快(这个请求会开200个iframe出来,每个iframe内部还有2个ajax请求)(期间浏览器会向服务器发送了大概600个请求),耗时1分钟内: 然后第二次请求的时候,发现很多请求一直处于pending状态(chrome的开发者工具可以看),等待很久也出不来页面,
看了这篇文章你还不懂傅里叶变换,那我就真没办法呢!
首先,请允许小编带着崇高的敬意向牛逼的作者"韩昊"表示感谢.按照原文要求,转载注明出处.内容整理自知乎!发布仅为学习交流!版权归原作者所有!原文地址:https://zhuanlan.zhihu.com/p/19759362 ---好文开始了-- 我保证这篇文章和你以前看过的所有文章都不同,这是 2012 年还在果壳的时候写的,但是当时没有来得及写完就出国了--于是拖了两年,嗯,我是拖延症患者-- 这篇文章的核心思想就是:要让读者在不看任何数学公式的情况下理解傅里叶分析. 傅里叶分析不
如果看了这篇文章你还不懂傅里叶变换,那就过来掐死我吧(转)
我保证这篇文章和你以前看过的所有文章都不同,这是 12 年还在果壳的时候写的,但是当时没有来得及写完就出国了--于是拖了两年,嗯,我是拖延症患者-- 这篇文章的核心思想就是: 要让读者在不看任何数学公式的情况下理解傅里叶分析. 傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式.但不幸的是,傅里叶分析的公式看起来太复杂了,所以很多大一新生上来就懵圈并从此对它深恶痛绝.老实说,这么有意思的东西居然成了大学里的杀手课程,不得不归咎于编教材的人实在是太严肃了.(您把教材写得
C# 处理应用程序减少内存占用
SetProcessWorkingSetSize减少内存占用 系统启动起来以后,内存占用越来越大,使用析构函数.GC.Collect什么的也不见效果,后来查了好久,找到了个办法,就是使用 SetProcessWorkingSetSize函数.这个函数是Windows API 函数.下面是使用的方法: [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "SetProce
Linux系统内存占用90%以上 ?
问题: [[email protected] zx_epp_db]# free -m total used free shared buffers cached Mem: 15953 14706 1246 0 39 12687 -/+ buffers/cache: 1979 13973 Swap: 8015 27 7988 ---------------------------------------------------------------------------------------
怎样使java程序减少内存占用(转载)
本文收集网上关于减少java程序占用的一些小知识点 (1)别用new Boolean(). 在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolean封装传递的,大部分ORM也是用Boolean来封装boolean类型的,比如: ps.setBoolean("isClosed",new Boolean(true)); ps.setBoolean("isClosed",new Boolean(isClosed));
降低Redis内存占用
1.降低redis内存占用的优点 1.有助于减少创建快照和加载快照所用的时间 2.提升载入AOF文件和重写AOF文件时的效率 3.缩短从服务器进行同步所需的时间 4.无需添加额外的硬件就可以让redis存贮更多的数据 回到顶部 2.短结构 Redis为列表.集合.散列.有序集合提供了一组配置选项,这些选项可以让redis以更节约的方式存储较短的结构. 回到顶部 2.1.ziplist压缩列表(列表.散列.有续集和) 通常情况下使用的存储方式 当列表.散列.有序集合的长度较短或者体积较小的时候,r
一步步优化JVM四:决定Java堆的大小以及内存占用
原文:http://blog.csdn.net/zhoutao198712/article/details/7783070 到目前为止,还没有做明确的优化工作.只是做了初始化选择工作,比如说:JVM部署模型.JVM运行环境.收集哪些垃圾回收器的信息以及需要遵守垃圾回收原则.这一步将介绍如何评估应用需要的内存大小以及Java堆大小.首先需要判断出应用存活的数据的大小,存活数据的大小是决定配置应用需要的Java堆大小的重要条件,也能够决定是否需要重新审视一下应用的内存需求或者修改应用程序以满足内存需