Skip to content

Python Pandas - 稀疏数据

在许多数据集中,很大一部分数据可能由单个常数值组成(通常是 0 或 NaN/缺失值)。使用标准(密集 dense)结构(如 NumPy 数组或常规 Pandas Series/DataFrame)存储这些数据集在内存方面效率较低。

Pandas 提供了专门设计用于提高内存效率的稀疏数据结构(Sparse Data Structures),特别是在数据主要由特定“填充值”(fill value)构成时。稀疏结构不是存储每个值,而是只存储非填充值及其位置。

现代 Pandas 通过 SparseDtype 扩展类型实现稀疏性。通常通过指定此 dtype 来创建稀疏 Series 或 DataFrame。

您可以通过提供 dtype=pd.SparseDtype(type, fill_value) 来创建一个稀疏 Series。

  • type:非稀疏值的数据类型(例如 float、int、bool)。
  • fill_value:被视为“稀疏”且未显式存储的值。默认值取决于 type(例如,float 为 np.nan,int 为 0,bool 为 False)。
import pandas as pd
import numpy as np
# 创建一个包含许多 NaN 的密集 Series
dense_series = pd.Series(np.random.randn(10))
dense_series[2:-2] = np.nan # 使大部分值为 NaN
print("Dense Series:")
print(dense_series)
print(f"内存使用(密集):{dense_series.memory_usage(deep=True)} bytes")
# 创建稀疏版本
sparse_dtype = pd.SparseDtype(float, np.nan) # float 类型,NaN 是填充值
sparse_series = dense_series.astype(sparse_dtype)
print("\nSparse Series:")
print(sparse_series)
print(f"内存使用(稀疏):{sparse_series.memory_usage(deep=True)} bytes")

输出(内存使用量可能因系统/版本略有差异):

Dense Series:
0 0.515631
1 -1.142257
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 0.631974
9 0.107211
dtype: float64
Memory usage (dense): 208 bytes
Sparse Series:
0 0.515631
1 -1.142257
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 0.631974
9 0.107211
dtype: Sparse[float64, nan]
Memory usage (sparse): 112 bytes

注意,稀疏 Series 打印出来时看起来与密集 Series 相同,但其 dtype 表示它是稀疏的,并且由于未显式存储每个位置的 NaN 值,其内存使用量显着降低。

.sparse.density 属性显示非填充值所占的比例。

print(f"\n稀疏 Series 的密度:{sparse_series.sparse.density:.2f}")

输出:

Density of sparse series: 0.40

这表示 40% 的值是非 NaN。

您可以使用 .sparse.to_dense() 或 .astype() 并指定非稀疏 dtype,将稀疏 Series 或 DataFrame 转换回其密集(dense)表示形式。

# 使用 .sparse.to_dense() 转换回密集格式
dense_again = sparse_series.sparse.to_dense()
print("\n转换回密集格式:")
print(dense_again)
print(f"内存使用(再次密集):{dense_again.memory_usage(deep=True)} bytes")

输出:

Converted back to dense:
0 0.515631
1 -1.142257
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 0.631974
9 0.107211
dtype: float64
Memory usage (dense again): 208 bytes

稀疏性也可以应用于 DataFrame,可以按列应用,也可以在适当情况下应用于整个 DataFrame。

import pandas as pd
import numpy as np
# 创建一个包含许多零的密集 DataFrame
dense_df = pd.DataFrame(np.random.randint(0, 2, size=(1000, 4)) * np.random.randint(0, 10, size=(1000,4)))
dense_df.iloc[10:] = 0 # 使大部分值为零
print(f"内存(密集 DF):{dense_df.memory_usage(deep=True).sum()} bytes")
# 转换为稀疏格式(int dtype, fill_value=0)
sparse_df = dense_df.astype(pd.SparseDtype('int', 0))
print(f"内存(稀疏 DF):{sparse_df.memory_usage(deep=True).sum()} bytes")
print(f"密度(稀疏 DF):{sparse_df.sparse.density:.4f}")
#print("\n稀疏 DataFrame 示例:")
#print(sparse_df.head(15)) # 显示头部数据可以看到结构

输出(内存/密度可能略有差异):

Memory (dense DF): 32128 bytes
Memory (sparse DF): 1560 bytes
Density (sparse DF): 0.0093

对于具有高稀疏度的大型数据集,内存节省可能非常可观。

SparseDtype 的默认 fill_value 取决于底层数据类型:

  • float:np.nan
  • int:0
  • bool:False
  • datetime64[ns]:pd.NaT (非时间 Not a Time)
  • timedelta64[ns]:pd.NaT

您可以根据需要显式指定不同的 fill_value,但通常使用默认值。

# 示例:fill_value = 1 的稀疏 Series
s_fill_one = pd.Series([1, 2, 1, 3, 1, 1], dtype=pd.SparseDtype(int, 1))
print("\nfill_value=1 的稀疏 Series:")
print(s_fill_one)
print(f"密度:{s_fill_one.sparse.density:.2f}") # 只有 2 和 3 是非填充值

输出:

Sparse series with fill_value=1:
0 1
1 2
2 1
3 3
4 1
5 1
dtype: Sparse[int64, 1]
Density: 0.33

稀疏数据结构在机器学习(例如,处理表示为词袋或 TF-IDF 矩阵的文本数据)和科学计算等领域特别有用,这些领域中大型稀疏矩阵很常见。