编写于2022.11.6
1、内存占用计算
做了个小实验,发现pandas读取文件时,内存占用是真的高:
import sys
# 源文件大小:12957KB,12.6MB
file = r"G:\test.csv"
df = pd.read_csv(file, encoding='utf-8')
print(str(sys.getsizeof(df)/1024/1024)+' MB')
# 180.20847511291504 MB
# 作为对比
with open(file, "r", encoding='utf-8') as f:
data = f.readlines()
# 注意list的实际内存占用计算方式
print(str((sys.getsizeof(data[0])*len(data) + sys.getsizeof(data))/1024/1024)+' MB')
# 41.51472091674805 MB
其它查看DataFrame内存占用的方式:
方式一:df.info(memory_usage=‘deep’),查看完整信息
方式二:df.memory_usage(deep=True).sum(),只查看内存占用(推荐)
方式三:sys.getsizeof(df)
可以很清楚的看到这两种读取方式在内存占用上的巨大差距,同时我还观察到,将文件更改为xls格式时(38.1M),用pandas来读取占用的内存也几乎一样大:180.2985897064209 MB。
原因应该是保存的内容相同,所以占用空间相同。
同时,我还测试了同样的数据,用python的数据类型和pandas的DataFrame来表示时的内存占用大小:
data = {'name': "张三", "age": 18, "sex": "男"}
print(str(sys.getsizeof(data))+' Bytes')
# 240 Bytes
df = pd.DataFrame(data, index=[0])
print(str(sys.getsizeof(df))+' Bytes')
# 210 Bytes
data = [1,2,3,4,5]
print(str(sys.getsizeof(data))+' Bytes')
# 104 Bytes
df = pd.DataFrame(data)
print(str(sys.getsizeof(df))+' Bytes')
# 192 Bytes
观察发现:
(1)在字典上,python占用内存更高(python字典占用内存是有名的高啊);
(2)在列表上,pandas占用内存更高。
言归正传,回到文件读取上,我有这么几个疑问:
1.为什么Pandas 读取文件这么占内存?
2.如何优化pandas的内存占用过大的问题?
2、为什么Pandas 读取文件这么占内存?
原因:
pandas的读取数据时的处理机制默认会按照最大的规格去设置数据类型,如int64
pandas数据格式总的说有三类:数字,类别,时间。
数字类型数据的具体格式有:int,float64…
类别类型数据的格式是object,也就是字符串,不可相加减。
时间类型数据的格式主要是datetime[ns]。
解决办法:
人工替换每列的数据类型,查看数据类型转换
3、如何优化pandas的内存占用过大的问题?
(1)已读取的数据进行类型转换
pandas读取时,遇到无法自动甄别的数据类型,会读取为****object类型,通过观察和手动更改object类型为最优的数据类型,将会大大减少内存占用。
参考:https://blog.csdn.net/m0_47384542/article/details/109742814
几种常见的转换:
- pd.to_numeric():提取数字型
- pd.to_datetime():提取日期格式数据
- df.astype(‘category’):字符转换为维度数据
- df.astype(‘bool’):转换为布尔数据
# 替换某列
# 方式一
df['A'] = df['A'].astype("int", errors="raise")
# 方式二
df['A'] = pd.to_numeric(df['A'], errors="coerce")
# 全部替换
df = df.apply(pd.to_numeric, errors='coerce') # 把能转换成数字的都转换成数字,不
# 21.022361755371094 MB, 新的内存占用
注意:astype()
方法存在着一些局限性,只要待转换的数据中存在非数字以外的字符,在使用astype()
方法进行类型转换时就会出现错误(**errors=‘raise’**会抛出错误,**errors=‘ignore’**当出现错误时会忽略该列的转换),而to_numeric()
函数的可以解决了这个问题(**errors=‘coerce’**当出现错误时会替换为NAN,此时某列中有无法甄别的字段,该列都会变成float64)。
更近一步的,可以将pandas的数据类型继续细化为numpy的数据类型:
dtypes | 范围下限(含) | 范围上限(含) |
---|---|---|
unit8 | 0 | 255 |
unit16 | 0 | 65535 |
unit16 | 0 | 65535 |
int8 | -128 | 127 |
int16 | -32768 | 32767 |
int32 | -2147483648 | 2147483647 |
int64 | –9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
子类型转换函数:
def reduce_mem_usage(df, verbose=True):
numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
# 计算当前占用的内存
start_mem = df.memory_usage(deep=True).sum() / 1024**2
# 循环每一列
for col in df.columns:
# 获取每一列的数据类型
col_type = df[col].dtypes
# 如果数据类型属于上面定义的类型之
if col_type in numerics:
# 计算该列数据的最小值和最大值 用于我们指定相应的数据类型
c_min = df[col].min()
c_max = df[col].max()
# 如果 该列的数据类型属于 int 类型,然后进行判断
if str(col_type)[:3] == 'int':
# 如果 该列最小的值 大于int8类型的最小值,并且最大值小于int8类型的最大值,则采用int8类型
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
# 同上
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
# 同上
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
# 同上
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
# 否则 则采用 float 的处理方法
else:
if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage(deep=True).sum() / 1024**2
if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
return df
df = reduce_mem_usage(df, verbose=True)
print(str(sys.getsizeof(df)/1024/1024)+' MB')
# 18.629207611083984 MB
经过上述操作,pandas读取文件后占用的的内存从180M减少到18M,内存占用减少了90%!有效!
参考:https://blog.csdn.net/weixin_44510615/article/details/115801785
(2)在读取时指定数据类型(推荐)
参考:https://blog.csdn.net/cainiao_python/article/details/119950400
首先,我们可将每一列的最佳类型存储在一个词典中,其中键值表示列名称,首先移除日期列,因为日期列需要不同的处理方式。
column_types = {'A':'category','B':'bool','C':'object',.....}
df = pd.read_csv('tests.csv',
dtype=column_types,
parse_dates=['date'],
infer_datetime_format=True)
此时,一步到位了!然后可进一步的进行上述的子类型转换,进一步减少内存占用。
4、总结
pandas读取文件占用内存多主要是没有准确识别每一列的数据类型,采用了object进行存储,所有的优化办法都是围绕数据类型转换进行的:一是在读取时指定最佳的数据类型,二是在读取后进行数据转换;更进一步的的优化操作有:(1)将数值向下转换为更高效的类型;(2)将字符串列转换为categorical类型。