向量化列 A 的列 B 的百分位值(对于组)
Posted
技术标签:
【中文标题】向量化列 A 的列 B 的百分位值(对于组)【英文标题】:vectorize percentile value of column B of column A (for groups) 【发布时间】:2017-06-23 21:17:30 【问题描述】:对于每对src
和dest
机场城市,我想返回a
列的百分位数,给定b
列的值。
我可以这样手动执行此操作:
只有 2 对 src/dest 的示例 df(我的实际 df 中有数千个):
dt src dest a b
0 2016-01-01 YYZ SFO 548.12 279.28
1 2016-01-01 DFW PDX 111.35 -65.50
2 2016-02-01 YYZ SFO 64.84 342.35
3 2016-02-01 DFW PDX 63.81 61.64
4 2016-03-01 YYZ SFO 614.29 262.83
'a': 0: 548.12,
1: 111.34999999999999,
2: 64.840000000000003,
3: 63.810000000000002,
4: 614.28999999999996,
5: -207.49000000000001,
6: 151.31999999999999,
7: -56.43,
8: 611.37,
9: -296.62,
10: 6417.5699999999997,
11: -376.25999999999999,
12: 465.12,
13: -821.73000000000002,
14: 1270.6700000000001,
15: -1410.0899999999999,
16: 1312.6600000000001,
17: -326.25999999999999,
18: 1683.3699999999999,
19: -24.440000000000001,
20: 583.60000000000002,
21: -5.2400000000000002,
22: 1122.74,
23: 195.21000000000001,
24: 97.040000000000006,
25: 133.94,
'b': 0: 279.27999999999997,
1: -65.5,
2: 342.35000000000002,
3: 61.640000000000001,
4: 262.82999999999998,
5: 115.89,
6: 268.63999999999999,
7: 2.3500000000000001,
8: 91.849999999999994,
9: 62.119999999999997,
10: 778.33000000000004,
11: -142.78,
12: 1675.53,
13: -214.36000000000001,
14: 983.80999999999995,
15: -207.62,
16: 632.13999999999999,
17: -132.53,
18: 422.36000000000001,
19: 13.470000000000001,
20: 642.73000000000002,
21: -144.59999999999999,
22: 213.15000000000001,
23: -50.200000000000003,
24: 338.27999999999997,
25: -129.69,
'dest': 0: 'SFO',
1: 'PDX',
2: 'SFO',
3: 'PDX',
4: 'SFO',
5: 'PDX',
6: 'SFO',
7: 'PDX',
8: 'SFO',
9: 'PDX',
10: 'SFO',
11: 'PDX',
12: 'SFO',
13: 'PDX',
14: 'SFO',
15: 'PDX',
16: 'SFO',
17: 'PDX',
18: 'SFO',
19: 'PDX',
20: 'SFO',
21: 'PDX',
22: 'SFO',
23: 'PDX',
24: 'SFO',
25: 'PDX',
'dt': 0: Timestamp('2016-01-01 00:00:00'),
1: Timestamp('2016-01-01 00:00:00'),
2: Timestamp('2016-02-01 00:00:00'),
3: Timestamp('2016-02-01 00:00:00'),
4: Timestamp('2016-03-01 00:00:00'),
5: Timestamp('2016-03-01 00:00:00'),
6: Timestamp('2016-04-01 00:00:00'),
7: Timestamp('2016-04-01 00:00:00'),
8: Timestamp('2016-05-01 00:00:00'),
9: Timestamp('2016-05-01 00:00:00'),
10: Timestamp('2016-06-01 00:00:00'),
11: Timestamp('2016-06-01 00:00:00'),
12: Timestamp('2016-07-01 00:00:00'),
13: Timestamp('2016-07-01 00:00:00'),
14: Timestamp('2016-08-01 00:00:00'),
15: Timestamp('2016-08-01 00:00:00'),
16: Timestamp('2016-09-01 00:00:00'),
17: Timestamp('2016-09-01 00:00:00'),
18: Timestamp('2016-10-01 00:00:00'),
19: Timestamp('2016-10-01 00:00:00'),
20: Timestamp('2016-11-01 00:00:00'),
21: Timestamp('2016-11-01 00:00:00'),
22: Timestamp('2016-12-01 00:00:00'),
23: Timestamp('2016-12-01 00:00:00'),
24: Timestamp('2017-01-01 00:00:00'),
25: Timestamp('2017-01-01 00:00:00'),
'src': 0: 'YYZ',
1: 'DFW',
2: 'YYZ',
3: 'DFW',
4: 'YYZ',
5: 'DFW',
6: 'YYZ',
7: 'DFW',
8: 'YYZ',
9: 'DFW',
10: 'YYZ',
11: 'DFW',
12: 'YYZ',
13: 'DFW',
14: 'YYZ',
15: 'DFW',
16: 'YYZ',
17: 'DFW',
18: 'YYZ',
19: 'DFW',
20: 'YYZ',
21: 'DFW',
22: 'YYZ',
23: 'DFW',
24: 'YYZ',
25: 'DFW'
我想要每组 src
和 dest
对的百分位数。所以每对应该只有 1 个百分位值。我只想对给定的b
执行百分位数,其中date = 2017-01-01
为每个src
和dest
对在整列a
的每一对。有意义吗?
我可以手动执行此操作,例如针对特定对 i.e. src=YYZ and dest=SFT
:
from scipy import stats
import datetime as dt
import pandas as pd
p0 = dt.datetime(2017,1,1)
# lets slice df for src=YYZ and dest = SFO
x = df[(df.src =='YYZ') &
(df.dest =='SFO') &
(df.dt ==p0)].b.values[0]
# given B, what percentile does it fall in for the entire column A for YYZ, SFO
stats.percentileofscore(df['a'],x)
61.53846153846154
在上述情况下,我为 YYZ 和 SFO 对手动执行此操作。但是,我的 df 中有数千对。
我如何vectorize
这个使用pandas features
而不是循环遍历每一对?
必须有一种方法可以使用groupby
并在函数上使用apply
?
我想要的 df 应该是这样的:
src dest percentile
0 YYZ SFO 61.54
1 DFW PDX 23.07
2 XXX YYY blahblah1
3 AAA BBB blahblah2
...
更新:
我实现了以下内容:
def b_percentile_a(df,x,y,b):
z = df[(df['src'] == x ) & (df['dest'] == y)].a
r = stats.percentileofscore(z,b)
return r
b_vector_df = df[df.dt == p0]
b_vector_df['p0_a_percentile_b'] = \
b_vector_df.apply(lambda x: b_percentile_a(df,x.src,x.dest,x.b), axis=1)
100
对需要 5.16
秒。我有55,000
对。所以这将花费~50
分钟。我需要运行这个36
次,所以它会占用several days
的运行时间。
必须有更快的方法吗?
【问题讨论】:
有人对如何使用pandas
功能在不需要手动循环的尽可能少的代码行中为每个 src/dest 组实现上述功能有任何想法吗?
在您针对特定对的示例计算中,您计算目标值相对于df.a
的所有 值的百分位数。那是你要的吗?您的描述似乎表明您想要计算目标值的百分位数,仅相对于具有特定 src
和 src
和 dest
的行的 df
,但这不是您的代码实际执行的操作。因此,它给出的结果与您最后给出的 b_percentile_a
函数不同。
另外,如果您关心性能,最好有一个真实的样本数据集作为基准。不同解决方案的性能可能会因数据的性质而异(例如,每个 src/dest 块有多大)。
只是为了更好地理解:55,000 对是指数据帧有 55,000 个条目,还是实际上有 55,000 个不同的 src 和 dest 组合(因此数据帧中有更多行)?
@BrenBarn - 我想要to calculate the percentile of the target value relative to only the rows of df with a particular src and dest
。每个 src/dest 对的所有 df.a 值。我相信我在底部的代码应该做到这一点(但它非常慢)。即b_percentile_a()
【参考方案1】:
请验证并评论这是否代表您的数据模型!
-
6 ^ 6 对 [AAA-ZZZ] = 46,656 被使用。
通常每个 PAIR 有 12 个 RECORDS
这是记录(0) PAIR(DFW PDX)
这是 SET(DFW PDX) = PAIR(DFW PDX) 的 13 条记录dt src dest a b 0: 2016-01-01 DFW PDX 111.35 -65.5
示例:计算 RECORD(0) 的百分位数dt src dest a b 0: 2016-01-01 DFW PDX 111.35 -65.5 1: 2016-02-01 DFW PDX 63.81 61.64 2: 2016-03-01 DFW PDX -207.49 115.89 3: 2016-04-01 DFW PDX -56.43 2.35 4: 2016-05-01 DFW PDX -296.62 62.12 5: 2016-06-01 DFW PDX -376.26 -142.78 6: 2016-07-01 DFW PDX -821.73 -214.36 7: 2016-08-01 DFW PDX -1410.09 -207.62 8: 2016-09-01 DFW PDX -326.26 -132.53 9: 2016-10-01 DFW PDX -24.44 13.47 10:2016-11-01 DFW PDX -5.24 -144.6 11:2016-12-01 DFW PDX 195.21 -50.2 12:2017-01-01 DFW PDX 133.94 -129.69
dt src dest a b 0: 2016-01-01 DFW PDX 111.35 -65.5
伪代码: stats.percentileofscore( SET( DFW PDX )[a0...a12], -65.5) = 46.15
示例:计算SET(DFW PDX)的百分位数
伪代码 在 SET(DFW PDX) 中记录: stats.percentileofscore(SET(DFW PDX)[a0...a12],record.b) 输出:pct0...pct12
使用 rank_searchsorted_list 不需要 'for record in' : rank_searchsorted_list(SET(DFW PDX)[a0...a12],SET(DFW PDX)[b0...b12]) 输出:[pct0...pct12]
这是SET(DFW PDX)矢量化
OBJECT = 'DFW PDX':[
['2016-01-01', '2016-02-01', '2016-03-01', '2016-04-01', '2016-05-01', '2016-06-01', '2016-07-01', '2016-08-01', '2016-09-01', '2016-10-01', '2016-11-01', '2016-12-01', '2017-01-01']
[111.35, 63.81, -207.49, -56.43, -296.62, -376.26, -821.73, -1410.09, -326.26, -24.44, -5.24, 195.21, 133.94]
[-65.5, 61.64, 115.89, 2.35, 62.12, -142.78, -214.36, -207.62, -132.53, 13.47, -144.6, -50.2, -129.69]
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
]
示例:计算 OBJECT 的百分位数(DFW PDX) 使用 stats.percentileofscore:
a = 1; b = 2
for b_value in OBJECT['DFW PDX'][b]:
stats.percentileofscore( OBJECT['DFW PDX'][a], b_value)
Output: pct0...pct12
使用 rank_searchsorted_list 不需要 'for b_value in':
a = 1; b = 2; pct = 3
vector = OBJECT['DFW PDX']
vector[pct] = rank_searchsorted_list( vector[a], vector[b] )
输出:
dt src dest a b pct scipy 0: 2016-01-01 DFW PDX 111.35 -65.5 46.15 46.15 1: 2016-02-01 DFW PDX 63.81 61.64 69.23 69.23 2: 2016-03-01 DFW PDX -207.49 115.89 84.61 84.61 3: 2016-04-01 DFW PDX -56.43 2.35 69.23 69.23 4: 2016-05-01 DFW PDX -296.62 62.12 69.23 69.23 5: 2016-06-01 DFW PDX -376.26 -142.78 46.15 46.15 6: 2016-07-01 DFW PDX -821.73 -214.36 38.46 38.46 7: 2016-08-01 DFW PDX -1410.09 -207.62 38.46 38.46 8: 2016-09-01 DFW PDX -326.26 -132.53 46.15 46.15 9: 2016-10-01 DFW PDX -24.44 13.47 69.23 69.23 10:2016-11-01 DFW PDX -5.24 -144.6 46.15 46.15 11:2016-12-01 DFW PDX 195.21 -50.2 53.84 53.84 12:2017-01-01 DFW PDX 133.94 -129.69 46.15 46.15
请核实并确认计算出的百分位数!
【讨论】:
【参考方案2】:大大节省了时间!
输出: a_list 的大小:49998 随机唯一值percentile_1(您给定的 df - scipy) 计算百分位数 104 次 - 0:00:07.777022 中的 104 条记录
percentile_9(使用给定 df 类 PercentileOfScore(rank_searchsorted_list))
计算百分位数 104 次 - 0:00:00.000609 中的 104 条记录_ dt src dest a b pct scipy _
0: 2016-01-01 YYZ SFO 54812 279.28 74.81299251970079 74.8129925197
1: 2016-01-01 DFW PDX 111.35 -65.5 24.66698667946718 24.6669866795
2: 2016-02-01 YYZ SFO 64.84 342.35 76.4810592423697 76.4810592424
3: 2016-02-01 DFW PDX 63.81 61.64 63.84655386215449 63.8465538622
...
24: 2017-01-01 YYZ SFO 97.04 338.28 76.3570542821712 76.3570542822
25: 2017-01-01 DFW PDX 133.94 -129.69 21.4668586743469 21.4668586743
查看scipy.percentileofscore
的实现我发现整个list( a )
每次调用 percentileofscore
时都会被复制、插入、排序、搜索。
我实现了自己的class PercentileOfScore
import numpy as np
class PercentileOfScore(object):
def __init__(self, aList):
self.a = np.array( aList )
self.a.sort()
self.n = float(len(self.a))
self.pct = self.__rank_searchsorted_list
# end def __init__
def __rank_searchsorted_list(self, score_list):
adx = np.searchsorted(self.a, score_list, side='right')
pct = []
for idx in adx:
# Python 2.x needs explicit type casting float(int)
pct.append( (float(idx) / self.n) * 100.0 )
return pct
# end def _rank_searchsorted_list
# end class PercentileOfScore
我认为def percentile_7
不能满足您的需求。 dt
不会考虑。
PctOS = None
def percentile_7(df_flat):
global PctOS
result =
for k in df_flat.pair_dict.keys():
# df_flat.pair_dict = 'src.dst': [b,b,...bn]
result[k] = PctOS.pct( df_flat.pair_dict[k] )
return result
# end def percentile_7
在您的手动示例中,您使用整个 df.a
。在此示例中为 dt_flat.a_list
,但我不确定这是否是您想要的?
from PercentileData import DF_flat
def main():
# DF_flat.data = 'dt.src.dest':[a,b]
df_flat = DF_flat()
# Instantiate Global PctOS
global PctOS
# df_flat.a_list = [a,a,...an]
PctOS = PercentileOfScore(df_flat.a_list)
result = percentile_7(df_flat)
# result = dict'src.dst':[pct,pct...pctn]
使用 Python:3.4.2 和 2.7.9 测试 - numpy:1.8.2
【讨论】:
这看起来很棒。你能分享percentile_7
和你的class PercentileOfScore
的代码吗?
这也适用于 python 2 吗?除了像 print 和 print() 这样的基本语法差异?
@AsheKetchum 使用 Python 2.7.9 测试,必须添加显式类型转换 float(int)。编辑了答案。 Python 2.7.9 也可以处理 print()。
为什么我们需要for idx in adx:
循环。相反,我们可以使用pct = ads / self.n * 100.0
来向量化除法,对吗?【参考方案3】:
通过将所有内容转换为 numpy 数组并将百分位数也构造为 numpy 数组,似乎获得了另一个相当大的加速:
# Get airport strings as indices
_, ir = np.unique(df['src'].values, return_inverse=True)
_, ic = np.unique(df['dest'].values, return_inverse=True)
# Get a and b columns
a = df['a'].values
b = df['b'].values
# Compute percentile scores in a numpy array
prc = np.zeros(a.shape)
for i in range(0, a.shape[0]):
prc[i] = stats.percentileofscore(a[np.logical_and(ir==ir[i], ic==ic[i])], b[i])
在具有 24000 个条目的数据帧上(参见下面的构造),运行 %%timeit
给出
1 loop, best of 3: 2.17 s per loop
但是,原来的版本
df['p0_a_percentile_b'] = \
df.apply(lambda x: b_percentile_a(df,x.src,x.dest,x.b), axis=1)
产量
1 loop, best of 3: 1min 2s per loop
这要慢得多。我还检查了两个 sn-ps 通过运行np.all(prc == df.p0_a_percentile_b.values)
产生相同的输出,产生True
。
附录:
我构建了一个数据框来测试这一点,在这里我分享了重现性的过程。我使用 100 个唯一机场名称获取了 2000 对机场,然后每对生成 12 个数据帧行,然后生成随机 a 和 b 列。
import pandas as pd
import numpy as np
import scipy.stats as stats
import numpy.matlib as mat
# Construct dataframe
T=12
N_airports = 100
N_entries = 2000
airports = np.arange(0, N_airports).astype('string')
src = mat.repmat(airports[np.random.randint(N_airports, size=(N_entries, ))], 1, T)
dest = mat.repmat(airports[np.random.randint(N_airports, size=(N_entries, ))], 1, T)
a = np.random.uniform(size=N_entries*T)
b = np.random.uniform(size=N_entries*T)
df = pd.DataFrame(np.vstack((src, dest, a, b)).T, columns=['src', 'dest', 'a', 'b'])
【讨论】:
【参考方案4】:您可以一次按多列分组。
# takes the b value at a specified point
# and returns its percentile of the full a array
def b_pct(df, p0):
bval = df.b[df.dt==p0]
assert bval.size == 1, 'can have only one entry per timestamp'
bval = bval.values[0]
# compute the percentile
return (df.a < bval).sum() / len(df.a)
# splits the full dataframe up into groups by (src, dest) trajectory and
# returns a dataframe of the form src, dest, percentile
def trajectory_b_percentile(df, p0):
percentile_df = pd.DataFrame([pd.Series([s, d, b_pct(g, p0)],
index=['src', 'dest', 'percentile'])
for ((s, d), g) in df.groupby(('src', 'dest'))])
return percentile_df
为了比较,上面的代码吐出来了
dt src dest a b p0_a_percentile_b
24 2017-01-01 YYZ SFO 97.04 338.28 23.076923
25 2017-01-01 DFW PDX 133.94 -129.69 46.153846
而 `trajectory_b_percentile' 返回
src dest percentile
0 DFW PDX 46.1538
1 YYZ SFO 23.0769
我没有看到 25 个条目有任何加速,但如果有更多条目,它应该会很明显。
【讨论】:
【参考方案5】:假设您有一个配对列表,例如 pairs = [[a,b], [c,d], ...]
并定义了 df,
r = stats.percentileofscore(z,b)
return r
for pair in pairs:
# get the corresponding rows for each pair
bvalues = df.loc[(df['src']==pair[0])&(df['dest']==pair[1])][['a', 'b']]
# apply the percentileofscore map
b_vector_df['p0_a_percentile_b'] = bvalues.b.apply(lambda x: stats.percentileofscore(bvalues.a, x))
我不完全确定目标是什么。我的理解是您读取每个 src、dest 对的 b 值并查找相应的 a 值和然后计算该 a 值的百分位数。让我知道这是否有帮助:)
编辑:假设您只使用date, src, dest, a, and b
的五列,您可以考虑使用仅包含这 5 列的数据框副本。它减少了每个提取步骤所需的工作量。我觉得只使用所需的数据量会更有效。
Selecting rows from a Dataframe based on values in multiple columns in pandas 是一个可能与您相关的讨论。
【讨论】:
以上是关于向量化列 A 的列 B 的百分位值(对于组)的主要内容,如果未能解决你的问题,请参考以下文章