【Pandas】优化读取文件内存占用过大的问题

编写于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]。

img

解决办法:
人工替换每列的数据类型,查看数据类型转换

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范围下限(含)范围上限(含)
unit80255
unit16065535
unit16065535
int8-128127
int16-3276832767
int32-21474836482147483647
int64–9,223,372,036,854,775,8089,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类型。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值