Skip to content

Commit a6df004

Browse files
committed
Merge pull request #10305 from sinhrks/pidx_order
BUG: PeriodIndex.order doesnt preserve freq
2 parents e9b1a10 + 9642ec9 commit a6df004

File tree

10 files changed

+659
-74
lines changed

10 files changed

+659
-74
lines changed

Diff for: doc/source/whatsnew/v0.17.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -612,5 +612,8 @@ Bug Fixes
612612
- Bug in ``io.common.get_filepath_or_buffer`` which caused reading of valid S3 files to fail if the bucket also contained keys for which the user does not have read permission (:issue:`10604`)
613613
- Bug in vectorised setting of timestamp columns with python ``datetime.date`` and numpy ``datetime64`` (:issue:`10408`, :issue:`10412`)
614614

615+
615616
- Bug in ``pd.DataFrame`` when constructing an empty DataFrame with a string dtype (:issue:`9428`)
616617
- Bug in ``pd.unique`` for arrays with the ``datetime64`` or ``timedelta64`` dtype that meant an array with object dtype was returned instead the original dtype (:issue: `9431`)
618+
- Bug in ``DatetimeIndex.take`` and ``TimedeltaIndex.take`` may not raise ``IndexError`` against invalid index (:issue:`10295`)
619+
- Bug in ``PeriodIndex.order`` reset freq (:issue:`10295`)

Diff for: pandas/core/index.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2486,7 +2486,7 @@ def get_slice_bound(self, label, side, kind):
24862486
if is_bool_dtype(slc):
24872487
slc = lib.maybe_booleans_to_slice(slc.view('u1'))
24882488
else:
2489-
slc = lib.maybe_indices_to_slice(slc.astype('i8'))
2489+
slc = lib.maybe_indices_to_slice(slc.astype('i8'), len(self))
24902490
if isinstance(slc, np.ndarray):
24912491
raise KeyError(
24922492
"Cannot get %s slice bound for non-unique label:"
@@ -5108,7 +5108,7 @@ def _maybe_to_slice(loc):
51085108
if not isinstance(loc, np.ndarray) or loc.dtype != 'int64':
51095109
return loc
51105110

5111-
loc = lib.maybe_indices_to_slice(loc)
5111+
loc = lib.maybe_indices_to_slice(loc, len(self))
51125112
if isinstance(loc, slice):
51135113
return loc
51145114

Diff for: pandas/lib.pyx

+31-6
Original file line numberDiff line numberDiff line change
@@ -633,17 +633,42 @@ def convert_timestamps(ndarray values):
633633

634634
return out
635635

636-
def maybe_indices_to_slice(ndarray[int64_t] indices):
636+
637+
def maybe_indices_to_slice(ndarray[int64_t] indices, int max_len):
637638
cdef:
638639
Py_ssize_t i, n = len(indices)
640+
int k, vstart, vlast, v
641+
642+
if n == 0:
643+
return slice(0, 0)
639644

640-
if not n or indices[0] < 0:
645+
vstart = indices[0]
646+
if vstart < 0 or max_len <= vstart:
641647
return indices
642648

643-
for i in range(1, n):
644-
if indices[i] - indices[i - 1] != 1:
645-
return indices
646-
return slice(indices[0], indices[n - 1] + 1)
649+
if n == 1:
650+
return slice(vstart, vstart + 1)
651+
652+
vlast = indices[n - 1]
653+
if vlast < 0 or max_len <= vlast:
654+
return indices
655+
656+
k = indices[1] - indices[0]
657+
if k == 0:
658+
return indices
659+
else:
660+
for i in range(2, n):
661+
v = indices[i]
662+
if v - indices[i - 1] != k:
663+
return indices
664+
665+
if k > 0:
666+
return slice(vstart, vlast + 1, k)
667+
else:
668+
if vlast == 0:
669+
return slice(vstart, None, k)
670+
else:
671+
return slice(vstart, vlast - 1, k)
647672

648673

649674
def maybe_booleans_to_slice(ndarray[uint8_t] mask):

Diff for: pandas/tests/test_index.py

+10
Original file line numberDiff line numberDiff line change
@@ -2266,6 +2266,16 @@ def test_get_loc_na(self):
22662266

22672267
idx = Float64Index([np.nan, 1, np.nan])
22682268
self.assertEqual(idx.get_loc(1), 1)
2269+
2270+
# representable by slice [0:2:2]
2271+
# self.assertRaises(KeyError, idx.slice_locs, np.nan)
2272+
sliced = idx.slice_locs(np.nan)
2273+
self.assertTrue(isinstance(sliced, tuple))
2274+
self.assertEqual(sliced, (0, 3))
2275+
2276+
# not representable by slice
2277+
idx = Float64Index([np.nan, 1, np.nan, np.nan])
2278+
self.assertEqual(idx.get_loc(1), 1)
22692279
self.assertRaises(KeyError, idx.slice_locs, np.nan)
22702280

22712281
def test_contains_nans(self):

Diff for: pandas/tests/test_lib.py

+183-54
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import numpy as np
55

66
import pandas as pd
7-
from pandas.lib import isscalar, item_from_zerodim, max_len_string_array
7+
import pandas.lib as lib
88
import pandas.util.testing as tm
99
from pandas.compat import u, PY2
1010

@@ -14,19 +14,19 @@ class TestMisc(tm.TestCase):
1414
def test_max_len_string_array(self):
1515

1616
arr = a = np.array(['foo', 'b', np.nan], dtype='object')
17-
self.assertTrue(max_len_string_array(arr), 3)
17+
self.assertTrue(lib.max_len_string_array(arr), 3)
1818

1919
# unicode
2020
arr = a.astype('U').astype(object)
21-
self.assertTrue(max_len_string_array(arr), 3)
21+
self.assertTrue(lib.max_len_string_array(arr), 3)
2222

2323
# bytes for python3
2424
arr = a.astype('S').astype(object)
25-
self.assertTrue(max_len_string_array(arr), 3)
25+
self.assertTrue(lib.max_len_string_array(arr), 3)
2626

2727
# raises
2828
tm.assertRaises(TypeError,
29-
lambda: max_len_string_array(arr.astype('U')))
29+
lambda: lib.max_len_string_array(arr.astype('U')))
3030

3131
def test_infer_dtype_bytes(self):
3232
compare = 'string' if PY2 else 'bytes'
@@ -39,68 +39,197 @@ def test_infer_dtype_bytes(self):
3939
arr = arr.astype(object)
4040
self.assertEqual(pd.lib.infer_dtype(arr), compare)
4141

42-
43-
class TestIsscalar(tm.TestCase):
42+
def test_maybe_indices_to_slice_left_edge(self):
43+
target = np.arange(100)
44+
45+
# slice
46+
indices = np.array([], dtype=np.int64)
47+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
48+
self.assertTrue(isinstance(maybe_slice, slice))
49+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
50+
51+
for end in [1, 2, 5, 20, 99]:
52+
for step in [1, 2, 4]:
53+
indices = np.arange(0, end, step, dtype=np.int64)
54+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
55+
self.assertTrue(isinstance(maybe_slice, slice))
56+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
57+
58+
# reverse
59+
indices = indices[::-1]
60+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
61+
self.assertTrue(isinstance(maybe_slice, slice))
62+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
63+
64+
# not slice
65+
for case in [[2, 1, 2, 0], [2, 2, 1, 0], [0, 1, 2, 1], [-2, 0, 2], [2, 0, -2]]:
66+
indices = np.array(case, dtype=np.int64)
67+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
68+
self.assertFalse(isinstance(maybe_slice, slice))
69+
self.assert_numpy_array_equal(maybe_slice, indices)
70+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
71+
72+
def test_maybe_indices_to_slice_right_edge(self):
73+
target = np.arange(100)
74+
75+
# slice
76+
for start in [0, 2, 5, 20, 97, 98]:
77+
for step in [1, 2, 4]:
78+
indices = np.arange(start, 99, step, dtype=np.int64)
79+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
80+
self.assertTrue(isinstance(maybe_slice, slice))
81+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
82+
83+
# reverse
84+
indices = indices[::-1]
85+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
86+
self.assertTrue(isinstance(maybe_slice, slice))
87+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
88+
89+
# not slice
90+
indices = np.array([97, 98, 99, 100], dtype=np.int64)
91+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
92+
self.assertFalse(isinstance(maybe_slice, slice))
93+
self.assert_numpy_array_equal(maybe_slice, indices)
94+
with self.assertRaises(IndexError):
95+
target[indices]
96+
with self.assertRaises(IndexError):
97+
target[maybe_slice]
98+
99+
indices = np.array([100, 99, 98, 97], dtype=np.int64)
100+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
101+
self.assertFalse(isinstance(maybe_slice, slice))
102+
self.assert_numpy_array_equal(maybe_slice, indices)
103+
with self.assertRaises(IndexError):
104+
target[indices]
105+
with self.assertRaises(IndexError):
106+
target[maybe_slice]
107+
108+
for case in [[99, 97, 99, 96], [99, 99, 98, 97], [98, 98, 97, 96]]:
109+
indices = np.array(case, dtype=np.int64)
110+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
111+
self.assertFalse(isinstance(maybe_slice, slice))
112+
self.assert_numpy_array_equal(maybe_slice, indices)
113+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
114+
115+
def test_maybe_indices_to_slice_both_edges(self):
116+
target = np.arange(10)
117+
118+
# slice
119+
for step in [1, 2, 4, 5, 8, 9]:
120+
indices = np.arange(0, 9, step, dtype=np.int64)
121+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
122+
self.assertTrue(isinstance(maybe_slice, slice))
123+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
124+
125+
# reverse
126+
indices = indices[::-1]
127+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
128+
self.assertTrue(isinstance(maybe_slice, slice))
129+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
130+
131+
# not slice
132+
for case in [[4, 2, 0, -2], [2, 2, 1, 0], [0, 1, 2, 1]]:
133+
indices = np.array(case, dtype=np.int64)
134+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
135+
self.assertFalse(isinstance(maybe_slice, slice))
136+
self.assert_numpy_array_equal(maybe_slice, indices)
137+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
138+
139+
def test_maybe_indices_to_slice_middle(self):
140+
target = np.arange(100)
141+
142+
# slice
143+
for start, end in [(2, 10), (5, 25), (65, 97)]:
144+
for step in [1, 2, 4, 20]:
145+
indices = np.arange(start, end, step, dtype=np.int64)
146+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
147+
self.assertTrue(isinstance(maybe_slice, slice))
148+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
149+
150+
# reverse
151+
indices = indices[::-1]
152+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
153+
self.assertTrue(isinstance(maybe_slice, slice))
154+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
155+
156+
# not slice
157+
for case in [[14, 12, 10, 12], [12, 12, 11, 10], [10, 11, 12, 11]]:
158+
indices = np.array(case, dtype=np.int64)
159+
maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
160+
self.assertFalse(isinstance(maybe_slice, slice))
161+
self.assert_numpy_array_equal(maybe_slice, indices)
162+
self.assert_numpy_array_equal(target[indices], target[maybe_slice])
163+
164+
165+
class Testisscalar(tm.TestCase):
44166

45167
def test_isscalar_builtin_scalars(self):
46-
self.assertTrue(isscalar(None))
47-
self.assertTrue(isscalar(True))
48-
self.assertTrue(isscalar(False))
49-
self.assertTrue(isscalar(0.))
50-
self.assertTrue(isscalar(np.nan))
51-
self.assertTrue(isscalar('foobar'))
52-
self.assertTrue(isscalar(b'foobar'))
53-
self.assertTrue(isscalar(u('efoobar')))
54-
self.assertTrue(isscalar(datetime(2014, 1, 1)))
55-
self.assertTrue(isscalar(date(2014, 1, 1)))
56-
self.assertTrue(isscalar(time(12, 0)))
57-
self.assertTrue(isscalar(timedelta(hours=1)))
58-
self.assertTrue(isscalar(pd.NaT))
168+
self.assertTrue(lib.isscalar(None))
169+
self.assertTrue(lib.isscalar(True))
170+
self.assertTrue(lib.isscalar(False))
171+
self.assertTrue(lib.isscalar(0.))
172+
self.assertTrue(lib.isscalar(np.nan))
173+
self.assertTrue(lib.isscalar('foobar'))
174+
self.assertTrue(lib.isscalar(b'foobar'))
175+
self.assertTrue(lib.isscalar(u('efoobar')))
176+
self.assertTrue(lib.isscalar(datetime(2014, 1, 1)))
177+
self.assertTrue(lib.isscalar(date(2014, 1, 1)))
178+
self.assertTrue(lib.isscalar(time(12, 0)))
179+
self.assertTrue(lib.isscalar(timedelta(hours=1)))
180+
self.assertTrue(lib.isscalar(pd.NaT))
59181

60182
def test_isscalar_builtin_nonscalars(self):
61-
self.assertFalse(isscalar({}))
62-
self.assertFalse(isscalar([]))
63-
self.assertFalse(isscalar([1]))
64-
self.assertFalse(isscalar(()))
65-
self.assertFalse(isscalar((1,)))
66-
self.assertFalse(isscalar(slice(None)))
67-
self.assertFalse(isscalar(Ellipsis))
183+
self.assertFalse(lib.isscalar({}))
184+
self.assertFalse(lib.isscalar([]))
185+
self.assertFalse(lib.isscalar([1]))
186+
self.assertFalse(lib.isscalar(()))
187+
self.assertFalse(lib.isscalar((1,)))
188+
self.assertFalse(lib.isscalar(slice(None)))
189+
self.assertFalse(lib.isscalar(Ellipsis))
68190

69191
def test_isscalar_numpy_array_scalars(self):
70-
self.assertTrue(isscalar(np.int64(1)))
71-
self.assertTrue(isscalar(np.float64(1.)))
72-
self.assertTrue(isscalar(np.int32(1)))
73-
self.assertTrue(isscalar(np.object_('foobar')))
74-
self.assertTrue(isscalar(np.str_('foobar')))
75-
self.assertTrue(isscalar(np.unicode_(u('foobar'))))
76-
self.assertTrue(isscalar(np.bytes_(b'foobar')))
77-
self.assertTrue(isscalar(np.datetime64('2014-01-01')))
78-
self.assertTrue(isscalar(np.timedelta64(1, 'h')))
192+
self.assertTrue(lib.isscalar(np.int64(1)))
193+
self.assertTrue(lib.isscalar(np.float64(1.)))
194+
self.assertTrue(lib.isscalar(np.int32(1)))
195+
self.assertTrue(lib.isscalar(np.object_('foobar')))
196+
self.assertTrue(lib.isscalar(np.str_('foobar')))
197+
self.assertTrue(lib.isscalar(np.unicode_(u('foobar'))))
198+
self.assertTrue(lib.isscalar(np.bytes_(b'foobar')))
199+
self.assertTrue(lib.isscalar(np.datetime64('2014-01-01')))
200+
self.assertTrue(lib.isscalar(np.timedelta64(1, 'h')))
79201

80202
def test_isscalar_numpy_zerodim_arrays(self):
81203
for zerodim in [np.array(1),
82204
np.array('foobar'),
83205
np.array(np.datetime64('2014-01-01')),
84206
np.array(np.timedelta64(1, 'h'))]:
85-
self.assertFalse(isscalar(zerodim))
86-
self.assertTrue(isscalar(item_from_zerodim(zerodim)))
207+
self.assertFalse(lib.isscalar(zerodim))
208+
self.assertTrue(lib.isscalar(lib.item_from_zerodim(zerodim)))
87209

88210
def test_isscalar_numpy_arrays(self):
89-
self.assertFalse(isscalar(np.array([])))
90-
self.assertFalse(isscalar(np.array([[]])))
91-
self.assertFalse(isscalar(np.matrix('1; 2')))
211+
self.assertFalse(lib.isscalar(np.array([])))
212+
self.assertFalse(lib.isscalar(np.array([[]])))
213+
self.assertFalse(lib.isscalar(np.matrix('1; 2')))
92214

93215
def test_isscalar_pandas_scalars(self):
94-
self.assertTrue(isscalar(pd.Timestamp('2014-01-01')))
95-
self.assertTrue(isscalar(pd.Timedelta(hours=1)))
96-
self.assertTrue(isscalar(pd.Period('2014-01-01')))
97-
98-
def test_isscalar_pandas_containers(self):
99-
self.assertFalse(isscalar(pd.Series()))
100-
self.assertFalse(isscalar(pd.Series([1])))
101-
self.assertFalse(isscalar(pd.DataFrame()))
102-
self.assertFalse(isscalar(pd.DataFrame([[1]])))
103-
self.assertFalse(isscalar(pd.Panel()))
104-
self.assertFalse(isscalar(pd.Panel([[[1]]])))
105-
self.assertFalse(isscalar(pd.Index([])))
106-
self.assertFalse(isscalar(pd.Index([1])))
216+
self.assertTrue(lib.isscalar(pd.Timestamp('2014-01-01')))
217+
self.assertTrue(lib.isscalar(pd.Timedelta(hours=1)))
218+
self.assertTrue(lib.isscalar(pd.Period('2014-01-01')))
219+
220+
def test_lisscalar_pandas_containers(self):
221+
self.assertFalse(lib.isscalar(pd.Series()))
222+
self.assertFalse(lib.isscalar(pd.Series([1])))
223+
self.assertFalse(lib.isscalar(pd.DataFrame()))
224+
self.assertFalse(lib.isscalar(pd.DataFrame([[1]])))
225+
self.assertFalse(lib.isscalar(pd.Panel()))
226+
self.assertFalse(lib.isscalar(pd.Panel([[[1]]])))
227+
self.assertFalse(lib.isscalar(pd.Index([])))
228+
self.assertFalse(lib.isscalar(pd.Index([1])))
229+
230+
231+
if __name__ == '__main__':
232+
import nose
233+
234+
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
235+
exit=False)

0 commit comments

Comments
 (0)