diff --git a/doc/source/release.rst b/doc/source/release.rst index 155def21fa3a9..dee5c464a1f9c 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -52,6 +52,7 @@ Highlights include: - ``pd.test()`` top-level nose test runner is available (:issue:`4327`) - Adding support for a ``RangeIndex`` as a specialized form of the ``Int64Index`` for memory savings, see :ref:`here `. - API breaking ``.resample`` changes to make it more ``.groupby`` like, see :ref:`here `. +- Removal of support for deprecated float indexers; these will now raise a ``TypeError``, see :ref:`here `. See the :ref:`v0.18.0 Whatsnew ` overview for an extensive list of all enhancements and bugs that have been fixed in 0.17.1. diff --git a/doc/source/whatsnew/v0.18.0.txt b/doc/source/whatsnew/v0.18.0.txt index 2dcb3fbbd5a0d..ec8c5e2c0ba3e 100644 --- a/doc/source/whatsnew/v0.18.0.txt +++ b/doc/source/whatsnew/v0.18.0.txt @@ -21,6 +21,7 @@ Highlights include: - ``pd.test()`` top-level nose test runner is available (:issue:`4327`) - Adding support for a ``RangeIndex`` as a specialized form of the ``Int64Index`` for memory savings, see :ref:`here `. - API breaking ``.resample`` changes to make it more ``.groupby`` like, see :ref:`here `. +- Removal of support for deprecated float indexers; these will now raise a ``TypeError``, see :ref:`here `. Check the :ref:`API Changes ` and :ref:`deprecations ` before updating. @@ -865,9 +866,45 @@ Deprecations is better handled by matplotlib's `style sheets`_ (:issue:`11783`). +.. _style sheets: http://matplotlib.org/users/style_sheets.html +.. _whatsnew_0180.float_indexers: -.. _style sheets: http://matplotlib.org/users/style_sheets.html +Removal of deprecated float indexers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In :issue:`4892` indexing with floating point numbers on a non-``Float64Index`` was deprecated (in version 0.14.0). +In 0.18.0, this deprecation warning is removed and these will now raise a ``TypeError``. (:issue:`12165`) + +Previous Behavior: + +.. code-block: + + In [1]: s = Series([1,2,3]) + In [2]: s[1.0] + FutureWarning: scalar indexers for index type Int64Index should be integers and not floating point + Out[2]: 2 + + In [3]: s.iloc[1.0] + FutureWarning: scalar indexers for index type Int64Index should be integers and not floating point + Out[3]: 2 + + In [4]: s.loc[1.0] + FutureWarning: scalar indexers for index type Int64Index should be integers and not floating point + Out[4]: 2 + +New Behavior: + +.. code-block: + + In [4]: s[1.0] + TypeError: cannot do label indexing on with these indexers [1.0] of + + In [4]: s.iloc[1.0] + TypeError: cannot do label indexing on with these indexers [1.0] of + + In [4]: s.loc[1.0] + TypeError: cannot do label indexing on with these indexers [1.0] of .. _whatsnew_0180.prior_deprecations: diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index b6f221322df52..22185c5676593 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -115,7 +115,11 @@ def _get_setitem_indexer(self, key): try: return self._convert_to_indexer(key, is_setter=True) - except TypeError: + except TypeError as e: + + # invalid indexer type vs 'other' indexing errors + if 'cannot do' in str(e): + raise raise IndexingError(key) def __setitem__(self, key, value): @@ -312,6 +316,18 @@ def _setitem_with_indexer(self, indexer, value): index = self.obj.index new_index = index.insert(len(index), indexer) + # we have a coerced indexer, e.g. a float + # that matches in an Int64Index, so + # we will not create a duplicate index, rather + # index to that element + # e.g. 0.0 -> 0 + # GH12246 + if index.is_unique: + new_indexer = index.get_indexer([new_index[-1]]) + if (new_indexer != -1).any(): + return self._setitem_with_indexer(new_indexer, + value) + # this preserves dtype of the value new_values = Series([value])._values if len(self.obj._values): @@ -1091,8 +1107,17 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False): """ labels = self.obj._get_axis(axis) - # if we are a scalar indexer and not type correct raise - obj = self._convert_scalar_indexer(obj, axis) + if isinstance(obj, slice): + return self._convert_slice_indexer(obj, axis) + + # try to find out correct indexer, if not type correct raise + try: + obj = self._convert_scalar_indexer(obj, axis) + except TypeError: + + # but we will allow setting + if is_setter: + pass # see if we are positional in nature is_int_index = labels.is_integer() @@ -1131,10 +1156,7 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False): return obj - if isinstance(obj, slice): - return self._convert_slice_indexer(obj, axis) - - elif is_nested_tuple(obj, labels): + if is_nested_tuple(obj, labels): return labels.get_locs(obj) elif is_list_like_indexer(obj): if is_bool_indexer(obj): @@ -1278,7 +1300,7 @@ def _get_slice_axis(self, slice_obj, axis=0): labels = obj._get_axis(axis) indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, - slice_obj.step) + slice_obj.step, kind=self.name) if isinstance(indexer, slice): return self._slice(indexer, axis=axis, kind='iloc') diff --git a/pandas/indexes/base.py b/pandas/indexes/base.py index 9064b77ef7e3d..172f81e5a6423 100644 --- a/pandas/indexes/base.py +++ b/pandas/indexes/base.py @@ -21,12 +21,12 @@ from pandas.core.missing import _clean_reindex_fill_method from pandas.core.common import (isnull, array_equivalent, is_object_dtype, is_datetimetz, ABCSeries, - ABCPeriodIndex, + ABCPeriodIndex, ABCMultiIndex, _values_from_object, is_float, is_integer, is_iterator, is_categorical_dtype, _ensure_object, _ensure_int64, is_bool_indexer, is_list_like, is_bool_dtype, - is_integer_dtype) + is_integer_dtype, is_float_dtype) from pandas.core.strings import StringAccessorMixin from pandas.core.config import get_option @@ -162,7 +162,46 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, if dtype is not None: try: - data = np.array(data, dtype=dtype, copy=copy) + + # we need to avoid having numpy coerce + # things that look like ints/floats to ints unless + # they are actually ints, e.g. '0' and 0.0 + # should not be coerced + # GH 11836 + if is_integer_dtype(dtype): + inferred = lib.infer_dtype(data) + if inferred == 'integer': + data = np.array(data, copy=copy, dtype=dtype) + elif inferred in ['floating', 'mixed-integer-float']: + + # if we are actually all equal to integers + # then coerce to integer + from .numeric import Int64Index, Float64Index + try: + res = data.astype('i8') + if (res == data).all(): + return Int64Index(res, copy=copy, + name=name) + except (TypeError, ValueError): + pass + + # return an actual float index + return Float64Index(data, copy=copy, dtype=dtype, + name=name) + + elif inferred == 'string': + pass + else: + data = data.astype(dtype) + elif is_float_dtype(dtype): + inferred = lib.infer_dtype(data) + if inferred == 'string': + pass + else: + data = data.astype(dtype) + else: + data = np.array(data, dtype=dtype, copy=copy) + except (TypeError, ValueError): pass @@ -930,35 +969,32 @@ def _convert_scalar_indexer(self, key, kind=None): kind : optional, type of the indexing operation (loc/ix/iloc/None) right now we are converting - floats -> ints if the index supports it """ - def to_int(): - ikey = int(key) - if ikey != key: - return self._invalid_indexer('label', key) - return ikey - if kind == 'iloc': if is_integer(key): return key - elif is_float(key): - key = to_int() - warnings.warn("scalar indexers for index type {0} should be " - "integers and not floating point".format( - type(self).__name__), - FutureWarning, stacklevel=5) - return key return self._invalid_indexer('label', key) + else: - if is_float(key): - if isnull(key): - return self._invalid_indexer('label', key) - warnings.warn("scalar indexers for index type {0} should be " - "integers and not floating point".format( - type(self).__name__), - FutureWarning, stacklevel=3) - return to_int() + if len(self): + + # we can safely disallow + # if we are not a MultiIndex + # or a Float64Index + # or have mixed inferred type (IOW we have the possiblity + # of a float in with say strings) + if is_float(key): + if not (isinstance(self, ABCMultiIndex,) or + self.is_floating() or self.is_mixed()): + return self._invalid_indexer('label', key) + + # we can disallow integers with loc + # if could not contain and integer + elif is_integer(key) and kind == 'loc': + if not (isinstance(self, ABCMultiIndex,) or + self.holds_integer() or self.is_mixed()): + return self._invalid_indexer('label', key) return key @@ -991,14 +1027,6 @@ def f(c): v = getattr(key, c) if v is None or is_integer(v): return v - - # warn if it's a convertible float - if v == int(v): - warnings.warn("slice indexers when using iloc should be " - "integers and not floating point", - FutureWarning, stacklevel=7) - return int(v) - self._invalid_indexer('slice {0} value'.format(c), v) return slice(*[f(c) for c in ['start', 'stop', 'step']]) @@ -1057,7 +1085,7 @@ def is_int(v): indexer = key else: try: - indexer = self.slice_indexer(start, stop, step) + indexer = self.slice_indexer(start, stop, step, kind=kind) except Exception: if is_index_slice: if self.is_integer(): @@ -1891,10 +1919,7 @@ def get_value(self, series, key): s = _values_from_object(series) k = _values_from_object(key) - # prevent integer truncation bug in indexing - if is_float(k) and not self.is_floating(): - raise KeyError - + k = self._convert_scalar_indexer(k, kind='getitem') try: return self._engine.get_value(s, k, tz=getattr(series.dtype, 'tz', None)) @@ -2236,6 +2261,7 @@ def reindex(self, target, method=None, level=None, limit=None, if self.equals(target): indexer = None else: + if self.is_unique: indexer = self.get_indexer(target, method=method, limit=limit, @@ -2722,7 +2748,9 @@ def _maybe_cast_slice_bound(self, label, side, kind): # datetimelike Indexes # reject them if is_float(label): - self._invalid_indexer('slice', label) + if not (kind in ['ix'] and (self.holds_integer() or + self.is_floating())): + self._invalid_indexer('slice', label) # we are trying to find integer bounds on a non-integer based index # this is rejected (generally .loc gets you here) diff --git a/pandas/indexes/numeric.py b/pandas/indexes/numeric.py index 61d93284adbbb..17119f7e8ae0b 100644 --- a/pandas/indexes/numeric.py +++ b/pandas/indexes/numeric.py @@ -42,9 +42,14 @@ def _maybe_cast_slice_bound(self, label, side, kind): """ # we are a numeric index, so we accept - # integer/floats directly - if not (com.is_integer(label) or com.is_float(label)): - self._invalid_indexer('slice', label) + # integer directly + if com.is_integer(label): + pass + + # disallow floats only if we not-strict + elif com.is_float(label): + if not (self.is_floating() or kind in ['ix']): + self._invalid_indexer('slice', label) return label @@ -200,6 +205,18 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, if dtype is None: dtype = np.float64 + dtype = np.dtype(dtype) + + # allow integer / object dtypes to be passed, but coerce to float64 + if dtype.kind in ['i', 'O']: + dtype = np.float64 + + elif dtype.kind in ['f']: + pass + + else: + raise TypeError("cannot support {0} dtype in " + "Float64Index".format(dtype)) try: subarr = np.array(data, dtype=dtype, copy=copy) diff --git a/pandas/tests/frame/test_indexing.py b/pandas/tests/frame/test_indexing.py index 55631ad831441..d5d0bd32a9356 100644 --- a/pandas/tests/frame/test_indexing.py +++ b/pandas/tests/frame/test_indexing.py @@ -212,11 +212,9 @@ def test_getitem_boolean(self): assert_frame_equal(subframe_obj, subframe) # test that Series indexers reindex - with tm.assert_produces_warning(UserWarning): - indexer_obj = indexer_obj.reindex(self.tsframe.index[::-1]) - - subframe_obj = self.tsframe[indexer_obj] - assert_frame_equal(subframe_obj, subframe) + indexer_obj = indexer_obj.reindex(self.tsframe.index[::-1]) + subframe_obj = self.tsframe[indexer_obj] + assert_frame_equal(subframe_obj, subframe) # test df[df > 0] for df in [self.tsframe, self.mixed_frame, @@ -1309,38 +1307,26 @@ def test_getitem_setitem_float_labels(self): df = DataFrame(np.random.randn(5, 5), index=index) # positional slicing only via iloc! - # stacklevel=False -> needed stacklevel depends on index type - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - result = df.iloc[1.0:5] - - expected = df.reindex([2.5, 3.5, 4.5, 5.0]) - assert_frame_equal(result, expected) - self.assertEqual(len(result), 4) + self.assertRaises(TypeError, lambda: df.iloc[1.0:5]) result = df.iloc[4:5] expected = df.reindex([5.0]) assert_frame_equal(result, expected) self.assertEqual(len(result), 1) - # GH 4892, float indexers in iloc are deprecated - import warnings - warnings.filterwarnings(action='error', category=FutureWarning) - cp = df.copy() def f(): cp.iloc[1.0:5] = 0 - self.assertRaises(FutureWarning, f) + self.assertRaises(TypeError, f) def f(): result = cp.iloc[1.0:5] == 0 # noqa - self.assertRaises(FutureWarning, f) + self.assertRaises(TypeError, f) self.assertTrue(result.values.all()) self.assertTrue((cp.iloc[0:1] == df.iloc[0:1]).values.all()) - warnings.filterwarnings(action='default', category=FutureWarning) - cp = df.copy() cp.iloc[4:5] = 0 self.assertTrue((cp.iloc[4:5] == 0).values.all()) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 735025cfca42e..465879dd62466 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -180,53 +180,46 @@ def test_constructor_simple_new(self): def test_constructor_dtypes(self): - for idx in [Index(np.array([1, 2, 3], dtype=int)), Index( - np.array( - [1, 2, 3], dtype=int), dtype=int), Index( - np.array( - [1., 2., 3.], dtype=float), dtype=int), Index( - [1, 2, 3], dtype=int), Index( - [1., 2., 3.], dtype=int)]: + for idx in [Index(np.array([1, 2, 3], dtype=int)), + Index(np.array([1, 2, 3], dtype=int), dtype=int), + Index([1, 2, 3], dtype=int)]: self.assertIsInstance(idx, Int64Index) - for idx in [Index(np.array([1., 2., 3.], dtype=float)), Index( - np.array( - [1, 2, 3], dtype=int), dtype=float), Index( - np.array( - [1., 2., 3.], dtype=float), dtype=float), Index( - [1, 2, 3], dtype=float), Index( - [1., 2., 3.], dtype=float)]: + # these should coerce + for idx in [Index(np.array([1., 2., 3.], dtype=float), dtype=int), + Index([1., 2., 3.], dtype=int)]: + self.assertIsInstance(idx, Int64Index) + + for idx in [Index(np.array([1., 2., 3.], dtype=float)), + Index(np.array([1, 2, 3], dtype=int), dtype=float), + Index(np.array([1., 2., 3.], dtype=float), dtype=float), + Index([1, 2, 3], dtype=float), + Index([1., 2., 3.], dtype=float)]: self.assertIsInstance(idx, Float64Index) - for idx in [Index(np.array( - [True, False, True], dtype=bool)), Index([True, False, True]), - Index( - np.array( - [True, False, True], dtype=bool), dtype=bool), - Index( - [True, False, True], dtype=bool)]: + for idx in [Index(np.array([True, False, True], dtype=bool)), + Index([True, False, True]), + Index(np.array([True, False, True], dtype=bool), dtype=bool), + Index([True, False, True], dtype=bool)]: self.assertIsInstance(idx, Index) self.assertEqual(idx.dtype, object) - for idx in [Index( - np.array([1, 2, 3], dtype=int), dtype='category'), Index( - [1, 2, 3], dtype='category'), Index( - np.array([np.datetime64('2011-01-01'), np.datetime64( - '2011-01-02')]), dtype='category'), Index( - [datetime(2011, 1, 1), datetime(2011, 1, 2) - ], dtype='category')]: + for idx in [Index(np.array([1, 2, 3], dtype=int), dtype='category'), + Index([1, 2, 3], dtype='category'), + Index(np.array([np.datetime64('2011-01-01'), + np.datetime64('2011-01-02')]), dtype='category'), + Index([datetime(2011, 1, 1), datetime(2011, 1, 2)], dtype='category')]: self.assertIsInstance(idx, CategoricalIndex) - for idx in [Index(np.array([np.datetime64('2011-01-01'), np.datetime64( - '2011-01-02')])), + for idx in [Index(np.array([np.datetime64('2011-01-01'), + np.datetime64('2011-01-02')])), Index([datetime(2011, 1, 1), datetime(2011, 1, 2)])]: self.assertIsInstance(idx, DatetimeIndex) - for idx in [Index( - np.array([np.datetime64('2011-01-01'), np.datetime64( - '2011-01-02')]), dtype=object), Index( - [datetime(2011, 1, 1), datetime(2011, 1, 2) - ], dtype=object)]: + for idx in [Index(np.array([np.datetime64('2011-01-01'), + np.datetime64('2011-01-02')]), dtype=object), + Index([datetime(2011, 1, 1), + datetime(2011, 1, 2)], dtype=object)]: self.assertNotIsInstance(idx, DatetimeIndex) self.assertIsInstance(idx, Index) self.assertEqual(idx.dtype, object) @@ -235,10 +228,9 @@ def test_constructor_dtypes(self): 1, 'D')])), Index([timedelta(1), timedelta(1)])]: self.assertIsInstance(idx, TimedeltaIndex) - for idx in [Index( - np.array([np.timedelta64(1, 'D'), np.timedelta64(1, 'D')]), - dtype=object), Index( - [timedelta(1), timedelta(1)], dtype=object)]: + for idx in [Index(np.array([np.timedelta64(1, 'D'), + np.timedelta64(1, 'D')]), dtype=object), + Index([timedelta(1), timedelta(1)], dtype=object)]: self.assertNotIsInstance(idx, TimedeltaIndex) self.assertIsInstance(idx, Index) self.assertEqual(idx.dtype, object) @@ -942,12 +934,17 @@ def test_slice_locs(self): self.assertEqual(idx2.slice_locs(10.5, -1), (0, n)) # int slicing with floats + # GH 4892, these are all TypeErrors idx = Index(np.array([0, 1, 2, 5, 6, 7, 9, 10], dtype=int)) - self.assertEqual(idx.slice_locs(5.0, 10.0), (3, n)) - self.assertEqual(idx.slice_locs(4.5, 10.5), (3, 8)) + self.assertRaises(TypeError, + lambda: idx.slice_locs(5.0, 10.0), (3, n)) + self.assertRaises(TypeError, + lambda: idx.slice_locs(4.5, 10.5), (3, 8)) idx2 = idx[::-1] - self.assertEqual(idx2.slice_locs(8.5, 1.5), (2, 6)) - self.assertEqual(idx2.slice_locs(10.5, -1), (0, n)) + self.assertRaises(TypeError, + lambda: idx2.slice_locs(8.5, 1.5), (2, 6)) + self.assertRaises(TypeError, + lambda: idx2.slice_locs(10.5, -1), (0, n)) def test_slice_locs_dup(self): idx = Index(['a', 'a', 'b', 'c', 'd', 'd']) diff --git a/pandas/tests/indexes/test_numeric.py b/pandas/tests/indexes/test_numeric.py index d14f7bbc680df..325e0df14a07e 100644 --- a/pandas/tests/indexes/test_numeric.py +++ b/pandas/tests/indexes/test_numeric.py @@ -379,10 +379,19 @@ def test_constructor(self): new_index = Int64Index(arr, copy=True) tm.assert_numpy_array_equal(new_index, self.index) val = arr[0] + 3000 + # this should not change index arr[0] = val self.assertNotEqual(new_index[0], val) + # interpret list-like + expected = Int64Index([5, 0]) + for cls in [Index, Int64Index]: + for idx in [cls([5, 0], dtype='int64'), + cls(np.array([5, 0]), dtype='int64'), + cls(Series([5, 0]), dtype='int64')]: + tm.assert_index_equal(idx, expected) + def test_constructor_corner(self): arr = np.array([1, 2, 3, 4], dtype=object) index = Int64Index(arr) diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index bf8f1aa3f6427..72fff3f82111e 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -1130,7 +1130,9 @@ def test_loc_getitem_label_out_of_range(self): self.check_result('label range', 'loc', 'f', 'ix', 'f', typs=['floats'], fails=TypeError) self.check_result('label range', 'loc', 20, 'ix', 20, - typs=['ints', 'labels', 'mixed'], fails=KeyError) + typs=['ints', 'mixed'], fails=KeyError) + self.check_result('label range', 'loc', 20, 'ix', 20, + typs=['labels'], fails=TypeError) self.check_result('label range', 'loc', 20, 'ix', 20, typs=['ts'], axes=0, fails=TypeError) self.check_result('label range', 'loc', 20, 'ix', 20, typs=['floats'], @@ -4200,7 +4202,7 @@ def f(): def f(): df.ix[100.0, :] = df.ix[0] - self.assertRaises(ValueError, f) + self.assertRaises(TypeError, f) def f(): df.ix[100, :] = df.ix[0] @@ -5120,27 +5122,24 @@ def check_index(index, error): # positional selection result1 = s[5] - result2 = s[5.0] + self.assertRaises(TypeError, lambda: s[5.0]) result3 = s.iloc[5] - result4 = s.iloc[5.0] + self.assertRaises(TypeError, lambda: s.iloc[5.0]) # by value - self.assertRaises(error, lambda: s.loc[5]) - self.assertRaises(error, lambda: s.loc[5.0]) + self.assertRaises(TypeError, lambda: s.loc[5]) + self.assertRaises(TypeError, lambda: s.loc[5.0]) # this is fallback, so it works result5 = s.ix[5] - result6 = s.ix[5.0] + self.assertRaises(error, lambda: s.ix[5.0]) - self.assertEqual(result1, result2) self.assertEqual(result1, result3) - self.assertEqual(result1, result4) self.assertEqual(result1, result5) - self.assertEqual(result1, result6) # string-like for index in [tm.makeStringIndex, tm.makeUnicodeIndex]: - check_index(index, KeyError) + check_index(index, TypeError) # datetimelike for index in [tm.makeDateIndex, tm.makeTimedeltaIndex, @@ -5150,32 +5149,21 @@ def check_index(index, error): # exact indexing when found on IntIndex s = Series(np.arange(10), dtype='int64') - result1 = s[5.0] - result2 = s.loc[5.0] - result3 = s.ix[5.0] + self.assertRaises(TypeError, lambda: s[5.0]) + self.assertRaises(TypeError, lambda: s.loc[5.0]) + self.assertRaises(TypeError, lambda: s.ix[5.0]) result4 = s[5] result5 = s.loc[5] result6 = s.ix[5] - self.assertEqual(result1, result2) - self.assertEqual(result1, result3) - self.assertEqual(result1, result4) - self.assertEqual(result1, result5) - self.assertEqual(result1, result6) + self.assertEqual(result4, result5) + self.assertEqual(result4, result6) def test_slice_indexer(self): def check_iloc_compat(s): - # invalid type for iloc (but works with a warning) - # check_stacklevel=False -> impossible to get it right for all - # index types - with self.assert_produces_warning(FutureWarning, - check_stacklevel=False): - s.iloc[6.0:8] - with self.assert_produces_warning(FutureWarning, - check_stacklevel=False): - s.iloc[6.0:8.0] - with self.assert_produces_warning(FutureWarning, - check_stacklevel=False): - s.iloc[6:8.0] + # these are exceptions + self.assertRaises(TypeError, lambda: s.iloc[6.0:8]) + self.assertRaises(TypeError, lambda: s.iloc[6.0:8.0]) + self.assertRaises(TypeError, lambda: s.iloc[6:8.0]) def check_slicing_positional(index): @@ -5229,22 +5217,30 @@ def check_slicing_positional(index): result3 = s.loc[2:5] assert_series_equal(result2, result3) - # float slicers on an int index + # float slicers on an int index with ix expected = Series([11, 12, 13], index=[6, 7, 8]) - for method in [lambda x: x.loc, lambda x: x.ix]: - result = method(s)[6.0:8.5] - assert_series_equal(result, expected) + result = s.ix[6.0:8.5] + assert_series_equal(result, expected) - result = method(s)[5.5:8.5] - assert_series_equal(result, expected) + result = s.ix[5.5:8.5] + assert_series_equal(result, expected) + + result = s.ix[5.5:8.0] + assert_series_equal(result, expected) - result = method(s)[5.5:8.0] - assert_series_equal(result, expected) + for method in ['loc', 'iloc']: + # make all float slicing fail for .loc with an int index + self.assertRaises(TypeError, + lambda: getattr(s, method)[6.0:8]) + self.assertRaises(TypeError, + lambda: getattr(s, method)[6.0:8.0]) + self.assertRaises(TypeError, + lambda: getattr(s, method)[6:8.0]) - # make all float slicing fail for [] with an int index - self.assertRaises(TypeError, lambda: s[6.0:8]) - self.assertRaises(TypeError, lambda: s[6.0:8.0]) - self.assertRaises(TypeError, lambda: s[6:8.0]) + # make all float slicing fail for [] with an int index + self.assertRaises(TypeError, lambda: s[6.0:8]) + self.assertRaises(TypeError, lambda: s[6.0:8.0]) + self.assertRaises(TypeError, lambda: s[6:8.0]) check_iloc_compat(s) @@ -5329,121 +5325,343 @@ def test_ix_empty_list_indexer_is_ok(self): assert_frame_equal(df.ix[[]], df.iloc[:0, :], check_index_type=True, check_column_type=True) - def test_deprecate_float_indexers(self): + def test_index_type_coercion(self): + + # GH 11836 + # if we have an index type and set it with something that looks + # to numpy like the same, but is actually, not + # (e.g. setting with a float or string '0') + # then we need to coerce to object + + # integer indexes + for s in [Series(range(5)), + Series(range(5), index=range(1, 6))]: + + self.assertTrue(s.index.is_integer()) + + for attr in ['ix', 'loc']: + s2 = s.copy() + getattr(s2, attr)[0.1] = 0 + self.assertTrue(s2.index.is_floating()) + self.assertTrue(getattr(s2, attr)[0.1] == 0) + + s2 = s.copy() + getattr(s2, attr)[0.0] = 0 + exp = s.index + if 0 not in s: + exp = Index(s.index.tolist() + [0]) + tm.assert_index_equal(s2.index, exp) + + s2 = s.copy() + getattr(s2, attr)['0'] = 0 + self.assertTrue(s2.index.is_object()) + + # setitem + s2 = s.copy() + s2[0.1] = 0 + self.assertTrue(s2.index.is_floating()) + self.assertTrue(s2[0.1] == 0) + + s2 = s.copy() + s2[0.0] = 0 + exp = s.index + if 0 not in s: + exp = Index(s.index.tolist() + [0]) + tm.assert_index_equal(s2.index, exp) + + s2 = s.copy() + s2['0'] = 0 + self.assertTrue(s2.index.is_object()) + + for s in [Series(range(5), index=np.arange(5.))]: + + self.assertTrue(s.index.is_floating()) + + for attr in ['ix', 'loc']: + + s2 = s.copy() + getattr(s2, attr)[0.1] = 0 + self.assertTrue(s2.index.is_floating()) + self.assertTrue(getattr(s2, attr)[0.1] == 0) + + s2 = s.copy() + getattr(s2, attr)[0.0] = 0 + tm.assert_index_equal(s2.index, s.index) + + s2 = s.copy() + getattr(s2, attr)['0'] = 0 + self.assertTrue(s2.index.is_object()) + + # setitem + s2 = s.copy() + s2[0.1] = 0 + self.assertTrue(s2.index.is_floating()) + self.assertTrue(s2[0.1] == 0) + + s2 = s.copy() + s2[0.0] = 0 + tm.assert_index_equal(s2.index, s.index) + + s2 = s.copy() + s2['0'] = 0 + self.assertTrue(s2.index.is_object()) + + def test_invalid_scalar_float_indexers(self): # GH 4892 - # deprecate allowing float indexers that are equal to ints to be used - # as indexers in non-float indices + # float_indexers should raise exceptions + # on appropriate Index types & accessors - import warnings - warnings.filterwarnings(action='error', category=FutureWarning) + for index in [tm.makeStringIndex, tm.makeUnicodeIndex, + tm.makeCategoricalIndex, + tm.makeDateIndex, tm.makeTimedeltaIndex, + tm.makePeriodIndex]: - def check_index(index): i = index(5) for s in [Series( np.arange(len(i)), index=i), DataFrame( np.random.randn( len(i), len(i)), index=i, columns=i)]: - self.assertRaises(FutureWarning, lambda: s.iloc[3.0]) - # setting + for attr in ['iloc', 'loc', 'ix', '__getitem__']: + def f(): + getattr(s, attr)()[3.0] + self.assertRaises(TypeError, f) + + # setting only fails with iloc as + # the others expand the index def f(): s.iloc[3.0] = 0 - - self.assertRaises(FutureWarning, f) + self.assertRaises(TypeError, f) # fallsback to position selection ,series only s = Series(np.arange(len(i)), index=i) s[3] - self.assertRaises(FutureWarning, lambda: s[3.0]) + self.assertRaises(TypeError, lambda: s[3.0]) - for index in [tm.makeStringIndex, tm.makeUnicodeIndex, - tm.makeDateIndex, tm.makeTimedeltaIndex, - tm.makePeriodIndex]: - check_index(index) + # integer index + for index in [tm.makeIntIndex, tm.makeRangeIndex]: - # ints - i = index(5) - for s in [Series(np.arange(len(i))), DataFrame( - np.random.randn( - len(i), len(i)), index=i, columns=i)]: - self.assertRaises(FutureWarning, lambda: s.iloc[3.0]) + i = index(5) + for s in [Series(np.arange(len(i))), + DataFrame(np.random.randn(len(i), len(i)), + index=i, columns=i)]: + + # any kind of get access should fail + for attr in ['iloc', 'loc', 'ix']: + def f(): + getattr(s, attr)[3.0] + self.assertRaises(TypeError, f) + error = KeyError if isinstance(s, DataFrame) else TypeError + self.assertRaises(error, lambda: s[3.0]) + + # setting only fails with iloc as + def f(): + s.iloc[3.0] = 0 + self.assertRaises(TypeError, f) - # on some arch's this doesn't provide a warning (and thus raise) - # and some it does - try: - s[3.0] - except: - pass + # other indexers will coerce to an object index + # tested explicity in: test_invalid_scalar_float_indexers + # above - # setting - def f(): - s.iloc[3.0] = 0 + # floats index + index = tm.makeFloatIndex(5) + for s in [Series(np.arange(len(index)), index=index), + DataFrame(np.random.randn(len(index), len(index)), + index=index, columns=index)]: + + # assert all operations except for iloc are ok + indexer = index[3] + expected = s.iloc[3] - self.assertRaises(FutureWarning, f) + if isinstance(s, Series): + compare = self.assertEqual + else: + compare = tm.assert_series_equal - # floats: these are all ok! - i = np.arange(5.) + for attr in ['loc', 'ix']: - for s in [Series( - np.arange(len(i)), index=i), DataFrame( - np.random.randn( - len(i), len(i)), index=i, columns=i)]: - with tm.assert_produces_warning(False): - s[3.0] + # getting + result = getattr(s, attr)[indexer] + compare(result, expected) - with tm.assert_produces_warning(False): - s[3] + # setting + s2 = s.copy() - self.assertRaises(FutureWarning, lambda: s.iloc[3.0]) + def f(): + getattr(s2, attr)[indexer] = expected + result = getattr(s2, attr)[indexer] + compare(result, expected) - with tm.assert_produces_warning(False): - s.iloc[3] + # random integer is a KeyError + self.assertRaises(KeyError, lambda: getattr(s, attr)[3]) - with tm.assert_produces_warning(False): - s.loc[3.0] + # iloc succeeds with an integer + result = s.iloc[3] + compare(result, expected) - with tm.assert_produces_warning(False): - s.loc[3] + s2 = s.copy() + + def f(): + s2.iloc[3] = expected + result = s2.iloc[3] + compare(result, expected) + + # iloc raises with a float + self.assertRaises(TypeError, lambda: s.iloc[3.0]) def f(): s.iloc[3.0] = 0 + self.assertRaises(TypeError, f) - self.assertRaises(FutureWarning, f) + # getitem - # slices - for index in [tm.makeIntIndex, tm.makeRangeIndex, tm.makeFloatIndex, - tm.makeStringIndex, tm.makeUnicodeIndex, - tm.makeDateIndex, tm.makePeriodIndex]: + # getting + if isinstance(s, DataFrame): + expected = s.iloc[:, 3] + result = s[indexer] + compare(result, expected) + + # setting + s2 = s.copy() + + def f(): + s2[indexer] = expected + result = s2[indexer] + compare(result, expected) + + # random integer is a KeyError + result = self.assertRaises(KeyError, lambda: s[3]) + + def test_invalid_slice_float_indexers(self): + + # GH 4892 + # float_indexers should raise exceptions + # on appropriate Index types & accessors + + for index in [tm.makeStringIndex, tm.makeUnicodeIndex, + tm.makeDateIndex, tm.makeTimedeltaIndex, + tm.makePeriodIndex]: index = index(5) - for s in [Series( - range(5), index=index), DataFrame( - np.random.randn(5, 2), index=index)]: + for s in [Series(range(5), index=index), + DataFrame(np.random.randn(5, 2), index=index)]: + + # getitem + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: + + def f(): + s.iloc[l] + self.assertRaises(TypeError, f) + + def f(): + s.loc[l] + self.assertRaises(TypeError, f) + + def f(): + s[l] + self.assertRaises(TypeError, f) + + def f(): + s.ix[l] + self.assertRaises(TypeError, f) + + # setitem + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: + + def f(): + s.iloc[l] = 0 + self.assertRaises(TypeError, f) + + def f(): + s.loc[l] = 0 + self.assertRaises(TypeError, f) + + def f(): + s[l] = 0 + self.assertRaises(TypeError, f) + + def f(): + s.ix[l] = 0 + self.assertRaises(TypeError, f) + + # same as above, but for Integer based indexes + for index in [tm.makeIntIndex, tm.makeRangeIndex]: + + index = index(5) + for s in [Series(range(5), index=index), + DataFrame(np.random.randn(5, 2), index=index)]: # getitem - self.assertRaises(FutureWarning, lambda: s.iloc[3.0:4]) - self.assertRaises(FutureWarning, lambda: s.iloc[3.0:4.0]) - self.assertRaises(FutureWarning, lambda: s.iloc[3:4.0]) + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: + + def f(): + s.iloc[l] + self.assertRaises(TypeError, f) + + def f(): + s.loc[l] + self.assertRaises(TypeError, f) + + def f(): + s[l] + self.assertRaises(TypeError, f) + + # ix allows float slicing + s.ix[l] # setitem - def f(): - s.iloc[3.0:4] = 0 + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: - self.assertRaises(FutureWarning, f) + def f(): + s.iloc[l] = 0 + self.assertRaises(TypeError, f) - def f(): - s.iloc[3:4.0] = 0 + def f(): + s.loc[l] = 0 + self.assertRaises(TypeError, f) - self.assertRaises(FutureWarning, f) + def f(): + s[l] = 0 + self.assertRaises(TypeError, f) - def f(): - s.iloc[3.0:4.0] = 0 + # ix allows float slicing + s.ix[l] = 0 - self.assertRaises(FutureWarning, f) + # same as above, but for floats + index = tm.makeFloatIndex(5) + for s in [Series(range(5), index=index), + DataFrame(np.random.randn(5, 2), index=index)]: - warnings.filterwarnings(action='ignore', category=FutureWarning) + # getitem + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: + + # ix is ok + result1 = s.ix[3:4] + result2 = s.ix[3.0:4] + result3 = s.ix[3.0:4.0] + result4 = s.ix[3:4.0] + self.assertTrue(result1.equals(result2)) + self.assertTrue(result1.equals(result3)) + self.assertTrue(result1.equals(result4)) + + # setitem + for l in [slice(3.0, 4), + slice(3, 4.0), + slice(3.0, 4.0)]: + + pass def test_float_index_to_mixed(self): df = DataFrame({0.0: np.random.rand(10), 1.0: np.random.rand(10)}) diff --git a/pandas/tseries/period.py b/pandas/tseries/period.py index 05ca65d6946fb..a25bb525c9970 100644 --- a/pandas/tseries/period.py +++ b/pandas/tseries/period.py @@ -678,7 +678,12 @@ def get_loc(self, key, method=None, tolerance=None): except TypeError: pass - key = Period(key, freq=self.freq) + try: + key = Period(key, freq=self.freq) + except ValueError: + # we cannot construct the Period + # as we have an invalid type + return self._invalid_indexer('label', key) try: return Index.get_loc(self, key.ordinal, method, tolerance) except KeyError: