Skip to content

BUG: Fix alignment of list-like objects for operations with DataFrame #17926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,7 @@ Reshaping
- Bug in :func:`pivot_table` where the result's columns did not preserve the categorical dtype of ``columns`` when ``dropna`` was ``False`` (:issue:`17842`)
- Bug in ``DataFrame.drop_duplicates`` where dropping with non-unique column names raised a ``ValueError`` (:issue:`17836`)
- Bug in :func:`unstack` which, when called on a list of levels, would discard the ``fillna`` argument (:issue:`13971`)
- 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`)

Numeric
^^^^^^^
Expand Down
27 changes: 15 additions & 12 deletions pandas/core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from pandas.core.dtypes.cast import maybe_upcast_putmask, find_common_type
from pandas.core.dtypes.generic import (
ABCSeries,
ABCDataFrame,
ABCIndex,
ABCPeriodIndex,
ABCDateOffset)
Expand Down Expand Up @@ -710,7 +711,7 @@ def safe_na_op(lvalues, rvalues):

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

if isinstance(right, pd.DataFrame):
if isinstance(right, ABCDataFrame):
return NotImplemented

left, right = _align_method_SERIES(left, right)
Expand Down Expand Up @@ -834,7 +835,7 @@ def wrapper(self, other, axis=None):
raise ValueError(msg)
return self._constructor(na_op(self.values, other.values),
index=self.index, name=name)
elif isinstance(other, pd.DataFrame): # pragma: no cover
elif isinstance(other, ABCDataFrame): # pragma: no cover
return NotImplemented
elif isinstance(other, (np.ndarray, pd.Index)):
# do not check length of zerodim array
Expand Down Expand Up @@ -941,7 +942,7 @@ def wrapper(self, other):
return filler(self._constructor(na_op(self.values, other.values),
index=self.index, name=name))

elif isinstance(other, pd.DataFrame):
elif isinstance(other, ABCDataFrame):
return NotImplemented

else:
Expand Down Expand Up @@ -1165,10 +1166,7 @@ def to_series(right):
right = left._constructor_sliced(right, index=left.columns)
return right

if isinstance(right, (list, tuple)):
right = to_series(right)

elif isinstance(right, np.ndarray) and right.ndim: # skips np scalar
if isinstance(right, np.ndarray):

if right.ndim == 1:
right = to_series(right)
Expand All @@ -1182,10 +1180,15 @@ def to_series(right):

right = left._constructor(right, index=left.index,
columns=left.columns)
else:
elif right.ndim > 2:
raise ValueError('Unable to coerce to Series/DataFrame, dim '
'must be <= 2: {dim}'.format(dim=right.shape))

elif (is_list_like(right) and
not isinstance(right, (ABCSeries, ABCDataFrame))):
# GH17901
right = to_series(right)

return right


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

other = _align_method_FRAME(self, other, axis)

if isinstance(other, pd.DataFrame): # Another DataFrame
if isinstance(other, ABCDataFrame): # Another DataFrame
return self._combine_frame(other, na_op, fill_value, level)
elif isinstance(other, ABCSeries):
return self._combine_series(other, na_op, fill_value, axis, level)
Expand Down Expand Up @@ -1300,7 +1303,7 @@ def f(self, other, axis=default_axis, level=None):

other = _align_method_FRAME(self, other, axis)

if isinstance(other, pd.DataFrame): # Another DataFrame
if isinstance(other, ABCDataFrame): # Another DataFrame
return self._flex_compare_frame(other, na_op, str_rep, level,
try_cast=False)

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

if isinstance(other, self._constructor):
return self._compare_constructor(other, na_op, try_cast=False)
elif isinstance(other, (self._constructor_sliced, pd.DataFrame,
elif isinstance(other, (self._constructor_sliced, ABCDataFrame,
ABCSeries)):
raise Exception("input needs alignment for this object [{object}]"
.format(object=self._constructor))
Expand Down
20 changes: 15 additions & 5 deletions pandas/tests/frame/test_operators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

from __future__ import print_function

from collections import deque
from datetime import datetime
import operator

Expand All @@ -10,7 +10,7 @@
from numpy import nan, random
import numpy as np

from pandas.compat import lrange
from pandas.compat import lrange, range
from pandas import compat
from pandas import (DataFrame, Series, MultiIndex, Timestamp,
date_range)
Expand Down Expand Up @@ -749,6 +749,15 @@ def test_arith_non_pandas_object(self):
added = DataFrame(df.values + val3, index=df.index, columns=df.columns)
assert_frame_equal(df.add(val3), added)

@pytest.mark.parametrize('values', [[1, 2], (1, 2), np.array([1, 2]),
range(1, 3), deque([1, 2])])
def test_arith_alignment_non_pandas_object(self, values):
# GH 17901
df = DataFrame({'A': [1, 1], 'B': [1, 1]})
expected = DataFrame({'A': [2, 2], 'B': [3, 3]})
result = df + values
assert_frame_equal(result, expected)

def test_combineFrame(self):
frame_copy = self.frame.reindex(self.frame.index[::2])

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

align = pd.core.ops._align_method_FRAME

for val in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype=np.int64)]:
for val in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype=np.int64),
range(1, 4)]:

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

# length mismatch
msg = 'Unable to coerce to Series, length must be 3: given 2'
for val in [[1, 2], (1, 2), np.array([1, 2])]:
for val in [[1, 2], (1, 2), np.array([1, 2]), range(1, 3)]:

with tm.assert_raises_regex(ValueError, msg):
align(df, val, 'index')

Expand Down