diff --git a/doc/source/release.rst b/doc/source/release.rst index 557c4b293a84e..8f782f31fd72c 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -319,6 +319,7 @@ See :ref:`Internal Refactoring` etc. (:issue:`4718`, :issue:`4628`) - Bug in using ``iloc/loc`` with a cross-sectional and duplicate indicies (:issue:`4726`) - Bug with using ``QUOTE_NONE`` with ``to_csv`` causing ``Exception``. (:issue:`4328`) + - Bug with Series indexing not raising an error when the right-hand-side has an incorrect length (:issue:`2702`) pandas 0.12 =========== diff --git a/pandas/core/internals.py b/pandas/core/internals.py index d025c7a7fcf6d..0d0f0135c1855 100644 --- a/pandas/core/internals.py +++ b/pandas/core/internals.py @@ -547,10 +547,41 @@ def setitem(self, indexer, value): dtype, _ = com._maybe_promote(arr_value.dtype) values = values.astype(dtype) + transf = (lambda x: x.T) if self.ndim == 2 else (lambda x: x) + values = transf(values) + l = len(values) + + # length checking + # boolean with truth values == len of the value is ok too + if isinstance(indexer, (np.ndarray, list)): + if is_list_like(value) and len(indexer) != len(value): + if not (isinstance(indexer, np.ndarray) and indexer.dtype == np.bool_ and len(indexer[indexer]) == len(value)): + raise ValueError("cannot set using a list-like indexer with a different length than the value") + + # slice + elif isinstance(indexer, slice): + + if is_list_like(value) and l: + start = indexer.start + stop = indexer.stop + step = indexer.step + if start is None: + start = 0 + elif start < 0: + start += l + if stop is None or stop > l: + stop = len(values) + elif stop < 0: + stop += l + if step is None: + step = 1 + elif step < 0: + step = abs(step) + if (stop-start) / step != len(value): + raise ValueError("cannot set using a slice indexer with a different length than the value") + try: # set and return a block - transf = (lambda x: x.T) if self.ndim == 2 else (lambda x: x) - values = transf(values) values[indexer] = value # coerce and try to infer the dtypes of the result @@ -561,7 +592,9 @@ def setitem(self, indexer, value): values = self._try_coerce_result(values) values = self._try_cast_result(values, dtype) return [make_block(transf(values), self.items, self.ref_items, ndim=self.ndim, fastpath=True)] - except: + except (ValueError, TypeError) as detail: + raise + except (Exception) as detail: pass return [ self ] diff --git a/pandas/core/series.py b/pandas/core/series.py index 8396de9c5997b..3b36d7c38e16d 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1096,10 +1096,9 @@ def _set_labels(self, key, value): self._set_values(indexer, value) def _set_values(self, key, value): - values = self.values if isinstance(key, Series): key = key.values - values[key] = _index.convert_scalar(values, value) + self._data = self._data.setitem(key,value) # help out SparseSeries _get_val_at = ndarray.__getitem__ diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 282dad5c0d6be..514245e82ac28 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -1284,6 +1284,59 @@ def f(): expected = Series(np.nan,index=[9]) assert_series_equal(result, expected) + def test_where_setitem_invalid(self): + + # GH 2702 + # make sure correct exceptions are raised on invalid list assignment + + # slice + s = Series(list('abc')) + def f(): + s[0:3] = list(range(27)) + self.assertRaises(ValueError, f) + + s[0:3] = list(range(3)) + expected = Series([0,1,2]) + assert_series_equal(s, expected) + + # slice with step + s = Series(list('abcdef')) + def f(): + s[0:4:2] = list(range(27)) + self.assertRaises(ValueError, f) + + s = Series(list('abcdef')) + s[0:4:2] = list(range(2)) + expected = Series([0,'b',1,'d','e','f']) + assert_series_equal(s, expected) + + # neg slices + s = Series(list('abcdef')) + def f(): + s[:-1] = list(range(27)) + self.assertRaises(ValueError, f) + + s[-3:-1] = list(range(2)) + expected = Series(['a','b','c',0,1,'f']) + assert_series_equal(s, expected) + + # list + s = Series(list('abc')) + def f(): + s[[0,1,2]] = list(range(27)) + self.assertRaises(ValueError, f) + + s = Series(list('abc')) + def f(): + s[[0,1,2]] = list(range(2)) + self.assertRaises(ValueError, f) + + # scalar + s = Series(list('abc')) + s[0] = list(range(10)) + expected = Series([list(range(10)),'b','c']) + assert_series_equal(s, expected) + def test_where_broadcast(self): # Test a variety of differently sized series for size in range(2, 6): @@ -2550,22 +2603,23 @@ def test_between(self): expected = s[5:16].dropna() assert_series_equal(result, expected) - def test_setitem_na_exception(self): - def testme1(): - s = Series([2, 3, 4, 5, 6, 7, 8, 9, 10]) - s[::2] = np.nan - - def testme2(): - s = Series([True, True, False, False]) - s[::2] = np.nan + def test_setitem_na(self): + # these induce dtype changes + expected = Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan]) + s = Series([2, 3, 4, 5, 6, 7, 8, 9, 10]) + s[::2] = np.nan + assert_series_equal(s, expected) - def testme3(): - s = Series(np.arange(10)) - s[:5] = np.nan + # get's coerced to float, right? + expected = Series([np.nan, 1, np.nan, 0]) + s = Series([True, True, False, False]) + s[::2] = np.nan + assert_series_equal(s, expected) - self.assertRaises(Exception, testme1) - self.assertRaises(Exception, testme2) - self.assertRaises(Exception, testme3) + expected = Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9]) + s = Series(np.arange(10)) + s[:5] = np.nan + assert_series_equal(s, expected) def test_scalar_na_cmp_corners(self): s = Series([2, 3, 4, 5, 6, 7, 8, 9, 10])