在Python pandas中自定义rolling_apply函数

时间:2013-03-18 21:11:24

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

设置

我有一个包含三列的DataFrame:

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

在每个时间实例中,记录两个值:一个具有“True”类别,另一个具有“False”类别。

滚动申请问题

在每个类别组中,我想计算一个数字并将其存储在每次结果列中。结果时间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

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

2 个答案:

答案 0 :(得分:7)

让我们来看一个例子:

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,dfCategory分组:

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,我们可以使用Value方法从subf中选择时间处于半开区间(t-60, t]的{​​{1}} :

ix

因此,我们可以通过应用subf.ix[(t-60 < subf['Time']) & (subf['Time'] <= t), 'Value'] 找到1到3之间Values的百分比:

between(1,3)

现在请记住,我们需要一个函数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(同样,因为percentageapplymap非常严格)。

那么我们如何摆脱这种干扰呢?解决方案是在percentage内定义toeach_category。 Python的范围规则表明,首先在Local范围内查找像subf这样的简单名称,然后是Enclosing范围,Global范围,最后是在Builtin范围内。调用percentage(t)并且Python遇到subf时,Python首先在Local范围内查找subf的值。由于subf不是percentage中的局部变量,因此Python会在函数toeach_category的Enclosing范围内查找它。它在那里找到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

答案 1 :(得分:2)

如果我正确理解您的问题陈述,如果您仅为了计算百分比而使用它,则可能会跳过rolling countrolling_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)