From a9c8d8514a31497d78e0abae8b2c8a4a8366e79b Mon Sep 17 00:00:00 2001 From: eicchen Date: Thu, 10 Jul 2025 15:42:45 -0500 Subject: [PATCH 1/5] Initial test case --- pandas/tests/frame/test_arithmetic.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index bc69ec388bf0c..eb79a17a75f5a 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -2199,3 +2199,19 @@ def test_mixed_col_index_dtype(using_infer_string): dtype = "string" expected.columns = expected.columns.astype(dtype) tm.assert_frame_equal(result, expected) + + +def test_df_mul_series_fill_value(): + # GH 61581 + data = np.arange(50).reshape(10, 5) + columns = list("ABCDE") + df = DataFrame(data, columns=columns) + for i in range(5): + df.iat[i, i] = np.nan + df.iat[i + 1, i] = np.nan + df.iat[i + 4, i] = np.nan + + df_result = df[["A", "B", "C", "D"]].mul(df["E"], axis=0, fill_value=5) + df_expected = df[["A", "B", "C", "D"]].mul(df["E"].fillna(5), axis=0) + + tm.assert_frame_equal(df_result, df_expected) From f303a04cf9dc4636869b5919ce4d460fda22d40a Mon Sep 17 00:00:00 2001 From: eicchen Date: Thu, 10 Jul 2025 17:05:27 -0500 Subject: [PATCH 2/5] Updated test case to account for results of mul being NaN if both inputs are NaN --- pandas/tests/frame/test_arithmetic.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index eb79a17a75f5a..37b0f764d7eed 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -2211,7 +2211,11 @@ def test_df_mul_series_fill_value(): df.iat[i + 1, i] = np.nan df.iat[i + 4, i] = np.nan - df_result = df[["A", "B", "C", "D"]].mul(df["E"], axis=0, fill_value=5) - df_expected = df[["A", "B", "C", "D"]].mul(df["E"].fillna(5), axis=0) + df_a = df.iloc[:, :-1] + df_b = df.iloc[:, -1] + nan_mask = df_a.isna().astype(int).mul(df_b.isna().astype(int), axis=0).astype(bool) + + df_result = df_a.mul(df_b, axis=0, fill_value=5) + df_expected = (df_a.fillna(5).mul(df_b.fillna(5), axis=0)).mask(nan_mask, np.nan) tm.assert_frame_equal(df_result, df_expected) From 5ac26a4a4ebad933da937bcc10906f2514cebc77 Mon Sep 17 00:00:00 2001 From: eicchen Date: Thu, 10 Jul 2025 17:21:49 -0500 Subject: [PATCH 3/5] Removed test cases which expect an error from fill_value --- pandas/tests/frame/test_arithmetic.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index 37b0f764d7eed..74e85d9c41922 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -628,12 +628,6 @@ def test_arith_flex_frame_corner(self, float_frame): expected = float_frame.sort_index() * np.nan tm.assert_frame_equal(result, expected) - with pytest.raises(NotImplementedError, match="fill_value"): - float_frame.add(float_frame.iloc[0], fill_value=3) - - with pytest.raises(NotImplementedError, match="fill_value"): - float_frame.add(float_frame.iloc[0], axis="index", fill_value=3) - @pytest.mark.parametrize("op", ["add", "sub", "mul", "mod"]) def test_arith_flex_series_ops(self, simple_frame, op): # after arithmetic refactor, add truediv here @@ -667,19 +661,6 @@ def test_arith_flex_series_broadcasting(self, any_real_numpy_dtype): result = df.div(df[0], axis="index") tm.assert_frame_equal(result, expected) - def test_arith_flex_zero_len_raises(self): - # GH 19522 passing fill_value to frame flex arith methods should - # raise even in the zero-length special cases - ser_len0 = Series([], dtype=object) - df_len0 = DataFrame(columns=["A", "B"]) - df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"]) - - with pytest.raises(NotImplementedError, match="fill_value"): - df.add(ser_len0, fill_value="E") - - with pytest.raises(NotImplementedError, match="fill_value"): - df_len0.sub(df["A"], axis=None, fill_value=3) - def test_flex_add_scalar_fill_value(self): # GH#12723 dat = np.array([0, 1, np.nan, 3, 4, 5], dtype="float") From a60fbb03a01766c5474d3ca68baf6ee5cfeb17e8 Mon Sep 17 00:00:00 2001 From: eicchen Date: Thu, 10 Jul 2025 17:46:37 -0500 Subject: [PATCH 4/5] Updated test case to include other operators which included fill_value --- pandas/tests/frame/test_arithmetic.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index 74e85d9c41922..5852a8fde42bb 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -2182,7 +2182,8 @@ def test_mixed_col_index_dtype(using_infer_string): tm.assert_frame_equal(result, expected) -def test_df_mul_series_fill_value(): +@pytest.mark.parametrize("op", ["add", "sub", "mul", "div", "mod", "truediv", "pow"]) +def test_df_series_fill_value(op): # GH 61581 data = np.arange(50).reshape(10, 5) columns = list("ABCDE") @@ -2196,7 +2197,9 @@ def test_df_mul_series_fill_value(): df_b = df.iloc[:, -1] nan_mask = df_a.isna().astype(int).mul(df_b.isna().astype(int), axis=0).astype(bool) - df_result = df_a.mul(df_b, axis=0, fill_value=5) - df_expected = (df_a.fillna(5).mul(df_b.fillna(5), axis=0)).mask(nan_mask, np.nan) + df_result = getattr(df_a, op)(df_b, axis=0, fill_value=5) + df_expected = getattr(df_a.fillna(5), op)(df_b.fillna(5), axis=0).mask( + nan_mask, np.nan + ) tm.assert_frame_equal(df_result, df_expected) From 87ecfc45def523df28266af0ad678d3876630a15 Mon Sep 17 00:00:00 2001 From: eicchen Date: Thu, 10 Jul 2025 17:48:36 -0500 Subject: [PATCH 5/5] Removed restriction on using fill_value with series Updated docs --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/frame.py | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 4e0e497379fa2..909453b4ee95e 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -778,6 +778,7 @@ MultiIndex - :func:`MultiIndex.get_level_values` accessing a :class:`DatetimeIndex` does not carry the frequency attribute along (:issue:`58327`, :issue:`57949`) - Bug in :class:`DataFrame` arithmetic operations in case of unaligned MultiIndex columns (:issue:`60498`) - Bug in :class:`DataFrame` arithmetic operations with :class:`Series` in case of unaligned MultiIndex (:issue:`61009`) +- Bug in :class:`DataFrame` arithmetic operations with :class:`Series` now works with ``fill_value`` parameter (:issue:`61581`) - Bug in :meth:`MultiIndex.from_tuples` causing wrong output with input of type tuples having NaN values (:issue:`60695`, :issue:`60988`) I/O diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 632ab12edd7e4..a7b955520eabc 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -8382,11 +8382,6 @@ def _flex_arith_method( if self._should_reindex_frame_op(other, op, axis, fill_value, level): return self._arith_method_with_reindex(other, op) - if isinstance(other, Series) and fill_value is not None: - # TODO: We could allow this in cases where we end up going - # through the DataFrame path - raise NotImplementedError(f"fill_value {fill_value} not supported.") - other = ops.maybe_prepare_scalar_for_op(other, self.shape) self, other = self._align_for_op(other, axis, flex=True, level=level)