Skip to content

Commit cd477c8

Browse files
jschendeljreback
authored andcommitted
BUG: Fix alignment of list-like objects (#17926)
1 parent a441d23 commit cd477c8

File tree

3 files changed

+31
-17
lines changed

3 files changed

+31
-17
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ Reshaping
10491049
- Bug in :func:`pivot_table` where the result's columns did not preserve the categorical dtype of ``columns`` when ``dropna`` was ``False`` (:issue:`17842`)
10501050
- Bug in ``DataFrame.drop_duplicates`` where dropping with non-unique column names raised a ``ValueError`` (:issue:`17836`)
10511051
- Bug in :func:`unstack` which, when called on a list of levels, would discard the ``fillna`` argument (:issue:`13971`)
1052+
- Bug in the alignment of ``range`` objects and other list-likes with ``DataFrame`` leading to operations being performed row-wise instead of column-wise (:issue:`17901`)
10521053

10531054
Numeric
10541055
^^^^^^^

pandas/core/ops.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from pandas.core.dtypes.cast import maybe_upcast_putmask, find_common_type
3737
from pandas.core.dtypes.generic import (
3838
ABCSeries,
39+
ABCDataFrame,
3940
ABCIndex,
4041
ABCPeriodIndex,
4142
ABCDateOffset)
@@ -710,7 +711,7 @@ def safe_na_op(lvalues, rvalues):
710711

711712
def wrapper(left, right, name=name, na_op=na_op):
712713

713-
if isinstance(right, pd.DataFrame):
714+
if isinstance(right, ABCDataFrame):
714715
return NotImplemented
715716

716717
left, right = _align_method_SERIES(left, right)
@@ -834,7 +835,7 @@ def wrapper(self, other, axis=None):
834835
raise ValueError(msg)
835836
return self._constructor(na_op(self.values, other.values),
836837
index=self.index, name=name)
837-
elif isinstance(other, pd.DataFrame): # pragma: no cover
838+
elif isinstance(other, ABCDataFrame): # pragma: no cover
838839
return NotImplemented
839840
elif isinstance(other, (np.ndarray, pd.Index)):
840841
# do not check length of zerodim array
@@ -941,7 +942,7 @@ def wrapper(self, other):
941942
return filler(self._constructor(na_op(self.values, other.values),
942943
index=self.index, name=name))
943944

944-
elif isinstance(other, pd.DataFrame):
945+
elif isinstance(other, ABCDataFrame):
945946
return NotImplemented
946947

947948
else:
@@ -1165,10 +1166,7 @@ def to_series(right):
11651166
right = left._constructor_sliced(right, index=left.columns)
11661167
return right
11671168

1168-
if isinstance(right, (list, tuple)):
1169-
right = to_series(right)
1170-
1171-
elif isinstance(right, np.ndarray) and right.ndim: # skips np scalar
1169+
if isinstance(right, np.ndarray):
11721170

11731171
if right.ndim == 1:
11741172
right = to_series(right)
@@ -1182,10 +1180,15 @@ def to_series(right):
11821180

11831181
right = left._constructor(right, index=left.index,
11841182
columns=left.columns)
1185-
else:
1183+
elif right.ndim > 2:
11861184
raise ValueError('Unable to coerce to Series/DataFrame, dim '
11871185
'must be <= 2: {dim}'.format(dim=right.shape))
11881186

1187+
elif (is_list_like(right) and
1188+
not isinstance(right, (ABCSeries, ABCDataFrame))):
1189+
# GH17901
1190+
right = to_series(right)
1191+
11891192
return right
11901193

11911194

@@ -1252,7 +1255,7 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
12521255

12531256
other = _align_method_FRAME(self, other, axis)
12541257

1255-
if isinstance(other, pd.DataFrame): # Another DataFrame
1258+
if isinstance(other, ABCDataFrame): # Another DataFrame
12561259
return self._combine_frame(other, na_op, fill_value, level)
12571260
elif isinstance(other, ABCSeries):
12581261
return self._combine_series(other, na_op, fill_value, axis, level)
@@ -1300,7 +1303,7 @@ def f(self, other, axis=default_axis, level=None):
13001303

13011304
other = _align_method_FRAME(self, other, axis)
13021305

1303-
if isinstance(other, pd.DataFrame): # Another DataFrame
1306+
if isinstance(other, ABCDataFrame): # Another DataFrame
13041307
return self._flex_compare_frame(other, na_op, str_rep, level,
13051308
try_cast=False)
13061309

@@ -1318,7 +1321,7 @@ def f(self, other, axis=default_axis, level=None):
13181321
def _comp_method_FRAME(func, name, str_rep, masker=False):
13191322
@Appender('Wrapper for comparison method {name}'.format(name=name))
13201323
def f(self, other):
1321-
if isinstance(other, pd.DataFrame): # Another DataFrame
1324+
if isinstance(other, ABCDataFrame): # Another DataFrame
13221325
return self._compare_frame(other, func, str_rep)
13231326
elif isinstance(other, ABCSeries):
13241327
return self._combine_series_infer(other, func, try_cast=False)
@@ -1411,7 +1414,7 @@ def f(self, other, axis=None):
14111414

14121415
if isinstance(other, self._constructor):
14131416
return self._compare_constructor(other, na_op, try_cast=False)
1414-
elif isinstance(other, (self._constructor_sliced, pd.DataFrame,
1417+
elif isinstance(other, (self._constructor_sliced, ABCDataFrame,
14151418
ABCSeries)):
14161419
raise Exception("input needs alignment for this object [{object}]"
14171420
.format(object=self._constructor))

pandas/tests/frame/test_operators.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
from __future__ import print_function
4-
4+
from collections import deque
55
from datetime import datetime
66
import operator
77

@@ -10,7 +10,7 @@
1010
from numpy import nan, random
1111
import numpy as np
1212

13-
from pandas.compat import lrange
13+
from pandas.compat import lrange, range
1414
from pandas import compat
1515
from pandas import (DataFrame, Series, MultiIndex, Timestamp,
1616
date_range)
@@ -749,6 +749,15 @@ def test_arith_non_pandas_object(self):
749749
added = DataFrame(df.values + val3, index=df.index, columns=df.columns)
750750
assert_frame_equal(df.add(val3), added)
751751

752+
@pytest.mark.parametrize('values', [[1, 2], (1, 2), np.array([1, 2]),
753+
range(1, 3), deque([1, 2])])
754+
def test_arith_alignment_non_pandas_object(self, values):
755+
# GH 17901
756+
df = DataFrame({'A': [1, 1], 'B': [1, 1]})
757+
expected = DataFrame({'A': [2, 2], 'B': [3, 3]})
758+
result = df + values
759+
assert_frame_equal(result, expected)
760+
752761
def test_combineFrame(self):
753762
frame_copy = self.frame.reindex(self.frame.index[::2])
754763

@@ -1200,8 +1209,8 @@ def test_alignment_non_pandas(self):
12001209
df = pd.DataFrame(np.random.randn(3, 3), index=index, columns=columns)
12011210

12021211
align = pd.core.ops._align_method_FRAME
1203-
1204-
for val in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype=np.int64)]:
1212+
for val in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype=np.int64),
1213+
range(1, 4)]:
12051214

12061215
tm.assert_series_equal(align(df, val, 'index'),
12071216
Series([1, 2, 3], index=df.index))
@@ -1210,7 +1219,8 @@ def test_alignment_non_pandas(self):
12101219

12111220
# length mismatch
12121221
msg = 'Unable to coerce to Series, length must be 3: given 2'
1213-
for val in [[1, 2], (1, 2), np.array([1, 2])]:
1222+
for val in [[1, 2], (1, 2), np.array([1, 2]), range(1, 3)]:
1223+
12141224
with tm.assert_raises_regex(ValueError, msg):
12151225
align(df, val, 'index')
12161226

0 commit comments

Comments
 (0)