最近我用python处理excel,使用的是pandas库,我发现pandas库非常占用内存,一直想研究下如何优化pandas的内存占用,但一直没腾出空来,最近终于有时间研究一把了,我先把优化方法写上,如果你想了解更多的内容,可以看一下XX这篇文章,我优化的思路来源https://www.sohu.com/a/484114754_121124370这篇文章。
以下直接提供了优化方法,如果你想知道为什么这么做,可以参考我的另一篇文章链接: python pandas 优化内存占用(二)
1.优化结果
1.1 优化前
我优化的是一个20万行,88列的excel文件,源文件有76.59M,因为业务敏感性的原因,源文件我不能贴出来。
先看一下优化之前excel占用了多少内存
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 200000 entries, 0 to 199999
# Data columns (total 88 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 column0 200000 non-null int64
# 1 column1 200000 non-null object
# 2 column2 200000 non-null int64
......
# 85 column85 199963 non-null float64
# 86 column86 199963 non-null object
# 87 column87 199963 non-null float64
# dtypes: float64(14), int64(3), object(71)
# memory usage: 965.9 MB
上面的输出我精简了一下,从上边的输出可以看出,我的excel文件有200000行,88列,其中有14个float64类型的列,3个int64类型的列,71个ojbect类型的列,总共占用内存965.9M。
上面的输出是使用如下的代码得到的。
gl.info(memory_usage='deep')
1.2 优化后
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 200000 entries, 0 to 199999
# Data columns (total 88 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 column0 200000 non-null uint64
# 1 column1 200000 non-null object
# 2 column2 200000 non-null uint64
......
# 84 是否小区 199963 non-null category
# 85 column85 199963 non-null category
# 86 column86 199963 non-null category
# 87 column87 199963 non-null category
# dtypes: category(81), object(5), uint64(2)
# memory usage: 143.8 MB
同样,我把优化后的输出也精简了一下,从上边的输出可以看出,我这个200000行,88列的excel文件优化后占用内存143.8M,优化效果还是非常明显的。
细心的小伙伴可能注意到一个问题,优化后,我的execl文件的列类型变化了,优化前有14个float64类型的列,3个int64类型的列,71个ojbect类型的列;优化后变成了81个category类型的列,5个object类型列,2个uint64类型的列。没错,正如你看到的,这就是优化的秘密,为什么这样可以优化内存使用,可以参看我的另一篇文章,下面我讲一下我是如何做到的。
2. 优化方法
正如小伙伴在上文中看到的,要想优化excel的内存占用大小,一个重要的思路是改变excel的列类型,如何获取优化后的列类型呢,代码如下
import pandas as pd
# 把excel中的数据类型转化成优化后的数据类型
def convert_to_right_type():# 读取文件gl = pd.read_excel("xx.xlsx")# 初始化一个DataFrameconverted_obj = pd.DataFrame()# 计算哪些列类型可以转换成category类型for col in gl.columns:num_unique_values = len(gl[col].unique())num_total_values = len(gl[col])if num_unique_values / num_total_values < 0.5:converted_obj.loc[:, col] = gl[col].astype('category')else:converted_obj.loc[:, col] = gl[col]# 计算哪些int类型列可以downcast成子类型gl_int = converted_obj.select_dtypes(include=['int'])converted_int = gl_int.apply(pd.to_numeric, downcast='unsigned')# 将转换后的int类型列合并回converted_objfor col in converted_int.columns:converted_obj[col] = converted_int[col]# 计算哪些float类型列可以downcast成子类型gl_float = converted_obj.select_dtypes(include=['float'])converted_float = gl_float.apply(pd.to_numeric, downcast='float')# 将转换后的 float 类型列合并回converted_objfor col in converted_float.columns:converted_obj[col] = converted_float[col]# 获取数据类型索引(索引是列名,值是数据类型)dtypes = converted_obj.dtypes# 获取列名dtypes_col = dtypes.index# 获取数据类型的名称dtypes_type = [i.name for i in dtypes.values]# 列名和类型字典column_types = dict(zip(dtypes_col, dtypes_type))# preview = {key: value for key, value in list(column_types.items())[:20]}# 获取字典preview = {key: value for key, value in list(column_types.items())}# 格式化输出, 使每个嵌套层级的缩进量为4个空格pp = pprint.PrettyPrinter(indent=4)# 打印字典pp.pprint(preview)
这段儿代码的输出类似如下:
column_types = {'column1': 'category','column2': 'uint64','column3': 'object','column4': 'uint64','column5': 'category'}
column_type 是个字典,它的key是excel文件的列名称,value是excel文件列对应的优化后的类型。
在读取excel的时候,指定excel的dtype,即可按照指定的列类型读取excel,类似这样
gl = pd.read_excel("XX.xlsx", dtype=column_types)
3. 测试方法
我是用如下的代码测试excel占用内存的大小的
# 测试内存占用
def mem_usage(pandas_obj):if isinstance(pandas_obj, pd.DataFrame):usage_b = pandas_obj.memory_usage(deep=True).sum()else: # we assume if not a df it's a seriesusage_b = pandas_obj.memory_usage(deep=True)usage_mb = usage_b / 1024 ** 2 # convert bytes to megabytesreturn "{:03.2f} MB".format(usage_mb)