37
37
construct_1d_object_array_from_listlike )
38
38
from pandas .core .dtypes .generic import (
39
39
ABCSeries ,
40
- ABCDataFrame ,
40
+ ABCDataFrame , ABCPanel ,
41
41
ABCIndex ,
42
42
ABCSparseSeries , ABCSparseArray )
43
43
@@ -711,6 +711,64 @@ def mask_cmp_op(x, y, op, allowed_types):
711
711
# Functions that add arithmetic methods to objects, given arithmetic factory
712
712
# methods
713
713
714
+ def _get_method_wrappers (cls ):
715
+ """
716
+ Find the appropriate operation-wrappers to use when defining flex/special
717
+ arithmetic, boolean, and comparison operations with the given class.
718
+
719
+ Parameters
720
+ ----------
721
+ cls : class
722
+
723
+ Returns
724
+ -------
725
+ arith_flex : function or None
726
+ comp_flex : function or None
727
+ arith_special : function
728
+ comp_special : function
729
+ bool_special : function
730
+
731
+ Notes
732
+ -----
733
+ None is only returned for SparseArray
734
+ """
735
+ if issubclass (cls , ABCSparseSeries ):
736
+ # Be sure to catch this before ABCSeries and ABCSparseArray,
737
+ # as they will both come see SparseSeries as a subclass
738
+ arith_flex = _flex_method_SERIES
739
+ comp_flex = _flex_method_SERIES
740
+ arith_special = _arith_method_SPARSE_SERIES
741
+ comp_special = _arith_method_SPARSE_SERIES
742
+ bool_special = _bool_method_SERIES
743
+ # TODO: I don't think the functions defined by bool_method are tested
744
+ elif issubclass (cls , ABCSeries ):
745
+ # Just Series; SparseSeries is caught above
746
+ arith_flex = _flex_method_SERIES
747
+ comp_flex = _flex_method_SERIES
748
+ arith_special = _arith_method_SERIES
749
+ comp_special = _comp_method_SERIES
750
+ bool_special = _bool_method_SERIES
751
+ elif issubclass (cls , ABCSparseArray ):
752
+ arith_flex = None
753
+ comp_flex = None
754
+ arith_special = _arith_method_SPARSE_ARRAY
755
+ comp_special = _arith_method_SPARSE_ARRAY
756
+ bool_special = _arith_method_SPARSE_ARRAY
757
+ elif issubclass (cls , ABCPanel ):
758
+ arith_flex = _flex_method_PANEL
759
+ comp_flex = _comp_method_PANEL
760
+ arith_special = _arith_method_PANEL
761
+ comp_special = _comp_method_PANEL
762
+ bool_special = _arith_method_PANEL
763
+ elif issubclass (cls , ABCDataFrame ):
764
+ # Same for DataFrame and SparseDataFrame
765
+ arith_flex = _arith_method_FRAME
766
+ comp_flex = _flex_comp_method_FRAME
767
+ arith_special = _arith_method_FRAME
768
+ comp_special = _comp_method_FRAME
769
+ bool_special = _arith_method_FRAME
770
+ return arith_flex , comp_flex , arith_special , comp_special , bool_special
771
+
714
772
715
773
def _create_methods (cls , arith_method , comp_method , bool_method ,
716
774
special = False ):
@@ -743,16 +801,18 @@ def _create_methods(cls, arith_method, comp_method, bool_method,
743
801
# yapf: enable
744
802
new_methods ['div' ] = new_methods ['truediv' ]
745
803
new_methods ['rdiv' ] = new_methods ['rtruediv' ]
804
+ if have_divmod :
805
+ # divmod doesn't have an op that is supported by numexpr
806
+ new_methods ['divmod' ] = arith_method (cls , divmod , special )
807
+
808
+ new_methods .update (dict (
809
+ eq = comp_method (cls , operator .eq , special ),
810
+ ne = comp_method (cls , operator .ne , special ),
811
+ lt = comp_method (cls , operator .lt , special ),
812
+ gt = comp_method (cls , operator .gt , special ),
813
+ le = comp_method (cls , operator .le , special ),
814
+ ge = comp_method (cls , operator .ge , special )))
746
815
747
- # Comp methods never had a default axis set
748
- if comp_method :
749
- new_methods .update (dict (
750
- eq = comp_method (cls , operator .eq , special ),
751
- ne = comp_method (cls , operator .ne , special ),
752
- lt = comp_method (cls , operator .lt , special ),
753
- gt = comp_method (cls , operator .gt , special ),
754
- le = comp_method (cls , operator .le , special ),
755
- ge = comp_method (cls , operator .ge , special )))
756
816
if bool_method :
757
817
new_methods .update (
758
818
dict (and_ = bool_method (cls , operator .and_ , special ),
@@ -762,9 +822,6 @@ def _create_methods(cls, arith_method, comp_method, bool_method,
762
822
rand_ = bool_method (cls , rand_ , special ),
763
823
ror_ = bool_method (cls , ror_ , special ),
764
824
rxor = bool_method (cls , rxor , special )))
765
- if have_divmod :
766
- # divmod doesn't have an op that is supported by numexpr
767
- new_methods ['divmod' ] = arith_method (cls , divmod , special )
768
825
769
826
if special :
770
827
dunderize = lambda x : '__{name}__' .format (name = x .strip ('_' ))
@@ -788,22 +845,17 @@ def add_methods(cls, new_methods):
788
845
789
846
# ----------------------------------------------------------------------
790
847
# Arithmetic
791
- def add_special_arithmetic_methods (cls , arith_method = None ,
792
- comp_method = None , bool_method = None ):
848
+ def add_special_arithmetic_methods (cls ):
793
849
"""
794
850
Adds the full suite of special arithmetic methods (``__add__``,
795
851
``__sub__``, etc.) to the class.
796
852
797
853
Parameters
798
854
----------
799
- arith_method : function (optional)
800
- factory for special arithmetic methods:
801
- f(cls, op, special)
802
- comp_method : function (optional)
803
- factory for rich comparison - signature: f(cls, op, special)
804
- bool_method : function (optional)
805
- factory for boolean methods - signature: f(cls, op, special)
855
+ cls : class
856
+ special methods will be defined and pinned to this class
806
857
"""
858
+ _ , _ , arith_method , comp_method , bool_method = _get_method_wrappers (cls )
807
859
new_methods = _create_methods (cls , arith_method , comp_method , bool_method ,
808
860
special = True )
809
861
# inplace operators (I feel like these should get passed an `inplace=True`
@@ -836,28 +888,26 @@ def f(self, other):
836
888
__ipow__ = _wrap_inplace_method (new_methods ["__pow__" ])))
837
889
if not compat .PY3 :
838
890
new_methods ["__idiv__" ] = _wrap_inplace_method (new_methods ["__div__" ])
839
- if bool_method :
840
- new_methods .update (
841
- dict (__iand__ = _wrap_inplace_method (new_methods ["__and__" ]),
842
- __ior__ = _wrap_inplace_method (new_methods ["__or__" ]),
843
- __ixor__ = _wrap_inplace_method (new_methods ["__xor__" ])))
891
+
892
+ new_methods .update (
893
+ dict (__iand__ = _wrap_inplace_method (new_methods ["__and__" ]),
894
+ __ior__ = _wrap_inplace_method (new_methods ["__or__" ]),
895
+ __ixor__ = _wrap_inplace_method (new_methods ["__xor__" ])))
844
896
845
897
add_methods (cls , new_methods = new_methods )
846
898
847
899
848
- def add_flex_arithmetic_methods (cls , flex_arith_method , flex_comp_method = None ):
900
+ def add_flex_arithmetic_methods (cls ):
849
901
"""
850
902
Adds the full suite of flex arithmetic methods (``pow``, ``mul``, ``add``)
851
903
to the class.
852
904
853
905
Parameters
854
906
----------
855
- flex_arith_method : function
856
- factory for flex arithmetic methods:
857
- f(cls, op, special)
858
- flex_comp_method : function, optional,
859
- factory for rich comparison - signature: f(cls, op, special)
907
+ cls : class
908
+ flex methods will be defined and pinned to this class
860
909
"""
910
+ flex_arith_method , flex_comp_method , _ , _ , _ = _get_method_wrappers (cls )
861
911
new_methods = _create_methods (cls , flex_arith_method ,
862
912
flex_comp_method , bool_method = None ,
863
913
special = False )
@@ -1284,14 +1334,6 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
1284
1334
return flex_wrapper
1285
1335
1286
1336
1287
- series_flex_funcs = dict (flex_arith_method = _flex_method_SERIES ,
1288
- flex_comp_method = _flex_method_SERIES )
1289
-
1290
- series_special_funcs = dict (arith_method = _arith_method_SERIES ,
1291
- comp_method = _comp_method_SERIES ,
1292
- bool_method = _bool_method_SERIES )
1293
-
1294
-
1295
1337
# -----------------------------------------------------------------------------
1296
1338
# DataFrame
1297
1339
@@ -1533,14 +1575,6 @@ def f(self, other):
1533
1575
return f
1534
1576
1535
1577
1536
- frame_flex_funcs = dict (flex_arith_method = _arith_method_FRAME ,
1537
- flex_comp_method = _flex_comp_method_FRAME )
1538
-
1539
- frame_special_funcs = dict (arith_method = _arith_method_FRAME ,
1540
- comp_method = _comp_method_FRAME ,
1541
- bool_method = _arith_method_FRAME )
1542
-
1543
-
1544
1578
# -----------------------------------------------------------------------------
1545
1579
# Panel
1546
1580
@@ -1629,16 +1663,38 @@ def f(self, other, axis=0):
1629
1663
return f
1630
1664
1631
1665
1632
- panel_special_funcs = dict (arith_method = _arith_method_PANEL ,
1633
- comp_method = _comp_method_PANEL ,
1634
- bool_method = _arith_method_PANEL )
1635
-
1636
- panel_flex_funcs = dict (flex_arith_method = _flex_method_PANEL ,
1637
- flex_comp_method = _comp_method_PANEL )
1638
-
1639
1666
# -----------------------------------------------------------------------------
1640
1667
# Sparse
1641
1668
1669
+ def _cast_sparse_series_op (left , right , opname ):
1670
+ """
1671
+ For SparseSeries operation, coerce to float64 if the result is expected
1672
+ to have NaN or inf values
1673
+
1674
+ Parameters
1675
+ ----------
1676
+ left : SparseArray
1677
+ right : SparseArray
1678
+ opname : str
1679
+
1680
+ Returns
1681
+ -------
1682
+ left : SparseArray
1683
+ right : SparseArray
1684
+ """
1685
+ opname = opname .strip ('_' )
1686
+
1687
+ if is_integer_dtype (left ) and is_integer_dtype (right ):
1688
+ # series coerces to float64 if result should have NaN/inf
1689
+ if opname in ('floordiv' , 'mod' ) and (right .values == 0 ).any ():
1690
+ left = left .astype (np .float64 )
1691
+ right = right .astype (np .float64 )
1692
+ elif opname in ('rfloordiv' , 'rmod' ) and (left .values == 0 ).any ():
1693
+ left = left .astype (np .float64 )
1694
+ right = right .astype (np .float64 )
1695
+
1696
+ return left , right
1697
+
1642
1698
1643
1699
def _arith_method_SPARSE_SERIES (cls , op , special ):
1644
1700
"""
@@ -1674,8 +1730,8 @@ def _sparse_series_op(left, right, op, name):
1674
1730
new_name = get_op_result_name (left , right )
1675
1731
1676
1732
from pandas .core .sparse .array import _sparse_array_op
1677
- result = _sparse_array_op (left .values , right .values , op , name ,
1678
- series = True )
1733
+ lvalues , rvalues = _cast_sparse_series_op (left .values , right .values , name )
1734
+ result = _sparse_array_op ( lvalues , rvalues , op , name )
1679
1735
return left ._constructor (result , index = new_index , name = new_name )
1680
1736
1681
1737
@@ -1697,7 +1753,7 @@ def wrapper(self, other):
1697
1753
dtype = getattr (other , 'dtype' , None )
1698
1754
other = SparseArray (other , fill_value = self .fill_value ,
1699
1755
dtype = dtype )
1700
- return _sparse_array_op (self , other , op , name , series = False )
1756
+ return _sparse_array_op (self , other , op , name )
1701
1757
elif is_scalar (other ):
1702
1758
with np .errstate (all = 'ignore' ):
1703
1759
fill = op (_get_fill (self ), np .asarray (other ))
@@ -1710,13 +1766,3 @@ def wrapper(self, other):
1710
1766
1711
1767
wrapper .__name__ = name
1712
1768
return wrapper
1713
-
1714
-
1715
- sparse_array_special_funcs = dict (arith_method = _arith_method_SPARSE_ARRAY ,
1716
- comp_method = _arith_method_SPARSE_ARRAY ,
1717
- bool_method = _arith_method_SPARSE_ARRAY )
1718
-
1719
- sparse_series_special_funcs = dict (arith_method = _arith_method_SPARSE_SERIES ,
1720
- comp_method = _arith_method_SPARSE_SERIES ,
1721
- bool_method = _bool_method_SERIES )
1722
- # TODO: I don't think the functions defined by bool_method are tested
0 commit comments