Pandas 2.2 中文官方教程和指南(二十四)(1)https://developer.aliyun.com/article/1508862
稀疏数据结构
pandas 提供了用于高效存储稀疏数据的数据结构。这些数据结构不一定是典型的“大部分为 0”的稀疏数据。相反,您可以将这些对象视为“压缩的”,其中任何与特定值匹配的数据(NaN
/ 缺失值,尽管可以选择任何值,包括 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
dtype。
稀疏对象存在是为了内存效率的原因。假设您有一个大多数为 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
正如您所看到的,密度(未“压缩”的值的百分比)非常低。这个稀疏对象在磁盘(pickled)和 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'
从功能上讲,它们的行为应该几乎与它们的密集对应物相同。
稀疏数组
arrays.SparseArray
是用于存储稀疏值数组的ExtensionArray
(有关扩展数组的更多信息,请参见 dtypes)。它是一个一维类似 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)
稀疏数组可以使用numpy.asarray()
转换为常规(密集)ndarray
In [18]: np.asarray(sparr) Out[18]: array([-1.9557, -1.6589, nan, nan, nan, 1.1589, 0.1453, nan, 0.606 , 1.3342]) ```## 稀疏 dtype `SparseArray.dtype` 属性存储两个信息 1. 非稀疏值的 dtype 1. 标量填充值 ```py In [19]: sparr.dtype Out[19]: Sparse[float64, nan]
可以通过仅传递 dtype 来构造SparseDtype
In [20]: pd.SparseDtype(np.dtype('datetime64[ns]')) Out[20]: Sparse[datetime64[ns], numpy.datetime64('NaT')]
在这种情况下,将使用默认填充值(对于 NumPy dtypes,通常是该 dtype 的“缺失”值)。可以传递显式填充值来覆盖此默认值
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]'
可用于在许多地方指定稀疏 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`。此命名空间提供了特定于稀疏数据的属性和方法。 ```py 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
类本身,用于从 scipy COO 矩阵创建具有稀疏数据的 Series。
为DataFrame
也添加了一个.sparse
访问器。更多信息请参见 Sparse accessor。 ## 稀疏计算
你可以将 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.])
转换
要将稀疏数据转换为稠密数据,使用.sparse
访问器
In [31]: sdf.sparse.to_dense() Out[31]: 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 ... ... ... ... ... 9995 NaN NaN NaN NaN 9996 NaN NaN NaN NaN 9997 NaN NaN NaN NaN 9998 0.509184 -0.774928 -1.369894 -0.382141 9999 0.280249 -1.648493 1.490865 -0.890819 [10000 rows x 4 columns]
从稠密到稀疏,使用带有SparseDtype
的DataFrame.astype()
。
In [32]: dense = pd.DataFrame({"A": [1, 0, 0, 1]}) In [33]: dtype = pd.SparseDtype(int, fill_value=0) In [34]: dense.astype(dtype) Out[34]: A 0 1 1 0 2 0 3 1 ```## 与*scipy.sparse*的交互 使用`DataFrame.sparse.from_spmatrix()`从稀疏矩阵创建具有稀疏值的`DataFrame`。 ```py In [35]: from scipy.sparse import csr_matrix In [36]: arr = np.random.random(size=(1000, 5)) In [37]: arr[arr < .9] = 0 In [38]: sp_arr = csr_matrix(arr) In [39]: sp_arr Out[39]: <1000x5 sparse matrix of type '<class 'numpy.float64'>' with 517 stored elements in Compressed Sparse Row format> In [40]: sdf = pd.DataFrame.sparse.from_spmatrix(sp_arr) In [41]: sdf.head() Out[41]: 0 1 2 3 4 0 0.95638 0 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0.999552 0 0 0.956153 0 In [42]: sdf.dtypes Out[42]: 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 [43]: sdf.sparse.to_coo() Out[43]: <1000x5 sparse matrix of type '<class 'numpy.float64'>' with 517 stored elements in COOrdinate format>
Series.sparse.to_coo()
用于将由MultiIndex
索引的具有稀疏值的Series
转换为scipy.sparse.coo_matrix
。
该方法需要具有两个或更多级别的MultiIndex
。
In [44]: s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan]) In [45]: 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 [46]: ss = s.astype('Sparse') In [47]: ss Out[47]: 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]
在下面的示例中,我们通过指定第一和第二个MultiIndex
级别定义行的标签,第三和第四个级别定义列的标签,将Series
转换为 2 维数组的稀疏表示。我们还指定列和行标签应在最终稀疏表示中排序。
In [48]: A, rows, columns = ss.sparse.to_coo( ....: row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True ....: ) ....: In [49]: A Out[49]: <3x4 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [50]: A.todense() Out[50]: matrix([[0., 0., 1., 3.], [3., 0., 0., 0.], [0., 0., 0., 0.]]) In [51]: rows Out[51]: [(1, 1), (1, 2), (2, 1)] In [52]: columns Out[52]: [('a', 0), ('a', 1), ('b', 0), ('b', 1)]
指定不同的行和列标签(并且不对它们进行排序)将产生不同的稀疏矩阵:
In [53]: A, rows, columns = ss.sparse.to_coo( ....: row_levels=["A", "B", "C"], column_levels=["D"], sort_labels=False ....: ) ....: In [54]: A Out[54]: <3x2 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [55]: A.todense() Out[55]: matrix([[3., 0.], [1., 3.], [0., 0.]]) In [56]: rows Out[56]: [(1, 2, 'a'), (1, 1, 'b'), (2, 1, 'b')] In [57]: columns Out[57]: [(0,), (1,)]
为从 scipy.sparse.coo_matrix
创建具有稀疏值的 Series
实现了一个方便的方法 Series.sparse.from_coo()
。
In [58]: from scipy import sparse In [59]: A = sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4)) In [60]: A Out[60]: <3x4 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [61]: A.todense() Out[61]: matrix([[0., 0., 1., 2.], [3., 0., 0., 0.], [0., 0., 0., 0.]])
默认行为(使用 dense_index=False
)只返回一个仅包含非空条目的 Series
。
In [62]: ss = pd.Series.sparse.from_coo(A) In [63]: ss Out[63]: 0 2 1.0 3 2.0 1 0 3.0 dtype: Sparse[float64, nan]
指定 dense_index=True
将导致索引为矩阵的行和列坐标的笛卡尔乘积。请注意,如果稀疏矩阵足够大(且稀疏),则这将消耗大量内存(相对于 dense_index=False
)。
In [64]: ss_dense = pd.Series.sparse.from_coo(A, dense_index=True) In [65]: ss_dense Out[65]: 1 0 3.0 2 NaN 3 NaN 0 0 NaN 2 1.0 3 2.0 0 NaN 2 1.0 3 2.0 dtype: Sparse[float64, nan] ```## 稀疏数组 `arrays.SparseArray` 是用于存储稀疏值数组的 `ExtensionArray`(有关扩展数组的更多信息,请参阅数据类型)。它是一个一维类似 ndarray 的对象,仅存储与 `fill_value` 不同的值: ```py 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)
使用 numpy.asarray()
可将稀疏数组转换为常规(密集)ndarray。
In [18]: np.asarray(sparr) Out[18]: array([-1.9557, -1.6589, nan, nan, nan, 1.1589, 0.1453, nan, 0.606 , 1.3342])
稀疏数据类型
SparseArray.dtype
属性存储两个信息
- 非稀疏值的数据类型
- 标量填充值
In [19]: sparr.dtype Out[19]: Sparse[float64, nan]
可以通过仅传递一个数据类型来构造 SparseDtype
。
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
。此命名空间提供了特定于稀疏数据的属性和方法。
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
类本身上可用于使用 scipy COO 矩阵创建具有稀疏数据的 Series。
为 DataFrame
添加了 .sparse
访问器。有关更多信息,请参阅稀疏访问器。
稀疏计算
您可以对 arrays.SparseArray
应用 NumPy ufuncs,并获得 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.])
转换
要将数据从稀疏转换为密集,使用 .sparse
访问器。
In [31]: sdf.sparse.to_dense() Out[31]: 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 ... ... ... ... ... 9995 NaN NaN NaN NaN 9996 NaN NaN NaN NaN 9997 NaN NaN NaN NaN 9998 0.509184 -0.774928 -1.369894 -0.382141 9999 0.280249 -1.648493 1.490865 -0.890819 [10000 rows x 4 columns]
从密集到稀疏,使用 DataFrame.astype()
和 SparseDtype
。
In [32]: dense = pd.DataFrame({"A": [1, 0, 0, 1]}) In [33]: dtype = pd.SparseDtype(int, fill_value=0) In [34]: dense.astype(dtype) Out[34]: A 0 1 1 0 2 0 3 1
与 scipy.sparse 的交互
使用 DataFrame.sparse.from_spmatrix()
可以从稀疏矩阵创建具有稀疏值的 DataFrame
。
In [35]: from scipy.sparse import csr_matrix In [36]: arr = np.random.random(size=(1000, 5)) In [37]: arr[arr < .9] = 0 In [38]: sp_arr = csr_matrix(arr) In [39]: sp_arr Out[39]: <1000x5 sparse matrix of type '<class 'numpy.float64'>' with 517 stored elements in Compressed Sparse Row format> In [40]: sdf = pd.DataFrame.sparse.from_spmatrix(sp_arr) In [41]: sdf.head() Out[41]: 0 1 2 3 4 0 0.95638 0 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0.999552 0 0 0.956153 0 In [42]: sdf.dtypes Out[42]: 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 [43]: sdf.sparse.to_coo() Out[43]: <1000x5 sparse matrix of type '<class 'numpy.float64'>' with 517 stored elements in COOrdinate format>
Series.sparse.to_coo()
方法用于将由 MultiIndex
索引的稀疏值的 Series
转换为 scipy.sparse.coo_matrix
。
该方法需要具有两个或更多级别的 MultiIndex
。
In [44]: s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan]) In [45]: 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 [46]: ss = s.astype('Sparse') In [47]: ss Out[47]: 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]
在下面的示例中,我们通过指定第一和第二个 MultiIndex
级别定义行的标签,第三和第四个级别定义列的标签,将 Series
转换为 2-d 数组的稀疏表示。我们还指定列和行标签应在最终稀疏表示中排序。
In [48]: A, rows, columns = ss.sparse.to_coo( ....: row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True ....: ) ....: In [49]: A Out[49]: <3x4 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [50]: A.todense() Out[50]: matrix([[0., 0., 1., 3.], [3., 0., 0., 0.], [0., 0., 0., 0.]]) In [51]: rows Out[51]: [(1, 1), (1, 2), (2, 1)] In [52]: columns Out[52]: [('a', 0), ('a', 1), ('b', 0), ('b', 1)]
指定不同的行和列标签(且不排序它们)会产生不同的稀疏矩阵:
In [53]: A, rows, columns = ss.sparse.to_coo( ....: row_levels=["A", "B", "C"], column_levels=["D"], sort_labels=False ....: ) ....: In [54]: A Out[54]: <3x2 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [55]: A.todense() Out[55]: matrix([[3., 0.], [1., 3.], [0., 0.]]) In [56]: rows Out[56]: [(1, 2, 'a'), (1, 1, 'b'), (2, 1, 'b')] In [57]: columns Out[57]: [(0,), (1,)]
一个方便的方法Series.sparse.from_coo()
被实现用于从scipy.sparse.coo_matrix
创建一个稀疏值的Series
。
In [58]: from scipy import sparse In [59]: A = sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4)) In [60]: A Out[60]: <3x4 sparse matrix of type '<class 'numpy.float64'>' with 3 stored elements in COOrdinate format> In [61]: A.todense() Out[61]: matrix([[0., 0., 1., 2.], [3., 0., 0., 0.], [0., 0., 0., 0.]])
默认行为(使用dense_index=False
)简单地返回一个只包含非空条目的Series
。
In [62]: ss = pd.Series.sparse.from_coo(A) In [63]: ss Out[63]: 0 2 1.0 3 2.0 1 0 3.0 dtype: Sparse[float64, nan]
指定dense_index=True
将导致一个索引,该索引是矩阵的行和列坐标的笛卡尔积。请注意,如果稀疏矩阵足够大(且稀疏),这将消耗大量内存(相对于dense_index=False
)。
In [64]: ss_dense = pd.Series.sparse.from_coo(A, dense_index=True) In [65]: ss_dense Out[65]: 1 0 3.0 2 NaN 3 NaN 0 0 NaN 2 1.0 3 2.0 0 NaN 2 1.0 3 2.0 dtype: Sparse[float64, nan]
Pandas 2.2 中文官方教程和指南(二十四)(3)https://developer.aliyun.com/article/1508867