Feature Type

  • [ ] Adding new functionality to pandas

  • [X] Changing existing functionality in pandas

  • [ ] Removing existing functionality in pandas

Problem Description

The currently very useful behaviour of .fillna is being deprecated.

a = pd.Series([True, False, None])
a
# 0     True
# 1    False
# 2     None
# dtype: object

Using a.fillna raises a warning:

a.fillna(True)
# [/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/3077193745.py:1](https://file+.vscode-resource.vscode-cdn.net/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/3077193745.py:1): FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
#   a.fillna(True)

Full message of the warning is:

FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set pd.set_option('future.no_silent_downcasting', True)

The proposed solutions don't work:

a.fillna(True).infer_objects(copy=False)
# [/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/1224563968.py:1](https://file+.vscode-resource.vscode-cdn.net/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/1224563968.py:1): FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
#   a.fillna(True).infer_objects(copy=False)

maybe I misunderstood the Warning message?

a.infer_objects(copy=False).fillna(True)
# [/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/2319989247.py:1](https://file+.vscode-resource.vscode-cdn.net/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/2319989247.py:1): FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
#   a.infer_objects(copy=False).fillna(True)    # maybe I misunderstood the message?

Let's try to opt-in...

with pd.option_context("future.no_silent_downcasting", True):
  r = a.fillna(True)
r
# 0     True
# 1    False
# 2     True
# dtype: object

No, it's no longer a bool Series...

Some online resources suggest first casting to bool...

a.astype(bool)
# 0     True
# 1    False
# 2    False
# dtype: bool

Looks like this is a potential replacement for .fillna(False) but not for .fillna(True)...

Wait, there's a downcast parameter for .fillna!

a.fillna(True, downcast=True)
# [/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/4257582953.py:1](https://file+.vscode-resource.vscode-cdn.net/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/4257582953.py:1): FutureWarning: The 'downcast' keyword in fillna is deprecated and will be removed in a future version. Use res.infer_objects(copy=False) to infer non-object dtype, or pd.to_numeric with the 'downcast' keyword to downcast numeric results.
#   a.fillna(True, downcast=True)
# [/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/4257582953.py:1](https://file+.vscode-resource.vscode-cdn.net/var/folders/66/bjmfs8315pjdbztwxmyvd7x80000gn/T/ipykernel_85835/4257582953.py:1): FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
#   a.fillna(True, downcast=True)

Oh no, it's deprecated as well, not I got TWO warnings...

Feature Description

Restore the functionality of .fillna WITHOUT the Warning.

a = pd.Series([True, False, None])
a.fillna(True)
# 0     True
# 1    False
# 2     True
# dtype: bool

Alternative Solutions

Currently, the only "correct" option is to use nullable Boolean type.

a.astype('boolean').fillna(True).astype(bool)
# or, if we're happy keeping the type `boolean`...
a.astype('boolean').fillna(True)

This is overly verbose, but would be acceptable if boolean was inferred automatically for [True, False, None] (or [True, False, np.nan]), but currently it's not...

(The additional confusion is that integer nullable types are distinguished by uppercase (int64 -> Int64) but boolean nullable type isn't (bool -> boolean)... so it took me a very long time to even find this solution!)

Additional Context

No response

Comment From: rhshadrach

Thanks for report! You mention that infer_objects didn't work, I'm not sure what you meant by this. Is it that you still get the warning? You need to use infer_objects with the future behavior like so:

ser = pd.Series([True, False, None])
with pd.option_context("future.no_silent_downcasting", True):
      print(ser.fillna(True).infer_objects(copy=False))

Comment From: WillAyd

This is overly verbose, but would be acceptable if boolean was inferred automatically for [True, False, None] (or [True, False, np.nan]), but currently it's not...

This is another good area where PDEP-13 would probably help a lot https://github.com/pandas-dev/pandas/pull/58455

If it helps, you can avoid the object-dtype based "boolean" array without an astype if you use the pd.BooleanDtype dtype argument as part of the constructor:

a = pd.Series([True, False, None], dtype=pd.BooleanDtype())

Yes it is more verbose, but the NA handling is a lot more sane than the default types baked into pandas

You can find a larger discussion of your issue in https://github.com/pandas-dev/pandas/issues/57734

Comment From: joaoe

Hi. I just hit this "bug" or feature. It's annoying.

Here's my fix

def bool_fillna_inplace(series: pd.Series) -> pd.Series:
    """
    Workaround for https://github.com/pandas-dev/pandas/issues/59831
    Replaces nan/None with False."""
    series[pd.isna(series)] = False
    return series

or an alternative return series & ~pd.isna(series) but that one does a bitwise and which would return False for even number, in case the series is not purely boolean with nans.

Comment From: jbrockmendel

https://github.com/pandas-dev/pandas/issues/59831#issuecomment-2359060509 is the correct solution here. Though the "copy" keyword is no longer needed in that snippet.