Repository: pandas-dev/pandas
Pull request #56064 (2023-11-19 16:17:52)

CoW: Add warning for fillna with inplace
xref https://github.com/pandas-dev/pandas/issues/56019


Patch:
diff --git a/doc/source/whatsnew/v2.1.4.rst b/doc/source/whatsnew/v2.1.4.rst
index 25afcbb3bb..2a04adf2ac 100644
--- a/doc/source/whatsnew/v2.1.4.rst
+++ b/doc/source/whatsnew/v2.1.4.rst
@@ -22,6 +22,8 @@ Fixed regressions
 Bug fixes
 ~~~~~~~~~
 - Bug in :class:`Series` constructor raising DeprecationWarning when ``index`` is a list of :class:`Series` (:issue:`55228`)
+- Fixed bug in :meth:`DataFrame.__setitem__` casting :class:`Index` with object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`)
+- Fixed bug in :meth:`Index.insert` casting object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`)
 -
 
 .. ---------------------------------------------------------------------------
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index 9be54e95bf..0e613760f4 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -404,6 +404,7 @@ Strings
 Interval
 ^^^^^^^^
 - Bug in :class:`Interval` ``__repr__`` not displaying UTC offsets for :class:`Timestamp` bounds. Additionally the hour, minute and second components will now be shown. (:issue:`55015`)
+- Bug in :meth:`IntervalIndex.factorize` and :meth:`Series.factorize` with :class:`IntervalDtype` with datetime64 or timedelta64 intervals not preserving non-nanosecond units (:issue:`56099`)
 - Bug in :meth:`IntervalIndex.from_arrays` when passed ``datetime64`` or ``timedelta64`` arrays with mismatched resolutions constructing an invalid ``IntervalArray`` object (:issue:`55714`)
 - Bug in :meth:`IntervalIndex.get_indexer` with datetime or timedelta intervals incorrectly matching on integer targets (:issue:`47772`)
 - Bug in :meth:`IntervalIndex.get_indexer` with timezone-aware datetime intervals incorrectly matching on a sequence of timezone-naive targets (:issue:`47772`)
diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py
index 91960173e7..2f1363f180 100644
--- a/pandas/core/arrays/interval.py
+++ b/pandas/core/arrays/interval.py
@@ -391,12 +391,7 @@ class IntervalArray(IntervalMixin, ExtensionArray):
 
     @classmethod
     def _from_factorized(cls, values: np.ndarray, original: IntervalArray) -> Self:
-        if len(values) == 0:
-            # An empty array returns object-dtype here. We can't create
-            # a new IA from an (empty) object-dtype array, so turn it into the
-            # correct dtype.
-            values = values.astype(original.dtype.subtype)
-        return cls(values, closed=original.closed)
+        return cls._from_sequence(values, dtype=original.dtype)
 
     _interval_shared_docs["from_breaks"] = textwrap.dedent(
         """
diff --git a/pandas/core/construction.py b/pandas/core/construction.py
index 4fbc32e5bb..a0a92a99ab 100644
--- a/pandas/core/construction.py
+++ b/pandas/core/construction.py
@@ -49,6 +49,7 @@ from pandas.core.dtypes.cast import (
 from pandas.core.dtypes.common import (
     is_list_like,
     is_object_dtype,
+    is_string_dtype,
     pandas_dtype,
 )
 from pandas.core.dtypes.dtypes import NumpyEADtype
@@ -548,6 +549,10 @@ def sanitize_array(
         # Avoid ending up with a NumpyExtensionArray
         dtype = dtype.numpy_dtype
 
+    object_index = False
+    if isinstance(data, ABCIndex) and data.dtype == object and dtype is None:
+        object_index = True
+
     # extract ndarray or ExtensionArray, ensure we have no NumpyExtensionArray
     data = extract_array(data, extract_numpy=True, extract_range=True)
 
@@ -601,6 +606,13 @@ def sanitize_array(
             subarr = data
             if data.dtype == object:
                 subarr = maybe_infer_to_datetimelike(data)
+                if (
+                    object_index
+                    and using_pyarrow_string_dtype()
+                    and is_string_dtype(subarr)
+                ):
+                    # Avoid inference when string option is set
+                    subarr = data
             elif data.dtype.kind == "U" and using_pyarrow_string_dtype():
                 from pandas.core.arrays.string_ import StringDtype
 
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index b23092e6f2..d723eb622b 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -7180,6 +7180,18 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
                         ChainedAssignmentError,
                         stacklevel=2,
                     )
+            elif not PYPY and not using_copy_on_write():
+                ctr = sys.getrefcount(self)
+                ref_count = REF_COUNT
+                if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
+                    # see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
+                    ref_count += 1
+                if ctr <= ref_count:
+                    warnings.warn(
+                        _chained_assignment_warning_method_msg,
+                        FutureWarning,
+                        stacklevel=2,
+                    )
 
         value, method = validate_fillna_kwargs(value, method)
         if method is not None:
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
index 687d6feb74..cbf7dc5ba6 100644
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -23,6 +23,7 @@ import numpy as np
 from pandas._config import (
     get_option,
     using_copy_on_write,
+    using_pyarrow_string_dtype,
 )
 
 from pandas._libs import (
@@ -6919,7 +6920,14 @@ class Index(IndexOpsMixin, PandasObject):
             loc = loc if loc >= 0 else loc - 1
             new_values[loc] = item
 
-        return Index._with_infer(new_values, name=self.name)
+        idx = Index._with_infer(new_values, name=self.name)
+        if (
+            using_pyarrow_string_dtype()
+            and is_string_dtype(idx.dtype)
+            and new_values.dtype == object
+        ):
+            idx = idx.astype(new_values.dtype)
+        return idx
 
     def drop(
         self,
diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py
index 05e6d2bcfb..d2b76decaa 100644
--- a/pandas/plotting/_matplotlib/boxplot.py
+++ b/pandas/plotting/_matplotlib/boxplot.py
@@ -15,6 +15,7 @@ from pandas.util._decorators import cache_readonly
 from pandas.util._exceptions import find_stack_level
 
 from pandas.core.dtypes.common import is_dict_like
+from pandas.core.dtypes.generic import ABCSeries
 from pandas.core.dtypes.missing import remove_na_arraylike
 
 import pandas as pd
@@ -362,7 +363,7 @@ def boxplot(
     if return_type not in BoxPlot._valid_return_types:
         raise ValueError("return_type must be {'axes', 'dict', 'both'}")
 
-    if isinstance(data, pd.Series):
+    if isinstance(data, ABCSeries):
         data = data.to_frame("x")
         column = "x"
 
diff --git a/pandas/plotting/_matplotlib/converter.py b/pandas/plotting/_matplotlib/converter.py
index 848fb77c94..0eb3318ac9 100644
--- a/pandas/plotting/_matplotlib/converter.py
+++ b/pandas/plotting/_matplotlib/converter.py
@@ -983,10 +983,7 @@ class TimeSeries_DateLocator(Locator):
 
     def _get_default_locs(self, vmin, vmax):
         """Returns the default locations of ticks."""
-        if self.plot_obj.date_axis_info is None:
-            self.plot_obj.date_axis_info = self.finder(vmin, vmax, self.freq)
-
-        locator = self.plot_obj.date_axis_info
+        locator = self.finder(vmin, vmax, self.freq)
 
         if self.isminor:
             return np.compress(locator["min"], locator["val"])
@@ -997,9 +994,6 @@ class TimeSeries_DateLocator(Locator):
         # axis calls Locator.set_axis inside set_m<xxxx>_formatter
 
         vi = tuple(self.axis.get_view_interval())
-        if vi != self.plot_obj.view_interval:
-            self.plot_obj.date_axis_info = None
-        self.plot_obj.view_interval = vi
         vmin, vmax = vi
         if vmax < vmin:
             vmin, vmax = vmax, vmin
@@ -1072,9 +1066,7 @@ class TimeSeries_DateFormatter(Formatter):
 
     def _set_default_format(self, vmin, vmax):
         """Returns the default ticks spacing."""
-        if self.plot_obj.date_axis_info is None:
-            self.plot_obj.date_axis_info = self.finder(vmin, vmax, self.freq)
-        info = self.plot_obj.date_axis_info
+        info = self.finder(vmin, vmax, self.freq)
 
         if self.isminor:
             format = np.compress(info["min"] & np.logical_not(info["maj"]), info)
@@ -1090,10 +1082,7 @@ class TimeSeries_DateFormatter(Formatter):
 
         self.locs = locs
 
-        (vmin, vmax) = vi = tuple(self.axis.get_view_interval())
-        if vi != self.plot_obj.view_interval:
-            self.plot_obj.date_axis_info = None
-        self.plot_obj.view_interval = vi
+        (vmin, vmax) = tuple(self.axis.get_view_interval())
         if vmax < vmin:
             (vmin, vmax) = (vmax, vmin)
         self._set_default_format(vmin, vmax)
diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py
index 6be8284d2a..7096c1281a 100644
--- a/pandas/plotting/_matplotlib/core.py
+++ b/pandas/plotting/_matplotlib/core.py
@@ -504,7 +504,7 @@ class MPLPlot(ABC):
         self._adorn_subplots(fig)
 
         for ax in self.axes:
-            self._post_plot_logic_common(ax, self.data)
+            self._post_plot_logic_common(ax)
             self._post_plot_logic(ax, self.data)
 
     @final
@@ -697,7 +697,7 @@ class MPLPlot(ABC):
         if is_empty:
             raise TypeError("no numeric data to plot")
 
-        self.data = numeric_data.apply(self._convert_to_ndarray)
+        self.data = numeric_data.apply(type(self)._convert_to_ndarray)
 
     def _make_plot(self, fig: Figure):
         raise AbstractMethodError(self)
@@ -714,21 +714,29 @@ class MPLPlot(ABC):
         tools.table(ax, data)
 
     @final
-    def _post_plot_logic_common(self, ax: Axes, data) -> None:
+    def _post_plot_logic_common(self, ax: Axes) -> None:
         """Common post process for each axes"""
         if self.orientation == "vertical" or self.orientation is None:
-            self._apply_axis_properties(ax.xaxis, rot=self.rot, fontsize=self.fontsize)
-            self._apply_axis_properties(ax.yaxis, fontsize=self.fontsize)
+            type(self)._apply_axis_properties(
+                ax.xaxis, rot=self.rot, fontsize=self.fontsize
+            )
+            type(self)._apply_axis_properties(ax.yaxis, fontsize=self.fontsize)
 
             if hasattr(ax, "right_ax"):
-                self._apply_axis_properties(ax.right_ax.yaxis, fontsize=self.fontsize)
+                type(self)._apply_axis_properties(
+                    ax.right_ax.yaxis, fontsize=self.fontsize
+                )
 
         elif self.orientation == "horizontal":
-            self._apply_axis_properties(ax.yaxis, rot=self.rot, fontsize=self.fontsize)
-            self._apply_axis_properties(ax.xaxis, fontsize=self.fontsize)
+            type(self)._apply_axis_properties(
+                ax.yaxis, rot=self.rot, fontsize=self.fontsize
+            )
+            type(self)._apply_axis_properties(ax.xaxis, fontsize=self.fontsize)
 
             if hasattr(ax, "right_ax"):
-                self._apply_axis_properties(ax.right_ax.yaxis, fontsize=self.fontsize)
+                type(self)._apply_axis_properties(
+                    ax.right_ax.yaxis, fontsize=self.fontsize
+                )
         else:  # pragma no cover
             raise ValueError
 
@@ -799,8 +807,9 @@ class MPLPlot(ABC):
                 self.axes[0].set_title(self.title)
 
     @final
+    @staticmethod
     def _apply_axis_properties(
-        self, axis: Axis, rot=None, fontsize: int | None = None
+        axis: Axis, rot=None, fontsize: int | None = None
     ) -> None:
         """
         Tick creation within matplotlib is reasonably expensive and is
@@ -1028,16 +1037,6 @@ class MPLPlot(ABC):
         ax.get_yaxis().set_visible(True)
         return ax
 
-    @final
-    @classmethod
-    def get_default_ax(cls, ax) -> None:
-        import matplotlib.pyplot as plt
-
-        if ax is None and len(plt.get_fignums()) > 0:
-            with plt.rc_context():
-                ax = plt.gca()
-            ax = cls._get_ax_layer(ax)
-
     @final
     def on_right(self, i: int):
         if isinstance(self.secondary_y, bool):
@@ -1192,7 +1191,7 @@ class MPLPlot(ABC):
     @final
     def _get_errorbars(
         self, label=None, index=None, xerr: bool = True, yerr: bool = True
-    ):
+    ) -> dict[str, Any]:
         errors = {}
 
         for kw, flag in zip(["xerr", "yerr"], [xerr, yerr]):
@@ -1590,12 +1589,12 @@ class LinePlot(MPLPlot):
         freq, data = maybe_resample(data, ax, kwds)
 
         # Set ax with freq info
-        decorate_axes(ax, freq, kwds)
+        decorate_axes(ax, freq)
         # digging deeper
         if hasattr(ax, "left_ax"):
-            decorate_axes(ax.left_ax, freq, kwds)
+            decorate_axes(ax.left_ax, freq)
         if hasattr(ax, "right_ax"):
-            decorate_axes(ax.right_ax, freq, kwds)
+            decorate_axes(ax.right_ax, freq)
         # TODO #54485
         ax._plot_data.append((data, self._kind, kwds))  # type: ignore[attr-defined]
 
diff --git a/pandas/plotting/_matplotlib/groupby.py b/pandas/plotting/_matplotlib/groupby.py
index d32741a1d1..cbb66065a8 100644
--- a/pandas/plotting/_matplotlib/groupby.py
+++ b/pandas/plotting/_matplotlib/groupby.py
@@ -7,9 +7,7 @@ import numpy as np
 from pandas.core.dtypes.missing import remove_na_arraylike
 
 from pandas import (
-    DataFrame,
     MultiIndex,
-    Series,
     concat,
 )
 
@@ -20,6 +18,11 @@ if TYPE_CHECKING:
 
     from pandas._typing import IndexLabel
 
+    from pandas import (
+        DataFrame,
+        Series,
+    )
+
 
 def create_iter_data_given_by(
     data: DataFrame, kind: str = "hist"
@@ -48,10 +51,10 @@ def create_iter_data_given_by(
 
     >>> import numpy as np
     >>> tuples = [('h1', 'a'), ('h1', 'b'), ('h2', 'a'), ('h2', 'b')]
-    >>> mi = MultiIndex.from_tuples(tuples)
+    >>> mi = pd.MultiIndex.from_tuples(tuples)
     >>> value = [[1, 3, np.nan, np.nan],
     ...          [3, 4, np.nan, np.nan], [np.nan, np.nan, 5, 6]]
-    >>> data = DataFrame(value, columns=mi)
+    >>> data = pd.DataFrame(value, columns=mi)
     >>> create_iter_data_given_by(data)
     {'h1':     h1
          a    b
@@ -104,7 +107,7 @@ def reconstruct_data_with_by(
     Examples
     --------
     >>> d = {'h': ['h1', 'h1', 'h2'], 'a': [1, 3, 5], 'b': [3, 4, 6]}
-    >>> df = DataFrame(d)
+    >>> df = pd.DataFrame(d)
     >>> reconstruct_data_with_by(df, by='h', cols=['a', 'b'])
        h1      h2
        a     b     a     b
diff --git a/pandas/plotting/_matplotlib/hist.py b/pandas/plotting/_matplotlib/hist.py
index de4fd91541..e610f1adb6 100644
--- a/pandas/plotting/_matplotlib/hist.py
+++ b/pandas/plotting/_matplotlib/hist.py
@@ -161,7 +161,7 @@ class HistPlot(LinePlot):
                 kwds.pop("color")
 
             if self.weights is not None:
-                kwds["weights"] = self._get_column_weights(self.weights, i, y)
+                kwds["weights"] = type(self)._get_column_weights(self.weights, i, y)
 
             y = reformat_hist_y_given_by(y, self.by)
 
@@ -284,7 +284,7 @@ class KdePlot(HistPlot):
 
     def _make_plot_keywords(self, kwds: dict[str, Any], y: np.ndarray) -> None:
         kwds["bw_method"] = self.bw_method
-        kwds["ind"] = self._get_ind(y, ind=self.ind)
+        kwds["ind"] = type(self)._get_ind(y, ind=self.ind)
 
     def _post_plot_logic(self, ax: Axes, data) -> None:
         ax.set_ylabel("Density")
@@ -292,7 +292,7 @@ class KdePlot(HistPlot):
 
 def _grouped_plot(
     plotf,
-    data,
+    data: Series | DataFrame,
     column=None,
     by=None,
     numeric_only: bool = True,
@@ -335,7 +335,7 @@ def _grouped_plot(
 
 
 def _grouped_hist(
-    data,
+    data: Series | DataFrame,
     column=None,
     by=None,
     ax=None,
@@ -417,7 +417,7 @@ def _grouped_hist(
 
 
 def hist_series(
-    self,
+    self: Series,
     by=None,
     ax=None,
     grid: bool = True,
@@ -495,7 +495,7 @@ def hist_series(
 
 
 def hist_frame(
-    data,
+    data: DataFrame,
     column=None,
     by=None,
     grid: bool = True,
diff --git a/pandas/plotting/_matplotlib/timeseries.py b/pandas/plotting/_matplotlib/timeseries.py
index f0b68e5dde..6d3579c7f7 100644
--- a/pandas/plotting/_matplotlib/timeseries.py
+++ b/pandas/plotting/_matplotlib/timeseries.py
@@ -111,8 +111,8 @@ def _is_sup(f1: str, f2: str) -> bool:
 
 def _upsample_others(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]) -> None:
     legend = ax.get_legend()
-    lines, labels = _replot_ax(ax, freq, kwargs)
-    _replot_ax(ax, freq, kwargs)
+    lines, labels = _replot_ax(ax, freq)
+    _replot_ax(ax, freq)
 
     other_ax = None
     if hasattr(ax, "left_ax"):
@@ -121,7 +121,7 @@ def _upsample_others(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]) -> None
         other_ax = ax.right_ax
 
     if other_ax is not None:
-        rlines, rlabels = _replot_ax(other_ax, freq, kwargs)
+        rlines, rlabels = _replot_ax(other_ax, freq)
         lines.extend(rlines)
         labels.extend(rlabels)
 
@@ -132,7 +132,7 @@ def _upsample_others(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]) -> None
         ax.legend(lines, labels, loc="best", title=title)
 
 
-def _replot_ax(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]):
+def _replot_ax(ax: Axes, freq: BaseOffset):
     data = getattr(ax, "_plot_data", None)
 
     # clear current axes and data
@@ -140,7 +140,7 @@ def _replot_ax(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]):
     ax._plot_data = []  # type: ignore[attr-defined]
     ax.clear()
 
-    decorate_axes(ax, freq, kwargs)
+    decorate_axes(ax, freq)
 
     lines = []
     labels = []
@@ -164,7 +164,7 @@ def _replot_ax(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]):
     return lines, labels
 
 
-def decorate_axes(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]) -> None:
+def decorate_axes(ax: Axes, freq: BaseOffset) -> None:
     """Initialize axes for time-series plotting"""
     if not hasattr(ax, "_plot_data"):
         # TODO #54485
@@ -175,15 +175,6 @@ def decorate_axes(ax: Axes, freq: BaseOffset, kwargs: dict[str, Any]) -> None:
     xaxis = ax.get_xaxis()
     # TODO #54485
     xaxis.freq = freq  # type: ignore[attr-defined]
-    if not hasattr(ax, "legendlabels"):
-        # TODO #54485
-        ax.legendlabels = [kwargs.get("label", None)]  # type: ignore[attr-defined]
-    else:
-        ax.legendlabels.append(kwargs.get("label", None))
-    # TODO #54485
-    ax.view_interval = None  # type: ignore[attr-defined]
-    # TODO #54485
-    ax.date_axis_info = None  # type: ignore[attr-defined]
 
 
 def _get_ax_freq(ax: Axes):


Test patch:
diff --git a/pandas/tests/copy_view/test_interp_fillna.py b/pandas/tests/copy_view/test_interp_fillna.py
index b2f3312abf..9b6d383f4e 100644
--- a/pandas/tests/copy_view/test_interp_fillna.py
+++ b/pandas/tests/copy_view/test_interp_fillna.py
@@ -10,6 +10,7 @@ from pandas import (
     Series,
     Timestamp,
     interval_range,
+    option_context,
 )
 import pandas._testing as tm
 from pandas.tests.copy_view.util import get_array
@@ -364,6 +365,17 @@ def test_fillna_chained_assignment(using_copy_on_write):
         with tm.raises_chained_assignment_error():
             df[["a"]].fillna(100, inplace=True)
         tm.assert_frame_equal(df, df_orig)
+    else:
+        with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+            with option_context("mode.chained_assignment", None):
+                df[["a"]].fillna(100, inplace=True)
+
+        with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+            with option_context("mode.chained_assignment", None):
+                df[df.a > 5].fillna(100, inplace=True)
+
+        with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+            df["a"].fillna(100, inplace=True)
 
 
 @pytest.mark.parametrize("func", ["interpolate", "ffill", "bfill"])
diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py
index 3cad2e73d3..b2d94ff5ff 100644
--- a/pandas/tests/frame/indexing/test_indexing.py
+++ b/pandas/tests/frame/indexing/test_indexing.py
@@ -1930,7 +1930,7 @@ def test_add_new_column_infer_string():
         df.loc[df["x"] == 1, "y"] = "1"
     expected = DataFrame(
         {"x": [1], "y": Series(["1"], dtype="string[pyarrow_numpy]")},
-        columns=Index(["x", "y"], dtype="string[pyarrow_numpy]"),
+        columns=Index(["x", "y"], dtype=object),
     )
     tm.assert_frame_equal(df, expected)
 
diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py
index 4e37d8ff3c..bd916f230b 100644
--- a/pandas/tests/frame/indexing/test_setitem.py
+++ b/pandas/tests/frame/indexing/test_setitem.py
@@ -753,6 +753,15 @@ class TestDataFrameSetItem:
         )
         tm.assert_frame_equal(df, expected)
 
+    def test_setitem_string_option_object_index(self):
+        # GH#55638
+        pytest.importorskip("pyarrow")
+        df = DataFrame({"a": [1, 2]})
+        with pd.option_context("future.infer_string", True):
+            df["b"] = Index(["a", "b"], dtype=object)
+        expected = DataFrame({"a": [1, 2], "b": Series(["a", "b"], dtype=object)})
+        tm.assert_frame_equal(df, expected)
+
     def test_setitem_frame_midx_columns(self):
         # GH#49121
         df = DataFrame({("a", "b"): [10]})
diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py
index a32f290d3a..f80ef09b50 100644
--- a/pandas/tests/frame/methods/test_fillna.py
+++ b/pandas/tests/frame/methods/test_fillna.py
@@ -58,7 +58,8 @@ class TestFillNA:
                 df[0].fillna(-1, inplace=True)
             assert np.isnan(arr[:, 0]).all()
         else:
-            df[0].fillna(-1, inplace=True)
+            with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+                df[0].fillna(-1, inplace=True)
             assert (arr[:, 0] == -1).all()
 
         # i.e. we didn't create a new 49-column block
diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py
index 8644e11db9..e56ee31abc 100644
--- a/pandas/tests/frame/test_block_internals.py
+++ b/pandas/tests/frame/test_block_internals.py
@@ -429,7 +429,8 @@ def test_update_inplace_sets_valid_block_values(using_copy_on_write):
         with tm.raises_chained_assignment_error():
             df["a"].fillna(1, inplace=True)
     else:
-        df["a"].fillna(1, inplace=True)
+        with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+            df["a"].fillna(1, inplace=True)
 
     # check we haven't put a Series into any block.values
     assert isinstance(df._mgr.blocks[0].values, Categorical)
diff --git a/pandas/tests/indexing/multiindex/test_chaining_and_caching.py b/pandas/tests/indexing/multiindex/test_chaining_and_caching.py
index 9bf7c601e4..c71b077a5e 100644
--- a/pandas/tests/indexing/multiindex/test_chaining_and_caching.py
+++ b/pandas/tests/indexing/multiindex/test_chaining_and_caching.py
@@ -34,12 +34,13 @@ def test_detect_chained_assignment(using_copy_on_write, warn_copy_on_write):
         with tm.raises_chained_assignment_error():
             zed["eyes"]["right"].fillna(value=555, inplace=True)
     elif warn_copy_on_write:
-        # TODO(CoW-warn) should warn
-        zed["eyes"]["right"].fillna(value=555, inplace=True)
+        with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+            zed["eyes"]["right"].fillna(value=555, inplace=True)
     else:
         msg = "A value is trying to be set on a copy of a slice from a DataFrame"
         with pytest.raises(SettingWithCopyError, match=msg):
-            zed["eyes"]["right"].fillna(value=555, inplace=True)
+            with tm.assert_produces_warning(FutureWarning, match="inplace method"):
+                zed["eyes"]["right"].fillna(value=555, inplace=True)
 
 
 @td.skip_array_manager_invalid_test  # with ArrayManager df.loc[0] is not a view
diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py
index 97119127b1..ce8d962fc3 100644
--- a/pandas/tests/test_algos.py
+++ b/pandas/tests/test_algos.py
@@ -536,6 +536,25 @@ class TestFactorize:
         tm.assert_numpy_array_equal(codes, expected_codes)
         tm.assert_index_equal(uniques, expected_uniques)
 
+    def test_factorize_interval_non_nano(self, unit):
+        # GH#56099
+        left = DatetimeIndex(["2016-01-01", np.nan, "2015-10-11"]).as_unit(unit)
+        right = DatetimeIndex(["2016-01-02", np.nan, "2015-10-15"]).as_unit(unit)
+        idx = IntervalIndex.from_arrays(left, right)
+        codes, cats = idx.factorize()
+        assert cats.dtype == f"interval[datetime64[{unit}], right]"
+
+        ts = Timestamp(0).as_unit(unit)
+        idx2 = IntervalIndex.from_arrays(left - ts, right - ts)
+        codes2, cats2 = idx2.factorize()
+        assert cats2.dtype == f"interval[timedelta64[{unit}], right]"
+
+        idx3 = IntervalIndex.from_arrays(
+            left.tz_localize("US/Pacific"), right.tz_localize("US/Pacific")
+        )
+        codes3, cats3 = idx3.factorize()
+        assert cats3.dtype == f"interval[datetime64[{unit}, US/Pacific], right]"
+
 
 class TestUnique:
     def test_ints(self):