Skip to content

Commit ae464f8

Browse files
committed
Merge pull request #7430 from jreback/mi_slicers
BUG: Bug in multi-index slicing with datetimelike ranges (strings and Timestamps) (GH7429)
2 parents 4233c0f + e96eb7d commit ae464f8

File tree

4 files changed

+48
-20
lines changed

4 files changed

+48
-20
lines changed

doc/source/v0.14.1.txt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,6 @@ Enhancements
137137

138138
Performance
139139
~~~~~~~~~~~
140-
141-
142-
143-
144-
145-
146-
147-
148-
149140
- Improvements in dtype inference for numeric operations involving yielding performance gains for dtypes: ``int64``, ``timedelta64``, ``datetime64`` (:issue:`7223`)
150141
- Improvements in Series.transform for signifcant performance gains (:issue`6496`)
151142

@@ -176,13 +167,13 @@ Bug Fixes
176167

177168

178169

179-
- BUG in ``DatetimeIndex.insert`` doesn't preserve ``name`` and ``tz`` (:issue:`7299`)
180-
- BUG in ``DatetimeIndex.asobject`` doesn't preserve ``name`` (:issue:`7299`)
181-
170+
- Bug in ``DatetimeIndex.insert`` doesn't preserve ``name`` and ``tz`` (:issue:`7299`)
171+
- Bug in ``DatetimeIndex.asobject`` doesn't preserve ``name`` (:issue:`7299`)
182172

183173

184174

185175

176+
- Bug in multi-index slicing with datetimelike ranges (strings and Timestamps), (:issue:`7429`)
186177

187178

188179

pandas/core/index.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import pandas.index as _index
1313
from pandas.lib import Timestamp, is_datetime_array
1414
from pandas.core.base import FrozenList, FrozenNDArray, IndexOpsMixin
15-
1615
from pandas.util.decorators import cache_readonly, deprecate
1716
from pandas.core.common import isnull, array_equivalent
1817
import pandas.core.common as com
@@ -3532,7 +3531,16 @@ def _get_level_indexer(self, key, level=0):
35323531
stop = level_index.get_loc(key.stop or len(level_index)-1)
35333532
step = key.step
35343533

3535-
if level > 0 or self.lexsort_depth == 0 or step is not None:
3534+
if isinstance(start,slice) or isinstance(stop,slice):
3535+
# we have a slice for start and/or stop
3536+
# a partial date slicer on a DatetimeIndex generates a slice
3537+
# note that the stop ALREADY includes the stopped point (if
3538+
# it was a string sliced)
3539+
m = np.zeros(len(labels),dtype=bool)
3540+
m[np.in1d(labels,np.arange(start.start,stop.stop,step))] = True
3541+
return m
3542+
3543+
elif level > 0 or self.lexsort_depth == 0 or step is not None:
35363544
# need to have like semantics here to right
35373545
# searching as when we are using a slice
35383546
# so include the stop+1 (so we include stop)
@@ -3571,6 +3579,8 @@ def get_locs(self, tup):
35713579
for passing to iloc
35723580
"""
35733581

3582+
from pandas.core.indexing import _is_null_slice
3583+
35743584
# must be lexsorted to at least as many levels
35753585
if not self.is_lexsorted_for_tuple(tup):
35763586
raise KeyError('MultiIndex Slicing requires the index to be fully lexsorted'
@@ -3598,10 +3608,12 @@ def _convert_indexer(r):
35983608
ranges.append(reduce(
35993609
np.logical_or,[ _convert_indexer(self._get_level_indexer(x, level=i)
36003610
) for x in k ]))
3601-
elif k == slice(None):
3602-
# include all from this level
3611+
elif _is_null_slice(k):
3612+
# empty slice
36033613
pass
3614+
36043615
elif isinstance(k,slice):
3616+
36053617
# a slice, include BOTH of the labels
36063618
ranges.append(self._get_level_indexer(k,level=i))
36073619
else:

pandas/core/indexing.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,16 +1635,12 @@ def _maybe_convert_ix(*args):
16351635

16361636
def _is_nested_tuple(tup, labels):
16371637
# check for a compatiable nested tuple and multiindexes among the axes
1638-
16391638
if not isinstance(tup, tuple):
16401639
return False
16411640

16421641
# are we nested tuple of: tuple,list,slice
16431642
for i, k in enumerate(tup):
16441643

1645-
#if i > len(axes):
1646-
# raise IndexingError("invalid indxing tuple passed, has too many indexers for this object")
1647-
#ax = axes[i]
16481644
if isinstance(k, (tuple, list, slice)):
16491645
return isinstance(labels, MultiIndex)
16501646

pandas/tests/test_indexing.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,35 @@ def test_multiindex_slicers_non_unique(self):
15651565
self.assertFalse(result.index.is_unique)
15661566
assert_frame_equal(result, expected)
15671567

1568+
def test_multiindex_slicers_datetimelike(self):
1569+
1570+
# GH 7429
1571+
# buggy/inconsistent behavior when slicing with datetime-like
1572+
import datetime
1573+
dates = [datetime.datetime(2012,1,1,12,12,12) + datetime.timedelta(days=i) for i in range(6)]
1574+
freq = [1,2]
1575+
index = MultiIndex.from_product([dates,freq], names=['date','frequency'])
1576+
1577+
df = DataFrame(np.arange(6*2*4,dtype='int64').reshape(-1,4),index=index,columns=list('ABCD'))
1578+
1579+
# multi-axis slicing
1580+
idx = pd.IndexSlice
1581+
expected = df.iloc[[0,2,4],[0,1]]
1582+
result = df.loc[(slice(Timestamp('2012-01-01 12:12:12'),Timestamp('2012-01-03 12:12:12')),slice(1,1)), slice('A','B')]
1583+
assert_frame_equal(result,expected)
1584+
1585+
result = df.loc[(idx[Timestamp('2012-01-01 12:12:12'):Timestamp('2012-01-03 12:12:12')],idx[1:1]), slice('A','B')]
1586+
assert_frame_equal(result,expected)
1587+
1588+
result = df.loc[(slice(Timestamp('2012-01-01 12:12:12'),Timestamp('2012-01-03 12:12:12')),1), slice('A','B')]
1589+
assert_frame_equal(result,expected)
1590+
1591+
# with strings
1592+
result = df.loc[(slice('2012-01-01 12:12:12','2012-01-03 12:12:12'),slice(1,1)), slice('A','B')]
1593+
assert_frame_equal(result,expected)
1594+
1595+
result = df.loc[(idx['2012-01-01 12:12:12':'2012-01-03 12:12:12'],1), idx['A','B']]
1596+
assert_frame_equal(result,expected)
15681597

15691598
def test_per_axis_per_level_doc_examples(self):
15701599

0 commit comments

Comments
 (0)