稀疏数据结构#
PANAS提供了有效存储稀疏数据的数据结构。在典型的“大部分为0”中,这些不一定是稀疏的。相反,您可以将这些对象视为“压缩”对象,其中任何数据都与特定值匹配 (NaN
/MISSING值,但可以选择任何值,包括0)。压缩的值实际上并不存储在数组中。
In [1]: arr = np.random.randn(10)
In [2]: arr[2:-2] = np.nan
In [3]: ts = pd.Series(pd.arrays.SparseArray(arr))
In [4]: ts
Out[4]:
0 0.469112
1 -0.282863
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 -0.861849
9 -2.104569
dtype: Sparse[float64, nan]
请注意dtype, Sparse[float64, nan]
。这个 nan
意味着数组中的元素是 nan
并不实际存储,只有非``nan``元素存储。那些非``nan``元素有一个 float64
数据类型。
稀疏对象的存在是出于内存效率的原因。假设你有一个很大的,大部分是NA的 DataFrame
:
In [5]: df = pd.DataFrame(np.random.randn(10000, 4))
In [6]: df.iloc[:9998] = np.nan
In [7]: sdf = df.astype(pd.SparseDtype("float", np.nan))
In [8]: sdf.head()
Out[8]:
0 1 2 3
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
In [9]: sdf.dtypes
Out[9]:
0 Sparse[float64, nan]
1 Sparse[float64, nan]
2 Sparse[float64, nan]
3 Sparse[float64, nan]
dtype: object
In [10]: sdf.sparse.density
Out[10]: 0.0002
如您所见,密度(未压缩的值的百分比)非常低。这个稀疏对象在磁盘上(Picked)和在Python解释器中占用的内存要少得多。
In [11]: 'dense : {:0.2f} bytes'.format(df.memory_usage().sum() / 1e3)
Out[11]: 'dense : 320.13 bytes'
In [12]: 'sparse: {:0.2f} bytes'.format(sdf.memory_usage().sum() / 1e3)
Out[12]: 'sparse: 0.22 bytes'
在功能上,它们的行为应该与密集的同类几乎相同。
SparseArray#
arrays.SparseArray
是一种 ExtensionArray
用于存储稀疏值的数组(请参见 数据类型 有关扩展阵列的更多信息)。它是一个一维ndarray对象,只存储不同于 fill_value
:
In [13]: arr = np.random.randn(10)
In [14]: arr[2:5] = np.nan
In [15]: arr[7:8] = np.nan
In [16]: sparr = pd.arrays.SparseArray(arr)
In [17]: sparr
Out[17]:
[-1.9556635297215477, -1.6588664275960427, nan, nan, nan, 1.1589328886422277, 0.14529711373305043, nan, 0.6060271905134522, 1.3342113401317768]
Fill: nan
IntIndex
Indices: array([0, 1, 5, 6, 8, 9], dtype=int32)
稀疏数组可以转换为规则(密集)ndarray numpy.asarray()
In [18]: np.asarray(sparr)
Out[18]:
array([-1.9557, -1.6589, nan, nan, nan, 1.1589, 0.1453,
nan, 0.606 , 1.3342])
SparseDtype#
这个 SparseArray.dtype
属性存储两条信息
非稀疏值的数据类型
标量填充值
In [19]: sparr.dtype
Out[19]: Sparse[float64, nan]
A SparseDtype
可以通过仅传递dtype来构造
In [20]: pd.SparseDtype(np.dtype('datetime64[ns]'))
Out[20]: Sparse[datetime64[ns], numpy.datetime64('NaT')]
在这种情况下,将使用默认填充值(对于NumPy数据类型,这通常是该数据类型的“缺失”值)。要覆盖此缺省值,可以改为传递显式填充值
In [21]: pd.SparseDtype(np.dtype('datetime64[ns]'),
....: fill_value=pd.Timestamp('2017-01-01'))
....:
Out[21]: Sparse[datetime64[ns], Timestamp('2017-01-01 00:00:00')]
最后,字符串别名 'Sparse[dtype]'
可用于在许多地方指定稀疏数据类型
In [22]: pd.array([1, 0, 0, 2], dtype='Sparse[int]')
Out[22]:
[1, 0, 0, 2]
Fill: 0
IntIndex
Indices: array([0, 3], dtype=int32)
稀疏访问器#
Pandas提供了一种 .sparse
访问器,类似于 .str
对于字符串数据, .cat
对于分类数据,以及 .dt
用于类似DATETIME的数据。此命名空间提供特定于稀疏数据的属性和方法。
In [23]: s = pd.Series([0, 0, 1, 2], dtype="Sparse[int]")
In [24]: s.sparse.density
Out[24]: 0.5
In [25]: s.sparse.fill_value
Out[25]: 0
此访问器仅适用于具有 SparseDtype
,以及在 Series
创建具有稀疏数据的系列的类本身。
0.25.0 新版功能.
稀疏计算#
您可以应用NumPy ufuncs 至 arrays.SparseArray
然后拿到一个 arrays.SparseArray
结果。
In [26]: arr = pd.arrays.SparseArray([1., np.nan, np.nan, -2., np.nan])
In [27]: np.abs(arr)
Out[27]:
[1.0, nan, nan, 2.0, nan]
Fill: nan
IntIndex
Indices: array([0, 3], dtype=int32)
这个 UFunc 也适用于 fill_value
。这是获得正确的密集结果所必需的。
In [28]: arr = pd.arrays.SparseArray([1., -1, -1, -2., -1], fill_value=-1)
In [29]: np.abs(arr)
Out[29]:
[1, 1, 1, 2.0, 1]
Fill: 1
IntIndex
Indices: array([3], dtype=int32)
In [30]: np.abs(arr).to_dense()
Out[30]: array([1., 1., 1., 2., 1.])
正在迁移#
备注
SparseSeries
和 SparseDataFrame
在Pandas1.0.0中被移除。本迁移指南旨在帮助您从以前的版本进行迁移。
在较老版本的Pandas中, SparseSeries
和 SparseDataFrame
类(如下所述)是处理稀疏数据的首选方法。随着扩展数组的出现,不再需要这些子类。使用具有稀疏值的常规Series或DataFrame可以更好地实现它们的目的。
备注
使用具有稀疏值的Series或DataFrame而不是SparseSeries或SparseDataFrame不会对性能或内存造成影响。
本节提供了一些关于将代码迁移到新样式的指导。提醒一下,您可以使用Python警告模块来控制警告。但我们建议您修改代码,而不是忽略警告。
Construction
从类似数组的类型中,使用常规 Series
或 DataFrame
构造函数具有 arrays.SparseArray
价值。
# Previous way
>>> pd.SparseDataFrame({"A": [0, 1]})
# New way
In [31]: pd.DataFrame({"A": pd.arrays.SparseArray([0, 1])})
Out[31]:
A
0 0
1 1
从SciPy稀疏矩阵中,使用 DataFrame.sparse.from_spmatrix()
,
# Previous way
>>> from scipy import sparse
>>> mat = sparse.eye(3)
>>> df = pd.SparseDataFrame(mat, columns=['A', 'B', 'C'])
# New way
In [32]: from scipy import sparse
In [33]: mat = sparse.eye(3)
In [34]: df = pd.DataFrame.sparse.from_spmatrix(mat, columns=['A', 'B', 'C'])
In [35]: df.dtypes
Out[35]:
A Sparse[float64, 0]
B Sparse[float64, 0]
C Sparse[float64, 0]
dtype: object
Conversion
从稀疏到密集,使用 .sparse
访问者
In [36]: df.sparse.to_dense()
Out[36]:
A B C
0 1.0 0.0 0.0
1 0.0 1.0 0.0
2 0.0 0.0 1.0
In [37]: df.sparse.to_coo()
Out[37]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in COOrdinate format>
从密集到稀疏,使用 DataFrame.astype()
使用一个 SparseDtype
。
In [38]: dense = pd.DataFrame({"A": [1, 0, 0, 1]})
In [39]: dtype = pd.SparseDtype(int, fill_value=0)
In [40]: dense.astype(dtype)
Out[40]:
A
0 1
1 0
2 0
3 1
稀疏属性
稀疏特定的属性,如 density
,可在 .sparse
访问者。
In [41]: df.sparse.density
Out[41]: 0.3333333333333333
一般差异
在一个 SparseDataFrame
, all 柱子很稀疏。一个 DataFrame
可以混合使用稀疏列和密集列。因此,将新列分配给 DataFrame
使用稀疏值不会自动将输入转换为稀疏。
# Previous Way
>>> df = pd.SparseDataFrame({"A": [0, 1]})
>>> df['B'] = [0, 0] # implicitly becomes Sparse
>>> df['B'].dtype
Sparse[int64, nan]
相反,您需要确保分配的值是稀疏的
In [42]: df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1])})
In [43]: df['B'] = [0, 0] # remains dense
In [44]: df['B'].dtype
Out[44]: dtype('int64')
In [45]: df['B'] = pd.arrays.SparseArray([0, 0])
In [46]: df['B'].dtype
Out[46]: Sparse[int64, 0]
这个 SparseDataFrame.default_kind
和 SparseDataFrame.default_fill_value
属性是不可替代的。
与Scipy.Sparse交互#
使用 DataFrame.sparse.from_spmatrix()
要创建 DataFrame
具有来自稀疏矩阵的稀疏值。
0.25.0 新版功能.
In [47]: from scipy.sparse import csr_matrix
In [48]: arr = np.random.random(size=(1000, 5))
In [49]: arr[arr < .9] = 0
In [50]: sp_arr = csr_matrix(arr)
In [51]: sp_arr
Out[51]:
<1000x5 sparse matrix of type '<class 'numpy.float64'>'
with 517 stored elements in Compressed Sparse Row format>
In [52]: sdf = pd.DataFrame.sparse.from_spmatrix(sp_arr)
In [53]: sdf.head()
Out[53]:
0 1 2 3 4
0 0.956380 0.0 0.0 0.000000 0.0
1 0.000000 0.0 0.0 0.000000 0.0
2 0.000000 0.0 0.0 0.000000 0.0
3 0.000000 0.0 0.0 0.000000 0.0
4 0.999552 0.0 0.0 0.956153 0.0
In [54]: sdf.dtypes
Out[54]:
0 Sparse[float64, 0]
1 Sparse[float64, 0]
2 Sparse[float64, 0]
3 Sparse[float64, 0]
4 Sparse[float64, 0]
dtype: object
支持所有稀疏格式,但不支持 COOrdinate
将转换格式,根据需要复制数据。要转换回COO格式的稀疏SciPy矩阵,您可以使用 DataFrame.sparse.to_coo()
方法:
In [55]: sdf.sparse.to_coo()
Out[55]:
<1000x5 sparse matrix of type '<class 'numpy.float64'>'
with 517 stored elements in COOrdinate format>
Series.sparse.to_coo()
被实现为将 Series
稀疏值由 MultiIndex
发送到 scipy.sparse.coo_matrix
。
该方法需要一个 MultiIndex
具有两个或两个以上级别的。
In [56]: s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan])
In [57]: s.index = pd.MultiIndex.from_tuples(
....: [
....: (1, 2, "a", 0),
....: (1, 2, "a", 1),
....: (1, 1, "b", 0),
....: (1, 1, "b", 1),
....: (2, 1, "b", 0),
....: (2, 1, "b", 1),
....: ],
....: names=["A", "B", "C", "D"],
....: )
....:
In [58]: ss = s.astype('Sparse')
In [59]: ss
Out[59]:
A B C D
1 2 a 0 3.0
1 NaN
1 b 0 1.0
1 3.0
2 1 b 0 NaN
1 NaN
dtype: Sparse[float64, nan]
在下面的示例中,我们将 Series
的稀疏表示形式,方法是指定第一个和第二个 MultiIndex
级别定义行的标签,第三级和第四级定义列的标签。我们还指定应该在最终的稀疏表示中对列和行标签进行排序。
In [60]: A, rows, columns = ss.sparse.to_coo(
....: row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True
....: )
....:
In [61]: A
Out[61]:
<3x4 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in COOrdinate format>
In [62]: A.todense()
Out[62]:
matrix([[0., 0., 1., 3.],
[3., 0., 0., 0.],
[0., 0., 0., 0.]])
In [63]: rows
Out[63]: [(1, 1), (1, 2), (2, 1)]
In [64]: columns
Out[64]: [('a', 0), ('a', 1), ('b', 0), ('b', 1)]
指定不同的行和列标签(并且不对其进行排序)会生成不同的稀疏矩阵:
In [65]: A, rows, columns = ss.sparse.to_coo(
....: row_levels=["A", "B", "C"], column_levels=["D"], sort_labels=False
....: )
....:
In [66]: A
Out[66]:
<3x2 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in COOrdinate format>
In [67]: A.todense()
Out[67]:
matrix([[3., 0.],
[1., 3.],
[0., 0.]])
In [68]: rows
Out[68]: [(1, 2, 'a'), (1, 1, 'b'), (2, 1, 'b')]
In [69]: columns
Out[69]: [(0,), (1,)]
一种方便的方法 Series.sparse.from_coo()
是用来创建 Series
属性中的稀疏值 scipy.sparse.coo_matrix
。
In [70]: from scipy import sparse
In [71]: A = sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4))
In [72]: A
Out[72]:
<3x4 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in COOrdinate format>
In [73]: A.todense()
Out[73]:
matrix([[0., 0., 1., 2.],
[3., 0., 0., 0.],
[0., 0., 0., 0.]])
默认行为(使用 dense_index=False
)简单地返回一个 Series
仅包含非空条目。
In [74]: ss = pd.Series.sparse.from_coo(A)
In [75]: ss
Out[75]:
0 2 1.0
3 2.0
1 0 3.0
dtype: Sparse[float64, nan]
指定 dense_index=True
将产生一个索引,该索引是矩阵的行和列坐标的笛卡尔积。请注意,这将消耗大量内存(相对于 dense_index=False
)如果稀疏矩阵足够大(且稀疏)。
In [76]: ss_dense = pd.Series.sparse.from_coo(A, dense_index=True)
In [77]: ss_dense
Out[77]:
0 0 NaN
1 NaN
2 1.0
3 2.0
1 0 3.0
1 NaN
2 NaN
3 NaN
2 0 NaN
1 NaN
2 NaN
3 NaN
dtype: Sparse[float64, nan]