Skip to content

Commit 163cc8a

Browse files
changhiskhanwesm
authored andcommitted
BUG: align input on setting via ix #1630
1 parent 667220a commit 163cc8a

File tree

4 files changed

+150
-5
lines changed

4 files changed

+150
-5
lines changed

RELEASE.rst

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pandas 0.9.0
7070
a class of TypeErrors that was occurring in code where the dtype of a
7171
column would depend on the presence of data or not (e.g. a SQL query having
7272
results) (#1783)
73+
- Setting parts of DataFrame/Panel using ix now aligns input Series/DataFrame
74+
(#1630)
7375

7476
**Bug fixes**
7577

pandas/core/indexing.py

+67-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
# "null slice"
1111
_NS = slice(None, None)
1212

13+
def _is_sequence(x):
14+
try:
15+
iter(x)
16+
assert(not isinstance(x, basestring))
17+
return True
18+
except Exception:
19+
return False
1320

1421
class IndexingError(Exception):
1522
pass
@@ -82,7 +89,7 @@ def _convert_tuple(self, key):
8289
return tuple(keyidx)
8390

8491
def _setitem_with_indexer(self, indexer, value):
85-
from pandas.core.frame import DataFrame
92+
from pandas.core.frame import DataFrame, Series
8693

8794
# also has the side effect of consolidating in-place
8895

@@ -92,6 +99,9 @@ def _setitem_with_indexer(self, indexer, value):
9299
if not isinstance(indexer, tuple):
93100
indexer = self._tuplify(indexer)
94101

102+
if isinstance(value, Series):
103+
value = self._align_series(indexer, value)
104+
95105
het_axis = self.obj._het_axis
96106
het_idx = indexer[het_axis]
97107

@@ -117,13 +127,66 @@ def _setitem_with_indexer(self, indexer, value):
117127
if isinstance(indexer, tuple):
118128
indexer = _maybe_convert_ix(*indexer)
119129

130+
if isinstance(value, Series):
131+
value = self._align_series(indexer, value)
132+
120133
if isinstance(value, DataFrame):
121-
value = value.values
122-
if not isinstance(self.obj, DataFrame):
123-
value = value.T
134+
value = self._align_frame(indexer, value)
124135

125136
self.obj.values[indexer] = value
126137

138+
def _align_series(self, indexer, ser):
139+
# indexer to assign Series can be tuple or scalar
140+
if isinstance(indexer, tuple):
141+
for i, idx in enumerate(indexer):
142+
ax = self.obj.axes[i]
143+
if _is_sequence(idx) or isinstance(idx, slice):
144+
new_ix = ax[idx]
145+
if ser.index.equals(new_ix):
146+
return ser.values.copy()
147+
return ser.reindex(new_ix).values
148+
149+
elif np.isscalar(indexer):
150+
if ser.index.equals(self.obj.index):
151+
return ser.values.copy()
152+
return ser.reindex(self.obj.index).values
153+
154+
raise ValueError('Incompatible indexer with Series')
155+
156+
def _align_frame(self, indexer, df):
157+
from pandas import DataFrame
158+
is_frame = isinstance(self.obj, DataFrame)
159+
if not is_frame:
160+
df = df.T
161+
if isinstance(indexer, tuple):
162+
idx, cols = None, None
163+
for i, ix in enumerate(indexer):
164+
ax = self.obj.axes[i]
165+
if _is_sequence(ix) or isinstance(ix, slice):
166+
if idx is None:
167+
idx = ax[ix]
168+
elif cols is None:
169+
cols = ax[ix]
170+
else:
171+
break
172+
173+
if idx is not None and cols is not None:
174+
if df.index.equals(idx) and df.columns.equals(cols):
175+
val = df.copy().values
176+
else:
177+
val = df.reindex(idx, columns=cols).values
178+
return val
179+
180+
elif np.isscalar(indexer) and not is_frame:
181+
idx = self.obj.axes[1]
182+
cols = self.obj.axes[2]
183+
184+
if idx.equals(df.index) and cols.equals(df.columns):
185+
return df.copy().values
186+
return df.reindex(idx, columns=cols).values
187+
188+
raise ValueError('Incompatible indexer with DataFrame')
189+
127190
def _getitem_tuple(self, tup):
128191
try:
129192
return self._getitem_lowerdim(tup)

pandas/tests/test_frame.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,40 @@ def test_setitem_fancy_mixed_2d(self):
686686

687687
assert_frame_equal(df, expected)
688688

689+
def test_ix_align(self):
690+
b = Series(randn(10))
691+
b.sort()
692+
df_orig = DataFrame(randn(10, 4))
693+
df = df_orig.copy()
694+
695+
df.ix[:, 0] = b
696+
assert_series_equal(df.ix[:, 0].reindex(b.index), b)
697+
698+
dft = df_orig.T
699+
dft.ix[0, :] = b
700+
assert_series_equal(dft.ix[0, :].reindex(b.index), b)
701+
702+
df = df_orig.copy()
703+
df.ix[:5, 0] = b
704+
s = df.ix[:5, 0]
705+
assert_series_equal(s, b.reindex(s.index))
706+
707+
dft = df_orig.T
708+
dft.ix[0, :5] = b
709+
s = dft.ix[0, :5]
710+
assert_series_equal(s, b.reindex(s.index))
711+
712+
df = df_orig.copy()
713+
idx = [0, 1, 3, 5]
714+
df.ix[idx, 0] = b
715+
s = df.ix[idx, 0]
716+
assert_series_equal(s, b.reindex(s.index))
717+
718+
dft = df_orig.T
719+
dft.ix[0, idx] = b
720+
s = dft.ix[0, idx]
721+
assert_series_equal(s, b.reindex(s.index))
722+
689723
def test_getitem_setitem_non_ix_labels(self):
690724
df = tm.makeTimeDataFrame()
691725

@@ -976,7 +1010,7 @@ def test_setitem_single_column_mixed(self):
9761010

9771011
def test_setitem_frame(self):
9781012
piece = self.frame.ix[:2, ['A', 'B']]
979-
self.frame.ix[-2:, ['A', 'B']] = piece
1013+
self.frame.ix[-2:, ['A', 'B']] = piece.values
9801014
assert_almost_equal(self.frame.ix[-2:, ['A', 'B']].values,
9811015
piece.values)
9821016

@@ -985,6 +1019,14 @@ def test_setitem_frame(self):
9851019
key = (slice(-2, None), ['A', 'B'])
9861020
self.assertRaises(ValueError, f, key, piece)
9871021

1022+
def test_setitem_frame_align(self):
1023+
piece = self.frame.ix[:2, ['A', 'B']]
1024+
piece.index = self.frame.index[-2:]
1025+
piece.columns = ['A', 'B']
1026+
self.frame.ix[-2:, ['A', 'B']] = piece
1027+
assert_almost_equal(self.frame.ix[-2:, ['A', 'B']].values,
1028+
piece.values)
1029+
9881030
def test_setitem_fancy_exceptions(self):
9891031
pass
9901032

pandas/tests/test_panel.py

+38
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,44 @@ def test_ix_setitem_slice_dataframe(self):
583583

584584
assert_frame_equal(a.ix[:, 22, [111, 333]], b)
585585

586+
def test_ix_align(self):
587+
from pandas import Series
588+
b = Series(np.random.randn(10))
589+
b.sort()
590+
df_orig = Panel(np.random.randn(3, 10, 2))
591+
df = df_orig.copy()
592+
593+
df.ix[0, :, 0] = b
594+
assert_series_equal(df.ix[0, :, 0].reindex(b.index), b)
595+
596+
df = df_orig.swapaxes(0, 1)
597+
df.ix[:, 0, 0] = b
598+
assert_series_equal(df.ix[:, 0, 0].reindex(b.index), b)
599+
600+
df = df_orig.swapaxes(1, 2)
601+
df.ix[0, 0, :] = b
602+
assert_series_equal(df.ix[0, 0, :].reindex(b.index), b)
603+
604+
def test_ix_frame_align(self):
605+
from pandas import DataFrame
606+
df = DataFrame(np.random.randn(2, 10))
607+
df.sort_index(inplace=True)
608+
p_orig = Panel(np.random.randn(3, 10, 2))
609+
610+
p = p_orig.copy()
611+
p.ix[0, :, :] = df
612+
out = p.ix[0, :, :].T.reindex(df.index, columns=df.columns)
613+
assert_frame_equal(out, df)
614+
615+
p = p_orig.copy()
616+
p.ix[0] = df
617+
out = p.ix[0].T.reindex(df.index, columns=df.columns)
618+
assert_frame_equal(out, df)
619+
620+
p = p_orig.copy()
621+
p.ix[0, [0, 1, 3, 5], -2:] = df
622+
out = p.ix[0, [0, 1, 3, 5], -2:]
623+
assert_frame_equal(out, df.T.reindex([0, 1, 3, 5], p.minor_axis[-2:]))
586624

587625
def _check_view(self, indexer, comp):
588626
cp = self.panel.copy()

0 commit comments

Comments
 (0)