让我们通过一个例子来进行说明:
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)
是一个函数,它以序列为输入并返回其中位于半开区间 [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
我们想要针对每一个时间,对《纽约时报》专栏进行处理。这可以通过使用
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
这样的裸名字首先在 Local 作用域中查找,然后在 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
pd.rolling_apply
需要一个固定的(整数)窗口大小。在这个问题中,窗口大小随着每一行而改变,因为窗口取决于“Time”列中的值。 - unutbu