diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index edd205860b4e4..0bd68a539a2ba 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -690,6 +690,7 @@ Indexing ^^^^^^^^ - Bug in :meth:`DataFrame.__getitem__` returning modified columns when called with ``slice`` in Python 3.12 (:issue:`57500`) - Bug in :meth:`DataFrame.from_records` throwing a ``ValueError`` when passed an empty list in ``index`` (:issue:`58594`) +- Bug in :meth:`DataFrame.loc` with inconsistent behavior of loc-set with 2 given indexes to Series (:issue:`59933`) - Bug in :meth:`MultiIndex.insert` when a new value inserted to a datetime-like level gets cast to ``NaT`` and fails indexing (:issue:`60388`) - Bug in printing :attr:`Index.names` and :attr:`MultiIndex.levels` would not escape single quotes (:issue:`60190`) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 8a493fef54d3b..bcb27d0320c91 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2349,11 +2349,17 @@ def _align_series( if isinstance(indexer, tuple): # flatten np.ndarray indexers + if ( + len(indexer) == 2 + and isinstance(indexer[1], np.ndarray) + and indexer[1].dtype == np.bool_ + ): + indexer = (indexer[0], np.where(indexer[1])[0]) + def ravel(i): return i.ravel() if isinstance(i, np.ndarray) else i indexer = tuple(map(ravel, indexer)) - aligners = [not com.is_null_slice(idx) for idx in indexer] sum_aligners = sum(aligners) single_aligner = sum_aligners == 1 @@ -2371,12 +2377,15 @@ def ravel(i): # we have a frame, with multiple indexers on both axes; and a # series, so need to broadcast (see GH5206) - if sum_aligners == self.ndim and all(is_sequence(_) for _ in indexer): + if all(is_sequence(_) or isinstance(_, slice) for _ in indexer): ser_values = ser.reindex(obj.axes[0][indexer[0]])._values # single indexer if len(indexer) > 1 and not multiindex_indexer: - len_indexer = len(indexer[1]) + if isinstance(indexer[1], slice): + len_indexer = len(obj.axes[1][indexer[1]]) + else: + len_indexer = len(indexer[1]) ser_values = ( np.tile(ser_values, len_indexer).reshape(len_indexer, -1).T ) diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 17e610bda93e4..8838fc7eed2f7 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -3296,6 +3296,66 @@ def test_loc_reindexing_of_empty_index(self): expected = DataFrame(index=[1, 1, 2, 2], data=["1", "1", "2", "2"]) tm.assert_frame_equal(df, expected) + @pytest.mark.parametrize( + "df, row_index, col_index, expected_df", + [ + [ + DataFrame([[1, 2, 3], [4, 5, 6]], columns=list("ABC")), + slice(0, 3), + ["A", "B", "C"], + DataFrame([[10, 10, 10], [20, 20, 20]], columns=list("ABC")), + ], + [ + DataFrame([[1, 2, 3], [4, 5, 6]], columns=list("ABC")), + slice(None), + ["A", "B", "C"], + DataFrame([[10, 10, 10], [20, 20, 20]], columns=list("ABC")), + ], + [ + DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")), + [True, True, True], + ["A", "B", "C"], + DataFrame( + [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC") + ), + ], + [ + DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")), + slice(0, 4), + ["A", "B", "C"], + DataFrame( + [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC") + ), + ], + [ + DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")), + slice(None), + slice("A", "C"), + DataFrame( + [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC") + ), + ], + [ + DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")), + slice(None), + Series( + { + "A": True, + "C": False, + "B": True, + } + ), + DataFrame([[10, 10, 3], [20, 20, 6], [30, 30, 9]], columns=list("ABC")), + ], + ], + ) + def test_loc_set_series_to_multiple_columns( + self, df, row_index, col_index, expected_df + ): + # GH 59933 + df.loc[row_index, col_index] = Series([10, 20, 30]) + tm.assert_frame_equal(df, expected_df) + def test_loc_setitem_matching_index(self): # GH 25548 s = Series(0.0, index=list("abcd"))