Skip to content

Commit cc722a8

Browse files
committed
ENH: align can accept Series, add axis argument for DataFrame, GH #461
1 parent 0085460 commit cc722a8

File tree

2 files changed

+87
-47
lines changed

2 files changed

+87
-47
lines changed

pandas/core/frame.py

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,37 +1170,49 @@ def xs(self, key, axis=0, copy=True):
11701170
#----------------------------------------------------------------------
11711171
# Reindexing and alignment
11721172

1173-
def align(self, other, join='outer', copy=True):
1173+
def align(self, other, join='outer', axis=None, copy=True):
11741174
"""
11751175
Align two DataFrame object on their index and columns with the specified
11761176
join method for each axis Index
11771177
11781178
Parameters
11791179
----------
1180-
other : DataFrame
1180+
other : DataFrame or Series
11811181
join : {'outer', 'inner', 'left', 'right'}, default 'outer'
1182+
axis : {0, 1, None}, default None
1183+
Align on index (0), columns (1), or both (None)
11821184
11831185
Returns
11841186
-------
11851187
(left, right) : (Series, Series)
11861188
Aligned Series
11871189
"""
1188-
if self.index.equals(other.index):
1189-
join_index = self.index
1190-
ilidx, iridx = None, None
1191-
else:
1192-
join_index, ilidx, iridx = self.index.join(other.index, how=join,
1193-
return_indexers=True)
1194-
1195-
if self.columns.equals(other.columns):
1196-
join_columns = self.columns
1197-
clidx, cridx = None, None
1198-
else:
1199-
join_columns, clidx, cridx = self.columns.join(other.columns,
1200-
how=join,
1190+
if isinstance(other, DataFrame):
1191+
return self._align_frame(other, join=join, axis=axis, copy=copy)
1192+
elif isinstance(other, Series):
1193+
return self._align_series(other, join=join, axis=axis, copy=copy)
1194+
else: # pragma: no cover
1195+
raise TypeError('unsupported type: %s' % type(other))
1196+
1197+
def _align_frame(self, other, join='outer', axis=None, copy=True):
1198+
# defaults
1199+
join_index = self.index
1200+
join_columns = self.columns
1201+
ilidx, iridx = None, None
1202+
clidx, cridx = None, None
1203+
1204+
if axis is None or axis == 0:
1205+
if not self.index.equals(other.index):
1206+
join_index, ilidx, iridx = self.index.join(other.index, how=join,
12011207
return_indexers=True)
12021208

1203-
def _align_frame(frame, row_idx, col_idx):
1209+
if axis is None or axis == 1:
1210+
if not self.columns.equals(other.columns):
1211+
join_columns, clidx, cridx = self.columns.join(other.columns,
1212+
how=join,
1213+
return_indexers=True)
1214+
1215+
def _align(frame, row_idx, col_idx):
12041216
new_data = frame._data
12051217
if row_idx is not None:
12061218
new_data = new_data.reindex_indexer(join_index, row_idx, axis=1)
@@ -1214,10 +1226,40 @@ def _align_frame(frame, row_idx, col_idx):
12141226

12151227
return DataFrame(new_data)
12161228

1217-
left = _align_frame(self, ilidx, clidx)
1218-
right = _align_frame(other, iridx, cridx)
1229+
left = _align(self, ilidx, clidx)
1230+
right = _align(other, iridx, cridx)
12191231
return left, right
12201232

1233+
def _align_series(self, other, join='outer', axis=None, copy=True):
1234+
fdata = self._data
1235+
if axis == 0:
1236+
join_index = self.index
1237+
lidx, ridx = None, None
1238+
if not self.index.equals(other.index):
1239+
join_index, lidx, ridx = self.index.join(other.index, how=join,
1240+
return_indexers=True)
1241+
1242+
if lidx is not None:
1243+
fdata = fdata.reindex_indexer(join_index, lidx, axis=1)
1244+
elif axis == 1:
1245+
join_index = self.columns
1246+
lidx, ridx = None, None
1247+
if not self.columns.equals(other.index):
1248+
join_index, lidx, ridx = self.columns.join(other.index, how=join,
1249+
return_indexers=True)
1250+
1251+
if lidx is not None:
1252+
fdata = fdata.reindex_items(join_index)
1253+
else:
1254+
raise ValueError('Must specify axis=0 or 1')
1255+
1256+
if copy and fdata is self._data:
1257+
fdata = fdata.copy()
1258+
1259+
left_result = DataFrame(fdata)
1260+
right_result = other if ridx is None else other.reindex(join_index)
1261+
return left_result, right_result
1262+
12211263
def reindex(self, index=None, columns=None, method=None, copy=True):
12221264
"""Conform Series to new index with optional filling logic, placing
12231265
NA/NaN in locations having no value in the previous index. A new object
@@ -1776,35 +1818,21 @@ def _combine_series_infer(self, other, func, fill_value=None):
17761818
return self._combine_match_columns(other, func, fill_value)
17771819

17781820
def _combine_match_index(self, other, func, fill_value=None):
1779-
new_index = self.index.union(other.index)
1780-
values = self.values
1781-
other_vals = other.values
1782-
1783-
# Operate row-wise
1784-
if not other.index.equals(new_index):
1785-
other_vals = other.reindex(new_index).values
1786-
1787-
if not self.index.equals(new_index):
1788-
values = self.reindex(new_index).values
1789-
1821+
left, right = self.align(other, join='outer', axis=0, copy=False)
17901822
if fill_value is not None:
17911823
raise NotImplementedError
1792-
1793-
return self._constructor(func(values.T, other_vals).T, index=new_index,
1824+
return self._constructor(func(left.values.T, right.values).T,
1825+
index=left.index,
17941826
columns=self.columns, copy=False)
17951827

17961828
def _combine_match_columns(self, other, func, fill_value=None):
1797-
newCols = self.columns.union(other.index)
1798-
1799-
# Operate column-wise
1800-
this = self.reindex(columns=newCols)
1801-
other = other.reindex(newCols).values
1802-
1829+
left, right = self.align(other, join='outer', axis=1, copy=False)
18031830
if fill_value is not None:
18041831
raise NotImplementedError
18051832

1806-
return self._constructor(func(this.values, other), index=self.index,
1807-
columns=newCols, copy=False)
1833+
return self._constructor(func(left.values, right.values),
1834+
index=self.index,
1835+
columns=left.columns, copy=False)
18081836

18091837
def _combine_const(self, other, func):
18101838
if not self:
@@ -1847,13 +1875,8 @@ def combine(self, other, func, fill_value=None):
18471875
if not self:
18481876
return other.copy()
18491877

1850-
new_index = self.index
1851-
this = self
1852-
1853-
if not self.index.equals(other.index):
1854-
new_index = self.index + other.index
1855-
this = self.reindex(new_index)
1856-
other = other.reindex(new_index)
1878+
this, other = self.align(other, axis=0, copy=False)
1879+
new_index = this.index
18571880

18581881
# sorts if possible
18591882
new_columns = this.columns.union(other.columns)

pandas/tests/test_frame.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2626,13 +2626,30 @@ def test_add_index(self):
26262626
self.assertRaises(Exception, df.set_index, 'A')
26272627

26282628
def test_align(self):
2629-
26302629
af, bf = self.frame.align(self.frame)
26312630
self.assert_(af._data is not self.frame._data)
26322631

26332632
af, bf = self.frame.align(self.frame, copy=False)
26342633
self.assert_(af._data is self.frame._data)
26352634

2635+
# axis = 0
2636+
other = self.frame.ix[:-5, :3]
2637+
af, bf = self.frame.align(other, axis=0)
2638+
self.assert_(bf.columns.equals(other.columns))
2639+
2640+
af, bf = self.frame.align(other, join='right', axis=0)
2641+
self.assert_(bf.columns.equals(other.columns))
2642+
self.assert_(bf.index.equals(other.index))
2643+
self.assert_(af.index.equals(other.index))
2644+
2645+
# axis = 1
2646+
af, bf = self.frame.align(other, axis=1)
2647+
self.assert_(bf.columns.equals(self.frame.columns))
2648+
self.assert_(bf.index.equals(other.index))
2649+
2650+
af, bf = self.frame.align(other, join='inner', axis=1)
2651+
self.assert_(bf.columns.equals(other.columns))
2652+
26362653
#----------------------------------------------------------------------
26372654
# Transposing
26382655

0 commit comments

Comments
 (0)