From 68c51c5aec18cb9aba2a0cfedc296f90b2463938 Mon Sep 17 00:00:00 2001 From: Andreas Berneryd Date: Sat, 19 Aug 2017 16:54:38 +0200 Subject: [PATCH 1/4] BUG: Fix strange behaviour of Series.iloc on MultiIndex Series (#17148) --- doc/source/whatsnew/v0.21.0.txt | 1 + pandas/core/indexing.py | 3 ++- pandas/tests/indexing/test_iloc.py | 34 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index 85685ed7b430d..f5f34bc2dd762 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -329,6 +329,7 @@ Indexing - Fixes ``DataFrame.loc`` for setting with alignment and tz-aware ``DatetimeIndex`` (:issue:`16889`) - Avoids ``IndexError`` when passing an Index or Series to ``.iloc`` with older numpy (:issue:`17193`) - Allow unicode empty strings as placeholders in multilevel columns in Python 2 (:issue:`17099`) +- Bug in :func:`Series.iloc` when used with increment or assignment and an int indexer on a ``MultiIndex`` ``Series`` causing the wrong indexes to be read from and written to (:issue:`17148`) I/O ^^^ diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 109183827de4e..c24eec682ed7e 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -146,7 +146,8 @@ def _get_setitem_indexer(self, key): return self._convert_tuple(key, is_setter=True) axis = self.obj._get_axis(0) - if isinstance(axis, MultiIndex): + + if isinstance(axis, MultiIndex) and not is_integer(key): try: return axis.get_loc(key) except Exception: diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index 31fee303a41e2..e8a0f82f14aa8 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -269,6 +269,40 @@ def test_iloc_setitem(self): expected = Series([0, 1, 0], index=[4, 5, 6]) tm.assert_series_equal(s, expected) + def test_iloc_setitem_int_multiindex_series(self): + # GH17148 + def check_scenario(data, indexes, values, expected_k): + df = pd.DataFrame( + data=data, + columns=['i', 'j', 'k']) + df.set_index(['i', 'j'], inplace=True) + + series = df.k.copy() + for i, v in zip(indexes, values): + series.iloc[i] += v + + df.k = expected_k + expected = df.k.copy() + tm.assert_series_equal(series, expected) + + check_scenario( + data=[[1, 22, 5], [1, 33, 6]], + indexes=[0, -1, 1], + values=[2, 3, 1], + expected_k=[7, 10]) + + check_scenario( + data=[[1, 3, 7], [2, 4, 8]], + indexes=[0, -1, 1], + values=[1, 1, 10], + expected_k=[8, 19]) + + check_scenario( + data=[[1, 11, 4], [2, 22, 5], [3, 33, 6]], + indexes=[0, -1, 1], + values=[4, 7, 10], + expected_k=[8, 15, 13]) + def test_iloc_setitem_list(self): # setitem with an iloc list From cf279666f5f0c62c3f5b7b2c5e18536a53570bed Mon Sep 17 00:00:00 2001 From: Andreas Berneryd Date: Sun, 20 Aug 2017 09:39:30 +0200 Subject: [PATCH 2/4] Fixed typo in whatsnew entry --- doc/source/whatsnew/v0.21.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index f5f34bc2dd762..e9e0b35857492 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -329,7 +329,7 @@ Indexing - Fixes ``DataFrame.loc`` for setting with alignment and tz-aware ``DatetimeIndex`` (:issue:`16889`) - Avoids ``IndexError`` when passing an Index or Series to ``.iloc`` with older numpy (:issue:`17193`) - Allow unicode empty strings as placeholders in multilevel columns in Python 2 (:issue:`17099`) -- Bug in :func:`Series.iloc` when used with increment or assignment and an int indexer on a ``MultiIndex`` ``Series`` causing the wrong indexes to be read from and written to (:issue:`17148`) +- Bug in :func:`Series.iloc` when used with inplace addition or assignment and an int indexer on a ``MultiIndex`` ``Series`` causing the wrong indexes to be read from and written to (:issue:`17148`) I/O ^^^ From abc45911c02825bc345ee3f6d0095368ac890d3e Mon Sep 17 00:00:00 2001 From: Andreas Berneryd Date: Sun, 20 Aug 2017 16:38:05 +0200 Subject: [PATCH 3/4] Fixed requested issues --- doc/source/whatsnew/v0.21.0.txt | 2 +- pandas/core/indexing.py | 2 +- pandas/tests/indexing/test_iloc.py | 54 ++++++++++++------------------ 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index e9e0b35857492..ab5071e8e9ff7 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -329,7 +329,7 @@ Indexing - Fixes ``DataFrame.loc`` for setting with alignment and tz-aware ``DatetimeIndex`` (:issue:`16889`) - Avoids ``IndexError`` when passing an Index or Series to ``.iloc`` with older numpy (:issue:`17193`) - Allow unicode empty strings as placeholders in multilevel columns in Python 2 (:issue:`17099`) -- Bug in :func:`Series.iloc` when used with inplace addition or assignment and an int indexer on a ``MultiIndex`` ``Series`` causing the wrong indexes to be read from and written to (:issue:`17148`) +- Bug in ``.iloc`` when used with inplace addition or assignment and an int indexer on a ``MultiIndex`` causing the wrong indexes to be read from and written to (:issue:`17148`) I/O ^^^ diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index c24eec682ed7e..757608128a73a 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -147,7 +147,7 @@ def _get_setitem_indexer(self, key): axis = self.obj._get_axis(0) - if isinstance(axis, MultiIndex) and not is_integer(key): + if isinstance(axis, MultiIndex) and self.name != 'iloc': try: return axis.get_loc(key) except Exception: diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index e8a0f82f14aa8..8a8c12b925f21 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -269,39 +269,29 @@ def test_iloc_setitem(self): expected = Series([0, 1, 0], index=[4, 5, 6]) tm.assert_series_equal(s, expected) - def test_iloc_setitem_int_multiindex_series(self): + @pytest.mark.parametrize( + "data, indexes, values, expected_k", [ + ([[1, 22, 5], [1, 33, 6]], [0, -1, 1], [2, 3, 1], [7, 10]), + ([[2, 22, 5], [2, 33, 6]], [0, -1, 1], [2, 3, 1], [7, 10]), + ([[1, 3, 7], [2, 4, 8]], [0, -1, 1], [1, 1, 10], [8, 19]), + ([[1, 11, 4], [2, 22, 5], [3, 33, 6]], [0, -1, 1], [4, 7, 10], + [8, 15, 13]) + ]) + def test_iloc_setitem_int_multiindex_series( + self, data, indexes, values, expected_k): # GH17148 - def check_scenario(data, indexes, values, expected_k): - df = pd.DataFrame( - data=data, - columns=['i', 'j', 'k']) - df.set_index(['i', 'j'], inplace=True) - - series = df.k.copy() - for i, v in zip(indexes, values): - series.iloc[i] += v - - df.k = expected_k - expected = df.k.copy() - tm.assert_series_equal(series, expected) - - check_scenario( - data=[[1, 22, 5], [1, 33, 6]], - indexes=[0, -1, 1], - values=[2, 3, 1], - expected_k=[7, 10]) - - check_scenario( - data=[[1, 3, 7], [2, 4, 8]], - indexes=[0, -1, 1], - values=[1, 1, 10], - expected_k=[8, 19]) - - check_scenario( - data=[[1, 11, 4], [2, 22, 5], [3, 33, 6]], - indexes=[0, -1, 1], - values=[4, 7, 10], - expected_k=[8, 15, 13]) + df = pd.DataFrame( + data=data, + columns=['i', 'j', 'k']) + df.set_index(['i', 'j'], inplace=True) + + series = df.k.copy() + for i, v in zip(indexes, values): + series.iloc[i] += v + + df.k = expected_k + expected = df.k.copy() + tm.assert_series_equal(series, expected) def test_iloc_setitem_list(self): From d7386f0ac0b86f129f838fc38c2b2714d488573e Mon Sep 17 00:00:00 2001 From: Andreas Berneryd Date: Sun, 20 Aug 2017 23:08:11 +0200 Subject: [PATCH 4/4] fixed test as per comments --- pandas/tests/indexing/test_iloc.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index 8a8c12b925f21..39569f0b0cb38 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -270,10 +270,15 @@ def test_iloc_setitem(self): tm.assert_series_equal(s, expected) @pytest.mark.parametrize( - "data, indexes, values, expected_k", [ - ([[1, 22, 5], [1, 33, 6]], [0, -1, 1], [2, 3, 1], [7, 10]), + 'data, indexes, values, expected_k', [ + # test without indexer value in first level of MultiIndex ([[2, 22, 5], [2, 33, 6]], [0, -1, 1], [2, 3, 1], [7, 10]), - ([[1, 3, 7], [2, 4, 8]], [0, -1, 1], [1, 1, 10], [8, 19]), + # test like code sample 1 in the issue + ([[1, 22, 555], [1, 33, 666]], [0, -1, 1], [200, 300, 100], + [755, 1066]), + # test like code sample 2 in the issue + ([[1, 3, 7], [2, 4, 8]], [0, -1, 1], [10, 10, 1000], [17, 1018]), + # test like code sample 3 in the issue ([[1, 11, 4], [2, 22, 5], [3, 33, 6]], [0, -1, 1], [4, 7, 10], [8, 15, 13]) ]) @@ -283,14 +288,14 @@ def test_iloc_setitem_int_multiindex_series( df = pd.DataFrame( data=data, columns=['i', 'j', 'k']) - df.set_index(['i', 'j'], inplace=True) + df = df.set_index(['i', 'j']) series = df.k.copy() for i, v in zip(indexes, values): series.iloc[i] += v - df.k = expected_k - expected = df.k.copy() + df['k'] = expected_k + expected = df.k tm.assert_series_equal(series, expected) def test_iloc_setitem_list(self):