pandas内存压缩

众所周知,数据挖掘比赛的数据通常都比较大,动不动就好几个G,如果直接读取全部数据很有可能会爆内存,对内存容量较小的电脑非常不友好。以下先放解决方法在放原理,各取所需。

一、解决方法

解决办法通常有分批读取压缩内存等,以下介绍pandas读取csv文件时压缩使用内存的方法。

注: 以下代码来自Datawhale的鱼佬,我只做解读。

def reduce_mem_usage(df, verbose=True):
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    start_mem = df.memory_usage().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()
            if str(col_type)[:3] == 'int':
                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)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif 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().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为pandas的DataFrame数据类型,verbosebool类型变量,表示是否要输出冗余结果(内存占用减少了多少)。

该方法的思路为:根据字段的最大值和最小值来匹配合适的数据类型。比如df中某一列变量(即字段)的最大值为1,最小值为0,那么我们将该字段的数据类型转换为占用内存更少的int16来存储这一变量。

使用案例:

这里展示一个我在推荐赛事中使用上述方法的案例,各位可以将data_ads换成自己的DataFrame类型的数据。

data_ads = pd.read_csv('./data_ads.csv')

print('压缩前 data_ads 内存占用为: {:.2f} MB'.format(data_ads.memory_usage().sum() / 1024**2))

data_ads = reduce_mem_usage(data_ads)

print('压缩后 data_ads 内存占用为: {:.2f} MB'.format(data_ads.memory_usage().sum() / 1024**2))

结果:

压缩前 data_ads 内存占用为: 2376.23 MB
Mem. usage decreased to 618.81 Mb (74.0% reduction)
压缩后 data_ads 内存占用为: 618.81 MB

可以看到压缩前data_ads占用了2个G的内存,压缩后只占用了不到1个G内存,减少了74%的内存占用,非常优雅!

二、原理

首先,我们来查看一下python中intfloat数据类型的存储范围和内存占用情况:

import sys

ints = [np.uint8(1), np.uint16(1), np.int8(1), np.int16(1),np.int32(1),np.int64(1)]
for i in ints:
    print(f'{type(i)} 占用内存 {sys.getsizeof(i)} 字节')
    print(np.iinfo(i))

floats = [np.float16(1), np.float32(1), np.float64(1)]
for i in floats:
    print(f'{type(i)} 占用内存 {sys.getsizeof(i)} 字节')
    print(np.finfo(i))

结果:

<class 'numpy.uint8'> 占用内存 25 字节
Machine parameters for uint8
---------------------------------------------------------------
min = 0
max = 255
---------------------------------------------------------------

<class 'numpy.uint16'> 占用内存 26 字节
Machine parameters for uint16
---------------------------------------------------------------
min = 0
max = 65535
---------------------------------------------------------------

<class 'numpy.int8'> 占用内存 25 字节
Machine parameters for int8
---------------------------------------------------------------
min = -128
max = 127
---------------------------------------------------------------

<class 'numpy.int16'> 占用内存 26 字节
Machine parameters for int16
---------------------------------------------------------------
min = -32768
max = 32767
---------------------------------------------------------------

<class 'numpy.int32'> 占用内存 28 字节
Machine parameters for int32
---------------------------------------------------------------
min = -2147483648
max = 2147483647
---------------------------------------------------------------

<class 'numpy.int64'> 占用内存 32 字节
Machine parameters for int64
---------------------------------------------------------------
min = -9223372036854775808
max = 9223372036854775807
---------------------------------------------------------------

<class 'numpy.float16'> 占用内存 26 字节
Machine parameters for float16
---------------------------------------------------------------
precision =   3   resolution = 1.00040e-03
machep =    -10   eps =        9.76562e-04
negep =     -11   epsneg =     4.88281e-04
minexp =    -14   tiny =       6.10352e-05
maxexp =     16   max =        6.55040e+04
nexp =        5   min =        -max
smallest_normal = 6.10352e-05   smallest_subnormal = 5.96046e-08
---------------------------------------------------------------

<class 'numpy.float32'> 占用内存 28 字节
Machine parameters for float32
---------------------------------------------------------------
precision =   6   resolution = 1.0000000e-06
machep =    -23   eps =        1.1920929e-07
negep =     -24   epsneg =     5.9604645e-08
minexp =   -126   tiny =       1.1754944e-38
maxexp =    128   max =        3.4028235e+38
nexp =        8   min =        -max
smallest_normal = 1.1754944e-38   smallest_subnormal = 1.4012985e-45
---------------------------------------------------------------

<class 'numpy.float64'> 占用内存 32 字节
Machine parameters for float64
---------------------------------------------------------------
precision =  15   resolution = 1.0000000000000001e-15
machep =    -52   eps =        2.2204460492503131e-16
negep =     -53   epsneg =     1.1102230246251565e-16
minexp =  -1022   tiny =       2.2250738585072014e-308
maxexp =   1024   max =        1.7976931348623157e+308
nexp =       11   min =        -max
smallest_normal = 2.2250738585072014e-308   smallest_subnormal = 4.9406564584124654e-324
---------------------------------------------------------------

将以上结果统计为下表:

数据类型范围下限(含)范围上限(含)内存占用(字节数)
unit80255( 2 8 − 1 2^{8}-1 28125
unit16065535( 2 16 − 1 2^{16}-1 216126
int8-128( − 2 7 -2^7 27127( 2 7 − 1 2^7-1 27125
int16-32768( − 2 15 -2^{15} 21532767( 2 15 − 1 2^{15}-1 215126
int32-2147483648( − 2 31 -2^{31} 2312147483647( 2 31 − 1 2^{31}-1 231128
int64-9223372036854775808( − 2 63 -2^{63} 2639223372036854775807( 2 63 − 1 2^{63}-1 263132
float16-655006550026
float32-3.4028235e+383.4028235e+3828
float64-1.7976931348623157e+3081.7976931348623157e+30832

注意intfloat数据类型没有谁比谁大的说法,区别在于int是整型,浮点型float后面跟了小数,可以计算精度,在对精度有要求的计算里更受欢迎。

pandas的底层数据存储和计算是基于numpy库的,pandas在读取数据时,尤其是读取数值类型时,会优先采用存储数值范围更大的int64或float64类型来存储数值数据,比如对数值1采用float64类型存储,这么做虽然可以保证数据在计算时不会出错,防止数据的数值范围超出数值类型的范围上限变为inf,但是,float64数据类型就是更占内存。

因此,如果一个变量它的取值范围在 [ 0 , 1 ] [0,1] [0,1]上,那么我们完全可以采用占用内存较少的int8类型来存储,这也就是前文解决方法的思路。

参考资料

[1] Datawhale_如何打一个推荐比赛V2.1 - 入门篇
[2] python pandas数据类型与占用内存–优化
[3] Python里为什么bool/int类型比float类型占用内存还大?

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin Davis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值