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

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

184 statements  

1from __future__ import annotations 

2 

3import numbers 

4from typing import ( 

5 TYPE_CHECKING, 

6 ClassVar, 

7 Self, 

8 cast, 

9) 

10 

11import numpy as np 

12 

13from pandas._libs import ( 

14 lib, 

15 missing as libmissing, 

16) 

17from pandas.util._decorators import set_module 

18 

19from pandas.core.dtypes.common import is_list_like 

20from pandas.core.dtypes.dtypes import register_extension_dtype 

21from pandas.core.dtypes.missing import isna 

22 

23from pandas.core import ops 

24from pandas.core.array_algos import masked_accumulations 

25from pandas.core.arrays.masked import ( 

26 BaseMaskedArray, 

27 BaseMaskedDtype, 

28) 

29 

30if TYPE_CHECKING: 

31 import pyarrow 

32 

33 from pandas._typing import ( 

34 DtypeObj, 

35 npt, 

36 type_t, 

37 ) 

38 

39 from pandas.core.dtypes.dtypes import ExtensionDtype 

40 

41 

42@register_extension_dtype 

43@set_module("pandas") 

44class BooleanDtype(BaseMaskedDtype): 

45 """ 

46 Extension dtype for boolean data. 

47 

48 This is a pandas Extension dtype for boolean data with support for 

49 missing values. BooleanDtype is the dtype companion to :class:`.BooleanArray`, 

50 which implements Kleene logic (sometimes called three-value logic) for 

51 logical operations. See :ref:`boolean.kleene` for more. 

52 

53 .. warning:: 

54 

55 BooleanDtype is considered experimental. The implementation and 

56 parts of the API may change without warning. 

57 

58 Attributes 

59 ---------- 

60 None 

61 

62 Methods 

63 ------- 

64 None 

65 

66 See Also 

67 -------- 

68 arrays.BooleanArray : Array of boolean (True/False) data with missing values. 

69 Int64Dtype : Extension dtype for int64 integer data. 

70 StringDtype : Extension dtype for string data. 

71 

72 Examples 

73 -------- 

74 >>> pd.BooleanDtype() 

75 BooleanDtype 

76 

77 >>> pd.array([True, False, None], dtype=pd.BooleanDtype()) 

78 <BooleanArray> 

79 [True, False, <NA>] 

80 Length: 3, dtype: boolean 

81 

82 >>> pd.array([True, False, None], dtype="boolean") 

83 <BooleanArray> 

84 [True, False, <NA>] 

85 Length: 3, dtype: boolean 

86 """ 

87 

88 name: ClassVar[str] = "boolean" 

89 

90 # The value used to fill '_data' to avoid upcasting 

91 _internal_fill_value = False 

92 

93 # https://github.com/python/mypy/issues/4125 

94 # error: Signature of "type" incompatible with supertype "BaseMaskedDtype" 

95 @property 

96 def type(self) -> type: # type: ignore[override] 

97 return np.bool_ 

98 

99 @property 

100 def kind(self) -> str: 

101 return "b" 

102 

103 @property 

104 def numpy_dtype(self) -> np.dtype: 

105 return np.dtype("bool") 

106 

107 def construct_array_type(self) -> type_t[BooleanArray]: 

108 """ 

109 Return the array type associated with this dtype. 

110 

111 Returns 

112 ------- 

113 type 

114 """ 

115 return BooleanArray 

116 

117 def __repr__(self) -> str: 

118 return "BooleanDtype" 

119 

120 @property 

121 def _is_boolean(self) -> bool: 

122 return True 

123 

124 @property 

125 def _is_numeric(self) -> bool: 

126 return True 

127 

128 def __from_arrow__( 

129 self, array: pyarrow.Array | pyarrow.ChunkedArray 

130 ) -> BooleanArray: 

131 """ 

132 Construct BooleanArray from pyarrow Array/ChunkedArray. 

133 """ 

134 import pyarrow 

135 

136 if array.type != pyarrow.bool_() and not pyarrow.types.is_null(array.type): 

137 raise TypeError(f"Expected array of boolean type, got {array.type} instead") 

138 

139 if isinstance(array, pyarrow.Array): 

140 chunks = [array] 

141 length = len(array) 

142 else: 

143 # pyarrow.ChunkedArray 

144 chunks = array.chunks 

145 length = array.length() 

146 

147 if pyarrow.types.is_null(array.type): 

148 mask = np.ones(length, dtype=bool) 

149 # No need to init data, since all null 

150 data = np.empty(length, dtype=bool) 

151 return BooleanArray(data, mask) 

152 

153 results = [] 

154 for arr in chunks: 

155 buflist = arr.buffers() 

156 data = pyarrow.BooleanArray.from_buffers( 

157 arr.type, len(arr), [None, buflist[1]], offset=arr.offset 

158 ).to_numpy(zero_copy_only=False) 

159 if arr.null_count != 0: 

160 mask = pyarrow.BooleanArray.from_buffers( 

161 arr.type, len(arr), [None, buflist[0]], offset=arr.offset 

162 ).to_numpy(zero_copy_only=False) 

163 mask = ~mask 

164 else: 

165 mask = np.zeros(len(arr), dtype=bool) 

166 

167 bool_arr = BooleanArray(data, mask) 

168 results.append(bool_arr) 

169 

170 if not results: 

171 return BooleanArray( 

172 np.array([], dtype=np.bool_), np.array([], dtype=np.bool_) 

173 ) 

174 else: 

175 return BooleanArray._concat_same_type(results) 

176 

177 

178def coerce_to_array( 

179 values, mask=None, copy: bool = False 

180) -> tuple[np.ndarray, np.ndarray]: 

181 """ 

182 Coerce the input values array to numpy arrays with a mask. 

183 

184 Parameters 

185 ---------- 

186 values : 1D list-like 

187 mask : bool 1D array, optional 

188 copy : bool, default False 

189 if True, copy the input 

190 

191 Returns 

192 ------- 

193 tuple of (values, mask) 

194 """ 

195 if isinstance(values, BooleanArray): 

196 if mask is not None: 

197 raise ValueError("cannot pass mask for BooleanArray input") 

198 values, mask = values._data, values._mask 

199 if copy: 

200 values = values.copy() 

201 mask = mask.copy() 

202 return values, mask 

203 

204 mask_values = None 

205 if isinstance(values, np.ndarray) and values.dtype == np.bool_: 

206 if copy: 

207 values = values.copy() 

208 elif isinstance(values, np.ndarray) and values.dtype.kind in "iufcb": 

209 mask_values = isna(values) 

210 

211 values_bool = np.zeros(len(values), dtype=bool) 

212 values_bool[~mask_values] = values[~mask_values].astype(bool) 

213 

214 if not np.all( 

215 values_bool[~mask_values].astype(values.dtype) == values[~mask_values] 

216 ): 

217 raise TypeError("Need to pass bool-like values") 

218 

219 values = values_bool 

220 else: 

221 values_object = np.asarray(values, dtype=object) 

222 

223 inferred_dtype = lib.infer_dtype(values_object, skipna=True) 

224 integer_like = ("floating", "integer", "mixed-integer-float") 

225 if inferred_dtype not in ("boolean", "empty", *integer_like): 

226 raise TypeError("Need to pass bool-like values") 

227 

228 # mypy does not narrow the type of mask_values to npt.NDArray[np.bool_] 

229 # within this branch, it assumes it can also be None 

230 mask_values = cast("npt.NDArray[np.bool_]", isna(values_object)) 

231 values = np.zeros(len(values), dtype=bool) 

232 values[~mask_values] = values_object[~mask_values].astype(bool) 

233 

234 # if the values were integer-like, validate it were actually 0/1's 

235 if (inferred_dtype in integer_like) and not ( 

236 np.all( 

237 values[~mask_values].astype(float) 

238 == values_object[~mask_values].astype(float) 

239 ) 

240 ): 

241 raise TypeError("Need to pass bool-like values") 

242 

243 if mask is None and mask_values is None: 

244 mask = np.zeros(values.shape, dtype=bool) 

245 elif mask is None: 

246 mask = mask_values 

247 elif isinstance(mask, np.ndarray) and mask.dtype == np.bool_: 

248 if mask_values is not None: 

249 mask = mask | mask_values 

250 elif copy: 

251 mask = mask.copy() 

252 else: 

253 mask = np.array(mask, dtype=bool) 

254 if mask_values is not None: 

255 mask = mask | mask_values 

256 

257 if values.shape != mask.shape: 

258 raise ValueError("values.shape and mask.shape must match") 

259 

260 return values, mask 

261 

262 

263@set_module("pandas.arrays") 

264class BooleanArray(BaseMaskedArray): 

265 """ 

266 Array of boolean (True/False) data with missing values. 

267 

268 This is a pandas Extension array for boolean data, under the hood 

269 represented by 2 numpy arrays: a boolean array with the data and 

270 a boolean array with the mask (True indicating missing). 

271 

272 BooleanArray implements Kleene logic (sometimes called three-value 

273 logic) for logical operations. See :ref:`boolean.kleene` for more. 

274 

275 To construct a BooleanArray from generic array-like input, use 

276 :func:`pandas.array` specifying ``dtype="boolean"`` (see examples 

277 below). 

278 

279 .. warning:: 

280 

281 BooleanArray is considered experimental. The implementation and 

282 parts of the API may change without warning. 

283 

284 Parameters 

285 ---------- 

286 values : numpy.ndarray 

287 A 1-d boolean-dtype array with the data. 

288 mask : numpy.ndarray 

289 A 1-d boolean-dtype array indicating missing values (True 

290 indicates missing). 

291 copy : bool, default False 

292 Whether to copy the `values` and `mask` arrays. 

293 

294 Attributes 

295 ---------- 

296 None 

297 

298 Methods 

299 ------- 

300 None 

301 

302 Returns 

303 ------- 

304 BooleanArray 

305 

306 See Also 

307 -------- 

308 array : Create an array from data with the appropriate dtype. 

309 BooleanDtype : Extension dtype for boolean data. 

310 Series : One-dimensional ndarray with axis labels (including time series). 

311 DataFrame : Two-dimensional, size-mutable, potentially heterogeneous tabular data. 

312 

313 Examples 

314 -------- 

315 Create a BooleanArray with :func:`pandas.array`: 

316 

317 >>> pd.array([True, False, None], dtype="boolean") 

318 <BooleanArray> 

319 [True, False, <NA>] 

320 Length: 3, dtype: boolean 

321 """ 

322 

323 _TRUE_VALUES = {"True", "TRUE", "true", "1", "1.0"} 

324 _FALSE_VALUES = {"False", "FALSE", "false", "0", "0.0"} 

325 

326 @classmethod 

327 def _simple_new(cls, values: np.ndarray, mask: npt.NDArray[np.bool_]) -> Self: 

328 result = super()._simple_new(values, mask) 

329 result._dtype = BooleanDtype() 

330 return result 

331 

332 def __init__( 

333 self, values: np.ndarray, mask: np.ndarray, copy: bool = False 

334 ) -> None: 

335 if not (isinstance(values, np.ndarray) and values.dtype == np.bool_): 

336 raise TypeError( 

337 "values should be boolean numpy array. Use " 

338 "the 'pd.array' function instead" 

339 ) 

340 self._dtype = BooleanDtype() 

341 super().__init__(values, mask, copy=copy) 

342 

343 @property 

344 def dtype(self) -> BooleanDtype: 

345 return self._dtype 

346 

347 @classmethod 

348 def _from_sequence_of_strings( 

349 cls, 

350 strings: list[str], 

351 *, 

352 dtype: ExtensionDtype, 

353 copy: bool = False, 

354 true_values: list[str] | None = None, 

355 false_values: list[str] | None = None, 

356 none_values: list[str] | None = None, 

357 ) -> BooleanArray: 

358 true_values_union = cls._TRUE_VALUES.union(true_values or []) 

359 false_values_union = cls._FALSE_VALUES.union(false_values or []) 

360 

361 if none_values is None: 

362 none_values = [] 

363 

364 def map_string(s) -> bool | None: 

365 if s in true_values_union: 

366 return True 

367 elif s in false_values_union: 

368 return False 

369 elif s in none_values: 

370 return None 

371 else: 

372 raise ValueError(f"{s} cannot be cast to bool") 

373 

374 scalars = np.array(strings, dtype=object) 

375 mask = isna(scalars) 

376 scalars[~mask] = list(map(map_string, scalars[~mask])) 

377 return cls._from_sequence(scalars, dtype=dtype, copy=copy) 

378 

379 _HANDLED_TYPES = (np.ndarray, numbers.Number, bool, np.bool_) 

380 

381 @classmethod 

382 def _coerce_to_array( 

383 cls, value, *, dtype: DtypeObj, copy: bool = False 

384 ) -> tuple[np.ndarray, np.ndarray]: 

385 if dtype: 

386 assert dtype == "boolean" 

387 return coerce_to_array(value, copy=copy) 

388 

389 def _logical_method(self, other, op): 

390 assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"} 

391 other_is_scalar = lib.is_scalar(other) 

392 mask = None 

393 

394 if isinstance(other, BooleanArray): 

395 other, mask = other._data, other._mask 

396 elif is_list_like(other): 

397 other = np.asarray(other, dtype="bool") 

398 if other.ndim > 1: 

399 return NotImplemented 

400 other, mask = coerce_to_array(other, copy=False) 

401 elif isinstance(other, np.bool_): 

402 other = other.item() 

403 

404 if other_is_scalar and other is not libmissing.NA and not lib.is_bool(other): 

405 raise TypeError( 

406 "'other' should be pandas.NA or a bool. " 

407 f"Got {type(other).__name__} instead." 

408 ) 

409 

410 if not other_is_scalar and len(self) != len(other): 

411 raise ValueError("Lengths must match") 

412 

413 if op.__name__ in {"or_", "ror_"}: 

414 result, mask = ops.kleene_or(self._data, other, self._mask, mask) 

415 elif op.__name__ in {"and_", "rand_"}: 

416 result, mask = ops.kleene_and(self._data, other, self._mask, mask) 

417 else: 

418 # i.e. xor, rxor 

419 result, mask = ops.kleene_xor(self._data, other, self._mask, mask) 

420 

421 # i.e. BooleanArray 

422 return self._maybe_mask_result(result, mask) 

423 

424 def _accumulate( 

425 self, name: str, *, skipna: bool = True, **kwargs 

426 ) -> BaseMaskedArray: 

427 data = self._data 

428 mask = self._mask 

429 if name in ("cummin", "cummax"): 

430 op = getattr(masked_accumulations, name) 

431 data, mask = op(data, mask, skipna=skipna, **kwargs) 

432 return self._simple_new(data, mask) 

433 else: 

434 from pandas.core.arrays import IntegerArray 

435 

436 return IntegerArray(data.astype(int), mask)._accumulate( 

437 name, skipna=skipna, **kwargs 

438 )