我想在稀疏pandas容器中使用较小的数据类型的原因是为了减少内存使用量。这在处理最初使用bool(例如来自to_dummies
)或小数字dtypes(例如int8)的数据时是相关的,这些数据在稀疏容器中都被转换为float64。
提供的示例使用适度的20k x 145数据帧。在实践中,我正在使用大约1e6 x 5e3的数据帧。
In []: bool_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 19849 entries, 0 to 19848
Columns: 145 entries, topic.party_nl.p.pvda to topic.sub_cat_Reizen
dtypes: bool(145)
memory usage: 2.7 MB
In []: bool_df.memory_usage(index=False).sum()
Out[]: 2878105
In []: bool_df.values.itemsize
Out[]: 1
这个数据帧的稀疏版本需要更少的内存,但是在给定原始dtype的情况下仍然比需要的大得多。
In []: sparse_df = bool_df.to_sparse(fill_value=False)
In []: sparse_df.info()
<class 'pandas.sparse.frame.SparseDataFrame'>
RangeIndex: 19849 entries, 0 to 19848
Columns: 145 entries, topic.party_nl.p.pvda to topic.sub_cat_Reizen
dtypes: float64(145)
memory usage: 1.1 MB
In []: sparse_df.memory_usage(index=False).sum()
Out[]: 1143456
In []: sparse_df.values.itemsize
Out[]: 8
即使这些数据相当稀疏,从bool到float64的dtype转换也会导致非填充值占用8倍的空间。
In []: sparse_df.memory_usage(index=False).describe()
Out[]:
count 145.000000
mean 7885.903448
std 17343.762402
min 8.000000
25% 640.000000
50% 1888.000000
75% 4440.000000
max 84688.000000
鉴于数据的稀疏性,人们希望更大幅度地减少内存大小:
In []: sparse_df.density
Out[]: 0.04966184346992205
SparseDataFrame
的列为SparseSeries
,使用SparseArray
作为基础numpy.ndarray
存储的包装。稀疏数据帧使用的字节数也可以(也)直接从这些ndarray计算:
In []: col64_nbytes = [
.....: sparse_df[col].values.sp_values.nbytes
.....: for col in sparse_df
.....: ]
In []: sum(col64_nbytes)
Out[]: 1143456
可以将ndarrays转换为使用较小的浮点数,从而可以计算数据帧在使用时需要多少内存。 float16s。正如人们所料,这将导致数据帧缩小4倍。
In []: col16_nbytes = [
.....: sparse_df[col].values.sp_values.astype('float16').nbytes
.....: for col in sparse_df
.....: ]
In []: sum(col16_nbytes)
Out[]: 285864
通过使用更合适的dtype,内存使用量可以减少到密集版本的10%,而float64稀疏数据帧减少到40%。对于我的数据,这可能会产生需要20 GB和5 GB可用内存的差异。
In []: sum(col64_nbytes) / bool_df.memory_usage(index=False).sum()
Out[]: 0.3972947477593764
In []: sum(col16_nbytes) / bool_df.memory_usage(index=False).sum()
Out[]: 0.0993236869398441
不幸的是,稀疏容器的dtype转换尚未在pandas中实现:
In []: sparse_df.astype('float16')
---------------------------------------------------
[...]/pandas/sparse/frame.py in astype(self, dtype)
245
246 def astype(self, dtype):
--> 247 raise NotImplementedError
248
249 def copy(self, deep=True):
NotImplementedError:
如何将SparseSeries
中的SparseDataFrame
转换为使用numpy.float16
数据类型,或者每个项目使用少于64个字节的其他dtype,而不是默认{{1} }}?
答案 0 :(得分:0)
SparseArray
构造函数可用于转换其基础ndarray
的dtype。要转换数据帧中的所有稀疏序列,可以迭代df系列,转换它们的数组,并用转换后的版本替换该系列。
import pandas as pd
import numpy as np
def convert_sparse_series_dtype(sparse_series, dtype):
dtype = np.dtype(dtype)
if 'float' not in str(dtype):
raise TypeError('Sparse containers only support float dtypes')
sparse_array = sparse_series.values
converted_sp_array = pd.SparseArray(sparse_array, dtype=dtype)
converted_sp_series = pd.SparseSeries(converted_sp_array)
return converted_sp_series
def convert_sparse_columns_dtype(sparse_dataframe, dtype):
for col_name in sparse_dataframe:
if isinstance(sparse_dataframe[col_name], pd.SparseSeries):
sparse_dataframe.loc[:, col_name] = convert_sparse_series_dtype(
sparse_dataframe[col_name], dtype
)
这实现了减少稀疏数据帧内存占用的既定目的:
In []: sparse_df.info()
<class 'pandas.sparse.frame.SparseDataFrame'>
RangeIndex: 19849 entries, 0 to 19848
Columns: 145 entries, topic.party_nl.p.pvda to topic.sub_cat_Reizen
dtypes: float64(145)
memory usage: 1.1 MB
In []: convert_sparse_columns_dtype(sparse_df, 'float16')
In []: sparse_df.info()
<class 'pandas.sparse.frame.SparseDataFrame'>
RangeIndex: 19849 entries, 0 to 19848
Columns: 145 entries, topic.party_nl.p.pvda to topic.sub_cat_Reizen
dtypes: float16(145)
memory usage: 279.2 KB
In []: bool_df.equals(sparse_df.to_dense().astype('bool'))
Out[]: True
然而,这是一个有点糟糕的解决方案,因为转换后的数据帧在与其他数据帧交互时表现不可预测。例如,当转换的稀疏数据帧与其他数据帧连接时,所有包含的系列都变为密集序列。对于未转换的稀疏数据帧,情况并非如此。它们在结果数据框中保持稀疏序列。