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

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

191 statements  

1from __future__ import annotations 

2 

3import numbers 

4from typing import ( 

5 TYPE_CHECKING, 

6 ClassVar, 

7 Self, 

8 cast, 

9) 

10import warnings 

11 

12import numpy as np 

13 

14from pandas._libs import ( 

15 lib, 

16 missing as libmissing, 

17) 

18from pandas.errors import Pandas4Warning 

19from pandas.util._decorators import set_module 

20from pandas.util._exceptions import find_stack_level 

21 

22from pandas.core.dtypes.common import is_list_like 

23from pandas.core.dtypes.dtypes import register_extension_dtype 

24from pandas.core.dtypes.missing import isna 

25 

26from pandas.core import ops 

27from pandas.core.array_algos import masked_accumulations 

28from pandas.core.arrays import ExtensionArray 

29from pandas.core.arrays.masked import ( 

30 BaseMaskedArray, 

31 BaseMaskedDtype, 

32) 

33 

34if TYPE_CHECKING: 

35 import pyarrow 

36 

37 from pandas._typing import ( 

38 DtypeObj, 

39 npt, 

40 type_t, 

41 ) 

42 

43 from pandas.core.dtypes.dtypes import ExtensionDtype 

44 

45 

46@register_extension_dtype 

47@set_module("pandas") 

48class BooleanDtype(BaseMaskedDtype): 

49 """ 

50 Extension dtype for boolean data. 

51 

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

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

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

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

56 

57 .. warning:: 

58 

59 BooleanDtype is considered experimental. The implementation and 

60 parts of the API may change without warning. 

61 

62 Attributes 

63 ---------- 

64 None 

65 

66 Methods 

67 ------- 

68 None 

69 

70 See Also 

71 -------- 

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

73 Int64Dtype : Extension dtype for int64 integer data. 

74 StringDtype : Extension dtype for string data. 

75 

76 Examples 

77 -------- 

78 >>> pd.BooleanDtype() 

79 BooleanDtype 

80 

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

82 <BooleanArray> 

83 [True, False, <NA>] 

84 Length: 3, dtype: boolean 

85 

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

87 <BooleanArray> 

88 [True, False, <NA>] 

89 Length: 3, dtype: boolean 

90 """ 

91 

92 name: ClassVar[str] = "boolean" 

93 

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

95 _internal_fill_value = False 

96 

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

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

99 @property 

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

101 return np.bool_ 

102 

103 @property 

104 def kind(self) -> str: 

105 return "b" 

106 

107 @property 

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

109 return np.dtype("bool") 

110 

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

112 """ 

113 Return the array type associated with this dtype. 

114 

115 Returns 

116 ------- 

117 type 

118 """ 

119 return BooleanArray 

120 

121 def __repr__(self) -> str: 

122 return "BooleanDtype" 

123 

124 @property 

125 def _is_boolean(self) -> bool: 

126 return True 

127 

128 @property 

129 def _is_numeric(self) -> bool: 

130 return True 

131 

132 def __from_arrow__( 

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

134 ) -> BooleanArray: 

135 """ 

136 Construct BooleanArray from pyarrow Array/ChunkedArray. 

137 """ 

138 import pyarrow 

139 

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

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

142 

143 if isinstance(array, pyarrow.Array): 

144 chunks = [array] 

145 length = len(array) 

146 else: 

147 # pyarrow.ChunkedArray 

148 chunks = array.chunks 

149 length = array.length() 

150 

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

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

153 # No need to init data, since all null 

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

155 return BooleanArray(data, mask) 

156 

157 results = [] 

158 for arr in chunks: 

159 buflist = arr.buffers() 

160 data = pyarrow.BooleanArray.from_buffers( 

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

162 ).to_numpy(zero_copy_only=False) 

163 if arr.null_count != 0: 

164 mask = pyarrow.BooleanArray.from_buffers( 

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

166 ).to_numpy(zero_copy_only=False) 

167 mask = ~mask 

168 else: 

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

170 

171 bool_arr = BooleanArray(data, mask) 

172 results.append(bool_arr) 

173 

174 if not results: 

175 return BooleanArray( 

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

177 ) 

178 else: 

179 return BooleanArray._concat_same_type(results) 

180 

181 

182def coerce_to_array( 

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

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

185 """ 

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

187 

188 Parameters 

189 ---------- 

190 values : 1D list-like 

191 mask : bool 1D array, optional 

192 copy : bool, default False 

193 if True, copy the input 

194 

195 Returns 

196 ------- 

197 tuple of (values, mask) 

198 """ 

199 if isinstance(values, BooleanArray): 

200 if mask is not None: 

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

202 values, mask = values._data, values._mask 

203 if copy: 

204 values = values.copy() 

205 mask = mask.copy() 

206 return values, mask 

207 

208 mask_values = None 

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

210 if copy: 

211 values = values.copy() 

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

213 mask_values = isna(values) 

214 

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

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

217 

218 if not np.all( 

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

220 ): 

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

222 

223 values = values_bool 

224 else: 

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

226 

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

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

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

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

231 

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

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

234 mask_values = cast("npt.NDArray[np.bool_]", isna(values_object)) # type: ignore[redundant-cast] 

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

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

237 

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

239 if (inferred_dtype in integer_like) and not ( 

240 np.all( 

241 values[~mask_values].astype(float) 

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

243 ) 

244 ): 

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

246 

247 if mask is None and mask_values is None: 

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

249 elif mask is None: 

250 mask = mask_values 

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

252 if mask_values is not None: 

253 mask = mask | mask_values 

254 elif copy: 

255 mask = mask.copy() 

256 else: 

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

258 if mask_values is not None: 

259 mask = mask | mask_values 

260 

261 if values.shape != mask.shape: 

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

263 

264 return values, mask 

265 

266 

267@set_module("pandas.arrays") 

268class BooleanArray(BaseMaskedArray): 

269 """ 

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

271 

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

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

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

275 

276 BooleanArray implements Kleene logic (sometimes called three-value 

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

278 

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

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

281 below). 

282 

283 .. warning:: 

284 

285 BooleanArray is considered experimental. The implementation and 

286 parts of the API may change without warning. 

287 

288 Parameters 

289 ---------- 

290 values : numpy.ndarray 

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

292 mask : numpy.ndarray 

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

294 indicates missing). 

295 copy : bool, default False 

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

297 

298 Attributes 

299 ---------- 

300 None 

301 

302 Methods 

303 ------- 

304 None 

305 

306 Returns 

307 ------- 

308 BooleanArray 

309 

310 See Also 

311 -------- 

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

313 BooleanDtype : Extension dtype for boolean data. 

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

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

316 

317 Examples 

318 -------- 

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

320 

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

322 <BooleanArray> 

323 [True, False, <NA>] 

324 Length: 3, dtype: boolean 

325 """ 

326 

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

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

329 

330 @classmethod 

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

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

333 result._dtype = BooleanDtype() 

334 return result 

335 

336 def __init__( 

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

338 ) -> None: 

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

340 raise TypeError( 

341 "values should be boolean numpy array. Use " 

342 "the 'pd.array' function instead" 

343 ) 

344 self._dtype = BooleanDtype() 

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

346 

347 @property 

348 def dtype(self) -> BooleanDtype: 

349 return self._dtype 

350 

351 @classmethod 

352 def _from_sequence_of_strings( 

353 cls, 

354 strings: list[str], 

355 *, 

356 dtype: ExtensionDtype, 

357 copy: bool = False, 

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

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

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

361 ) -> BooleanArray: 

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

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

364 

365 if none_values is None: 

366 none_values = [] 

367 

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

369 if s in true_values_union: 

370 return True 

371 elif s in false_values_union: 

372 return False 

373 elif s in none_values: 

374 return None 

375 else: 

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

377 

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

379 mask = isna(scalars) 

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

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

382 

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

384 

385 @classmethod 

386 def _coerce_to_array( 

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

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

389 if dtype: 

390 assert dtype == "boolean" 

391 return coerce_to_array(value, copy=copy) 

392 

393 def _logical_method(self, other, op): 

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

395 ops.disallow_datetimelike_logical_op(self, other, op) 

396 other_is_scalar = lib.is_scalar(other) 

397 mask = None 

398 

399 if isinstance(other, BooleanArray): 

400 other, mask = other._data, other._mask 

401 elif is_list_like(other): 

402 if not isinstance( 

403 other, (list, ExtensionArray, np.ndarray) 

404 ) and not ops.has_castable_attr(other): 

405 warnings.warn( 

406 f"Operation with {type(other).__name__} is deprecated. " 

407 "In a future version these will be treated as scalar-like. " 

408 "To retain the old behavior, explicitly wrap in a Series " 

409 "instead.", 

410 Pandas4Warning, 

411 stacklevel=find_stack_level(), 

412 ) 

413 

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

415 if other.ndim > 1: 

416 return NotImplemented 

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

418 elif isinstance(other, np.bool_): 

419 other = other.item() 

420 

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

422 raise TypeError( 

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

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

425 ) 

426 

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

428 raise ValueError("Lengths must match") 

429 

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

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

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

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

434 else: 

435 # i.e. xor, rxor 

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

437 

438 # i.e. BooleanArray 

439 return self._maybe_mask_result(result, mask) 

440 

441 def _accumulate( 

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

443 ) -> BaseMaskedArray: 

444 data = self._data 

445 mask = self._mask 

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

447 op = getattr(masked_accumulations, name) 

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

449 return self._simple_new(data, mask) 

450 else: 

451 from pandas.core.arrays import IntegerArray 

452 

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

454 name, skipna=skipna, **kwargs 

455 )