我正在滚动的时间窗口内创建一个百分位排名,并希望帮助改进我的方法。
我的数据帧有一个多索引,第一级设置为日期时间,第二级设置为标识符。最终,我希望滚动窗口评估尾随的 n 个周期,包括当前周期,并生成相应的百分位排名。
我参考了下面显示的帖子,但发现它们处理数据的方式与我的意图略有不同。在这些帖子中,最终函数按标识符分组结果,然后按日期时间分组,而我希望在我的函数中使用滚动数据面板(日期和标识符)。
在熊猫的多索引数据帧上使用滚动函数
熊猫卷窗百分位排名
这是我所追求的一个例子。
创建示例数据帧:
num_days = 5
np.random.seed(8675309)
stock_data = {
"AAPL": np.random.randint(1, max_value, size=num_days),
"MSFT": np.random.randint(1, max_value, size=num_days),
"WMT": np.random.randint(1, max_value, size=num_days),
"TSLA": np.random.randint(1, max_value, size=num_days)
}
dates = pd.date_range(
start="2013-01-03",
periods=num_days,
freq=BDay()
)
sample_df = pd.DataFrame(stock_data, index=dates)
sample_df = sample_df.stack().to_frame(name='data')
sample_df.index.names = ['date', 'ticker']
哪些输出:
date ticker
2013-01-03 AAPL 2
MSFT 93
TSLA 39
WMT 21
2013-01-04 AAPL 141
MSFT 43
TSLA 205
WMT 20
2013-01-07 AAPL 256
MSFT 93
TSLA 103
WMT 25
2013-01-08 AAPL 233
MSFT 60
TSLA 13
WMT 104
2013-01-09 AAPL 19
MSFT 120
TSLA 282
WMT 293
下面的代码将sample_df
分解为 2 天增量,并在滚动时间窗口内生成排名与排名。所以它很接近,但不是我所追求的。
sample_df.reset_index(level=1, drop=True)[['data']]
.apply(
lambda x: x.groupby(pd.Grouper(level=0, freq='2d')).rank()
)
然后我尝试了下面显示的内容,也没有太多运气。
from scipy.stats import rankdata
def rank(x):
return rankdata(x, method='ordinal')[-1]
sample_df.reset_index(level=1, drop=True)
.rolling(window="2d", min_periods=1)
.apply(
lambda x: rank(x)
)
我终于找到了我正在寻找的输出,但公式似乎有点做作,所以我希望确定一种更优雅的方法(如果存在的话)。
import numpy as np
import pandas as pd
from pandas.tseries.offsets import BDay
window_length = 1
target_column = "data"
def rank(df, target_column, ids, window_length):
percentile_ranking = []
list_of_ids = []
date_index = df.index.get_level_values(0).unique()
for date in date_index:
rolling_start_date = date - BDay(window_length)
first_date = date_index[0] + BDay(window_length)
trailing_values = df.loc[rolling_start_date:date, target_column]
# Only calc rolling percentile after the rolling window has lapsed
if date < first_date:
pass
else:
percentile_ranking.append(
df.loc[date, target_column].apply(
lambda x: stats.percentileofscore(trailing_values, x, kind="rank")
)
)
list_of_ids.append(df.loc[date, ids])
ranks, output_ids = pd.concat(percentile_ranking), pd.concat(list_of_ids)
df = pd.DataFrame(
ranks.values, index=[ranks.index, output_ids], columns=["percentile_rank"]
)
return df
ranks = rank(
sample_df.reset_index(level=1),
window_length=1,
ids='ticker',
target_column="data"
)
sample_df.join(ranks)
我感觉我的rank
功能超出了这里的需求。我感谢任何想法/反馈,以帮助简化此代码以达到下面的输出。谢谢!
data percentile_rank
date ticker
2013-01-03 AAPL 2 NaN
MSFT 93 NaN
TSLA 39 NaN
WMT 21 NaN
2013-01-04 AAPL 141 87.5
MSFT 43 62.5
TSLA 205 100.0
WMT 20 25.0
2013-01-07 AAPL 256 100.0
MSFT 93 50.0
TSLA 103 62.5
WMT 25 25.0
2013-01-08 AAPL 233 87.5
MSFT 60 37.5
TSLA 13 12.5
WMT 104 75.0
2013-01-09 AAPL 19 25.0
MSFT 120 62.5
TSLA 282 87.5
WMT 293 100.0
编辑:最初的答案是采用没有滚动效果的 2d 组,只对出现的前两天进行分组。如果您想每 2 天滚动一次:
- 数据帧透视以将日期保留为索引,将股票代码保留为列
pivoted = sample_df.reset_index().pivot('date','ticker','data')
输出
ticker AAPL MSFT TSLA WMT
date
2013-01-03 2 93 39 21
2013-01-04 141 43 205 20
2013-01-07 256 93 103 25
2013-01-08 233 60 13 104
2013-01-09 19 120 282 293
- 现在我们可以应用一个
rolling
函数,并在该滚动的同一窗口中考虑所有股票
from scipy.stats import rankdata
def pctile(s):
wdw = sample_df.loc[s.index,:].values.flatten() ##get all stock values in the period
ranked = rankdata(wdw) / len(wdw)*100 ## their percentile
return ranked[np.where(wdw == s[len(s)-1])][0] ## return this value's percentile
pivoted_pctile = pivoted.rolling('2D').apply(pctile, raw=False)
输出
ticker AAPL MSFT TSLA WMT
date
2013-01-03 25.0 100.0 75.0 50.0
2013-01-04 87.5 62.5 100.0 25.0
2013-01-07 100.0 50.0 75.0 25.0
2013-01-08 87.5 37.5 12.5 75.0
2013-01-09 25.0 62.5 87.5 100.0
为了恢复原始格式,我们只需融化结果:
pd.melt(pivoted_pctile.reset_index(),'date')
.sort_values(['date', 'ticker']).reset_index()
输出
value
date ticker
2013-01-03 AAPL 25.0
MSFT 100.0
TSLA 75.0
WMT 50.0
2013-01-04 AAPL 87.5
MSFT 62.5
TSLA 100.0
WMT 25.0
2013-01-07 AAPL 100.0
MSFT 50.0
TSLA 75.0
WMT 25.0
2013-01-08 AAPL 87.5
MSFT 37.5
TSLA 12.5
WMT 75.0
2013-01-09 AAPL 25.0
MSFT 62.5
TSLA 87.5
WMT 100.0
如果您希望在一次执行中:
pd.melt(
sample_df
.reset_index()
.pivot('date','ticker','data')
.rolling('2D').apply(pctile, raw=False)
.reset_index(),'date')
.sort_values(['date', 'ticker']).set_index(['date','ticker'])
请注意,在第 7 天,这与你显示的不同。这实际上是滚动的,所以在第 7 天,因为没有第 6 天,因此仅对当天的值进行排名,因为数据窗口只有 4 个值,窗口不向前看。这与当天的结果不同。
源语言
这是您可能正在寻找的东西吗?我将日期(2 天)的groupby
与transform
相结合,因此观测值的数量与提供的序列相同。如您所见,我保留了窗口组的第一个观察结果。
df = sample_df.reset_index()
df['percentile_rank'] = df.groupby([pd.Grouper(key='date',freq='2D')]['data']
.transform(lambda x: x.rank(ascending=True)/len(x)*100)
输出
Out[19]:
date ticker data percentile_rank
0 2013-01-03 AAPL 2 12.5
1 2013-01-03 MSFT 93 75.0
2 2013-01-03 WMT 39 50.0
3 2013-01-03 TSLA 21 37.5
4 2013-01-04 AAPL 141 87.5
5 2013-01-04 MSFT 43 62.5
6 2013-01-04 WMT 205 100.0
7 2013-01-04 TSLA 20 25.0
8 2013-01-07 AAPL 256 100.0
9 2013-01-07 MSFT 93 50.0
10 2013-01-07 WMT 103 62.5
11 2013-01-07 TSLA 25 25.0
12 2013-01-08 AAPL 233 87.5
13 2013-01-08 MSFT 60 37.5
14 2013-01-08 WMT 13 12.5
15 2013-01-08 TSLA 104 75.0
16 2013-01-09 AAPL 19 25.0
17 2013-01-09 MSFT 120 50.0
18 2013-01-09 WMT 282 75.0
19 2013-01-09 TSLA 293 100.0