Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/array_algos/take.py: 16%

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

210 statements  

1from __future__ import annotations 

2 

3import functools 

4from typing import ( 

5 TYPE_CHECKING, 

6 cast, 

7 overload, 

8) 

9import warnings 

10 

11import numpy as np 

12 

13from pandas._libs import ( 

14 algos as libalgos, 

15 lib, 

16) 

17from pandas.errors import Pandas4Warning 

18from pandas.util._exceptions import find_stack_level 

19 

20from pandas.core.dtypes.cast import maybe_promote 

21from pandas.core.dtypes.common import ( 

22 ensure_platform_int, 

23 is_1d_only_ea_dtype, 

24) 

25from pandas.core.dtypes.missing import na_value_for_dtype 

26 

27from pandas.core.construction import ensure_wrapped_if_datetimelike 

28 

29if TYPE_CHECKING: 

30 from pandas._typing import ( 

31 ArrayLike, 

32 AxisInt, 

33 npt, 

34 ) 

35 

36 from pandas.core.arrays._mixins import NDArrayBackedExtensionArray 

37 from pandas.core.arrays.base import ExtensionArray 

38 

39 

40@overload 

41def take_nd( 

42 arr: np.ndarray, 

43 indexer, 

44 axis: AxisInt = ..., 

45 fill_value=..., 

46 allow_fill: bool = ..., 

47) -> np.ndarray: ... 

48 

49 

50@overload 

51def take_nd( 

52 arr: ExtensionArray, 

53 indexer, 

54 axis: AxisInt = ..., 

55 fill_value=..., 

56 allow_fill: bool = ..., 

57) -> ArrayLike: ... 

58 

59 

60def take_nd( 

61 arr: ArrayLike, 

62 indexer, 

63 axis: AxisInt = 0, 

64 fill_value=lib.no_default, 

65 allow_fill: bool = True, 

66) -> ArrayLike: 

67 """ 

68 Specialized Cython take which sets NaN values in one pass 

69 

70 This dispatches to ``take`` defined on ExtensionArrays. 

71 

72 Note: this function assumes that the indexer is a valid(ated) indexer with 

73 no out of bound indices. 

74 

75 Parameters 

76 ---------- 

77 arr : np.ndarray or ExtensionArray 

78 Input array. 

79 indexer : ndarray 

80 1-D array of indices to take, subarrays corresponding to -1 value 

81 indices are filed with fill_value 

82 axis : int, default 0 

83 Axis to take from 

84 fill_value : any, default np.nan 

85 Fill value to replace -1 values with 

86 allow_fill : bool, default True 

87 If False, indexer is assumed to contain no -1 values so no filling 

88 will be done. This short-circuits computation of a mask. Result is 

89 undefined if allow_fill == False and -1 is present in indexer. 

90 

91 Returns 

92 ------- 

93 subarray : np.ndarray or ExtensionArray 

94 May be the same type as the input, or cast to an ndarray. 

95 """ 

96 if fill_value is lib.no_default: 

97 fill_value = na_value_for_dtype(arr.dtype, compat=False) 

98 elif lib.is_np_dtype(arr.dtype, "mM"): 

99 dtype, fill_value = maybe_promote(arr.dtype, fill_value) 

100 if dtype != arr.dtype: 

101 # GH#53910 

102 warnings.warn( 

103 "reindexing with a fill_value that cannot be held by the " 

104 "original dtype is deprecated. Explicitly cast to a common " 

105 f"dtype (in this case {dtype}) instead.", 

106 Pandas4Warning, 

107 stacklevel=find_stack_level(), 

108 ) 

109 if arr.dtype != dtype: 

110 # EA.take is strict about returning a new object of the same type 

111 # so for that case cast upfront 

112 arr = arr.astype(dtype) 

113 

114 if not isinstance(arr, np.ndarray): 

115 # i.e. ExtensionArray, 

116 # includes for EA to catch DatetimeArray, TimedeltaArray 

117 if not is_1d_only_ea_dtype(arr.dtype): 

118 # i.e. DatetimeArray, TimedeltaArray 

119 arr = cast("NDArrayBackedExtensionArray", arr) 

120 return arr.take( 

121 indexer, fill_value=fill_value, allow_fill=allow_fill, axis=axis 

122 ) 

123 

124 return arr.take(indexer, fill_value=fill_value, allow_fill=allow_fill) 

125 

126 arr = np.asarray(arr) 

127 return _take_nd_ndarray(arr, indexer, axis, fill_value, allow_fill) 

128 

129 

130def _take_nd_ndarray( 

131 arr: np.ndarray, 

132 indexer: npt.NDArray[np.intp] | None, 

133 axis: AxisInt, 

134 fill_value, 

135 allow_fill: bool, 

136) -> np.ndarray: 

137 if indexer is None: 

138 indexer = np.arange(arr.shape[axis], dtype=np.intp) 

139 dtype, fill_value = arr.dtype, arr.dtype.type() 

140 else: 

141 indexer = ensure_platform_int(indexer) 

142 

143 dtype, fill_value, mask_info = _take_preprocess_indexer_and_fill_value( 

144 arr, indexer, fill_value, allow_fill 

145 ) 

146 

147 flip_order = False 

148 if arr.ndim == 2 and arr.flags.f_contiguous: 

149 flip_order = True 

150 

151 if flip_order: 

152 arr = arr.T 

153 axis = arr.ndim - axis - 1 

154 

155 # at this point, it's guaranteed that dtype can hold both the arr values 

156 # and the fill_value 

157 out_shape_ = list(arr.shape) 

158 out_shape_[axis] = len(indexer) 

159 out_shape = tuple(out_shape_) 

160 if arr.flags.f_contiguous and axis == arr.ndim - 1: 

161 # minor tweak that can make an order-of-magnitude difference 

162 # for dataframes initialized directly from 2-d ndarrays 

163 # (s.t. df.values is c-contiguous and df._mgr.blocks[0] is its 

164 # f-contiguous transpose) 

165 out = np.empty(out_shape, dtype=dtype, order="F") 

166 else: 

167 out = np.empty(out_shape, dtype=dtype) 

168 

169 func = _get_take_nd_function( 

170 arr.ndim, arr.dtype, out.dtype, axis=axis, mask_info=mask_info 

171 ) 

172 if mask_info is not None: 

173 _, needs_fill = mask_info 

174 else: 

175 needs_fill = allow_fill 

176 func(arr, indexer, out, fill_value, allow_fill=needs_fill) 

177 

178 if flip_order: 

179 out = out.T 

180 return out 

181 

182 

183def take_2d_multi( 

184 arr: np.ndarray, 

185 indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]], 

186 fill_value=np.nan, 

187) -> np.ndarray: 

188 """ 

189 Specialized Cython take which sets NaN values in one pass. 

190 """ 

191 # This is only called from one place in DataFrame._reindex_multi, 

192 # so we know indexer is well-behaved. 

193 assert indexer is not None 

194 assert indexer[0] is not None 

195 assert indexer[1] is not None 

196 

197 row_idx, col_idx = indexer 

198 

199 row_idx = ensure_platform_int(row_idx) 

200 col_idx = ensure_platform_int(col_idx) 

201 indexer = row_idx, col_idx 

202 mask_info = None 

203 

204 if lib.is_float(fill_value) and fill_value.is_integer(): 

205 # Avoid warning if possible 

206 fill_value = int(fill_value) 

207 

208 # check for promotion based on types only (do this first because 

209 # it's faster than computing a mask) 

210 dtype, fill_value = maybe_promote(arr.dtype, fill_value) 

211 if dtype != arr.dtype: 

212 # check if promotion is actually required based on indexer 

213 row_mask = row_idx == -1 

214 col_mask = col_idx == -1 

215 row_needs = row_mask.any() 

216 col_needs = col_mask.any() 

217 mask_info = (row_mask, col_mask), (row_needs, col_needs) 

218 

219 if not (row_needs or col_needs): 

220 # if not, then depromote, set fill_value to dummy 

221 # (it won't be used but we don't want the cython code 

222 # to crash when trying to cast it to dtype) 

223 dtype, fill_value = arr.dtype, arr.dtype.type() 

224 

225 if dtype != arr.dtype and not ( 

226 arr.dtype.kind in "iub" 

227 and lib.is_float(fill_value) 

228 and np.isnan(fill_value) 

229 ): 

230 # GH#53910 

231 warnings.warn( 

232 "reindexing with a fill_value that cannot be held by the " 

233 "original dtype is deprecated. Explicitly cast to a common " 

234 f"dtype (in this case {dtype}) instead.", 

235 Pandas4Warning, 

236 stacklevel=find_stack_level(), 

237 ) 

238 

239 # at this point, it's guaranteed that dtype can hold both the arr values 

240 # and the fill_value 

241 out_shape = len(row_idx), len(col_idx) 

242 out = np.empty(out_shape, dtype=dtype) 

243 

244 func = _take_2d_multi_dict.get((arr.dtype.name, out.dtype.name), None) 

245 if func is None and arr.dtype != out.dtype: 

246 func = _take_2d_multi_dict.get((out.dtype.name, out.dtype.name), None) 

247 if func is not None: 

248 func = _convert_wrapper(func, out.dtype) 

249 

250 # datetime64/timedelta64 of any resolution use int64 storage 

251 if func is None and arr.dtype.kind in "mM" and arr.dtype == out.dtype: 

252 func = _view_wrapper( 

253 libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

254 ) 

255 

256 if mask_info is not None: 

257 _, (row_needs, col_needs) = mask_info 

258 needs_fill = row_needs or col_needs 

259 else: 

260 needs_fill = True 

261 

262 if func is not None: 

263 func(arr, indexer, out=out, fill_value=fill_value, allow_fill=needs_fill) 

264 else: 

265 # test_reindex_multi 

266 _take_2d_multi_object( 

267 arr, indexer, out, fill_value=fill_value, mask_info=mask_info 

268 ) 

269 

270 return out 

271 

272 

273@functools.lru_cache 

274def _get_take_nd_function_cached( 

275 ndim: int, arr_dtype: np.dtype, out_dtype: np.dtype, axis: AxisInt 

276): 

277 """ 

278 Part of _get_take_nd_function below that doesn't need `mask_info` and thus 

279 can be cached (mask_info potentially contains a numpy ndarray which is not 

280 hashable and thus cannot be used as argument for cached function). 

281 """ 

282 tup = (arr_dtype.name, out_dtype.name) 

283 if ndim == 1: 

284 func = _take_1d_dict.get(tup, None) 

285 elif ndim == 2: 

286 if axis == 0: 

287 func = _take_2d_axis0_dict.get(tup, None) 

288 else: 

289 func = _take_2d_axis1_dict.get(tup, None) 

290 if func is not None: 

291 return func 

292 

293 # We get here with string, uint, float16, and complex dtypes that could 

294 # potentially be handled in algos_take_helper. 

295 # Also a couple with (M8[ns], object) and (m8[ns], object) 

296 tup = (out_dtype.name, out_dtype.name) 

297 if ndim == 1: 

298 func = _take_1d_dict.get(tup, None) 

299 elif ndim == 2: 

300 if axis == 0: 

301 func = _take_2d_axis0_dict.get(tup, None) 

302 else: 

303 func = _take_2d_axis1_dict.get(tup, None) 

304 if func is not None: 

305 func = _convert_wrapper(func, out_dtype) 

306 return func 

307 

308 # datetime64/timedelta64 of any resolution use int64 storage; 

309 # the dispatch dicts only have entries for ns resolution, so handle 

310 # other resolutions here. 

311 if arr_dtype.kind in "mM" and arr_dtype == out_dtype: 

312 if ndim == 1: 

313 base_func = libalgos.take_1d_int64_int64 

314 elif axis == 0: 

315 base_func = libalgos.take_2d_axis0_int64_int64 

316 else: 

317 base_func = libalgos.take_2d_axis1_int64_int64 

318 return _view_wrapper(base_func, np.int64, np.int64, fill_wrap=np.int64) 

319 

320 return None 

321 

322 

323def _get_take_nd_function( 

324 ndim: int, 

325 arr_dtype: np.dtype, 

326 out_dtype: np.dtype, 

327 axis: AxisInt = 0, 

328 mask_info=None, 

329): 

330 """ 

331 Get the appropriate "take" implementation for the given dimension, axis 

332 and dtypes. 

333 """ 

334 func = None 

335 if ndim <= 2: 

336 # for this part we don't need `mask_info` -> use the cached algo lookup 

337 func = _get_take_nd_function_cached(ndim, arr_dtype, out_dtype, axis) 

338 

339 if func is None: 

340 

341 def func(arr, indexer, out, fill_value=np.nan, allow_fill: bool = True) -> None: 

342 indexer = ensure_platform_int(indexer) 

343 _take_nd_object( 

344 arr, indexer, out, axis=axis, fill_value=fill_value, mask_info=mask_info 

345 ) 

346 

347 return func 

348 

349 

350def _view_wrapper(f, arr_dtype=None, out_dtype=None, fill_wrap=None): 

351 def wrapper( 

352 arr: np.ndarray, 

353 indexer: np.ndarray, 

354 out: np.ndarray, 

355 fill_value=np.nan, 

356 allow_fill: bool = True, 

357 ) -> None: 

358 if arr_dtype is not None: 

359 arr = arr.view(arr_dtype) 

360 if out_dtype is not None: 

361 out = out.view(out_dtype) 

362 if fill_wrap is not None: 

363 fill_value = fill_value.view("i8") 

364 

365 f(arr, indexer, out, fill_value=fill_value, allow_fill=allow_fill) 

366 

367 return wrapper 

368 

369 

370def _convert_wrapper(f, conv_dtype): 

371 def wrapper( 

372 arr: np.ndarray, 

373 indexer: np.ndarray, 

374 out: np.ndarray, 

375 fill_value=np.nan, 

376 allow_fill: bool = True, 

377 ) -> None: 

378 if conv_dtype == object: 

379 # GH#39755 avoid casting dt64/td64 to integers 

380 arr = ensure_wrapped_if_datetimelike(arr) 

381 arr = arr.astype(conv_dtype) 

382 f(arr, indexer, out, fill_value=fill_value, allow_fill=allow_fill) 

383 

384 return wrapper 

385 

386 

387_take_1d_dict = { 

388 ("int8", "int8"): libalgos.take_1d_int8_int8, 

389 ("int8", "int32"): libalgos.take_1d_int8_int32, 

390 ("int8", "int64"): libalgos.take_1d_int8_int64, 

391 ("int8", "float64"): libalgos.take_1d_int8_float64, 

392 ("int16", "int16"): libalgos.take_1d_int16_int16, 

393 ("int16", "int32"): libalgos.take_1d_int16_int32, 

394 ("int16", "int64"): libalgos.take_1d_int16_int64, 

395 ("int16", "float64"): libalgos.take_1d_int16_float64, 

396 ("int32", "int32"): libalgos.take_1d_int32_int32, 

397 ("int32", "int64"): libalgos.take_1d_int32_int64, 

398 ("int32", "float64"): libalgos.take_1d_int32_float64, 

399 ("int64", "int64"): libalgos.take_1d_int64_int64, 

400 ("uint8", "uint8"): libalgos.take_1d_bool_bool, 

401 ("uint16", "uint16"): libalgos.take_1d_uint16_uint16, 

402 ("uint32", "uint32"): libalgos.take_1d_uint32_uint32, 

403 ("uint64", "uint64"): libalgos.take_1d_uint64_uint64, 

404 ("int64", "float64"): libalgos.take_1d_int64_float64, 

405 ("float32", "float32"): libalgos.take_1d_float32_float32, 

406 ("float32", "float64"): libalgos.take_1d_float32_float64, 

407 ("float64", "float64"): libalgos.take_1d_float64_float64, 

408 ("object", "object"): libalgos.take_1d_object_object, 

409 ("bool", "bool"): _view_wrapper(libalgos.take_1d_bool_bool, np.uint8, np.uint8), 

410 ("bool", "object"): _view_wrapper(libalgos.take_1d_bool_object, np.uint8, None), 

411 ("datetime64[ns]", "datetime64[ns]"): _view_wrapper( 

412 libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64 

413 ), 

414 ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper( 

415 libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64 

416 ), 

417} 

418 

419_take_2d_axis0_dict = { 

420 ("int8", "int8"): libalgos.take_2d_axis0_int8_int8, 

421 ("int8", "int32"): libalgos.take_2d_axis0_int8_int32, 

422 ("int8", "int64"): libalgos.take_2d_axis0_int8_int64, 

423 ("int8", "float64"): libalgos.take_2d_axis0_int8_float64, 

424 ("int16", "int16"): libalgos.take_2d_axis0_int16_int16, 

425 ("int16", "int32"): libalgos.take_2d_axis0_int16_int32, 

426 ("int16", "int64"): libalgos.take_2d_axis0_int16_int64, 

427 ("int16", "float64"): libalgos.take_2d_axis0_int16_float64, 

428 ("int32", "int32"): libalgos.take_2d_axis0_int32_int32, 

429 ("int32", "int64"): libalgos.take_2d_axis0_int32_int64, 

430 ("int32", "float64"): libalgos.take_2d_axis0_int32_float64, 

431 ("int64", "int64"): libalgos.take_2d_axis0_int64_int64, 

432 ("int64", "float64"): libalgos.take_2d_axis0_int64_float64, 

433 ("uint8", "uint8"): libalgos.take_2d_axis0_bool_bool, 

434 ("uint16", "uint16"): libalgos.take_2d_axis0_uint16_uint16, 

435 ("uint32", "uint32"): libalgos.take_2d_axis0_uint32_uint32, 

436 ("uint64", "uint64"): libalgos.take_2d_axis0_uint64_uint64, 

437 ("float32", "float32"): libalgos.take_2d_axis0_float32_float32, 

438 ("float32", "float64"): libalgos.take_2d_axis0_float32_float64, 

439 ("float64", "float64"): libalgos.take_2d_axis0_float64_float64, 

440 ("object", "object"): libalgos.take_2d_axis0_object_object, 

441 ("bool", "bool"): _view_wrapper( 

442 libalgos.take_2d_axis0_bool_bool, np.uint8, np.uint8 

443 ), 

444 ("bool", "object"): _view_wrapper( 

445 libalgos.take_2d_axis0_bool_object, np.uint8, None 

446 ), 

447 ("datetime64[ns]", "datetime64[ns]"): _view_wrapper( 

448 libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

449 ), 

450 ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper( 

451 libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

452 ), 

453} 

454 

455_take_2d_axis1_dict = { 

456 ("int8", "int8"): libalgos.take_2d_axis1_int8_int8, 

457 ("int8", "int32"): libalgos.take_2d_axis1_int8_int32, 

458 ("int8", "int64"): libalgos.take_2d_axis1_int8_int64, 

459 ("int8", "float64"): libalgos.take_2d_axis1_int8_float64, 

460 ("int16", "int16"): libalgos.take_2d_axis1_int16_int16, 

461 ("int16", "int32"): libalgos.take_2d_axis1_int16_int32, 

462 ("int16", "int64"): libalgos.take_2d_axis1_int16_int64, 

463 ("int16", "float64"): libalgos.take_2d_axis1_int16_float64, 

464 ("int32", "int32"): libalgos.take_2d_axis1_int32_int32, 

465 ("int32", "int64"): libalgos.take_2d_axis1_int32_int64, 

466 ("int32", "float64"): libalgos.take_2d_axis1_int32_float64, 

467 ("int64", "int64"): libalgos.take_2d_axis1_int64_int64, 

468 ("int64", "float64"): libalgos.take_2d_axis1_int64_float64, 

469 ("uint8", "uint8"): libalgos.take_2d_axis1_bool_bool, 

470 ("uint16", "uint16"): libalgos.take_2d_axis1_uint16_uint16, 

471 ("uint32", "uint32"): libalgos.take_2d_axis1_uint32_uint32, 

472 ("uint64", "uint64"): libalgos.take_2d_axis1_uint64_uint64, 

473 ("float32", "float32"): libalgos.take_2d_axis1_float32_float32, 

474 ("float32", "float64"): libalgos.take_2d_axis1_float32_float64, 

475 ("float64", "float64"): libalgos.take_2d_axis1_float64_float64, 

476 ("object", "object"): libalgos.take_2d_axis1_object_object, 

477 ("bool", "bool"): _view_wrapper( 

478 libalgos.take_2d_axis1_bool_bool, np.uint8, np.uint8 

479 ), 

480 ("bool", "object"): _view_wrapper( 

481 libalgos.take_2d_axis1_bool_object, np.uint8, None 

482 ), 

483 ("datetime64[ns]", "datetime64[ns]"): _view_wrapper( 

484 libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

485 ), 

486 ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper( 

487 libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

488 ), 

489} 

490 

491_take_2d_multi_dict = { 

492 ("int8", "int8"): libalgos.take_2d_multi_int8_int8, 

493 ("int8", "int32"): libalgos.take_2d_multi_int8_int32, 

494 ("int8", "int64"): libalgos.take_2d_multi_int8_int64, 

495 ("int8", "float64"): libalgos.take_2d_multi_int8_float64, 

496 ("int16", "int16"): libalgos.take_2d_multi_int16_int16, 

497 ("int16", "int32"): libalgos.take_2d_multi_int16_int32, 

498 ("int16", "int64"): libalgos.take_2d_multi_int16_int64, 

499 ("int16", "float64"): libalgos.take_2d_multi_int16_float64, 

500 ("int32", "int32"): libalgos.take_2d_multi_int32_int32, 

501 ("int32", "int64"): libalgos.take_2d_multi_int32_int64, 

502 ("int32", "float64"): libalgos.take_2d_multi_int32_float64, 

503 ("int64", "int64"): libalgos.take_2d_multi_int64_int64, 

504 ("int64", "float64"): libalgos.take_2d_multi_int64_float64, 

505 ("float32", "float32"): libalgos.take_2d_multi_float32_float32, 

506 ("float32", "float64"): libalgos.take_2d_multi_float32_float64, 

507 ("float64", "float64"): libalgos.take_2d_multi_float64_float64, 

508 ("object", "object"): libalgos.take_2d_multi_object_object, 

509 ("bool", "bool"): _view_wrapper( 

510 libalgos.take_2d_multi_bool_bool, np.uint8, np.uint8 

511 ), 

512 ("bool", "object"): _view_wrapper( 

513 libalgos.take_2d_multi_bool_object, np.uint8, None 

514 ), 

515 ("datetime64[ns]", "datetime64[ns]"): _view_wrapper( 

516 libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

517 ), 

518 ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper( 

519 libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64 

520 ), 

521} 

522 

523 

524def _take_nd_object( 

525 arr: np.ndarray, 

526 indexer: npt.NDArray[np.intp], 

527 out: np.ndarray, 

528 axis: AxisInt, 

529 fill_value, 

530 mask_info, 

531) -> None: 

532 if mask_info is not None: 

533 mask, needs_masking = mask_info 

534 else: 

535 mask = indexer == -1 

536 needs_masking = mask.any() 

537 if arr.dtype != out.dtype: 

538 arr = arr.astype(out.dtype) 

539 if arr.shape[axis] > 0: 

540 arr.take(indexer, axis=axis, out=out) 

541 if needs_masking: 

542 outindexer = [slice(None)] * arr.ndim 

543 outindexer[axis] = mask 

544 out[tuple(outindexer)] = fill_value 

545 

546 

547def _take_2d_multi_object( 

548 arr: np.ndarray, 

549 indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]], 

550 out: np.ndarray, 

551 fill_value, 

552 mask_info, 

553) -> None: 

554 # this is not ideal, performance-wise, but it's better than raising 

555 # an exception (best to optimize in Cython to avoid getting here) 

556 row_idx, col_idx = indexer # both np.intp 

557 if mask_info is not None: 

558 (row_mask, col_mask), (row_needs, col_needs) = mask_info 

559 else: 

560 row_mask = row_idx == -1 

561 col_mask = col_idx == -1 

562 row_needs = row_mask.any() 

563 col_needs = col_mask.any() 

564 if fill_value is not None: 

565 if row_needs: 

566 out[row_mask, :] = fill_value 

567 if col_needs: 

568 out[:, col_mask] = fill_value 

569 for i, u_ in enumerate(row_idx): 

570 if u_ != -1: 

571 for j, v in enumerate(col_idx): 

572 if v != -1: 

573 out[i, j] = arr[u_, v] 

574 

575 

576def _take_preprocess_indexer_and_fill_value( 

577 arr: np.ndarray, 

578 indexer: npt.NDArray[np.intp], 

579 fill_value, 

580 allow_fill: bool, 

581 mask: npt.NDArray[np.bool_] | None = None, 

582): 

583 mask_info: tuple[np.ndarray | None, bool] | None = None 

584 

585 if not allow_fill: 

586 dtype, fill_value = arr.dtype, arr.dtype.type() 

587 mask_info = None, False 

588 else: 

589 # check for promotion based on types only (do this first because 

590 # it's faster than computing a mask) 

591 if lib.is_float(fill_value) and fill_value.is_integer(): 

592 # Avoid warning if possible 

593 fill_value = int(fill_value) 

594 dtype, fill_value = maybe_promote(arr.dtype, fill_value) 

595 if dtype != arr.dtype: 

596 if not ( 

597 (lib.is_float(fill_value) and np.isnan(fill_value)) 

598 or (arr.dtype.kind == "U" and isinstance(fill_value, str)) 

599 ): 

600 # GH#53910 

601 warnings.warn( 

602 "reindexing with a fill_value that cannot be held by the " 

603 "original dtype is deprecated. Explicitly cast to a common " 

604 f"dtype (in this case {dtype}) instead.", 

605 Pandas4Warning, 

606 stacklevel=find_stack_level(), 

607 ) 

608 # check if promotion is actually required based on indexer 

609 if mask is not None: 

610 needs_masking = True 

611 else: 

612 mask = indexer == -1 

613 needs_masking = bool(mask.any()) 

614 mask_info = mask, needs_masking 

615 if not needs_masking: 

616 # if not, then depromote, set fill_value to dummy 

617 # (it won't be used but we don't want the cython code 

618 # to crash when trying to cast it to dtype) 

619 dtype, fill_value = arr.dtype, arr.dtype.type() 

620 

621 return dtype, fill_value, mask_info