如何优化大数据集内存占用?在用Pandas进行数据分析时,首先对读取的数据清洗操作包括剔除空列、去除不合要求的表头、设置列名等,而经常忽略对数据列设置相应的数据类型,而数据类型设置对大数据集内存占用产生重要影响。
1、优化数据类型减少内存占用
一般来说pandas 它会自动推断出数据类型,如果数值型列数据包括了缺失值,推断数据类型就会自动填充为浮点型。推断的数据类型并不一定是最优,有时候会产生意想不到的结果。
通常情况下,Pandas对读取的数据列默认是设置为object数据类型,这种通用类型因自身的兼容性会导致所读取的数据占据较大的内存空间,倘若能给它们设置合适的数据类型,就可以降低该数据集的实际内存占用,从而提升运行效率。
pandas、python 和 numpy 之间类型总结
Pandas dtype | Python type | NumPy type | Usage |
---|---|---|---|
object | str or mixed | string_, unicode_, mixed types | Text or mixed numeric and non-numeric values |
int64 | int | int_, int8, int16, int32, int64, uint8, uint16, uint32, uint64 | Integer numbers |
float64 | float | float_, float16, float32, float64 | Floating point numbers |
bool | bool | bool_ | True/False values |
datetime64 | NA | datetime64[ns] | Date and time values |
timedelta[ns] | NA | NA | Differences between two datetimes |
category | NA | NA | Finite list of text values |
测试数据集:Index of /ml/machine-learning-databases/00616 (uci.edu)
首先看一下数据集的各列分别是什么数据类型:
代码语言:javascript复制
import pandas as pd
df = pd.read_csv('Tetuan City power consumption.csv')
print(df.dtypes)
从上图可以看出,数据类型分别为object和int64两种,从数据的显示情况来看,DateTime列可以设置为日期类型,重新设置对比如下:
代码语言:javascript复制
import pandas as pd
df1 =df.copy()
print(df1.head())
df1['DateTime'] = pd.to_datetime(df1['DateTime'])
print(df1.dtypes)
利用DataFrame的memory_usage属性对内存测量,需要为memory_usage添加deep=True:
代码语言:javascript复制df.memory_usage(deep=True)
对比df和df1数据集内存使用量进行求和,并设置为以Mb为单位:
代码语言:javascript复制print(f"{df.memory_usage(deep=True).sum()/1024**2:.2f}Mb")
print(f"{df1.memory_usage(deep=True).sum()/1024**2:.2f}Mb")
从上图可以看出,通过对数据列的数据类型设置,其内存有了明显下降,然而我们还可以继续进行设置,因为Pandas中的浮点类型有float16、float32、float64三类,它们对应不同的小数范围:
代码语言:javascript复制import numpy as np
print(f"{np.finfo(np.float16).min}~{np.finfo(np.float16).max}")
print(f"{np.finfo(np.float32).min}~{np.finfo(np.float32).max}")
print(f"{np.finfo(np.float64).min}~{np.finfo(np.float64).max}")
数据集小数默认均是floa64,那么究竟应该是哪种浮点型类型合适呢,我们来看一下各列最小~最大值范围:
代码语言:javascript复制df.describe()
从结果来看,我们可以将各列均设置为float16即可满足:
代码语言:javascript复制df1['Temperature'] = df1['Temperature'].astype(np.float16)
df1['Humidity'] = df1['Humidity'].astype(np.float16)
df1['Wind Speed'] = df1['Wind Speed'].astype(np.float16)
df1['general diffuse flows'] = df1['general diffuse flows'].astype(np.float16)
df1['diffuse flows'] = df1['diffuse flows'].astype(np.float16)
df1['general diffuse flows'] = df1['general diffuse flows'].astype(np.float16)
df1['Zone 1 Power Consumption'] = df1['Zone 1 Power Consumption'].astype(np.float16)
df1['Zone 2 Power Consumption'] = df1['Zone 2 Power Consumption'].astype(np.float16)
df1['Zone 3 Power Consumption'] = df1['Zone 3 Power Consumption'].astype(np.float16)
print(df1.dtypes)
最后看一下df1的内存使用大小:
代码语言:javascript复制print(f"{df1.memory_usage(deep=True).sum()/1024**2:.2f}Mb")
1.20Mb
内存占用从3.6Mb减小到了1.2Mb,数据类型优化设置确实有效降低内存使用。
当字段多手动确实麻烦,自动设置数据集的合理数据类型。
思路:遍历每一列,然后找出该列的最大值与最小值,我们将这些最大最小值与子类型当中的最大最小值去做比较,选择字节数最小的子类型。
代码如下:
代码语言:javascript复制def reset_datatype(df):
def _reset_datatype(x):
unique_data = list(x.unique())
try:
unique_data.remove(np.nan)
except:
pass
cat_ratio = len(unique_data) / len(x)
if 'int' in x.dtype.name:
c_min = x.min()
c_max = x.max()
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
x = x.astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
x = x.astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
x = x.astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
x = x.astype(np.int64)
elif 'float' in x.dtype.name:
c_min = x.min()
c_max = x.max()
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
x = x.astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
x = x.astype(np.float32)
else:
x = x.astype(np.float64)
else:
try:
x = pd.to_datetime(x)
except:
if all(str(i).lower() in ['true', 'false'] for i in unique_data):
x = x.astype('boolean')
elif cat_ratio < 0.1:
x = x.astype('category')
elif all(isinstance(i, str) for i in unique_data):
x = x.astype('string')
return x
return df.apply(lambda x: _reset_datatype(x))
2、数据分块
read_csv()方法当中的chunksize参数
read_csv()方法当中的chunksize参数顾名思义就是对于超大csv文件,我们可以分块来进行读取,例如文件当中有7000万行的数据,我们将chunksize参数设置为100万,每次分100万来分批读取。
df_chunk = pd.read_csv(r'data.csv', chunksize=1000000)
df_chunk并非是一个DataFrame对象,而是一个可迭代的对象。
小结
本文对于Pandas读取csv后的数据占用内存问题进行了分析,并给出了通过对数据类型合理设置来减小大数据集内存占用。