Index.take has two bugs when allow_fill=True and fill_value is passed explicitly:
1. fill_value is silently discarded
The caller's fill_value is ignored; self._na_value is always substituted:
# Note: we discard fill_value and use self._na_value, only relevant
# in the case where allow_fill is True and fill_value is not None
taken = algos.take(
values, indices, allow_fill=allow_fill, fill_value=self._na_value
)
2. _can_hold_na gate raises on integer dtypes
>>> pd.Index([1, 2, 3]).take([0, -1], allow_fill=True, fill_value=99)
ValueError: Unable to fill values because Index cannot contain NA
The caller provided a valid fill value that the dtype can hold, but Index.take rejects it because _can_hold_na is False for integer dtypes.
Consequences
These force internal callers to bypass Index.take and work on the underlying arrays directly:
frame.py idxmin/idxmax: algorithms.take(index._values, ...) instead of index.take(...)
reshape/merge.py: appends a sentinel value to the index, with a comment: "We do not use allow_fill and fill_value because it throws a ValueError on integer indices"
indexes/base.py reset_index: take-then-putmask as two operations
io/formats/excel.py: guards allow_fill on levels._can_hold_na
indexes/multi.py: algos.take_nd(lev._values, ...) instead of lev.take(...)
Proposed fix
- Respect the caller's
fill_value
- Remove the
_can_hold_na gate (let the underlying array handle type promotion)
- Simplify the workaround sites
This is backward-compatible: the existing gate (allow_fill=True + fill_value is None → numpy-style take) is preserved, so bare idx.take(indices) is unchanged.
Separately, there are questions about the right long-term defaults for allow_fill (currently True for backward compat but arguably should be False to match ExtensionArray.take), but that would require a deprecation cycle and is out of scope here.
Index.takehas two bugs whenallow_fill=Trueandfill_valueis passed explicitly:1.
fill_valueis silently discardedThe caller's
fill_valueis ignored;self._na_valueis always substituted:2.
_can_hold_nagate raises on integer dtypesThe caller provided a valid fill value that the dtype can hold, but
Index.takerejects it because_can_hold_nais False for integer dtypes.Consequences
These force internal callers to bypass
Index.takeand work on the underlying arrays directly:frame.pyidxmin/idxmax:algorithms.take(index._values, ...)instead ofindex.take(...)reshape/merge.py: appends a sentinel value to the index, with a comment: "We do not use allow_fill and fill_value because it throws a ValueError on integer indices"indexes/base.pyreset_index: take-then-putmask as two operationsio/formats/excel.py: guardsallow_fillonlevels._can_hold_naindexes/multi.py:algos.take_nd(lev._values, ...)instead oflev.take(...)Proposed fix
fill_value_can_hold_nagate (let the underlying array handle type promotion)This is backward-compatible: the existing gate (
allow_fill=True+fill_value is None→ numpy-style take) is preserved, so bareidx.take(indices)is unchanged.Separately, there are questions about the right long-term defaults for
allow_fill(currentlyTruefor backward compat but arguably should beFalseto matchExtensionArray.take), but that would require a deprecation cycle and is out of scope here.