Skip to content

ENH: Move SettingWithCopyError to error/__init__.py per GH27656 #47151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/reference/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Exceptions and warnings
errors.ParserError
errors.ParserWarning
errors.PerformanceWarning
errors.SettingWithCopyError
errors.SpecificationError
errors.UnsortedIndexError
errors.UnsupportedFunctionCall
Expand Down
4 changes: 2 additions & 2 deletions doc/source/user_guide/indexing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,7 @@ chained indexing expression, you can set the :ref:`option <options>`
``mode.chained_assignment`` to one of these values:

* ``'warn'``, the default, means a ``SettingWithCopyWarning`` is printed.
* ``'raise'`` means pandas will raise a ``SettingWithCopyException``
* ``'raise'`` means pandas will raise a ``SettingWithCopyError``
you have to deal with.
* ``None`` will suppress the warnings entirely.

Expand Down Expand Up @@ -1953,7 +1953,7 @@ Last, the subsequent example will **not** work at all, and so should be avoided:
>>> dfd.loc[0]['a'] = 1111
Traceback (most recent call last)
...
SettingWithCopyException:
SettingWithCopyError:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead

Expand Down
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Other enhancements
- A :class:`errors.PerformanceWarning` is now thrown when using ``string[pyarrow]`` dtype with methods that don't dispatch to ``pyarrow.compute`` methods (:issue:`42613`)
- Added ``numeric_only`` argument to :meth:`Resampler.sum`, :meth:`Resampler.prod`, :meth:`Resampler.min`, :meth:`Resampler.max`, :meth:`Resampler.first`, and :meth:`Resampler.last` (:issue:`46442`)
- ``times`` argument in :class:`.ExponentialMovingWindow` now accepts ``np.timedelta64`` (:issue:`47003`)
- :class:`DataError` and :class:`SpecificationError` are now exposed in ``pandas.errors`` (:issue:`27656`)
- :class:`DataError`, :class:`SpecificationError`, and :class:`SettingWithCopyError` are now exposed in ``pandas.errors`` (:issue:`27656`)

.. ---------------------------------------------------------------------------
.. _whatsnew_150.notable_bug_fixes:
Expand Down
4 changes: 0 additions & 4 deletions pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@
from pandas import Index


class SettingWithCopyError(ValueError):
pass


class SettingWithCopyWarning(Warning):
pass

Expand Down
3 changes: 2 additions & 1 deletion pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
from pandas.errors import (
AbstractMethodError,
InvalidIndexError,
SettingWithCopyError,
)
from pandas.util._decorators import (
deprecate_kwarg,
Expand Down Expand Up @@ -3949,7 +3950,7 @@ def _check_setitem_copy(self, t="setting", force=False):
)

if value == "raise":
raise com.SettingWithCopyError(t)
raise SettingWithCopyError(t)
elif value == "warn":
warnings.warn(t, com.SettingWithCopyWarning, stacklevel=find_stack_level())

Expand Down
21 changes: 21 additions & 0 deletions pandas/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,24 @@ class SpecificationError(Exception):
>>> df.groupby('A').agg(['min', 'min']) # doctest: +SKIP
... # SpecificationError: nested renamer is not supported
"""


class SettingWithCopyError(ValueError):
"""
Exception is raised when trying to set on a copied slice from a dataframe and
the mode.chained_assignment is set to 'raise.' This can happen unintentionally
when chained indexing.

For more information, see 'Evaluation order matters' on
https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html

For more information, see 'Indexing view versus copy' on
https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html

Examples
--------
>>> pd.options.mode.chained_assignment = 'raise'
>>> df = pd.DataFrame({'A': [1, 1, 1, 2, 2]}, columns=['A'])
>>> df.loc[0:3]['A'] = 'a' # doctest: +SKIP
... # SettingWithCopyError: A value is trying to be set on a copy of a...
"""
14 changes: 8 additions & 6 deletions pandas/tests/frame/indexing/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import pytest

from pandas._libs import iNaT
from pandas.errors import InvalidIndexError
from pandas.errors import (
InvalidIndexError,
SettingWithCopyError,
)
import pandas.util._test_decorators as td

from pandas.core.dtypes.common import is_integer
Expand All @@ -27,7 +30,6 @@
notna,
)
import pandas._testing as tm
import pandas.core.common as com

# We pass through a TypeError raised by numpy
_slice_msg = "slice indices must be integers or None or have an __index__ method"
Expand Down Expand Up @@ -303,7 +305,7 @@ def test_setitem(self, float_frame):
smaller = float_frame[:2]

msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
smaller["col10"] = ["1", "2"]

assert smaller["col10"].dtype == np.object_
Expand Down Expand Up @@ -546,7 +548,7 @@ def test_fancy_getitem_slice_mixed(self, float_frame, float_string_frame):
assert np.shares_memory(sliced["C"]._values, float_frame["C"]._values)

msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
sliced.loc[:, "C"] = 4.0

assert (float_frame["C"] == 4).all()
Expand Down Expand Up @@ -1003,7 +1005,7 @@ def test_iloc_row_slice_view(self, using_array_manager):
assert np.shares_memory(df[2], subset[2])

msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
subset.loc[:, 2] = 0.0

exp_col = original[2].copy()
Expand Down Expand Up @@ -1046,7 +1048,7 @@ def test_iloc_col_slice_view(self, using_array_manager):

# and that we are setting a copy
msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
subset.loc[:, 8] = 0.0

assert (df[8] == 0).all()
Expand Down
9 changes: 5 additions & 4 deletions pandas/tests/frame/indexing/test_xs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import numpy as np
import pytest

from pandas.errors import SettingWithCopyError

from pandas import (
DataFrame,
Index,
Expand All @@ -12,7 +14,6 @@
concat,
)
import pandas._testing as tm
import pandas.core.common as com

from pandas.tseries.offsets import BDay

Expand Down Expand Up @@ -120,7 +121,7 @@ def test_xs_view(self, using_array_manager):
# INFO(ArrayManager) with ArrayManager getting a row as a view is
# not possible
msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
dm.xs(2)[:] = 20
assert not (dm.xs(2) == 20).any()
else:
Expand Down Expand Up @@ -183,7 +184,7 @@ def test_xs_setting_with_copy_error(self, multiindex_dataframe_random_data):
# setting this will give a SettingWithCopyError
# as we are trying to write a view
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
result[:] = 10

def test_xs_setting_with_copy_error_multiple(self, four_level_index_dataframe):
Expand All @@ -194,7 +195,7 @@ def test_xs_setting_with_copy_error_multiple(self, four_level_index_dataframe):
# setting this will give a SettingWithCopyError
# as we are trying to write a view
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
result[:] = 10

@pytest.mark.parametrize("key, level", [("one", "second"), (["one"], ["second"])])
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexing/multiindex/test_chaining_and_caching.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import pytest

from pandas.errors import SettingWithCopyError
import pandas.util._test_decorators as td

from pandas import (
Expand All @@ -9,7 +10,6 @@
Series,
)
import pandas._testing as tm
import pandas.core.common as com


def test_detect_chained_assignment():
Expand All @@ -30,7 +30,7 @@ def test_detect_chained_assignment():
zed = DataFrame(events, index=["a", "b"], columns=multiind)

msg = "A value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
zed["eyes"]["right"].fillna(value=555, inplace=True)


Expand Down
6 changes: 3 additions & 3 deletions pandas/tests/indexing/multiindex/test_setitem.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import pytest

from pandas.errors import SettingWithCopyError
import pandas.util._test_decorators as td

import pandas as pd
Expand All @@ -14,7 +15,6 @@
notna,
)
import pandas._testing as tm
import pandas.core.common as com


def assert_equal(a, b):
Expand Down Expand Up @@ -491,7 +491,7 @@ def test_frame_setitem_copy_raises(multiindex_dataframe_random_data):
# will raise/warn as its chained assignment
df = multiindex_dataframe_random_data.T
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["foo"]["one"] = 2


Expand All @@ -500,7 +500,7 @@ def test_frame_setitem_copy_no_write(multiindex_dataframe_random_data):
expected = frame
df = frame.copy()
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["foo"]["one"] = 2

result = df
Expand Down
25 changes: 13 additions & 12 deletions pandas/tests/indexing/test_chaining_and_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np
import pytest

from pandas.errors import SettingWithCopyError
import pandas.util._test_decorators as td

import pandas as pd
Expand Down Expand Up @@ -182,10 +183,10 @@ def test_detect_chained_assignment_raises(self, using_array_manager):
assert df._is_copy is None

if not using_array_manager:
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["A"][0] = -5

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["A"][1] = np.nan

assert df["A"]._is_copy is None
Expand All @@ -210,7 +211,7 @@ def test_detect_chained_assignment_fails(self):
}
)

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.loc[0]["A"] = -5

@pytest.mark.arm_slow
Expand All @@ -225,7 +226,7 @@ def test_detect_chained_assignment_doc_example(self):
)
assert df._is_copy is None

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
indexer = df.a.str.startswith("o")
df[indexer]["c"] = 42

Expand All @@ -235,11 +236,11 @@ def test_detect_chained_assignment_object_dtype(self, using_array_manager):
expected = DataFrame({"A": [111, "bbb", "ccc"], "B": [1, 2, 3]})
df = DataFrame({"A": ["aaa", "bbb", "ccc"], "B": [1, 2, 3]})

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.loc[0]["A"] = 111

if not using_array_manager:
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["A"][0] = 111

df.loc[0, "A"] = 111
Expand Down Expand Up @@ -360,7 +361,7 @@ def test_detect_chained_assignment_undefined_column(self):
df = DataFrame(np.arange(0, 9), columns=["count"])
df["group"] = "b"

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.iloc[0:5]["group"] = "a"

@pytest.mark.arm_slow
Expand All @@ -376,14 +377,14 @@ def test_detect_chained_assignment_changing_dtype(self, using_array_manager):
}
)

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.loc[2]["D"] = "foo"

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.loc[2]["C"] = "foo"

if not using_array_manager:
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df["C"][2] = "foo"
else:
# INFO(ArrayManager) for ArrayManager it doesn't matter if it's
Expand All @@ -399,7 +400,7 @@ def test_setting_with_copy_bug(self):
)
mask = pd.isna(df.c)

with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df[["c"]][mask] = df[["b"]][mask]

def test_setting_with_copy_bug_no_warning(self):
Expand All @@ -418,7 +419,7 @@ def test_detect_chained_assignment_warnings_errors(self):
df.loc[0]["A"] = 111

with option_context("chained_assignment", "raise"):
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
df.loc[0]["A"] = 111

def test_detect_chained_assignment_warnings_filter_and_dupe_cols(self):
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/series/accessors/test_dt_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pytz

from pandas._libs.tslibs.timezones import maybe_get_tz
from pandas.errors import SettingWithCopyError

from pandas.core.dtypes.common import (
is_integer_dtype,
Expand All @@ -37,7 +38,6 @@
PeriodArray,
TimedeltaArray,
)
import pandas.core.common as com

ok_for_period = PeriodArray._datetimelike_ops
ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"]
Expand Down Expand Up @@ -288,7 +288,7 @@ def test_dt_accessor_not_writeable(self):
# trying to set a copy
msg = "modifications to a property of a datetimelike.+not supported"
with pd.option_context("chained_assignment", "raise"):
with pytest.raises(com.SettingWithCopyError, match=msg):
with pytest.raises(SettingWithCopyError, match=msg):
ser.dt.hour[0] = 5

@pytest.mark.parametrize(
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"NumbaUtilError",
"DataError",
"SpecificationError",
"SettingWithCopyError",
],
)
def test_exception_importable(exc):
Expand Down