Skip to content

Commit a7d1103

Browse files
jbrockmendeljreback
authored andcommitted
Frame ops prelims - de-duplicate, remove unused kwargs (#19522)
1 parent 4e1fcba commit a7d1103

File tree

6 files changed

+61
-71
lines changed

6 files changed

+61
-71
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ Numeric
582582
- Bug in :class:`Index` multiplication and division methods where operating with a ``Series`` would return an ``Index`` object instead of a ``Series`` object (:issue:`19042`)
583583
- Bug in the :class:`DataFrame` constructor in which data containing very large positive or very large negative numbers was causing ``OverflowError`` (:issue:`18584`)
584584
- Bug in :class:`Index` constructor with ``dtype='uint64'`` where int-like floats were not coerced to :class:`UInt64Index` (:issue:`18400`)
585+
- Bug in :class:`DataFrame` flex arithmetic (e.g. `df.add(other, fill_value=foo)`) with a `fill_value` other than ``None`` failed to raise ``NotImplementedError`` in corner cases where either the frame or ``other`` has length zero (:issue:`19522`)
585586

586587

587588
Indexing

pandas/core/frame.py

+18-31
Original file line numberDiff line numberDiff line change
@@ -3915,8 +3915,7 @@ def reorder_levels(self, order, axis=0):
39153915
# ----------------------------------------------------------------------
39163916
# Arithmetic / combination related
39173917

3918-
def _combine_frame(self, other, func, fill_value=None, level=None,
3919-
try_cast=True):
3918+
def _combine_frame(self, other, func, fill_value=None, level=None):
39203919
this, other = self.align(other, join='outer', level=level, copy=False)
39213920
new_index, new_columns = this.index, this.columns
39223921

@@ -3968,52 +3967,40 @@ def f(i):
39683967

39693968
def _combine_series(self, other, func, fill_value=None, axis=None,
39703969
level=None, try_cast=True):
3970+
if fill_value is not None:
3971+
raise NotImplementedError("fill_value {fill} not supported."
3972+
.format(fill=fill_value))
3973+
39713974
if axis is not None:
39723975
axis = self._get_axis_name(axis)
39733976
if axis == 'index':
3974-
return self._combine_match_index(other, func, level=level,
3975-
fill_value=fill_value,
3976-
try_cast=try_cast)
3977+
return self._combine_match_index(other, func, level=level)
39773978
else:
39783979
return self._combine_match_columns(other, func, level=level,
3979-
fill_value=fill_value,
39803980
try_cast=try_cast)
3981-
return self._combine_series_infer(other, func, level=level,
3982-
fill_value=fill_value,
3983-
try_cast=try_cast)
3984-
3985-
def _combine_series_infer(self, other, func, level=None,
3986-
fill_value=None, try_cast=True):
3987-
if len(other) == 0:
3988-
return self * np.nan
3981+
else:
3982+
if not len(other):
3983+
return self * np.nan
39893984

3990-
if len(self) == 0:
3991-
# Ambiguous case, use _series so works with DataFrame
3992-
return self._constructor(data=self._series, index=self.index,
3993-
columns=self.columns)
3985+
if not len(self):
3986+
# Ambiguous case, use _series so works with DataFrame
3987+
return self._constructor(data=self._series, index=self.index,
3988+
columns=self.columns)
39943989

3995-
return self._combine_match_columns(other, func, level=level,
3996-
fill_value=fill_value,
3997-
try_cast=try_cast)
3990+
# default axis is columns
3991+
return self._combine_match_columns(other, func, level=level,
3992+
try_cast=try_cast)
39983993

3999-
def _combine_match_index(self, other, func, level=None,
4000-
fill_value=None, try_cast=True):
3994+
def _combine_match_index(self, other, func, level=None):
40013995
left, right = self.align(other, join='outer', axis=0, level=level,
40023996
copy=False)
4003-
if fill_value is not None:
4004-
raise NotImplementedError("fill_value %r not supported." %
4005-
fill_value)
40063997
return self._constructor(func(left.values.T, right.values).T,
40073998
index=left.index, columns=self.columns,
40083999
copy=False)
40094000

4010-
def _combine_match_columns(self, other, func, level=None,
4011-
fill_value=None, try_cast=True):
4001+
def _combine_match_columns(self, other, func, level=None, try_cast=True):
40124002
left, right = self.align(other, join='outer', axis=1, level=level,
40134003
copy=False)
4014-
if fill_value is not None:
4015-
raise NotImplementedError("fill_value %r not supported" %
4016-
fill_value)
40174004

40184005
new_data = left._data.eval(func=func, other=right,
40194006
axes=[left.columns, self.index],

pandas/core/indexes/base.py

+20-27
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ def _try_get_item(x):
8080
return x
8181

8282

83+
def _make_invalid_op(name):
84+
"""
85+
Return a binary method that always raises a TypeError.
86+
87+
Parameters
88+
----------
89+
name : str
90+
91+
Returns
92+
-------
93+
invalid_op : function
94+
"""
95+
def invalid_op(self, other=None):
96+
raise TypeError("cannot perform {name} with this index type: "
97+
"{typ}".format(name=name, typ=type(self)))
98+
99+
invalid_op.__name__ = name
100+
return invalid_op
101+
102+
83103
class InvalidIndexError(Exception):
84104
pass
85105

@@ -3916,30 +3936,12 @@ def _evaluate_compare(self, other):
39163936
@classmethod
39173937
def _add_numeric_methods_add_sub_disabled(cls):
39183938
""" add in the numeric add/sub methods to disable """
3919-
3920-
def _make_invalid_op(name):
3921-
def invalid_op(self, other=None):
3922-
raise TypeError("cannot perform {name} with this index type: "
3923-
"{typ}".format(name=name, typ=type(self)))
3924-
3925-
invalid_op.__name__ = name
3926-
return invalid_op
3927-
39283939
cls.__add__ = cls.__radd__ = __iadd__ = _make_invalid_op('__add__') # noqa
39293940
cls.__sub__ = __isub__ = _make_invalid_op('__sub__') # noqa
39303941

39313942
@classmethod
39323943
def _add_numeric_methods_disabled(cls):
39333944
""" add in numeric methods to disable other than add/sub """
3934-
3935-
def _make_invalid_op(name):
3936-
def invalid_op(self, other=None):
3937-
raise TypeError("cannot perform {name} with this index type: "
3938-
"{typ}".format(name=name, typ=type(self)))
3939-
3940-
invalid_op.__name__ = name
3941-
return invalid_op
3942-
39433945
cls.__pow__ = cls.__rpow__ = _make_invalid_op('__pow__')
39443946
cls.__mul__ = cls.__rmul__ = _make_invalid_op('__mul__')
39453947
cls.__floordiv__ = cls.__rfloordiv__ = _make_invalid_op('__floordiv__')
@@ -4147,15 +4149,6 @@ def logical_func(self, *args, **kwargs):
41474149
@classmethod
41484150
def _add_logical_methods_disabled(cls):
41494151
""" add in logical methods to disable """
4150-
4151-
def _make_invalid_op(name):
4152-
def invalid_op(self, other=None):
4153-
raise TypeError("cannot perform {name} with this index type: "
4154-
"{typ}".format(name=name, typ=type(self)))
4155-
4156-
invalid_op.__name__ = name
4157-
return invalid_op
4158-
41594152
cls.all = _make_invalid_op('all')
41604153
cls.any = _make_invalid_op('any')
41614154

pandas/core/ops.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1106,12 +1106,13 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
11061106
if isinstance(other, ABCDataFrame): # Another DataFrame
11071107
return self._combine_frame(other, na_op, fill_value, level)
11081108
elif isinstance(other, ABCSeries):
1109-
return self._combine_series(other, na_op, fill_value, axis, level)
1109+
return self._combine_series(other, na_op, fill_value, axis, level,
1110+
try_cast=True)
11101111
else:
11111112
if fill_value is not None:
11121113
self = self.fillna(fill_value)
11131114

1114-
return self._combine_const(other, na_op)
1115+
return self._combine_const(other, na_op, try_cast=True)
11151116

11161117
f.__name__ = name
11171118

@@ -1172,7 +1173,8 @@ def f(self, other):
11721173
if isinstance(other, ABCDataFrame): # Another DataFrame
11731174
return self._compare_frame(other, func, str_rep)
11741175
elif isinstance(other, ABCSeries):
1175-
return self._combine_series_infer(other, func, try_cast=False)
1176+
return self._combine_series(other, func,
1177+
axis=None, try_cast=False)
11761178
else:
11771179

11781180
# straight boolean comparisons we want to allow all columns

pandas/core/sparse/frame.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,7 @@ def xs(self, key, axis=0, copy=False):
540540
# ----------------------------------------------------------------------
541541
# Arithmetic-related methods
542542

543-
def _combine_frame(self, other, func, fill_value=None, level=None,
544-
try_cast=True):
543+
def _combine_frame(self, other, func, fill_value=None, level=None):
545544
this, other = self.align(other, join='outer', level=level, copy=False)
546545
new_index, new_columns = this.index, this.columns
547546

@@ -584,12 +583,9 @@ def _combine_frame(self, other, func, fill_value=None, level=None,
584583
default_fill_value=new_fill_value
585584
).__finalize__(self)
586585

587-
def _combine_match_index(self, other, func, level=None, fill_value=None,
588-
try_cast=True):
586+
def _combine_match_index(self, other, func, level=None):
589587
new_data = {}
590588

591-
if fill_value is not None:
592-
raise NotImplementedError("'fill_value' argument is not supported")
593589
if level is not None:
594590
raise NotImplementedError("'level' argument is not supported")
595591

@@ -605,6 +601,7 @@ def _combine_match_index(self, other, func, level=None, fill_value=None,
605601
new_data[col] = func(series.values, other.values)
606602

607603
# fill_value is a function of our operator
604+
fill_value = None
608605
if isna(other.fill_value) or isna(self.default_fill_value):
609606
fill_value = np.nan
610607
else:
@@ -615,15 +612,12 @@ def _combine_match_index(self, other, func, level=None, fill_value=None,
615612
new_data, index=new_index, columns=self.columns,
616613
default_fill_value=fill_value).__finalize__(self)
617614

618-
def _combine_match_columns(self, other, func, level=None, fill_value=None,
619-
try_cast=True):
615+
def _combine_match_columns(self, other, func, level=None, try_cast=True):
620616
# patched version of DataFrame._combine_match_columns to account for
621617
# NumPy circumventing __rsub__ with float64 types, e.g.: 3.0 - series,
622618
# where 3.0 is numpy.float64 and series is a SparseSeries. Still
623619
# possible for this to happen, which is bothersome
624620

625-
if fill_value is not None:
626-
raise NotImplementedError("'fill_value' argument is not supported")
627621
if level is not None:
628622
raise NotImplementedError("'level' argument is not supported")
629623

pandas/tests/frame/test_operators.py

+13
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,19 @@ def test_arith_flex_frame(self):
381381
with tm.assert_raises_regex(NotImplementedError, 'fill_value'):
382382
self.frame.add(self.frame.iloc[0], axis='index', fill_value=3)
383383

384+
def test_arith_flex_zero_len_raises(self):
385+
# GH#19522 passing fill_value to frame flex arith methods should
386+
# raise even in the zero-length special cases
387+
ser_len0 = pd.Series([])
388+
df_len0 = pd.DataFrame([], columns=['A', 'B'])
389+
df = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B'])
390+
391+
with tm.assert_raises_regex(NotImplementedError, 'fill_value'):
392+
df.add(ser_len0, fill_value='E')
393+
394+
with tm.assert_raises_regex(NotImplementedError, 'fill_value'):
395+
df_len0.sub(df['A'], axis=None, fill_value=3)
396+
384397
def test_binary_ops_align(self):
385398

386399
# test aligning binary ops

0 commit comments

Comments
 (0)