панды: композиция для связанных методов, таких как .resample(), .rolling() и т. д.

Я хотел бы построить расширение pandas.DataFrame, назовем его SPDF, которое могло бы делать что-то сверх того, что может простой DataFrame:

import pandas as pd
import numpy as np


def to_spdf(func):
    """Transform generic output of `func` to SPDF.

    Returns
    -------
    wrapper : callable
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return SPDF(res)

    return wrapper


class SPDF:
    """Special-purpose dataframe.

    Parameters
    ----------
    df : pandas.DataFrame

    """

    def __init__(self, df):
        self.df = df

    def __repr__(self):
        return repr(self.df)

    def __getattr__(self, item):
        res = getattr(self.df, item)

        if callable(res):
            res = to_spdf(res)

        return res


if __name__ == "__main__":

    # construct a generic SPDF
    df = pd.DataFrame(np.eye(4))
    an_spdf = SPDF(df)

    # call .diff() to obtain another SPDF
    print(an_spdf.diff())

Прямо сейчас методы DataFrame, которые возвращают еще один DataFrame, например .diff() в приведенном выше MWE, возвращают мне еще один SPDF, и это здорово. Однако я также хотел бы обмануть цепные методы, такие как .resample('M').last() или .rolling(2).mean(), чтобы получить SPDF в самом конце. До сих пор я терпел неудачу, потому что .rolling() и тому подобное имеют тип callable, а моя оболочка to_spdf пытается построить SPDF из их вывода, не «ожидая» .mean() или любой другой последней части выражения. Любые идеи, как решить эту проблему?

Спасибо.


person Igor Pozdeev    schedule 11.07.2018    source источник
comment
Мне не хватает цели SPDF. На что это даст вам обычный DataFrame, на что не способен?   -  person igrinis    schedule 16.07.2018
comment
Не могли бы вы показать, как вы пришли к своей проблеме? Начиная с вашего MWE, я проверяю, возвращают ли методы цепочки SPDF в самом конце и получают ожидаемый результат (т.е. isinstance(an_spdf.rolling(2).mean(), SPDF) возвращает True)   -  person Tomas Farias    schedule 16.07.2018
comment
@TomasFarias ты прав! Я немного упростил и не заметил, что предоставленный MWE действительно работает.   -  person Igor Pozdeev    schedule 23.07.2018


Ответы (2)


Вы должны правильно подклассифицировать dataframe. Чтобы заставить работать copy-constructor методы, pandas описывает, что вы должны установить _constructor (вместе с другой информацией).

Вы можете сделать что-то вроде следующего:

class SPDF(DataFrame):

    @property
    def _constructor(self):
        return SPDF

Если вам нужно сохранить пользовательские attributes (не functions - они будут там), во время copy-constructor методов (например, diff), то вы можете сделать что-то вроде следующего

class SPDF(DataFrame):
    _metadata = ['prop']
    prop = 1

    @property
    def _constructor(self):
        return SPDF

Обратите внимание, что результат соответствует желанию:

df = SPDF(np.eye(4))
print(type(df))
[<class '__main__.SPDF'>]
new = df.diff()
print(type(new))
[<class '__main__.SPDF'>]
person modesitt    schedule 17.07.2018
comment
+1 за указание на _constructor. Все еще не идеально, но, вероятно, станет популярным в будущем. - person Igor Pozdeev; 23.07.2018

Если вы не хотите создавать подкласс DataFrame, вы можете ввести другой класс, например PendingSPDF, и обернуть в него объекты, не относящиеся к фрейму данных:

import pandas as pd
import numpy as np


def to_spdf(func):
    """Transform generic output of `func` to SPDF.

    Returns
    -------
    wrapper : callable
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        if isinstance(res, pd.DataFrame):
            return SPDF(res)
        else:
            return PendingSPDF(res)

    return wrapper

class SPDF:
    """Special-purpose dataframe.

    Parameters
    ----------
    df : pandas.DataFrame

    """

    def __init__(self, df):
        self.df = df

    def __repr__(self):
        return repr(self.df)

    def __getattr__(self, item):
        res = getattr(self.df, item)

        if callable(res):
            res = to_spdf(res)

        return res

class PendingSPDF:
    def __init__(self, df):
        self.df = df

    def __getattr__(self, item):
        res = getattr(self.df, item)

        if callable(res):
            res = to_spdf(res)

        return res

if __name__ == "__main__":

    # construct a generic SPDF
    df = pd.DataFrame(np.eye(4))
    an_spdf = SPDF(df)

    # call .diff() to obtain another SPDF
    print(an_spdf.diff())
person mhsekhavat    schedule 20.07.2018