diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 9840e4e94d28c..f24bfe25cc6ef 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -233,7 +233,7 @@ Other - Trying to set the ``display.precision``, ``display.max_rows`` or ``display.max_columns`` using :meth:`set_option` to anything but a ``None`` or a positive int will raise a ``ValueError`` (:issue:`23348`) - Using :meth:`DataFrame.replace` with overlapping keys in a nested dictionary will no longer raise, now matching the behavior of a flat dictionary (:issue:`27660`) - :meth:`DataFrame.to_csv` and :meth:`Series.to_csv` now support dicts as ``compression`` argument with key ``'method'`` being the compression method and others as additional compression options when the compression method is ``'zip'``. (:issue:`26023`) -- +- :meth:`DataFrame.fillna` when using axis=1 previously failed to replace NaN values (:issue:`17399` and :issue:`17409`) .. _whatsnew_1000.contributors: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 68308b2f83b60..da95e854908d5 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6274,7 +6274,8 @@ def fillna( ) new_data = self._data.fillna( - value=value, limit=limit, inplace=inplace, downcast=downcast + value=value, limit=limit, inplace=inplace, + downcast=downcast, axis: Axis=axis ) elif isinstance(value, (dict, ABCSeries)): diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 29d225443d18a..56793fe83e420 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -81,6 +81,7 @@ from pandas.core.nanops import nanpercentile from pandas.io.formats.printing import pprint_thing +from pandas_typing import Axis class Block(PandasObject): @@ -386,7 +387,7 @@ def apply(self, func, **kwargs): return result - def fillna(self, value, limit=None, inplace=False, downcast=None): + def fillna(self, value, limit=None, inplace=False, downcast=None, axis: Axis = 0): """ fillna on the block with the value. If we fail, then convert to ObjectBlock and try again """ @@ -398,13 +399,10 @@ def fillna(self, value, limit=None, inplace=False, downcast=None): raise ValueError("Limit must be an integer") if limit < 1: raise ValueError("Limit must be greater than 0") - mask[mask.cumsum(self.ndim - 1) > limit] = False + mask[mask.cumsum(int(axis == 0 and self.ndim > 1)) > limit] = False if not self._can_hold_na: - if inplace: - return self - else: - return self.copy() + return self if inplace else self.copy() if self._can_hold_element(value): # equivalent: _try_coerce_args(value) would not raise @@ -1842,7 +1840,7 @@ def concat_same_type(self, to_concat, placement=None): placement = placement or slice(0, len(values), 1) return self.make_block_same_class(values, ndim=self.ndim, placement=placement) - def fillna(self, value, limit=None, inplace=False, downcast=None): + def fillna(self, value, limit=None, inplace=False, downcast=None, axis: Axis = 0): values = self.values if inplace else self.values.copy() values = values.fillna(value=value, limit=limit) return [ @@ -2406,15 +2404,15 @@ def concat_same_type(self, to_concat, placement=None): return ObjectBlock(values, ndim=self.ndim, placement=placement) return super().concat_same_type(to_concat, placement) - def fillna(self, value, limit=None, inplace=False, downcast=None): + def fillna(self, value, limit=None, inplace=False, downcast=None, axis: Axis = 0): # We support filling a DatetimeTZ with a `value` whose timezone # is different by coercing to object. if self._can_hold_element(value): - return super().fillna(value, limit, inplace, downcast) + return super().fillna(value, limit, inplace, downcast, axis) # different timezones, or a non-tz return self.astype(object).fillna( - value, limit=limit, inplace=inplace, downcast=downcast + value, limit=limit, inplace=inplace, downcast=downcast, axis=axis ) def setitem(self, indexer, value): diff --git a/pandas/tests/frame/test_missing.py b/pandas/tests/frame/test_missing.py index 94667ecfa837d..f9ffaef659bb4 100644 --- a/pandas/tests/frame/test_missing.py +++ b/pandas/tests/frame/test_missing.py @@ -671,6 +671,21 @@ def test_fillna_columns(self): expected = df.astype(float).fillna(method="ffill", axis=1) assert_frame_equal(result, expected) + def test_fillna_rows(self): + #GH17399 + df = pd.DataFrame({ + "a": [1,2,3,4], + "b": [5,np.nan,7,8], + "c": [9,10,11,np.nan]}) + + expected = pd.DataFrame({ + "a": [1,2,3,4], + "b": [5,6,7,8], + "c": [9,10,11,6]}) + + result = df.fillna(df.mean(axis=1)) + tm.assert_frame_equal(result, expected) + def test_fillna_invalid_method(self, float_frame): with pytest.raises(ValueError, match="ffil"): float_frame.fillna(method="ffil")