Repository: pandas-dev/pandas
Pull request #56662 (2023-12-28 19:04:00)

Backport PR #56641 on branch 2.2.x (DOC: Add optional dependencies table in 2.2 whatsnew)
Backport PR #56641: DOC: Add optional dependencies table in 2.2 whatsnew


Patch:
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index d60dbefd83..1da18cd9be 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -417,15 +417,63 @@ Backwards incompatible API changes
 
 Increased minimum versions for dependencies
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-For `optional libraries <https://pandas.pydata.org/docs/getting_started/install.html>`_ the general recommendation is to use the latest version.
-The following table lists the lowest version per library that is currently being tested throughout the development of pandas.
-Optional libraries below the lowest tested version may still work, but are not considered supported.
-
-+-----------------+-----------------+---------+
-| Package         | Minimum Version | Changed |
-+=================+=================+=========+
-| mypy (dev)      | 1.8.0           |    X    |
-+-----------------+-----------------+---------+
+For `optional dependencies <https://pandas.pydata.org/docs/getting_started/install.html>`_ the general recommendation is to use the latest version.
+Optional dependencies below the lowest tested version may still work but are not considered supported.
+The following table lists the optional dependencies that have had their minimum tested version increased.
+
++-----------------+---------------------+
+| Package         | New Minimum Version |
++=================+=====================+
+| beautifulsoup4  | 4.11.2              |
++-----------------+---------------------+
+| blosc           | 1.21.3              |
++-----------------+---------------------+
+| bottleneck      | 1.3.6               |
++-----------------+---------------------+
+| fastparquet     | 2022.12.0           |
++-----------------+---------------------+
+| fsspec          | 2022.11.0           |
++-----------------+---------------------+
+| gcsfs           | 2022.11.0           |
++-----------------+---------------------+
+| lxml            | 4.9.2               |
++-----------------+---------------------+
+| matplotlib      | 3.6.3               |
++-----------------+---------------------+
+| numba           | 0.56.4              |
++-----------------+---------------------+
+| numexpr         | 2.8.4               |
++-----------------+---------------------+
+| qtpy            | 2.3.0               |
++-----------------+---------------------+
+| openpyxl        | 3.1.0               |
++-----------------+---------------------+
+| psycopg2        | 2.9.6               |
++-----------------+---------------------+
+| pyreadstat      | 1.2.0               |
++-----------------+---------------------+
+| pytables        | 3.8.0               |
++-----------------+---------------------+
+| pyxlsb          | 1.0.10              |
++-----------------+---------------------+
+| s3fs            | 2022.11.0           |
++-----------------+---------------------+
+| scipy           | 1.10.0              |
++-----------------+---------------------+
+| sqlalchemy      | 2.0.0               |
++-----------------+---------------------+
+| tabulate        | 0.9.0               |
++-----------------+---------------------+
+| xarray          | 2022.12.0           |
++-----------------+---------------------+
+| xlsxwriter      | 3.0.5               |
++-----------------+---------------------+
+| zstandard       | 0.19.0              |
++-----------------+---------------------+
+| pyqt5           | 5.15.8              |
++-----------------+---------------------+
+| tzdata          | 2022.7              |
++-----------------+---------------------+
 
 See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for more.
 
@@ -761,6 +809,7 @@ Interval
 
 Indexing
 ^^^^^^^^
+- Bug in :meth:`DataFrame.loc` mutating a boolean indexer when :class:`DataFrame` has a :class:`MultiIndex` (:issue:`56635`)
 - Bug in :meth:`DataFrame.loc` when setting :class:`Series` with extension dtype into NumPy dtype (:issue:`55604`)
 - Bug in :meth:`Index.difference` not returning a unique set of values when ``other`` is empty or ``other`` is considered non-comparable (:issue:`55113`)
 - Bug in setting :class:`Categorical` values into a :class:`DataFrame` with numpy dtypes raising ``RecursionError`` (:issue:`52927`)
diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py
index 2a4e027e2b..02a841a207 100644
--- a/pandas/core/indexes/multi.py
+++ b/pandas/core/indexes/multi.py
@@ -3488,6 +3488,8 @@ class MultiIndex(Index):
                         "is not the same length as the index"
                     )
                 lvl_indexer = np.asarray(k)
+                if indexer is None:
+                    lvl_indexer = lvl_indexer.copy()
 
             elif is_list_like(k):
                 # a collection of labels to include from this level (these are or'd)


Test patch:
diff --git a/pandas/tests/copy_view/test_indexing.py b/pandas/tests/copy_view/test_indexing.py
index 6f3850ab64..2681c07f01 100644
--- a/pandas/tests/copy_view/test_indexing.py
+++ b/pandas/tests/copy_view/test_indexing.py
@@ -1224,6 +1224,27 @@ def test_series_midx_tuples_slice(using_copy_on_write, warn_copy_on_write):
         tm.assert_series_equal(ser, expected)
 
 
+def test_midx_read_only_bool_indexer():
+    # GH#56635
+    def mklbl(prefix, n):
+        return [f"{prefix}{i}" for i in range(n)]
+
+    idx = pd.MultiIndex.from_product(
+        [mklbl("A", 4), mklbl("B", 2), mklbl("C", 4), mklbl("D", 2)]
+    )
+    cols = pd.MultiIndex.from_tuples(
+        [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")], names=["lvl0", "lvl1"]
+    )
+    df = DataFrame(1, index=idx, columns=cols).sort_index().sort_index(axis=1)
+
+    mask = df[("a", "foo")] == 1
+    expected_mask = mask.copy()
+    result = df.loc[pd.IndexSlice[mask, :, ["C1", "C3"]], :]
+    expected = df.loc[pd.IndexSlice[:, :, ["C1", "C3"]], :]
+    tm.assert_frame_equal(result, expected)
+    tm.assert_series_equal(mask, expected_mask)
+
+
 def test_loc_enlarging_with_dataframe(using_copy_on_write):
     df = DataFrame({"a": [1, 2, 3]})
     rhs = DataFrame({"b": [1, 2, 3], "c": [4, 5, 6]})