From 62179010396e5dbfd2be20d9b012db2a77854900 Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Wed, 25 Oct 2017 21:10:34 +0100 Subject: [PATCH 1/6] ERR: Improve error message on non-sorted input with .truncate --- pandas/core/generic.py | 4 ++++ pandas/tests/frame/test_timeseries.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 9af4b889ac5a0..f2cb96bb33653 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6338,6 +6338,10 @@ def truncate(self, before=None, after=None, axis=None, copy=True): axis = self._get_axis_number(axis) ax = self._get_axis(axis) + if (not ax.is_monotonic_increasing and + not ax.is_monotonic_decreasing): + raise ValueError("truncate requires a sorted index") + # if we have a date index, convert to dates, otherwise # treat like a slice if ax.is_all_dates: diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py index d3f58434a91ae..9703e1c0d5275 100644 --- a/pandas/tests/frame/test_timeseries.py +++ b/pandas/tests/frame/test_timeseries.py @@ -377,6 +377,31 @@ def test_truncate_copy(self): truncated.values[:] = 5. assert not (self.tsframe.values[5:11] == 5).any() + def test_truncate_nonsortedindex(self): + # GH 17935 + + df = pd.DataFrame({'A': ['a', 'b', 'c', 'd', 'e']}, + index=[5, 3, 2, 9, 0]) + with tm.assert_raises_regex(ValueError, + 'truncate requires a sorted index'): + df.truncate(before=3, after=9) + + rng = pd.date_range('2011-01-01', '2012-01-01', freq='W') + ts = pd.Series(np.random.randn(len(rng)), index=rng) + with tm.assert_raises_regex(ValueError, + 'truncate requires a sorted index'): + ts.sort_values(ascending=False).truncate(before='2011-11', + after='2011-12') + + df = pd.DataFrame({3: np.random.randn(5), + 20: np.random.randn(5), + 2: np.random.randn(5), + 0: np.random.randn(5)}, + columns=[3, 20, 2, 0]) + with tm.assert_raises_regex(ValueError, + 'truncate requires a sorted index'): + df.truncate(before=2, after=20, axis=1) + def test_asfreq(self): offset_monthly = self.tsframe.asfreq(offsets.BMonthEnd()) rule_monthly = self.tsframe.asfreq('BM') From a67955369014df2676db20da92482dc40cf8d781 Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Fri, 27 Oct 2017 17:36:55 +0100 Subject: [PATCH 2/6] add tests and comments --- doc/source/whatsnew/v0.22.0.txt | 3 +++ pandas/core/generic.py | 2 ++ pandas/tests/frame/test_timeseries.py | 4 +++- pandas/tests/series/test_timeseries.py | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 8afdd1b2e22b3..0119d0e65b517 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -46,6 +46,9 @@ Other API Changes - :class:`Timestamp` will no longer silently ignore invalid ``freq`` arguments (:issue:`5168`) - :class:`CacheableOffset` and :class:`WeekDay` are no longer available in the ``pandas.tseries.offsets`` module (:issue:`17830`) - `tseries.frequencies.get_freq_group()` and `tseries.frequencies.DAYS` are removed from the public API (:issue:`18034`) +======= +- :func:`Series.truncate` and :func:`DataFrame.truncate` will raise a ``ValueError`` if the index is not sorted (:issue:`17935`) + .. _whatsnew_0220.deprecations: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index f2cb96bb33653..cf1e8494d7d41 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6338,6 +6338,8 @@ def truncate(self, before=None, after=None, axis=None, copy=True): axis = self._get_axis_number(axis) ax = self._get_axis(axis) + # GH 17935 + # Check that index is sorted if (not ax.is_monotonic_increasing and not ax.is_monotonic_decreasing): raise ValueError("truncate requires a sorted index") diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py index 9703e1c0d5275..ed5e84235982b 100644 --- a/pandas/tests/frame/test_timeseries.py +++ b/pandas/tests/frame/test_timeseries.py @@ -387,7 +387,9 @@ def test_truncate_nonsortedindex(self): df.truncate(before=3, after=9) rng = pd.date_range('2011-01-01', '2012-01-01', freq='W') - ts = pd.Series(np.random.randn(len(rng)), index=rng) + ts = pd.DataFrame({'A': np.random.randn(len(rng)), + 'B': np.random.randn(len(rng))}, + index=rng) with tm.assert_raises_regex(ValueError, 'truncate requires a sorted index'): ts.sort_values(ascending=False).truncate(before='2011-11', diff --git a/pandas/tests/series/test_timeseries.py b/pandas/tests/series/test_timeseries.py index 6018260708335..3547c56b55f51 100644 --- a/pandas/tests/series/test_timeseries.py +++ b/pandas/tests/series/test_timeseries.py @@ -236,6 +236,22 @@ def test_truncate(self): before=self.ts.index[-1] + offset, after=self.ts.index[0] - offset) + def test_truncate_nonsortedindex(self): + # GH 17935 + + s = pd.Series(['a', 'b', 'c', 'd', 'e'], + index=[5, 3, 2, 9, 0]) + with tm.assert_raises_regex(ValueError, + 'truncate requires a sorted index'): + s.truncate(before=3, after=9) + + rng = pd.date_range('2011-01-01', '2012-01-01', freq='W') + ts = pd.Series(np.random.randn(len(rng)), index=rng) + with tm.assert_raises_regex(ValueError, + 'truncate requires a sorted index'): + ts.sort_values(ascending=False).truncate(before='2011-11', + after='2011-12') + def test_asfreq(self): ts = Series([0., 1., 2.], index=[datetime(2009, 10, 30), datetime( 2009, 11, 30), datetime(2009, 12, 31)]) From 1d47030660437e31a99f46e8b30316093da64a5f Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Wed, 15 Nov 2017 22:27:32 +0000 Subject: [PATCH 3/6] clean whatsnewo --- doc/source/whatsnew/v0.22.0.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 0119d0e65b517..e43a543b301fc 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -46,7 +46,6 @@ Other API Changes - :class:`Timestamp` will no longer silently ignore invalid ``freq`` arguments (:issue:`5168`) - :class:`CacheableOffset` and :class:`WeekDay` are no longer available in the ``pandas.tseries.offsets`` module (:issue:`17830`) - `tseries.frequencies.get_freq_group()` and `tseries.frequencies.DAYS` are removed from the public API (:issue:`18034`) -======= - :func:`Series.truncate` and :func:`DataFrame.truncate` will raise a ``ValueError`` if the index is not sorted (:issue:`17935`) From 1850cdd5a48a461ee73e85aa298b223c11636dfc Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Thu, 16 Nov 2017 20:25:04 +0000 Subject: [PATCH 4/6] fix whatsnew and failing test_truncate_nonsortedindex --- doc/source/whatsnew/v0.22.0.txt | 2 +- pandas/core/generic.py | 3 +-- pandas/tests/frame/test_timeseries.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index e43a543b301fc..44d8844795d3c 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -46,7 +46,7 @@ Other API Changes - :class:`Timestamp` will no longer silently ignore invalid ``freq`` arguments (:issue:`5168`) - :class:`CacheableOffset` and :class:`WeekDay` are no longer available in the ``pandas.tseries.offsets`` module (:issue:`17830`) - `tseries.frequencies.get_freq_group()` and `tseries.frequencies.DAYS` are removed from the public API (:issue:`18034`) -- :func:`Series.truncate` and :func:`DataFrame.truncate` will raise a ``ValueError`` if the index is not sorted (:issue:`17935`) +- :func:`Series.truncate` and :func:`DataFrame.truncate` will raise a ``ValueError`` if the index is not sorted instead of an unhelpful ``KeyError`` (:issue:`17935`) .. _whatsnew_0220.deprecations: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index cf1e8494d7d41..5f0630feba653 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6340,8 +6340,7 @@ def truncate(self, before=None, after=None, axis=None, copy=True): # GH 17935 # Check that index is sorted - if (not ax.is_monotonic_increasing and - not ax.is_monotonic_decreasing): + if not ax.is_monotonic_increasing and not ax.is_monotonic_decreasing: raise ValueError("truncate requires a sorted index") # if we have a date index, convert to dates, otherwise diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py index ed5e84235982b..f40d08a99ba83 100644 --- a/pandas/tests/frame/test_timeseries.py +++ b/pandas/tests/frame/test_timeseries.py @@ -392,8 +392,8 @@ def test_truncate_nonsortedindex(self): index=rng) with tm.assert_raises_regex(ValueError, 'truncate requires a sorted index'): - ts.sort_values(ascending=False).truncate(before='2011-11', - after='2011-12') + ts.sort_values('A', ascending=False).truncate(before='2011-11', + after='2011-12') df = pd.DataFrame({3: np.random.randn(5), 20: np.random.randn(5), From e0d40b9680e8c478d65bd4e13e01a3cd0cb581c4 Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Thu, 16 Nov 2017 20:30:02 +0000 Subject: [PATCH 5/6] fix failing test_truncate --- pandas/tests/series/test_period.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pandas/tests/series/test_period.py b/pandas/tests/series/test_period.py index b4ff25d2630b8..9d5ef5e51ff20 100644 --- a/pandas/tests/series/test_period.py +++ b/pandas/tests/series/test_period.py @@ -272,10 +272,9 @@ def test_truncate(self): pd.Period('2017-09-03') ]) series2 = pd.Series([1, 2, 3], index=idx2) - result2 = series2.truncate(after='2017-09-02') + result2 = series2.sort_index().truncate(after='2017-09-02') expected_idx2 = pd.PeriodIndex([ - pd.Period('2017-09-03'), pd.Period('2017-09-02') ]) - tm.assert_series_equal(result2, pd.Series([1, 2], index=expected_idx2)) + tm.assert_series_equal(result2, pd.Series([2], index=expected_idx2)) From f1a761663b65b3ac80422b139da9bd780661c695 Mon Sep 17 00:00:00 2001 From: Paul Reidy Date: Thu, 16 Nov 2017 22:03:57 +0000 Subject: [PATCH 6/6] more lint issues --- pandas/tests/frame/test_timeseries.py | 4 ++-- pandas/tests/series/test_timeseries.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py index f40d08a99ba83..d6d5ccc6487c4 100644 --- a/pandas/tests/frame/test_timeseries.py +++ b/pandas/tests/frame/test_timeseries.py @@ -388,8 +388,8 @@ def test_truncate_nonsortedindex(self): rng = pd.date_range('2011-01-01', '2012-01-01', freq='W') ts = pd.DataFrame({'A': np.random.randn(len(rng)), - 'B': np.random.randn(len(rng))}, - index=rng) + 'B': np.random.randn(len(rng))}, + index=rng) with tm.assert_raises_regex(ValueError, 'truncate requires a sorted index'): ts.sort_values('A', ascending=False).truncate(before='2011-11', diff --git a/pandas/tests/series/test_timeseries.py b/pandas/tests/series/test_timeseries.py index 3547c56b55f51..e782293d98ead 100644 --- a/pandas/tests/series/test_timeseries.py +++ b/pandas/tests/series/test_timeseries.py @@ -240,7 +240,7 @@ def test_truncate_nonsortedindex(self): # GH 17935 s = pd.Series(['a', 'b', 'c', 'd', 'e'], - index=[5, 3, 2, 9, 0]) + index=[5, 3, 2, 9, 0]) with tm.assert_raises_regex(ValueError, 'truncate requires a sorted index'): s.truncate(before=3, after=9)