Repository: pandas-dev/pandas

Issue:
Duplicate index with FloatIndex giving 'ValueError: Length mismatch'
Not certain if this is a bug or defined behaviour (but then the error message is not clear in any case).

In 0.13.1:

```
In [28]: df = pd.DataFrame(np.random.randn(9).reshape(3,3), index=[0.1,0.2,0.2],
 columns=['a','b','c'])
In [29]: df
Out[29]:
            a         b         c
0.1  1.711117  1.218853 -1.322363
0.2  0.956266  0.230374 -1.005935
0.2 -0.137729 -0.993931 -0.902793

In [30]: df.ix[0.2,'a']
Out[30]: array([ 0.95626607, -0.13772877])

In [31]: df.ix[0.2]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
...
ValueError: Length mismatch: Expected axis has 0 elements, new values have 2 ele
ments
```

In master, both (`df.loc[0.2]` and `df.loc[0.2, 'a']`) give this error message. Wile for integer index, this works.



Discussion:
hmm...would say that's a bug (also your first example should return a Series).

about the Series, that is indeed how it is with integer indices

hm duplicate fun i'll take this ... @jreback is this a blocker?

oh nvm i c u marked as 0.14.1

sure

this has been an enlightening experience. @jreback you are a hero for squashing all of these duplicate indexing bugs.

couple of things:
1. i've fixed the `ValueError` bug, just needed an annoying copypaste of the `IndexEngine._maybe_get_bool_indexer` with a type change and a subtle slicing `KeyError` because of duplicates. This should be fused-typed at some point to eliminate the copypasting between the different engine types on this method
2. doesn't look like `ix` really ever returned a `Series` for duplicate indices, and that's because it calls `self.obj.get_value(*key)` which is designed for single element access and directly pulls the `ndarray` from the underlying index engine.
   
   BUT

if you remove that line then `_getitem_tuple` does upcasting and the aptly named `TestDataFrame.test_single_element_ix_dont_upcast` breaks :disappointed:

i see two ways to deal with this:
1. break the test (bad)
2. don't upcast (deeper into the rabbit hole)

I'll submit a PR for the `ValueError` and open a separate issue for the `ix` insanity.


Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -1539,7 +1539,7 @@ def get_value(self, index, col, takeable=False):
         value : scalar value
         """
 
-        if takeable is True:
+        if takeable:
             series = self._iget_item_cache(col)
             return series.values[index]
 
diff --git a/pandas/core/index.py b/pandas/core/index.py
--- a/pandas/core/index.py
+++ b/pandas/core/index.py
@@ -1713,52 +1713,43 @@ def slice_locs(self, start=None, end=None):
         """
 
         is_unique = self.is_unique
-        if start is None:
-            start_slice = 0
-        else:
-            try:
-                start_slice = self.get_loc(start)
-
-                if not is_unique:
 
-                    # get_loc will return a boolean array for non_uniques
-                    # if we are not monotonic
-                    if isinstance(start_slice, (ABCSeries, np.ndarray)):
-                        raise KeyError("cannot peform a slice operation "
-                                       "on a non-unique non-monotonic index")
-
-                if isinstance(start_slice, slice):
-                    start_slice = start_slice.start
+        def _get_slice(starting_value, offset, search_side, slice_property,
+                       search_value):
+            if search_value is None:
+                return starting_value
 
-            except KeyError:
-                if self.is_monotonic:
-                    start_slice = self.searchsorted(start, side='left')
-                else:
-                    raise
-
-        if end is None:
-            end_slice = len(self)
-        else:
             try:
-                end_slice = self.get_loc(end)
+                slc = self.get_loc(search_value)
 
                 if not is_unique:
 
                     # get_loc will return a boolean array for non_uniques
-                    if isinstance(end_slice, np.ndarray):
-                        raise KeyError("cannot perform a slice operation "
+                    # if we are not monotonic
+                    if isinstance(slc, np.ndarray):
+                        raise KeyError("cannot peform a slice operation "
                                        "on a non-unique non-monotonic index")
 
-                if isinstance(end_slice, slice):
-                    end_slice = end_slice.stop
+                if isinstance(slc, slice):
+                    slc = getattr(slc, slice_property)
                 else:
-                    end_slice += 1
+                    slc += offset
 
             except KeyError:
                 if self.is_monotonic:
-                    end_slice = self.searchsorted(end, side='right')
+                    if not is_unique:
+                        slc = search_value
+                    else:
+                        slc = self.searchsorted(search_value,
+                                                side=search_side)
                 else:
                     raise
+            return slc
+
+        start_slice = _get_slice(0, offset=0, search_side='left',
+                                 slice_property='start', search_value=start)
+        end_slice = _get_slice(len(self), offset=1, search_side='right',
+                               slice_property='stop', search_value=end)
 
         return start_slice, end_slice
 
@@ -1994,11 +1985,12 @@ def _convert_slice_indexer(self, key, typ=None):
         """ convert a slice indexer, by definition these are labels
             unless we are iloc """
         if typ == 'iloc':
-            return super(Float64Index, self)._convert_slice_indexer(key, typ=typ)
+            return super(Float64Index, self)._convert_slice_indexer(key,
+                                                                    typ=typ)
 
         # allow floats here
-        self._validate_slicer(
-            key, lambda v: v is None or is_integer(v) or is_float(v))
+        validator = lambda v: v is None or is_integer(v) or is_float(v)
+        self._validate_slicer(key, validator)
 
         # translate to locations
         return self.slice_indexer(key.start, key.stop, key.step)


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -1747,6 +1747,41 @@ def test_reversed_reindex_ffill_raises(self):
         self.assertRaises(ValueError, df.reindex, dr[::-1], method='ffill')
         self.assertRaises(ValueError, df.reindex, dr[::-1], method='bfill')
 
+    def test_getitem_ix_float_duplicates(self):
+        df = pd.DataFrame(np.random.randn(3, 3),
+                          index=[0.1, 0.2, 0.2], columns=list('abc'))
+        expect = df.iloc[1:]
+        tm.assert_frame_equal(df.loc[0.2], expect)
+        tm.assert_frame_equal(df.ix[0.2], expect)
+
+        expect = df.iloc[1:, 0]
+        tm.assert_series_equal(df.loc[0.2, 'a'], expect)
+
+        df.index = [1, 0.2, 0.2]
+        expect = df.iloc[1:]
+        tm.assert_frame_equal(df.loc[0.2], expect)
+        tm.assert_frame_equal(df.ix[0.2], expect)
+
+        expect = df.iloc[1:, 0]
+        tm.assert_series_equal(df.loc[0.2, 'a'], expect)
+
+        df = pd.DataFrame(np.random.randn(4, 3),
+                          index=[1, 0.2, 0.2, 1], columns=list('abc'))
+        expect = df.iloc[1:-1]
+        tm.assert_frame_equal(df.loc[0.2], expect)
+        tm.assert_frame_equal(df.ix[0.2], expect)
+
+        expect = df.iloc[1:-1, 0]
+        tm.assert_series_equal(df.loc[0.2, 'a'], expect)
+
+        df.index = [0.1, 0.2, 2, 0.2]
+        expect = df.iloc[[1, -1]]
+        tm.assert_frame_equal(df.loc[0.2], expect)
+        tm.assert_frame_equal(df.ix[0.2], expect)
+
+        expect = df.iloc[[1, -1], 0]
+        tm.assert_series_equal(df.loc[0.2, 'a'], expect)
+
 
 _seriesd = tm.getSeriesData()
 _tsd = tm.getTimeSeriesData()