Skip to content

Commit

Permalink
ENH: raise AmbiguousIndexError when location not found in iteger-inde…
Browse files Browse the repository at this point in the history
…xed Series. Start of #328 API changes
  • Loading branch information
wesm committed Jan 6, 2012
1 parent 0bdbe8d commit f3ca67d
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 32 deletions.
3 changes: 3 additions & 0 deletions pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
class PandasError(Exception):
pass

class AmbiguousIndexError(PandasError, KeyError):
pass

def isnull(obj):
'''
Replacement for numpy.isnan / -numpy.isfinite which is suitable
Expand Down
6 changes: 4 additions & 2 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ def _sanitize_column(self, value):
else:
value = np.repeat(value, len(self.index))

return value
return np.asarray(value)

def pop(self, item):
"""
Expand Down Expand Up @@ -1729,7 +1729,9 @@ def reset_index(self):

# to ndarray and maybe infer different dtype
level_values = lev.values
level_values = lib.maybe_convert_objects(level_values)
if level_values.dtype == np.object_:
level_values = lib.maybe_convert_objects(level_values)

new_obj.insert(0, col_name, level_values.take(lab))
else:
name = self.index.name
Expand Down
13 changes: 9 additions & 4 deletions pandas/core/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def __new__(cls, data, dtype=None, copy=False, name=None):
# other iterable of some kind
subarr = _asarray_tuplesafe(data, dtype=object)

if lib.is_integer_array(subarr) and dtype is None:
return Int64Index(subarr.astype('i8'), name=name)

subarr = subarr.view(cls)
subarr.name = name
return subarr
Expand All @@ -74,7 +77,8 @@ def __array_finalize__(self, obj):
self.name = getattr(obj, 'name', None)

def astype(self, dtype):
return Index(self.values.astype(dtype), name=self.name)
return Index(self.values.astype(dtype), name=self.name,
dtype=dtype)

@property
def dtype(self):
Expand Down Expand Up @@ -857,13 +861,14 @@ def __new__(cls, data, dtype=None, copy=False, name=None):
subarr.name = name
return subarr

@property
def inferred_type(self):
return 'integer'

@property
def _constructor(self):
return Int64Index

def astype(self, dtype):
return Index(self.values.astype(dtype))

@property
def dtype(self):
return np.dtype('int64')
Expand Down
40 changes: 25 additions & 15 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

from pandas.core.common import (isnull, notnull, _is_bool_indexer,
_default_index, _maybe_upcast,
_asarray_tuplesafe)
_asarray_tuplesafe,
AmbiguousIndexError)
from pandas.core.daterange import DateRange
from pandas.core.index import Index, MultiIndex, _ensure_index
from pandas.core.indexing import _SeriesIndexer, _maybe_droplevels
Expand Down Expand Up @@ -137,6 +138,8 @@ class Series(np.ndarray, generic.PandasObject):

_AXIS_NAMES = dict((v, k) for k, v in _AXIS_NUMBERS.iteritems())

__slots__ = ['_index', 'name']

def __new__(cls, data=None, index=None, dtype=None, name=None,
copy=False):
if data is None:
Expand Down Expand Up @@ -208,20 +211,22 @@ def __hash__(self):
raise TypeError('unhashable type')

_index = None
def _get_index(self):
return self._index
index = lib.SeriesIndex()

# def _get_index(self):
# return self._index

def _set_index(self, index):
if not isinstance(index, _INDEX_TYPES):
raise TypeError("Expected index to be in %s; was %s."
% (_INDEX_TYPES, type(index)))
# def _set_index(self, index):
# if not isinstance(index, _INDEX_TYPES):
# raise TypeError("Expected index to be in %s; was %s."
# % (_INDEX_TYPES, type(index)))

if len(self) != len(index):
raise AssertionError('Lengths of index and values did not match!')
# if len(self) != len(index):
# raise AssertionError('Lengths of index and values did not match!')

self._index = _ensure_index(index)
# self._index = _ensure_index(index)

index = property(fget=_get_index, fset=_set_index)
# index = property(fget=_get_index, fset=_set_index)

def __array_finalize__(self, obj):
"""
Expand Down Expand Up @@ -264,22 +269,27 @@ def ix(self):
return self._ix

def __getitem__(self, key):
index = self.index

# Label-based
try:
return self.index._engine.get_value(self, key)
return index._engine.get_value(self, key)
except KeyError, e1:
if isinstance(self.index, MultiIndex):
if isinstance(index, MultiIndex):
values = self.values
try:
loc = self.index.get_loc(key)
loc = index.get_loc(key)
# TODO: what if a level contains tuples??
new_index = self.index[loc]
new_index = index[loc]
new_index = _maybe_droplevels(new_index, key)
return Series(values[loc], index=new_index,
name=self.name)
except KeyError:
pass

if index.inferred_type == 'integer':
raise AmbiguousIndexError(key)

try:
return _gin.get_value_at(self, key)
except IndexError:
Expand Down
8 changes: 5 additions & 3 deletions pandas/sparse/tests/test_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,10 @@ def _check_getitem(sp, dense):
# j = np.float64(i)
# assert_almost_equal(sp[j], dense[j])

# API change 1/6/2012
# negative getitem works
for i in xrange(len(dense)):
assert_almost_equal(sp[-i], dense[-i])
# for i in xrange(len(dense)):
# assert_almost_equal(sp[-i], dense[-i])

_check_getitem(self.bseries, self.bseries.to_dense())
_check_getitem(self.btseries, self.btseries.to_dense())
Expand Down Expand Up @@ -563,7 +564,8 @@ def test_valid(self):
fill_value=0)

sp_valid = sp.valid()
assert_almost_equal(sp_valid, sp.to_dense().valid())
assert_almost_equal(sp_valid.values,
sp.to_dense().valid().values)
self.assert_(sp_valid.index.equals(sp.to_dense().valid().index))
self.assertEquals(len(sp_valid.sp_values), 2)

Expand Down
17 changes: 17 additions & 0 deletions pandas/src/properties.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,20 @@ cdef class AxisProperty(object):

def __set__(self, obj, value):
obj._set_axis(self.axis, value)

cdef class SeriesIndex(object):
cdef:
Py_ssize_t axis
object _check_type

def __init__(self):
from pandas.core.index import _ensure_index
self._check_type = _ensure_index

def __get__(self, obj, type):
return obj._index

def __set__(self, obj, value):
if len(obj) != len(value):
raise AssertionError('Index length did not match values')
obj._index = self._check_type(value)
9 changes: 9 additions & 0 deletions pandas/tests/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,15 @@ def test_constructor_corner(self):
arr = np.array([1, '2', 3, '4'], dtype=object)
self.assertRaises(TypeError, Int64Index, arr)

def test_coerce_list(self):
# coerce things
arr = Index([1, 2, 3, 4])
self.assert_(type(arr) == Int64Index)

# but not if explicit dtype passed
arr = Index([1, 2, 3, 4], dtype=object)
self.assert_(type(arr) == Index)

def test_dtype(self):
self.assert_(self.index.dtype == np.int64)

Expand Down
9 changes: 7 additions & 2 deletions pandas/tests/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ def test_fromDict(self):
def test_setindex(self):
# wrong type
series = self.series.copy()
self.assertRaises(TypeError, series._set_index, None)
self.assertRaises(TypeError, setattr, series, 'index', None)

# wrong length
series = self.series.copy()
self.assertRaises(AssertionError, series._set_index,
self.assertRaises(AssertionError, setattr, series, 'index',
np.arange(len(series) - 1))

# works
Expand Down Expand Up @@ -400,6 +400,11 @@ def test_getitem_box_float64(self):
value = self.ts[5]
self.assert_(isinstance(value, np.float64))

def test_getitem_ambiguous_keyerror(self):
s = Series(range(10), index=range(0, 20, 2))
self.assertRaises(KeyError, s.__getitem__, 1)
self.assertRaises(KeyError, s.ix.__getitem__, 1)

def test_slice(self):
numSlice = self.series[10:20]
numSliceEnd = self.series[-10:]
Expand Down
12 changes: 6 additions & 6 deletions pandas/tests/test_tseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,31 @@ def test_merge_indexer(self):
old = Index([1, 5, 10])
new = Index(range(12))

filler = lib.merge_indexer_object(new, old.indexMap)
filler = lib.merge_indexer_int64(new, old.indexMap)

expect_filler = [-1, 0, -1, -1, -1, 1, -1, -1, -1, -1, 2, -1]
self.assert_(np.array_equal(filler, expect_filler))

# corner case
old = Index([1, 4])
new = Index(range(5, 10))
filler = lib.merge_indexer_object(new, old.indexMap)
filler = lib.merge_indexer_int64(new, old.indexMap)
expect_filler = [-1, -1, -1, -1, -1]
self.assert_(np.array_equal(filler, expect_filler))

def test_backfill(self):
old = Index([1, 5, 10])
new = Index(range(12))

filler = lib.backfill_object(old, new, old.indexMap, new.indexMap)
filler = lib.backfill_int64(old, new, old.indexMap, new.indexMap)

expect_filler = [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, -1]
self.assert_(np.array_equal(filler, expect_filler))

# corner case
old = Index([1, 4])
new = Index(range(5, 10))
filler = lib.backfill_object(old, new, old.indexMap, new.indexMap)
filler = lib.backfill_int64(old, new, old.indexMap, new.indexMap)

expect_filler = [-1, -1, -1, -1, -1]
self.assert_(np.array_equal(filler, expect_filler))
Expand All @@ -60,15 +60,15 @@ def test_pad(self):
old = Index([1, 5, 10])
new = Index(range(12))

filler = lib.pad_object(old, new, old.indexMap, new.indexMap)
filler = lib.pad_int64(old, new, old.indexMap, new.indexMap)

expect_filler = [-1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2]
self.assert_(np.array_equal(filler, expect_filler))

# corner case
old = Index([5, 10])
new = Index(range(5))
filler = lib.pad_object(old, new, old.indexMap, new.indexMap)
filler = lib.pad_int64(old, new, old.indexMap, new.indexMap)
expect_filler = [-1, -1, -1, -1, -1]
self.assert_(np.array_equal(filler, expect_filler))

Expand Down

0 comments on commit f3ca67d

Please sign in to comment.