diff --git a/pandas/core/frame.py b/pandas/core/frame.py index c12208db983e2..b3b9e53f48271 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -77,6 +77,7 @@ ABCIndexClass, ABCMultiIndex, ABCSeries, + ABCSparseDataFrame, ) from pandas.core.dtypes.missing import isna, notna @@ -5334,6 +5335,15 @@ def _combine_frame(self, other, func, fill_value=None, level=None): # overhead if possible by doing this check once _arith_op = func + elif isinstance(self, ABCSparseDataFrame): + + def _arith_op(left, right): + # wrap the non-sparse case below with to_dense and to_sparse + dleft = left.to_dense() + dright = right.to_dense() + result = dleft._binop(dright, func, fill_value=fill_value) + return result.to_sparse(fill_value=left.fill_value) + else: def _arith_op(left, right): @@ -5356,7 +5366,11 @@ def _combine_match_index(self, other, func, level=None): left, right = self.align(other, join="outer", axis=0, level=level, copy=False) # at this point we have `left.index.equals(right.index)` - if left._is_mixed_type or right._is_mixed_type: + if ( + left._is_mixed_type + or right._is_mixed_type + or isinstance(self, ABCSparseDataFrame) + ): # operate column-wise; avoid costly object-casting in `.values` new_data = ops.dispatch_to_series(left, right, func) else: diff --git a/pandas/core/sparse/frame.py b/pandas/core/sparse/frame.py index aaa99839144b4..a6d15ab4e2d4b 100644 --- a/pandas/core/sparse/frame.py +++ b/pandas/core/sparse/frame.py @@ -533,57 +533,10 @@ def _set_value(self, index, col, value, takeable=False): # ---------------------------------------------------------------------- # Arithmetic-related methods - def _combine_frame(self, other, func, fill_value=None, level=None): - this, other = self.align(other, join="outer", level=level, copy=False) + def align(self, other, *args, **kwargs): + this, other = super().align(other, *args, **kwargs) this._default_fill_value = self._default_fill_value - - new_data = {} - if fill_value is not None: - # TODO: be a bit more intelligent here - for col in this.columns: - if col in this and col in other: - dleft = this[col].to_dense() - dright = other[col].to_dense() - result = dleft._binop(dright, func, fill_value=fill_value) - result = result.to_sparse(fill_value=this[col].fill_value) - new_data[col] = result - else: - - for col in this.columns: - if col in this and col in other: - new_data[col] = func(this[col], other[col]) - - return this._construct_result(other, new_data, func) - - def _combine_match_index(self, other, func, level=None): - this, other = self.align(other, join="outer", axis=0, level=level, copy=False) - this._default_fill_value = self._default_fill_value - - new_data = {} - for col in this.columns: - new_data[col] = func(this[col], other) - - return this._construct_result(other, new_data, func) - - def _combine_match_columns(self, other, func, level=None): - # patched version of DataFrame._combine_match_columns to account for - # NumPy circumventing __rsub__ with float64 types, e.g.: 3.0 - series, - # where 3.0 is numpy.float64 and series is a SparseSeries. Still - # possible for this to happen, which is bothersome - - left, right = self.align(other, join="outer", axis=1, level=level, copy=False) - assert left.columns.equals(right.index) - left._default_fill_value = self._default_fill_value - - new_data = {} - for col in left.columns: - new_data[col] = func(left[col], right[col]) - - # TODO: using this changed some behavior, see GH#28025 - return left._construct_result(other, new_data, func) - - def _combine_const(self, other, func): - return self._apply_columns(lambda x: func(x, other)) + return this, other def _construct_result(self, other, result, func): """ @@ -639,7 +592,8 @@ def _get_op_result_fill_value(self, other, func): fill_value = self.default_fill_value else: - raise NotImplementedError(type(other)) + # reached via _combine_const + fill_value = self.default_fill_value return fill_value