From bf456034ef8b0f5859e2892d27e0c9ca052d69cd Mon Sep 17 00:00:00 2001 From: rekcahpassyla <0xdeadcafebeef@gmail.com> Date: Fri, 22 May 2015 10:57:07 +0100 Subject: [PATCH 1/5] BUG: Check for size != 0 before trying to insert #10193 --- pandas/tests/test_series.py | 10 ++++++++++ pandas/tseries/index.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 925cfa875196c..a2a5df8ab8be0 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -1439,6 +1439,16 @@ def test_setitem(self): expected = self.series.append(Series([1],index=['foobar'])) assert_series_equal(s,expected) + # Test for issue #10193 + series = pd.TimeSeries() + series[pd.datetime(2012, 1, 1)] = 47 + expected = pd.TimeSeries(47, [pd.datetime(2012, 1, 1)]) + assert_series_equal(series, expected) + + series = pd.TimeSeries(0, pd.date_range('2011-01-01', '2011-01-01'))[:0] + series[pd.datetime(2012, 1, 1)] = 47 + assert_series_equal(series, expected) + def test_setitem_dtypes(self): # change dtypes diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index bd0869b9525b7..f360503abb1a8 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -1521,7 +1521,7 @@ def insert(self, loc, item): if zone != izone: raise ValueError('Passed item and index have different timezone') # check freq can be preserved on edge cases - if self.freq is not None: + if self.size and self.freq is not None: if (loc == 0 or loc == -len(self)) and item + self.freq == self[0]: freq = self.freq elif (loc == len(self)) and item - self.freq == self[-1]: From 0bef2a0286e2d4839c574974b8da88c3676ff2fd Mon Sep 17 00:00:00 2001 From: rekcahpassyla <0xdeadcafebeef@gmail.com> Date: Tue, 26 May 2015 13:29:03 +0100 Subject: [PATCH 2/5] Set is_copy and call _check_setitem_copy to trigger warning --- pandas/core/series.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 8ef9adb1d24a4..23b75bd1a95f3 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -562,7 +562,10 @@ def _get_with(self, key): # other: fancy integer or otherwise if isinstance(key, slice): indexer = self.index._convert_slice_indexer(key, kind='getitem') - return self._get_values(indexer) + values = self._get_values(indexer) + is_copy = values._is_view + values._set_is_copy(self, copy=is_copy) + return values elif isinstance(key, ABCDataFrame): raise TypeError('Indexing a Series with DataFrame is not supported, '\ 'use the appropriate DataFrame column') @@ -684,6 +687,7 @@ def setitem(key, value): # do the setitem cacher_needs_updating = self._check_is_chained_assignment_possible() setitem(key, value) + self._check_setitem_copy() if cacher_needs_updating: self._maybe_update_cacher() From 0ab36b1c7b4127ed44e5af7fbeb6ea7464d90542 Mon Sep 17 00:00:00 2001 From: rekcahpassyla <0xdeadcafebeef@gmail.com> Date: Tue, 26 May 2015 13:29:28 +0100 Subject: [PATCH 3/5] Make warning/error message generic --- pandas/core/generic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b747f0a2ceacb..1c4d0b9ba5ae1 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1286,18 +1286,18 @@ def _check_setitem_copy(self, stacklevel=4, t='setting', force=False): elif t == 'referant': t = ("\n" "A value is trying to be set on a copy of a slice from a " - "DataFrame\n\n" + "{}\n\n" "See the the caveats in the documentation: " "http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy") else: t = ("\n" "A value is trying to be set on a copy of a slice from a " - "DataFrame.\n" + "{}.\n" "Try using .loc[row_indexer,col_indexer] = value instead\n\n" "See the the caveats in the documentation: " "http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy") - + t = t.format(type(self).__name__) if value == 'raise': raise SettingWithCopyError(t) elif value == 'warn': From 65840de2d119eafeac657715178e1caa25407d0f Mon Sep 17 00:00:00 2001 From: rekcahpassyla <0xdeadcafebeef@gmail.com> Date: Tue, 26 May 2015 13:29:49 +0100 Subject: [PATCH 4/5] Add test for setting copy, also change existing tests to avoid SettingWithCopyError where not desired --- pandas/tests/test_series.py | 69 +++++++++++++++++++---------- pandas/tseries/tests/test_period.py | 5 ++- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index a2a5df8ab8be0..2fbc072fe8f68 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -569,7 +569,6 @@ def setUp(self): self.ts = _ts.copy() self.ts.name = 'ts' - self.series = tm.makeStringSeries() self.series.name = 'series' @@ -1360,11 +1359,12 @@ def test_slice(self): self.assertTrue(tm.equalContents(numSliceEnd, np.array(self.series)[-10:])) - - # test return view - sl = self.series[10:20] - sl[:] = 0 - self.assertTrue((self.series[10:20] == 0).all()) + # After fix for #10193, this is needed + with pd.option_context('chained_assignment', 'warn'): + # test return view + sl = self.series[10:20] + sl[:] = 0 + self.assertTrue((self.series[10:20] == 0).all()) def test_slice_can_reorder_not_uniquely_indexed(self): s = Series(1, index=['a', 'a', 'b', 'b', 'c']) @@ -1440,14 +1440,34 @@ def test_setitem(self): assert_series_equal(s,expected) # Test for issue #10193 - series = pd.TimeSeries() - series[pd.datetime(2012, 1, 1)] = 47 - expected = pd.TimeSeries(47, [pd.datetime(2012, 1, 1)]) + key = datetime(2012, 1, 1) + series = pd.Series() + series[key] = 47 + expected = pd.Series(47, [key]) assert_series_equal(series, expected) - series = pd.TimeSeries(0, pd.date_range('2011-01-01', '2011-01-01'))[:0] - series[pd.datetime(2012, 1, 1)] = 47 - assert_series_equal(series, expected) + test_values = [ + (pd.date_range('2011-01-01', '2011-01-01'), datetime(2012, 1, 1)), + (pd.Int64Index([1, 2, 3]), 5) + ] + for idx, key in test_values: + + series = pd.Series(0, idx) + series2 = series[:0] + + def f(): + series2[key] = 47 + + expected = pd.Series(47, [key]) + + with pd.option_context('chained_assignment', 'raise'): + self.assertRaises(com.SettingWithCopyError, f) + + with pd.option_context('chained_assignment', 'warn'): + f() + assert_series_equal(series2, expected) + # also check that original series remains unchanged. + assert_series_equal(series, pd.Series(0, idx)) def test_setitem_dtypes(self): @@ -4281,15 +4301,14 @@ def test_underlying_data_conversion(self): # GH 3970 # these are chained assignments as well - pd.set_option('chained_assignment',None) - df = DataFrame({ "aa":range(5), "bb":[2.2]*5}) - df["cc"] = 0.0 - ck = [True]*len(df) - df["bb"].iloc[0] = .13 - df_tmp = df.iloc[ck] - df["bb"].iloc[0] = .15 - self.assertEqual(df['bb'].iloc[0], 0.15) - pd.set_option('chained_assignment','raise') + with pd.option_context('chained_assignment',None): + df = DataFrame({ "aa":range(5), "bb":[2.2]*5}) + df["cc"] = 0.0 + ck = [True]*len(df) + df["bb"].iloc[0] = .13 + df_tmp = df.iloc[ck] + df["bb"].iloc[0] = .15 + self.assertEqual(df['bb'].iloc[0], 0.15) # GH 3217 df = DataFrame(dict(a = [1,3], b = [np.nan, 2])) @@ -4826,9 +4845,11 @@ def test_rank(self): tm._skip_if_no_scipy() from scipy.stats import rankdata - self.ts[::2] = np.nan - self.ts[:10][::3] = 4. - + # After fix for #10193, this is needed + with pd.option_context('chained_assignment',None): + self.ts[::2] = np.nan + self.ts[:10][::3] = 4. + ranks = self.ts.rank() oranks = self.ts.astype('O').rank() diff --git a/pandas/tseries/tests/test_period.py b/pandas/tseries/tests/test_period.py index 0218af63ca7d6..e05ce1b9baaf4 100644 --- a/pandas/tseries/tests/test_period.py +++ b/pandas/tseries/tests/test_period.py @@ -1669,7 +1669,10 @@ def test_index_duplicate_periods(self): result = ts[2007] expected = ts[1:3] assert_series_equal(result, expected) - result[:] = 1 + # After fix for #10193, this is necessary + with pd.option_context('chained_assignment',None): + result[:] = 1 + self.assertTrue((ts[1:3] == 1).all()) # not monotonic From ac3b65675742fb1685b1da97387d201b0590f2f6 Mon Sep 17 00:00:00 2001 From: rekcahpassyla <0xdeadcafebeef@gmail.com> Date: Tue, 26 May 2015 15:42:51 +0100 Subject: [PATCH 5/5] 2.6 compatibility --- pandas/core/generic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 1c4d0b9ba5ae1..0deef1b7cd3b7 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1286,23 +1286,25 @@ def _check_setitem_copy(self, stacklevel=4, t='setting', force=False): elif t == 'referant': t = ("\n" "A value is trying to be set on a copy of a slice from a " - "{}\n\n" + "{name}\n\n" "See the the caveats in the documentation: " "http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy") else: t = ("\n" "A value is trying to be set on a copy of a slice from a " - "{}.\n" + "{name}.\n" "Try using .loc[row_indexer,col_indexer] = value instead\n\n" "See the the caveats in the documentation: " "http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy") - t = t.format(type(self).__name__) + + t = t.format(name=type(self).__name__) + if value == 'raise': raise SettingWithCopyError(t) elif value == 'warn': warnings.warn(t, SettingWithCopyWarning, stacklevel=stacklevel) - + def __delitem__(self, key): """ Delete item