【问题标题】:Customizing rolling_apply function in Python pandas在 Python pandas 中自定义 rolling_apply 函数
【发布时间】:2013-03-07 09:23:40
【问题描述】:

设置

我有一个包含三列的 DataFrame:

  • “类别”包含 True 和 False,我已完成 df.groupby('Category') 以按这些值分组。
  • “时间”包含记录值的时间戳(以秒为单位)
  • “值”包含值本身。

在每个实例中,记录两个值:一个具有“真”类别,另一个具有“假”类别。

滚动申请问题

在每个类别组内,我想计算一个数字并将其存储在每次的列结果中。结果是时间 t-60t 之间的值在 1 和 3 之间的百分比。

完成此操作的最简单方法可能是通过 rolling_count 计算该时间间隔内的值的总数,然后执行 rolling_apply 以仅计算该时间间隔中介于 1 和 3 之间的值。

到目前为止,这是我的代码:

groups = df.groupby(['Category'])
for key, grp in groups:
    grp = grp.reindex(grp['Time']) # reindex by time so we can count with rolling windows
    grp['total'] = pd.rolling_count(grp['Value'], window=60) # count number of values in the last 60 seconds
    grp['in_interval'] = ? ## Need to count number of values where 1<v<3 in the last 60 seconds

    grp['Result'] = grp['in_interval'] / grp['total'] # percentage of values between 1 and 3 in the last 60 seconds

找到grp['in_interval'] 的正确rolling_apply() 调用是什么?

【问题讨论】:

    标签: python group-by pandas aggregate data-analysis


    【解决方案1】:

    让我们看一个例子:

    import pandas as pd
    import numpy as np
    np.random.seed(1)
    
    def setup(regular=True):
        N = 10
        x = np.arange(N)
        a = np.arange(N)
        b = np.arange(N)
    
        if regular:
            timestamps = np.linspace(0, 120, N)
        else:
            timestamps = np.random.uniform(0, 120, N)
    
        df = pd.DataFrame({
            'Category': [True]*N + [False]*N,
            'Time': np.hstack((timestamps, timestamps)),
            'Value': np.hstack((a,b))
            })
        return df
    
    df = setup(regular=False)
    df.sort(['Category', 'Time'], inplace=True)
    

    所以 DataFrame df 看起来像这样:

    In [4]: df
    Out[4]: 
       Category       Time  Value    Result
    12    False   0.013725      2  1.000000
    15    False  11.080631      5  0.500000
    14    False  17.610707      4  0.333333
    16    False  22.351225      6  0.250000
    13    False  36.279909      3  0.400000
    17    False  41.467287      7  0.333333
    18    False  47.612097      8  0.285714
    10    False  50.042641      0  0.250000
    19    False  64.658008      9  0.125000
    11    False  86.438939      1  0.333333
    2      True   0.013725      2  1.000000
    5      True  11.080631      5  0.500000
    4      True  17.610707      4  0.333333
    6      True  22.351225      6  0.250000
    3      True  36.279909      3  0.400000
    7      True  41.467287      7  0.333333
    8      True  47.612097      8  0.285714
    0      True  50.042641      0  0.250000
    9      True  64.658008      9  0.125000
    1      True  86.438939      1  0.333333
    

    现在,复制@herrfz,让我们定义

    def between(a, b):
        def between_percentage(series):
            return float(len(series[(a <= series) & (series < b)])) / float(len(series))
        return between_percentage
    

    between(1,3) 是一个函数,它以 Series 作为输入并返回位于半开区间 [1,3) 中的元素的分数。例如,

    In [9]: series = pd.Series([1,2,3,4,5])
    
    In [10]: between(1,3)(series)
    Out[10]: 0.4
    

    现在我们将使用我们的 DataFrame,df,并按 Category 分组:

    df.groupby(['Category'])
    

    对于 groupby 对象中的每个组,我们需要应用一个函数:

    df['Result'] = df.groupby(['Category']).apply(toeach_category)
    

    函数toeach_category 将一个(子)DataFrame 作为输入,并返回一个DataFrame 作为输出。整个结果将分配给 df 的一个新列,称为 Result

    现在toeach_category 必须做什么?如果我们这样写toeach_category

    def toeach_category(subf):
        print(subf)
    

    然后我们看到每个subf 都是一个像这样的DataFrame(当Category 为False 时):

       Category       Time  Value    Result
    12    False   0.013725      2  1.000000
    15    False  11.080631      5  0.500000
    14    False  17.610707      4  0.333333
    16    False  22.351225      6  0.250000
    13    False  36.279909      3  0.400000
    17    False  41.467287      7  0.333333
    18    False  47.612097      8  0.285714
    10    False  50.042641      0  0.250000
    19    False  64.658008      9  0.125000
    11    False  86.438939      1  0.333333
    

    我们想要获取 Times 列,并且对于每个时间,应用一个函数。通过applymap 完成:

    def toeach_category(subf):
        result = subf[['Time']].applymap(percentage)
    

    函数percentage 将时间值作为输入,并返回一个值作为输出。该值将是值介于 1 和 3 之间的行的分数。applymap 非常严格:percentage 不能接受任何其他参数。

    给定一个时间t,我们可以使用ix方法从subf中选择时间处于半开区间(t-60, t]Values:

    subf.ix[(t-60 < subf['Time']) & (subf['Time'] <= t), 'Value']
    

    因此我们可以通过应用between(1,3) 来找到介于 1 和 3 之间的 Values 的百分比:

    between(1,3)(subf.ix[(t-60 < subf['Time']) & (subf['Time'] <= t), 'Value'])
    

    现在请记住,我们需要一个函数 percentage,它将 t 作为输入并返回上述表达式作为输出:

    def percentage(t):
        return between(1,3)(subf.ix[(t-60 < subf['Time']) & (subf['Time'] <= t), 'Value'])
    

    但请注意percentage 依赖于subf,我们不能将subf 作为参数传递给percentage(同样,因为applymap 非常严格)。

    那么我们如何摆脱困境呢?解决方案是在toeach_category 内定义percentage。 Python 的作用域规则说,像subf 这样的裸名首先在本地作用域中查找,然后是封闭作用域、全局作用域,最后是内置作用域。当percentage(t) 被调用并且Python 遇到subf 时,Python 首先在Local 作用域中查找subf 的值。由于subf 不是percentage 中的局部变量,Python 在函数toeach_category 的封闭范围内查找它。它在那里找到subf。完美的。这正是我们所需要的。

    所以现在我们有了函数toeach_category

    def toeach_category(subf):
        def percentage(t):
            return between(1, 3)(
                subf.ix[(t - 60 < subf['Time']) & (subf['Time'] <= t), 'Value'])
        result = subf[['Time']].applymap(percentage)
        return result
    

    把它们放在一起,

    import pandas as pd
    import numpy as np
    np.random.seed(1)
    
    
    def setup(regular=True):
        N = 10
        x = np.arange(N)
        a = np.arange(N)
        b = np.arange(N)
    
        if regular:
            timestamps = np.linspace(0, 120, N)
        else:
            timestamps = np.random.uniform(0, 120, N)
    
        df = pd.DataFrame({
            'Category': [True] * N + [False] * N,
            'Time': np.hstack((timestamps, timestamps)),
            'Value': np.hstack((a, b))
        })
        return df
    
    
    def between(a, b):
        def between_percentage(series):
            return float(len(series[(a <= series) & (series < b)])) / float(len(series))
        return between_percentage
    
    
    def toeach_category(subf):
        def percentage(t):
            return between(1, 3)(
                subf.ix[(t - 60 < subf['Time']) & (subf['Time'] <= t), 'Value'])
        result = subf[['Time']].applymap(percentage)
        return result
    
    
    df = setup(regular=False)
    df.sort(['Category', 'Time'], inplace=True)
    df['Result'] = df.groupby(['Category']).apply(toeach_category)
    print(df)
    

    产量

       Category       Time  Value    Result
    12    False   0.013725      2  1.000000
    15    False  11.080631      5  0.500000
    14    False  17.610707      4  0.333333
    16    False  22.351225      6  0.250000
    13    False  36.279909      3  0.200000
    17    False  41.467287      7  0.166667
    18    False  47.612097      8  0.142857
    10    False  50.042641      0  0.125000
    19    False  64.658008      9  0.000000
    11    False  86.438939      1  0.166667
    2      True   0.013725      2  1.000000
    5      True  11.080631      5  0.500000
    4      True  17.610707      4  0.333333
    6      True  22.351225      6  0.250000
    3      True  36.279909      3  0.200000
    7      True  41.467287      7  0.166667
    8      True  47.612097      8  0.142857
    0      True  50.042641      0  0.125000
    9      True  64.658008      9  0.000000
    1      True  86.438939      1  0.166667
    

    【讨论】:

    • 这看起来是一个有趣且详细的解决方案。但是为什么不使用 apply_rolling 作为最初的问题呢?确定可以使用吗?我有一个非常相似的问题。
    • @Carl: pd.rolling_apply 需要一个固定的(整数)窗口大小。在这个问题中,窗口大小随每一行而变化,因为窗口取决于Time 列中的值。
    • 感谢 unutbu,非常感谢。在固定窗口大小的情况下,如何使用它?
    • 这个问题相当广泛。请打开一个说明详细信息的新问题,或谷歌“pandas pd.rolling_apply site: stackoverflow.com”以查找大量示例。
    • 还有一个例子in the docs
    【解决方案2】:

    如果我正确理解您的问题陈述,如果您只是为了计算百分比而使用rolling count,您可能会跳过它。 rolling_apply 将一个执行聚合的函数作为参数,即一个将数组作为输入并返回一个数字作为输出的函数。

    考虑到这一点,让我们首先定义一个函数:

    def between_1_3_perc(x):
        # pandas Series is basically a numpy array, we can do boolean indexing
        return float(len(x[(x > 1) & (x < 3)])) / float(len(x))
    

    然后在for循环中使用函数名作为rolling_apply的参数:

    grp['Result'] = pd.rolling_apply(grp['Value'], 60, between_1_3_perc)
    

    【讨论】:

      猜你喜欢
      • 2014-01-28
      • 2014-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-05
      • 2016-05-16
      • 2014-10-29
      • 1970-01-01
      相关资源
      最近更新 更多