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