滚动窗口百分位数在多索引熊猫数据帧上的排名



我正在滚动的时间窗口内创建一个百分位排名,并希望帮助改进我的方法。

我的数据帧有一个多索引,第一级设置为日期时间,第二级设置为标识符。最终,我希望滚动窗口评估尾随的 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 天滚动一次:

  1. 数据帧透视以将日期保留为索引,将股票代码保留为列
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
  1. 现在我们可以应用一个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 天)的groupbytransform相结合,因此观测值的数量与提供的序列相同。如您所见,我保留了窗口组的第一个观察结果。

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

最新更新