python-3.x - 多索引 Pandas DataFrame 上的滚动窗口百分位数排名

标签 python-3.x pandas rolling-computation

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

我的 DataFrame 有一个多重索引,第一级设置为日期时间,第二级设置为标识符。最终,我希望滚动窗口能够评估尾随的 n 个周期(包括当前周期),并生成相应的百分位数排名。

我引用了下面显示的帖子,但发现他们处理数据的方式与我的意图有点不同。在这些帖子中,最终函数按标识符然后按日期时间对结果进行分组,而我希望在函数中使用滚动数据面板(日期和标识符)。

using rolling functions on multi-index dataframe in pandas

Panda rolling window percentile rank

这是我所追求的一个例子。

创建示例数据帧:

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
  • 现在我们可以应用滚动函数并考虑该滚动内同一窗口中的所有股票
  • 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
    

    关于python-3.x - 多索引 Pandas DataFrame 上的滚动窗口百分位数排名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57634513/

    相关文章:

    python-3.x - 将 npz 转换为 csv 格式时出现关键错误

    Python GSpead 变化监听器?

    python - xlsx writer - 如何仅在单元格的一侧获得粗边框?

    Python/ Pandas : Finding a left and right max

    python Pandas 。如何使用滚动窗口进行减法

    python-3.x - 指定的至少一个标签必须在 y_true 中,目标向量是数字

    python - 更新 PriorityQueue

    python - Pandas 使用日期索引将每小时数据分组为每日总和

    python - 加速pandas groupby中的滚动总和计算

    python - 带有 groupby 和条件的 Pandas 滚动总和