优化pandas内存入门

本文介绍了如何优化Pandas在处理大数据时的内存使用,包括理解数据子类型、数值型数据的向下转型、类型和类型优化,以及如何利用参数在数据读取时完成优化。通过这些方法,可以显著提高内存效率并提升分析速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在平常是用pandas的时候,虽然用的很愉快,但遇到数据量很大的时候总是很慢,这篇文章主要介绍了一些简单优化dataframe使用内存的方法,大大提高pandas使用的效率。

简单的概念

# coding: utf-8
import pandas as pd

# 读取数据
df = pd.read_csv("test.csv")

# 得到精确的内存信息
df.info(memory_usage='deep')

# 说明:之后都用df来表示读取到的dataframe

pandas中每一个数据类型都有一个专门的类来处理。

  • ObjectBlock: 字符串列的块
  • FloatBlock: 浮点数列的块
  • Numpy ndarray:整型和浮点数值的块(非常快,用C数组构建的)
for dtype in ['float', 'int', 'object']:
    # 选中对应的dtype列
    selected_dtype = df.select_dtypes(include=[dtype])
    # 查看内存使用量的平均值
    mean_usage_b = selected_dtype.memory_usage(deep=True).mean()
    # 获取到的数据单位为K, 这里转换一下
    mean_usage_mb = mean_usage_b / 1024 ** 2
    print("Average memory usage for {} columns:{:03.2f} MB".format(dtype, mean_usage_mb))

这里我们可以发现object类占用内存最大

子类型(subtype)

memory usagefloatintuintdatetimeboolobject
1 bytesint8uint8bool
2 bytesfloat16int16uint16
4 bytesfloat32int32uint32
8 bytesfloat64int64uint64datetime64
variableobject

一个int8类型值使用1个字节的存储空间,可以表示256(2^8)个二进制数,即-128到127的所有整数值。根据表可以得出,对于每个列,应该尽可能”抠”一点, 物尽其用。这里的uint表示无符号整型,可以更有效地处理正整数的列。

优化

数值型数据

这里可以使用to_numeric()对数值类型进行downcast(向下转型)的操作。

# 选中整数型的列
df_int_columns = df.select_dtypes(include=['int'])
# 向下转型
df_int_columns.apply(pd.to_numeric, downcast='unsigned')

# 选中浮点型数据
df_float_columns = df.select_dtypes(include=['float'])
# 向下转型
df_float_columns.apply(pd.to_numeric, downcast='float')

这里能够有效的利用内存,但是缺点很明显的就是只能用于数值型数据,而且优化的空间有限。

object型数据

object类型实际上使用的是Python字符串对应的值,由于Python解释性语言的特性,字符串存储方式很碎片化(消耗更多内存,访问速度更慢)。object列中的每个元素都是一个指针。object类型的数据内存使用情况是可变的。如果每个字符串是单独存储的,那么实际上字符串占用内存是很大。

In [1]: from sys import getsizeof
# 字符串本身内存使用情况
In [2]: s1 = "hello world!"
In [3]: s2 = "hello pandas!"
In [4]: getsizeof(s1), getsizeof(s2)
Out[4]: (61, 62)

# 字符串(object)在pandas中内存使用情况
In [9]: import pandas as pd
In [10]: ser = pd.Series([s1, s2])
In [11]: ser.apply(getsizeof)
Out[11]:
0    61
1    62
dtype: int64

这里通过观察看到,在这里pandas使用了int64来存储,实际占用大小与字符串本身是一样的。这里可以使用Categoricals来优化object类型。Categoricals的工作原理我理解为,某个object类有有限的分类情况(比如只有redyellowblueblack等颜色相关),那么Categoricals将这些分类object对象转换为int子类型(对应上面的0, 1, 2, 3)

# 选中object类型
df_obj_columns = df.select_dtypes(include=['object'])
# 查看object类型列的相关信息
df_obj_columns.describe()

如果unique(不同值)的数量很少,那么就可以使用这种优化方案。

# 假设某一列符合优化条件,为df_obj_less,使类型转换为category
df_category_column = df_obj_less.astype('category')
# 转换之后,主观观察数据没有什么区别,只是类型改变了
# 观察转换之后实际上的数据
df_category_column.cat.codes
# 变成了由0,1,2等数字构成的int8类型的数据

这里提升的空间远远超过第一步优化的空间,具体要结合数据来检验(最好貌似可以减少98%的使用量)。这里有个很大的缺点就是无法进行数值计算,即没有办法使用pd.Series.min()pd.Series.max()等与数值相关操作。

还有一个问题就是在有多少个unique(不同值)的情况下才使用这种方法。首先,毫无疑问的是如果需要计算操作的列是不能使用的。第二是如果unique的比例小于50%(个人觉得比例应该更小)就可以使用这种情况,如果过多的话,转换之后消耗的内存会更多(不仅需要存储string还有int)。

附上筛选unique比例小于50%的代码

# 提取unique少于50%的object列
df_obj_columns = df.select_dtypes(include=['object'])
for obj_col in df_obj_columns:
    unique_values = len(df_obj_columns[obj_col].unique())
    total_values = len(df_obj_columns[obj_col])
    if unique_values / total_values < 0.5:
        obj_col.astype('category')

# 建议提前copy一份数据,再做上面的操作

datetime类型

这里其实不能叫做优化,因为这里是将数值型数据转换为datetime类型,虽然提高了内存的使用,但是转换为datetime类型的数据更容易进行分析(时间序列分析)。当然如果有datetime类型不在分析的范围内,自然可以无视。

# 假设df_num_col为需要转换的列, 这里format格式看情况更改
df_num_col = pd.to_datetime(df_num_col, format="%Y%m%d")

总结

写了这么多,如果每次等加载完数据再做优化操作,感觉有些鸡肋。但是其实可以使用read_csv等读取函数的几个参数来帮助我们在读取的时候就完成优化步骤。

# 首先,需要整理出每一列最终的数据类型,组成一个dict
dtypes_lst = [col.name for name in df.dtypes]
column_types = dict(zip(dtypes.index, dtypes_lst))

# 重新读取数据, dtype指定数据格式,parse_dates指定列转换格式为datetime,infer_datetime_format尝试解析字符串形式的datatime格式(在一些情况下可以加快解析速度5-10倍)
new_df = pd.read_csv('test.csv', dtpye=column_types, parse_dates=['date'], infer_datetime_format=True)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值