Repository: pandas-dev/pandas

Issue:
BUG: .ix get and set behave differently
I'm really sorry if this is a duplicate issue of #2997, but it seems to me that this is a little bit different. Here the `.ix` when getting a value interprets the integer as label, and when setting a value it interprets it as a positional index.

``` python
>>> import numpy as np
>>> import pandas as pd
>>> df = pd.DataFrame(np.arange(16).reshape((4, 4)),
                      columns=['a', 'b', 8, 'c'],
                      index=['e', 7, 'f', 'g'])
>>> df
    a   b   8   c
e   0   1   2   3
7   4   5   6   7
f   8   9  10  11
g  12  13  14  15
>>> df.ix['e', 8]
2
>>> df.ix['e', 8] = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pandas/core/indexing.py", line 90, in __setitem__
    self._setitem_with_indexer(indexer, value)
  File "pandas/core/indexing.py", line 190, in _setitem_with_indexer
    values[indexer] = value
IndexError: index 8 is out of bounds for axis 1 with size 4
>>> df.loc['e', 8] = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pandas/core/indexing.py", line 90, in __setitem__
    self._setitem_with_indexer(indexer, value)
  File "pandas/core/indexing.py", line 190, in _setitem_with_indexer
    values[indexer] = value
IndexError: index 8 is out of bounds for axis 1 with size 4
```

I see this issue with the 0.12 version and also with the latest git checkout.

_Update: @jreback noticed that the `.loc` is not working either._



Discussion:
breaks `.loc` too, which is very odd (as it doesn't have all of the crazy logic as `.ix`)....marking as a bug

@alefnula not directly related to this, but could you have a look at #4515 and comment if you'd like?


Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -783,9 +783,25 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False):
         - No, prefer label-based indexing
         """
         labels = self.obj._get_axis(axis)
+
+        # if we are a scalar indexer and not type correct raise
+        obj = self._convert_scalar_indexer(obj, axis)
+
+        # see if we are positional in nature
         is_int_index = labels.is_integer()
+        is_int_positional = com.is_integer(obj) and not is_int_index
 
-        if com.is_integer(obj) and not is_int_index:
+        # if we are a label return me
+        try:
+            return labels.get_loc(obj)
+        except (KeyError, TypeError):
+            pass
+        except (ValueError):
+            if not is_int_positional:
+                raise
+
+        # a positional
+        if is_int_positional:
 
             # if we are setting and its not a valid location
             # its an insert which fails by definition
@@ -795,11 +811,6 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False):
 
             return obj
 
-        try:
-            return labels.get_loc(obj)
-        except (KeyError, TypeError):
-            pass
-
         if isinstance(obj, slice):
             return self._convert_slice_indexer(obj, axis)
 


Test patch:
diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py
--- a/pandas/tests/test_indexing.py
+++ b/pandas/tests/test_indexing.py
@@ -1070,6 +1070,26 @@ def test_ix_assign_column_mixed(self):
         df['b'].ix[[1,3]] = [100,-100]
         assert_frame_equal(df,expected)
 
+    def test_ix_get_set_consistency(self):
+
+        # GH 4544
+        # ix/loc get/set not consistent when
+        # a mixed int/string index
+        df = DataFrame(np.arange(16).reshape((4, 4)),
+                       columns=['a', 'b', 8, 'c'],
+                       index=['e', 7, 'f', 'g'])
+
+        self.assert_(df.ix['e', 8] == 2)
+        self.assert_(df.loc['e', 8] == 2)
+
+        df.ix['e', 8] = 42
+        self.assert_(df.ix['e', 8] == 42)
+        self.assert_(df.loc['e', 8] == 42)
+
+        df.loc['e', 8] = 45
+        self.assert_(df.ix['e', 8] == 45)
+        self.assert_(df.loc['e', 8] == 45)
+
     def test_iloc_mask(self):
 
         # GH 3631, iloc with a mask (of a series) should raise
diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py
--- a/pandas/tests/test_series.py
+++ b/pandas/tests/test_series.py
@@ -878,7 +878,7 @@ def test_setitem_float_labels(self):
         tmp = s.copy()
 
         s.ix[1] = 'zoo'
-        tmp.values[1] = 'zoo'
+        tmp.iloc[2] = 'zoo'
 
         assert_series_equal(s, tmp)