Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/arrays/sparse/accessor.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

115 statements  

1"""Sparse accessor""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING 

6 

7import numpy as np 

8 

9from pandas.compat._optional import import_optional_dependency 

10 

11from pandas.core.dtypes.cast import find_common_type 

12from pandas.core.dtypes.dtypes import SparseDtype 

13from pandas.core.dtypes.missing import isna 

14 

15from pandas.core.accessor import ( 

16 PandasDelegate, 

17 delegate_names, 

18) 

19from pandas.core.arrays.sparse.array import SparseArray 

20 

21if TYPE_CHECKING: 

22 from scipy.sparse import ( 

23 coo_matrix, 

24 spmatrix, 

25 ) 

26 

27 from pandas._typing import IndexLabel 

28 

29 from pandas import ( 

30 DataFrame, 

31 Series, 

32 ) 

33 

34 

35class BaseAccessor: 

36 _validation_msg = "Can only use the '.sparse' accessor with Sparse data." 

37 

38 def __init__(self, data=None) -> None: 

39 self._parent = data 

40 self._validate(data) 

41 

42 def _validate(self, data) -> None: 

43 raise NotImplementedError 

44 

45 

46@delegate_names( 

47 SparseArray, ["npoints", "density", "fill_value", "sp_values"], typ="property" 

48) 

49class SparseAccessor(BaseAccessor, PandasDelegate): 

50 """ 

51 Accessor for SparseArray from other sparse matrix data types. 

52 

53 Provides methods and properties to work with the underlying sparse data 

54 in a Series or DataFrame. It allows accessing sparse-specific attributes 

55 such as fill value, density, and stored non-fill values, as well as 

56 converting to and from scipy sparse matrices. 

57 

58 Parameters 

59 ---------- 

60 data : Series or DataFrame 

61 The Series or DataFrame to which the SparseAccessor is attached. 

62 

63 See Also 

64 -------- 

65 Series.sparse.to_coo : Create a scipy.sparse.coo_matrix from a Series with 

66 MultiIndex. 

67 Series.sparse.from_coo : Create a Series with sparse values from a 

68 scipy.sparse.coo_matrix. 

69 

70 Examples 

71 -------- 

72 >>> ser = pd.Series([0, 0, 2, 2, 2], dtype="Sparse[int]") 

73 >>> ser.sparse.density 

74 0.6 

75 >>> ser.sparse.sp_values 

76 array([2, 2, 2]) 

77 """ 

78 

79 def _validate(self, data) -> None: 

80 if not isinstance(data.dtype, SparseDtype): 

81 raise AttributeError(self._validation_msg) 

82 

83 def _delegate_property_get(self, name: str, *args, **kwargs): 

84 return getattr(self._parent.array, name) 

85 

86 def _delegate_method(self, name: str, *args, **kwargs): 

87 if name == "from_coo": 

88 return self.from_coo(*args, **kwargs) 

89 elif name == "to_coo": 

90 return self.to_coo(*args, **kwargs) 

91 else: 

92 raise ValueError 

93 

94 @classmethod 

95 def from_coo(cls, A, dense_index: bool = False) -> Series: 

96 """ 

97 Create a Series with sparse values from a scipy.sparse.coo_matrix. 

98 

99 This method takes a ``scipy.sparse.coo_matrix`` (coordinate format) as input and 

100 returns a pandas ``Series`` where the non-zero elements are represented as 

101 sparse values. The index of the Series can either include only the coordinates 

102 of non-zero elements (default behavior) or the full sorted set of coordinates 

103 from the matrix if ``dense_index`` is set to `True`. 

104 

105 Parameters 

106 ---------- 

107 A : scipy.sparse.coo_matrix 

108 The sparse matrix in coordinate format from which the sparse Series 

109 will be created. 

110 dense_index : bool, default False 

111 If False (default), the index consists of only the 

112 coords of the non-null entries of the original coo_matrix. 

113 If True, the index consists of the full sorted 

114 (row, col) coordinates of the coo_matrix. 

115 

116 Returns 

117 ------- 

118 s : Series 

119 A Series with sparse values. 

120 

121 See Also 

122 -------- 

123 DataFrame.sparse.from_spmatrix : Create a new DataFrame from a scipy sparse 

124 matrix. 

125 scipy.sparse.coo_matrix : A sparse matrix in COOrdinate format. 

126 

127 Examples 

128 -------- 

129 >>> from scipy import sparse 

130 

131 >>> A = sparse.coo_matrix( 

132 ... ([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4) 

133 ... ) 

134 >>> A 

135 <COOrdinate sparse matrix of dtype 'float64' 

136 with 3 stored elements and shape (3, 4)> 

137 

138 >>> A.todense() 

139 matrix([[0., 0., 1., 2.], 

140 [3., 0., 0., 0.], 

141 [0., 0., 0., 0.]]) 

142 

143 >>> ss = pd.Series.sparse.from_coo(A) 

144 >>> ss 

145 0 2 1.0 

146 3 2.0 

147 1 0 3.0 

148 dtype: Sparse[float64, nan] 

149 """ 

150 from pandas import Series 

151 from pandas.core.arrays.sparse.scipy_sparse import coo_to_sparse_series 

152 

153 result = coo_to_sparse_series(A, dense_index=dense_index) 

154 result = Series(result.array, index=result.index, copy=False) 

155 

156 return result 

157 

158 def to_coo( 

159 self, row_levels=(0,), column_levels=(1,), sort_labels: bool = False 

160 ) -> tuple[coo_matrix, list[IndexLabel], list[IndexLabel]]: 

161 """ 

162 Create a scipy.sparse.coo_matrix from a Series with MultiIndex. 

163 

164 Use row_levels and column_levels to determine the row and column 

165 coordinates respectively. row_levels and column_levels are the names 

166 (labels) or numbers of the levels. {row_levels, column_levels} must be 

167 a partition of the MultiIndex level names (or numbers). 

168 

169 Parameters 

170 ---------- 

171 row_levels : tuple/list 

172 MultiIndex levels to use for row coordinates, specified by name or index. 

173 column_levels : tuple/list 

174 MultiIndex levels to use for column coordinates, specified by name or index. 

175 sort_labels : bool, default False 

176 Sort the row and column labels before forming the sparse matrix. 

177 When `row_levels` and/or `column_levels` refer to a single level, 

178 set to `True` for a faster execution. 

179 

180 Returns 

181 ------- 

182 y : scipy.sparse.coo_matrix 

183 The sparse matrix in coordinate format. 

184 rows : list (row labels) 

185 Labels corresponding to the row coordinates. 

186 columns : list (column labels) 

187 Labels corresponding to the column coordinates. 

188 

189 See Also 

190 -------- 

191 Series.sparse.from_coo : Create a Series with sparse values from a 

192 scipy.sparse.coo_matrix. 

193 

194 Examples 

195 -------- 

196 >>> s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan]) 

197 >>> s.index = pd.MultiIndex.from_tuples( 

198 ... [ 

199 ... (1, 2, "a", 0), 

200 ... (1, 2, "a", 1), 

201 ... (1, 1, "b", 0), 

202 ... (1, 1, "b", 1), 

203 ... (2, 1, "b", 0), 

204 ... (2, 1, "b", 1), 

205 ... ], 

206 ... names=["A", "B", "C", "D"], 

207 ... ) 

208 >>> s 

209 A B C D 

210 1 2 a 0 3.0 

211 1 NaN 

212 1 b 0 1.0 

213 1 3.0 

214 2 1 b 0 NaN 

215 1 NaN 

216 dtype: float64 

217 

218 >>> ss = s.astype("Sparse") 

219 >>> ss 

220 A B C D 

221 1 2 a 0 3.0 

222 1 NaN 

223 1 b 0 1.0 

224 1 3.0 

225 2 1 b 0 NaN 

226 1 NaN 

227 dtype: Sparse[float64, nan] 

228 

229 >>> A, rows, columns = ss.sparse.to_coo( 

230 ... row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True 

231 ... ) 

232 >>> A 

233 <COOrdinate sparse matrix of dtype 'float64' 

234 with 3 stored elements and shape (3, 4)> 

235 >>> A.todense() 

236 matrix([[0., 0., 1., 3.], 

237 [3., 0., 0., 0.], 

238 [0., 0., 0., 0.]]) 

239 

240 >>> rows 

241 [(1, 1), (1, 2), (2, 1)] 

242 >>> columns 

243 [('a', 0), ('a', 1), ('b', 0), ('b', 1)] 

244 """ 

245 from pandas.core.arrays.sparse.scipy_sparse import sparse_series_to_coo 

246 

247 A, rows, columns = sparse_series_to_coo( 

248 self._parent, row_levels, column_levels, sort_labels=sort_labels 

249 ) 

250 return A, rows, columns 

251 

252 def to_dense(self) -> Series: 

253 """ 

254 Convert a Series from sparse values to dense. 

255 

256 Returns 

257 ------- 

258 Series: 

259 A Series with the same values, stored as a dense array. 

260 

261 Examples 

262 -------- 

263 >>> series = pd.Series(pd.arrays.SparseArray([0, 1, 0])) 

264 >>> series 

265 0 0 

266 1 1 

267 2 0 

268 dtype: Sparse[int64, 0] 

269 

270 >>> series.sparse.to_dense() 

271 0 0 

272 1 1 

273 2 0 

274 dtype: int64 

275 """ 

276 from pandas import Series 

277 

278 return Series( 

279 self._parent.array.to_dense(), 

280 index=self._parent.index, 

281 name=self._parent.name, 

282 copy=False, 

283 ) 

284 

285 

286class SparseFrameAccessor(BaseAccessor, PandasDelegate): 

287 """ 

288 DataFrame accessor for sparse data. 

289 

290 It allows users to interact with a `DataFrame` that contains sparse data types 

291 (`SparseDtype`). It provides methods and attributes to efficiently work with sparse 

292 storage, reducing memory usage while maintaining compatibility with standard pandas 

293 operations. 

294 

295 Parameters 

296 ---------- 

297 data : scipy.sparse.spmatrix 

298 Must be convertible to csc format. 

299 

300 See Also 

301 -------- 

302 DataFrame.sparse.density : Ratio of non-sparse points to total (dense) data points. 

303 

304 Examples 

305 -------- 

306 >>> df = pd.DataFrame({"a": [1, 2, 0, 0], "b": [3, 0, 0, 4]}, dtype="Sparse[int]") 

307 >>> df.sparse.density 

308 np.float64(0.5) 

309 """ 

310 

311 def _validate(self, data) -> None: 

312 dtypes = data.dtypes 

313 if not all(isinstance(t, SparseDtype) for t in dtypes): 

314 raise AttributeError(self._validation_msg) 

315 

316 @classmethod 

317 def from_spmatrix(cls, data, index=None, columns=None) -> DataFrame: 

318 """ 

319 Create a new DataFrame from a scipy sparse matrix. 

320 

321 This method converts a scipy sparse matrix into a pandas DataFrame 

322 with sparse array columns for memory efficiency. 

323 

324 Parameters 

325 ---------- 

326 data : scipy.sparse.spmatrix 

327 Must be convertible to csc format. 

328 index, columns : Index, optional 

329 Row and column labels to use for the resulting DataFrame. 

330 Defaults to a RangeIndex. 

331 

332 Returns 

333 ------- 

334 DataFrame 

335 Each column of the DataFrame is stored as a 

336 :class:`arrays.SparseArray`. 

337 

338 See Also 

339 -------- 

340 DataFrame.sparse.to_coo : Return the contents of the frame as a 

341 sparse SciPy COO matrix. 

342 

343 Examples 

344 -------- 

345 >>> import scipy.sparse 

346 >>> mat = scipy.sparse.eye(3, dtype=float) 

347 >>> pd.DataFrame.sparse.from_spmatrix(mat) 

348 0 1 2 

349 0 1.0 0.0 0.0 

350 1 0.0 1.0 0.0 

351 2 0.0 0.0 1.0 

352 """ 

353 from pandas._libs.sparse import IntIndex 

354 

355 from pandas import DataFrame 

356 

357 data = data.tocsc() 

358 index, columns = cls._prep_index(data, index, columns) 

359 n_rows, n_columns = data.shape 

360 # We need to make sure indices are sorted, as we create 

361 # IntIndex with no input validation (i.e. check_integrity=False ). 

362 # Indices may already be sorted in scipy in which case this adds 

363 # a small overhead. 

364 data.sort_indices() 

365 indices = data.indices 

366 indptr = data.indptr 

367 array_data = data.data 

368 # scipy's unstored entries are zeros, not missing values, see GH#59212 

369 zero = np.array(0, dtype=array_data.dtype).item() 

370 dtype = SparseDtype(array_data.dtype, zero) 

371 arrays = [] 

372 for i in range(n_columns): 

373 sl = slice(indptr[i], indptr[i + 1]) 

374 idx = IntIndex(n_rows, indices[sl], check_integrity=False) 

375 arr = SparseArray._simple_new(array_data[sl], idx, dtype) 

376 arrays.append(arr) 

377 return DataFrame._from_arrays( 

378 arrays, columns=columns, index=index, verify_integrity=False 

379 ) 

380 

381 def to_dense(self) -> DataFrame: 

382 """ 

383 Convert a DataFrame with sparse values to dense. 

384 

385 This method converts all SparseArray columns in the DataFrame to 

386 regular NumPy arrays. 

387 

388 Returns 

389 ------- 

390 DataFrame 

391 A DataFrame with the same values stored as dense arrays. 

392 

393 See Also 

394 -------- 

395 DataFrame.sparse.density : Ratio of non-sparse points to total 

396 (dense) data points. 

397 

398 Examples 

399 -------- 

400 >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0])}) 

401 >>> df.sparse.to_dense() 

402 A 

403 0 0 

404 1 1 

405 2 0 

406 """ 

407 data = {k: v.array.to_dense() for k, v in self._parent.items()} 

408 return self._parent._constructor( 

409 data, index=self._parent.index, columns=self._parent.columns 

410 ) 

411 

412 def to_coo(self) -> spmatrix: 

413 """ 

414 Return the contents of the frame as a sparse SciPy COO matrix. 

415 

416 This method converts the DataFrame to a scipy COO (Coordinate) sparse 

417 matrix format for interoperability with scipy.sparse routines. 

418 

419 Returns 

420 ------- 

421 scipy.sparse.spmatrix 

422 If the caller is heterogeneous and contains booleans or objects, 

423 the result will be of dtype=object. See Notes. 

424 

425 Raises 

426 ------ 

427 ValueError 

428 If any column's ``fill_value`` is not zero. 

429 

430 See Also 

431 -------- 

432 DataFrame.sparse.to_dense : Convert a DataFrame with sparse values to dense. 

433 

434 Notes 

435 ----- 

436 The dtype will be the lowest-common-denominator type (implicit 

437 upcasting); that is to say if the dtypes (even of numeric types) 

438 are mixed, the one that accommodates all will be chosen. 

439 

440 e.g. If the dtypes are float16 and float32, dtype will be upcast to 

441 float32. By numpy.find_common_type convention, mixing int64 and 

442 and uint64 will result in a float64 dtype. 

443 

444 Examples 

445 -------- 

446 >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0, 1])}) 

447 >>> df.sparse.to_coo() 

448 <COOrdinate sparse matrix of dtype 'int64' 

449 with 2 stored elements and shape (4, 1)> 

450 """ 

451 import_optional_dependency("scipy") 

452 from scipy.sparse import coo_matrix 

453 

454 dtype = find_common_type(self._parent.dtypes.to_list()) 

455 if isinstance(dtype, SparseDtype): 

456 dtype = dtype.subtype 

457 

458 cols, rows, data = [], [], [] 

459 for col, (_, ser) in enumerate(self._parent.items()): 

460 sp_arr = ser.array 

461 # isna first: `pd.NA != 0` is NA, which is not usable as a condition 

462 if isna(sp_arr.fill_value) or sp_arr.fill_value != 0: 

463 raise ValueError("fill value must be 0 when converting to COO matrix") 

464 

465 row = sp_arr.sp_index.indices 

466 cols.append(np.repeat(col, len(row))) 

467 rows.append(row) 

468 data.append(sp_arr.sp_values.astype(dtype, copy=False)) 

469 

470 cols_arr = np.concatenate(cols) 

471 rows_arr = np.concatenate(rows) 

472 data_arr = np.concatenate(data) 

473 return coo_matrix((data_arr, (rows_arr, cols_arr)), shape=self._parent.shape) 

474 

475 @property 

476 def density(self) -> float: 

477 """ 

478 Ratio of non-sparse points to total (dense) data points. 

479 

480 This property returns the proportion of the data that is not sparse 

481 (i.e., not equal to the fill value), as a value between 0 and 1. An 

482 empty DataFrame has no density and returns ``nan``. 

483 

484 See Also 

485 -------- 

486 DataFrame.sparse.from_spmatrix : Create a new DataFrame from a 

487 scipy sparse matrix. 

488 

489 Examples 

490 -------- 

491 >>> df = pd.DataFrame({"A": pd.arrays.SparseArray([0, 1, 0, 1])}) 

492 >>> df.sparse.density 

493 np.float64(0.5) 

494 """ 

495 if len(self._parent.columns) == 0: 

496 # mean of an empty list is undefined 

497 return np.nan 

498 return np.mean([column.array.density for _, column in self._parent.items()]) 

499 

500 @staticmethod 

501 def _prep_index(data, index, columns): 

502 from pandas.core.indexes.api import ( 

503 default_index, 

504 ensure_index, 

505 ) 

506 

507 N, K = data.shape 

508 if index is None: 

509 index = default_index(N) 

510 else: 

511 index = ensure_index(index) 

512 if columns is None: 

513 columns = default_index(K) 

514 else: 

515 columns = ensure_index(columns) 

516 

517 if len(columns) != K: 

518 raise ValueError(f"Column length mismatch: {len(columns)} vs. {K}") 

519 if len(index) != N: 

520 raise ValueError(f"Index length mismatch: {len(index)} vs. {N}") 

521 return index, columns