非零项不一致:pandas的稀疏数组与密集数组

编程语言 2026-07-09

原文

我的理解是,稀疏矩阵和密集矩阵存储的是相同的信息,只是内部格式不同。我有一个大部分值为False的稀疏矩阵:

(Pdb) x
[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, ...]
Fill: False
IntIndex
Indices: array([  73004,   73005,   73007, ..., 2629273, 2629274, 2629275],
      dtype=int32)

检查nonzero的值是否与上面的表示一致:

(Pdb) x.nonzero()
(array([  73004,   73005,   73007, ..., 2629273, 2629274, 2629275],
      dtype=int32),)

这里是我看到有些意外的地方:

(Pdb) x[73004]
False

更多出乎意料的结果:

(Pdb) y = x.to_dense()
(Pdb) y.nonzero()
(array([ 456646,  658112,  692257, ..., 2608127, 2608154, 2608292]),)

显然,稀疏数组和密集数组并不仅在内部方式上不同,因为经过一次往返后你不会得到相同的结果:

(Pdb) z = pd.arrays.SparseArray(x.to_dense())
(Pdb) z
[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, ...]
Fill: False
IntIndex
Indices: array([ 456646,  658112,  692257, ..., 2608127, 2608154, 2608292],
      dtype=int32)

我在这里到底漏掉了什么?

编辑

下面的实现可能并非最简,但它用于重现一个类似上文x的稀疏数组:

import pandas as pd

sa = pd.arrays.SparseArray([1,-1,2,1,-2,4], fill_value=-2, dtype=float)
as_int = pd.DataFrame(sa)[0].apply(int)
x = ((as_int > 0) & (as_int & 1) > 0).values

我看到的是:

(Pdb) print(f"{pd.__version__}: {x.nonzero()}")
1.1.5: (array([0, 1, 2, 3, 5], dtype=int32),)

我在新版本的pandas中无法重现。在2.2.3中,产生x的那一行会导致以下警告:

FutureWarning: Allowing arbitrary scalar fill_value in SparseDtype is deprecated. In a future version, the fill_value must be a valid value for the SparseDtype.subtype.
>>> print(f"{pd.__version__}: {x.nonzero()}")
2.2.3: (array([0, 3], dtype=int32),)

3.0.2上,产生x的那一行会报错:

ValueError: fill_value must be a valid value for the SparseDtype.subtype

解决方案

显然,稀疏数组和密集数组并不仅在内部格式上有差异,因为经过一次往返后得到的结果并不相同:

稀疏数组和密集数组之间有一个相当重要的区别:稀疏数组对零值可以有多种表示方式,而密集数组只有一种表示。

例如,在这段代码中,a和b拥有不同的内部表示,但语义相同。

import pandas as pd
from pandas._libs.sparse import IntIndex
sparse_index = IntIndex(3, np.array([0, 1, 2]))
a = pd.arrays.SparseArray([0, 1, 2], sparse_index=sparse_index)
sparse_index = IntIndex(3, np.array([1, 2]))
b = pd.arrays.SparseArray([1, 2], sparse_index=sparse_index)
print(a)
print(b)

输出:

[0, 1, 2]
Fill: 0
IntIndex
Indices: array([0, 1, 2], dtype=int32)

[0, 1, 2]
Fill: 0
IntIndex
Indices: array([1, 2], dtype=int32)

从阅读 SparseArray 这段代码,我不太清楚SparseArray是否允许在sp\_values数组中包含fill\_value值。 __init__ 会过滤掉冗余值,除非提供sparse\_index对象。在这种情况下,它避免重复过滤冗余值的工作,在某些情况下这可能会非常慢。

我对Pandas的稀疏实现并没有太多经验——我只对SciPy的稀疏实现有过贡献。就SciPy而言,在数组中允许出现“显式零”是完全允许的。对我来说,这里的问题是否在于你可以得到一个在sp\_values中包含fill\_value的 SparseArray,还是在于 SparseArray.nonzero() 包含的代码假设sp\_values不会有冗余值。

尝试使用SparseArray时,似乎并不难找到让sp\_values中出现fill\_value的方法,即使不滥用前面那个示例中的私有API。

例如,导致fill\_value的 binops可能会产生冗余值:

import numpy as np
import pandas as pd

a = pd.arrays.SparseArray([True,  True,  True,  False])
b = pd.arrays.SparseArray([False, False, False, True])
print(a)
print(b)
result = a & b
print(result)

输出:

[True, True, True, False]
Fill: False
IntIndex
Indices: array([0, 1, 2], dtype=int32)

[False, False, False, True]
Fill: False
IntIndex
Indices: array([3], dtype=int32)

[False, False, False, False]
Fill: False
IntIndex
Indices: array([0, 1, 2, 3], dtype=int32)

在这个例子中,所有的False值都被显式存储。

另一方面,我确实找到了一个过去的Pandas问题,提示这种状态下的SparseArray不是一个有效的值。这意味着你在Pandas中用来获取这个SparseArray的任何方法,某种程度上都存在一个错误。

如果你能找出导致包含sp\_value条目等于fill\_value的 SparseArray的 Pandas操作序列,我建议你联系Pandas开发者

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章