Skip to content
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

ENH: Add the moment function as DataFrame and Series method WITH namespacing #10702

Closed
wants to merge 1 commit into from
Closed
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
126 changes: 124 additions & 2 deletions pandas/stats/moments.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@

When ignore_na is True (reproducing pre-0.15.0 behavior), weights are based on
relative positions. For example, the weights of x and y used in calculating
the final weighted average of [x, None, y] are 1-alpha and 1 (if adjust is
the final weighted average of [x, None, y] are 1-alpha and 1 (if adjust is
True), and 1-alpha and alpha (if adjust is False).
"""

Expand Down Expand Up @@ -399,7 +399,7 @@ def _rolling_moment(arg, window, func, minp, axis=0, freq=None, center=False,

if center:
result = _center_window(result, window, axis)

return return_hook(result)


Expand Down Expand Up @@ -1036,3 +1036,125 @@ def expanding_apply(arg, func, min_periods=1, freq=None,
window = max(len(arg), min_periods) if min_periods else len(arg)
return rolling_apply(arg, window, func, min_periods=min_periods, freq=freq,
args=args, kwargs=kwargs)

#----------------------------------------------------------------------
# Add all the methods to DataFrame and Series
import sys
thismodule = sys.modules[__name__]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are creating lots of machinery that can simply use PandasDelegate, see pandas/tseries.common.py for an example

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from pandas.core.base import AccessorProperty
from functools import update_wrapper
from pandas.core import common as com
from pandas.core.base import PandasDelegate


RollingMethods = type(
"RollingMethods",
(),
{
fct_name.replace("rolling_", ""): staticmethod(
getattr(thismodule, fct_name)
)
for fct_name in __all__
if fct_name.startswith("rolling_")
}
)

ExpandingMethods = type(
"ExpandingMethods",
(),
{
fct_name.replace("expanding_", ""): staticmethod(
getattr(thismodule, fct_name)
)
for fct_name in __all__
if fct_name.startswith("expanding_")
}
)

EwmMethods = type(
"EwmMethods",
(),
{
fct_name.replace("ewm", ""): staticmethod(
getattr(thismodule, fct_name)
)
for fct_name in __all__
if fct_name.startswith("ewm")
}
)

class MomentDelegator(PandasDelegate):
prefix = None
klass = None

def __init__(self, series, prefix, klass):
self.series = series
def _delegate_method(self, name, *args, **kwargs):
method = getattr(self.klass, name)
return method(self.series, *args, **kwargs)

@classmethod
def generate_make_moment_accessor(cls):
prefix = cls.prefix
klass = cls.klass
def moment_rolling_accessor(self):
check_dtype(self, prefix)
return cls(self, prefix, klass)
return moment_rolling_accessor

class RollingDelegator(MomentDelegator):
prefix = "rolling_"
klass = RollingMethods

RollingDelegator._add_delegate_accessors(
delegate=RollingMethods,
accessors=RollingMethods.__dict__.keys(),
typ='method'
)

class ExpandingDelegator(MomentDelegator):
prefix = "expanding_"
klass = ExpandingMethods

ExpandingDelegator._add_delegate_accessors(
delegate=ExpandingMethods,
accessors=ExpandingMethods.__dict__.keys(),
typ='method'
)

class EwmDelegator(MomentDelegator):
prefix = "ewm"
klass = EwmMethods

EwmDelegator._add_delegate_accessors(
delegate=EwmMethods,
accessors=EwmMethods.__dict__.keys(),
typ='method'
)

def check_dtype(self, name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a method on MomentDelegator

if isinstance(self, Series) \
and not (
com.is_float_dtype(self.dtype)
or
com.is_integer_dtype(self.dtype)
):
raise AttributeError(
"Can only use .{} accessor with floats or int values".format(name)
)
if isinstance(self, DataFrame) \
and not True in [ # check whether there is one dtype float or integer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you not any(.....) more idiomatic

com.is_float_dtype(t) or com.is_integer_dtype(t)
for t in self.dtypes]:
raise AttributeError(
"Can only use .{}".format(name) +
" accessor if there exist at least one column of dtype floats or int"
)

DataFrame.rolling = AccessorProperty(RollingDelegator, RollingDelegator.generate_make_moment_accessor())
DataFrame.expanding = AccessorProperty(ExpandingDelegator, ExpandingDelegator.generate_make_moment_accessor())
DataFrame.ewm = AccessorProperty(EwmDelegator, EwmDelegator.generate_make_moment_accessor())
Series.rolling = AccessorProperty(RollingDelegator, RollingDelegator.generate_make_moment_accessor())
Series.expanding = AccessorProperty(ExpandingDelegator, ExpandingDelegator.generate_make_moment_accessor())
Series.ewm = AccessorProperty(EwmDelegator, EwmDelegator.generate_make_moment_accessor())