|
1 | 1 | # pylint: disable=W0231,E1101
|
| 2 | +import operator |
2 | 3 |
|
3 | 4 | import numpy as np
|
| 5 | +from pandas.core.common import bind_method |
4 | 6 |
|
5 | 7 | from pandas.core.index import MultiIndex
|
6 | 8 | import pandas.core.indexing as indexing
|
7 | 9 | from pandas.core.indexing import _maybe_convert_indices
|
8 | 10 | from pandas.tseries.index import DatetimeIndex
|
9 | 11 | import pandas.core.common as com
|
10 | 12 | import pandas.lib as lib
|
| 13 | +from pandas.util import py3compat |
11 | 14 |
|
12 | 15 |
|
13 | 16 | class PandasError(Exception):
|
@@ -35,6 +38,76 @@ def __hash__(self):
|
35 | 38 | raise TypeError('{0!r} objects are mutable, thus they cannot be'
|
36 | 39 | ' hashed'.format(self.__class__.__name__))
|
37 | 40 |
|
| 41 | + #---------------------------------------------------------------------- |
| 42 | + # Arithmetic! |
| 43 | + @classmethod |
| 44 | + def _add_special_arithmetic_methods(cls, arith_method=None, radd_func=None, comp_method=None, bool_method=None, |
| 45 | + use_numexpr=True): |
| 46 | + """ |
| 47 | + Adds the full suite of special arithmetic methods (``__add__``, ``__sub__``, etc.) to the class. |
| 48 | +
|
| 49 | + Parameters |
| 50 | + ---------- |
| 51 | + flex_arith_method : factory for flex arithmetic methods, with op string: |
| 52 | + f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs) |
| 53 | + radd_func : Possible replacement for ``lambda x, y: y + x`` for compatibility |
| 54 | + flex_comp_method : optional, factory for rich comparison - signature: f(op, name, str_rep) |
| 55 | + use_numexpr : whether to accelerate with numexpr, defaults to True |
| 56 | + """ |
| 57 | + radd_func = radd_func or operator.add |
| 58 | + # in frame, special methods have default_axis = None, comp methods use 'columns' |
| 59 | + new_methods = create_methods(arith_method, radd_func, comp_method, bool_method, use_numexpr, default_axis=None, |
| 60 | + special=True) |
| 61 | + |
| 62 | + # inplace operators (I feel like these should get passed an `inplace=True` |
| 63 | + # or just be removed |
| 64 | + new_methods.update(dict( |
| 65 | + __iadd__=new_methods["__add__"], |
| 66 | + __isub__=new_methods["__sub__"], |
| 67 | + __imul__=new_methods["__mul__"], |
| 68 | + __itruediv__=new_methods["__truediv__"], |
| 69 | + __ipow__=new_methods["__pow__"] |
| 70 | + )) |
| 71 | + if not py3compat.PY3: |
| 72 | + new_methods["__idiv__"] = new_methods["__div__"] |
| 73 | + for name, method in new_methods.items(): |
| 74 | + if name not in cls.__dict__: |
| 75 | + bind_method(cls, name, method) |
| 76 | + # DELETEME SOON! |
| 77 | + else: |
| 78 | + print("Not overwriting existing func %r" % name) |
| 79 | + |
| 80 | + @classmethod |
| 81 | + def _add_flex_arithmetic_methods(cls, flex_arith_method, radd_func=None, flex_comp_method=None, |
| 82 | + flex_bool_method=None, use_numexpr=True): |
| 83 | + """ |
| 84 | + Adds the full suite of flex arithmetic methods (``pow``, ``mul``, ``add``) to the class. |
| 85 | +
|
| 86 | + Parameters |
| 87 | + ---------- |
| 88 | + flex_arith_method : factory for flex arithmetic methods, with op string: |
| 89 | + f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs) |
| 90 | + radd_func : Possible replacement for ``lambda x, y: y + x`` for compatibility |
| 91 | + flex_comp_method : optional, factory for rich comparison - signature: f(op, name, str_rep) |
| 92 | + use_numexpr : whether to accelerate with numexpr, defaults to True |
| 93 | + """ |
| 94 | + radd_func = radd_func or operator.add |
| 95 | + # in frame, default axis is 'columns', doesn't matter for series and panel |
| 96 | + new_methods = create_methods( |
| 97 | + flex_arith_method, radd_func, flex_comp_method, flex_bool_method, |
| 98 | + use_numexpr, default_axis='columns', special=False) |
| 99 | + new_methods.update(dict( |
| 100 | + multiply=new_methods['mul'], |
| 101 | + subtract=new_methods['sub'], |
| 102 | + divide=new_methods['div'] |
| 103 | + )) |
| 104 | + |
| 105 | + for name, method in new_methods.items(): |
| 106 | + if name not in cls.__dict__: |
| 107 | + bind_method(cls, name, method) |
| 108 | + # DELETEME SOON! |
| 109 | + else: |
| 110 | + print("Not overwriting existing func %r" % name) |
38 | 111 |
|
39 | 112 | #----------------------------------------------------------------------
|
40 | 113 | # Axis name business
|
@@ -1158,3 +1231,78 @@ def truncate(self, before=None, after=None, copy=True):
|
1158 | 1231 | result = result.copy()
|
1159 | 1232 |
|
1160 | 1233 | return result
|
| 1234 | + |
| 1235 | + |
| 1236 | + |
| 1237 | + |
| 1238 | +def create_methods(arith_method, radd_func, comp_method, bool_method, use_numexpr, special=False, default_axis='columns'): |
| 1239 | + # NOTE: Only frame cares about default_axis, specifically: special methods have default axis None, |
| 1240 | + # whereas flex methods have default axis 'columns' |
| 1241 | + # if we're not using numexpr, then don't pass a str_rep |
| 1242 | + if use_numexpr: |
| 1243 | + op = lambda x: x |
| 1244 | + else: |
| 1245 | + op = lambda x: None |
| 1246 | + if special: |
| 1247 | + def names(x): |
| 1248 | + if x[-1] == "_": |
| 1249 | + return "__%s_" % x |
| 1250 | + else: |
| 1251 | + return "__%s__" % x |
| 1252 | + else: |
| 1253 | + names = lambda x: x |
| 1254 | + radd_func = radd_func or operator.add |
| 1255 | + # Inframe, all special methods have default_axis=None, flex methods have default_axis set to the default (columns) |
| 1256 | + new_methods = dict( |
| 1257 | + add=arith_method(operator.add, names('add'), op('+'), default_axis=default_axis), |
| 1258 | + radd=arith_method(radd_func, names('radd'), op('+'), default_axis=default_axis), |
| 1259 | + sub=arith_method(operator.sub, names('sub'), op('-'), default_axis=default_axis), |
| 1260 | + mul=arith_method(operator.mul, names('mul'), op('*'), default_axis=default_axis), |
| 1261 | + truediv=arith_method(operator.truediv, names('truediv'), op('/'), |
| 1262 | + truediv=True, fill_zeros=np.inf, default_axis=default_axis), |
| 1263 | + floordiv=arith_method(operator.floordiv, names('floordiv'), op('//'), |
| 1264 | + default_axis=default_axis, fill_zeros=np.inf), |
| 1265 | + # Causes a floating point exception in the tests when numexpr |
| 1266 | + # enabled, so for now no speedup |
| 1267 | + mod=arith_method(operator.mod, names('mod'), default_axis=default_axis, |
| 1268 | + fill_zeros=np.nan), |
| 1269 | + pow=arith_method(operator.pow, names('pow'), op('**'), default_axis=default_axis), |
| 1270 | + # not entirely sure why this is necessary, but previously was included |
| 1271 | + # so it's here to maintain compatibility |
| 1272 | + rmul=arith_method(operator.mul, names('rmul'), default_axis=default_axis), |
| 1273 | + rsub=arith_method(lambda x, y: y - x, names('rsub'), default_axis=default_axis), |
| 1274 | + rtruediv=arith_method(lambda x, y: operator.truediv(y, x), names('rtruediv'), op('/'), |
| 1275 | + truediv=True, fill_zeros=np.inf, default_axis=default_axis), |
| 1276 | + rfloordiv=arith_method(lambda x, y: operator.floordiv(y, x), names('rfloordiv'), op('//'), |
| 1277 | + default_axis=default_axis, fill_zeros=np.inf), |
| 1278 | + rpow=arith_method(lambda x, y: y ** x, names('rpow'), default_axis=default_axis), |
| 1279 | + rmod=arith_method(lambda x, y: y % x, names('rmod'), default_axis=default_axis), |
| 1280 | + ) |
| 1281 | + if not py3compat.PY3: |
| 1282 | + new_methods["div"] = arith_method(operator.div, names('div'), op('/'), |
| 1283 | + truediv=False, fill_zeros=np.inf, default_axis=default_axis) |
| 1284 | + new_methods["rdiv"] = arith_method(lambda x, y: operator.div(y, x), names('rdiv'), op('/'), |
| 1285 | + truediv=False, fill_zeros=np.inf, default_axis=default_axis) |
| 1286 | + else: |
| 1287 | + new_methods["div"] = arith_method(operator.truediv, names('div'), op('/'), |
| 1288 | + truediv=True, fill_zeros=np.inf, default_axis=default_axis) |
| 1289 | + # Comp methods never had a default axis set |
| 1290 | + if comp_method: |
| 1291 | + new_methods.update(dict( |
| 1292 | + eq=comp_method(operator.eq, names('eq'), op('==')), |
| 1293 | + ne=comp_method(operator.ne, names('ne'), op('!=')), |
| 1294 | + lt=comp_method(operator.lt, names('lt'), op('<')), |
| 1295 | + gt=comp_method(operator.gt, names('gt'), op('>')), |
| 1296 | + le=comp_method(operator.le, names('le'), op('<=')), |
| 1297 | + ge=comp_method(operator.ge, names('ge'), op('>=')), |
| 1298 | + )) |
| 1299 | + if bool_method: |
| 1300 | + new_methods.update(dict( |
| 1301 | + and_=bool_method(operator.and_, names('and_ [&]'), op('&')), |
| 1302 | + or_=bool_method(operator.or_, names('or_ [|]'), op('|')), |
| 1303 | + # For some reason ``^`` wasn't used in original. |
| 1304 | + xor=bool_method(operator.xor, names('xor [^]')) |
| 1305 | + )) |
| 1306 | + |
| 1307 | + new_methods = dict((names(k), v) for k, v in new_methods.items()) |
| 1308 | + return new_methods |
0 commit comments