簡體   English   中英

根據 pandas DataFrame 中的最后 N 行比較兩列

[英]Compare two columns based on last N rows in a pandas DataFrame

我想對“ groupby ”進行分組,並根據每組的最后 N 行計算最大值和最大值之后另一列的最小值之間的百分比。 具體來說,

df

ts_code high low
0   A   20  10
1   A   30  5
2   A   40  20
3   A   50  10
4   A   20  30
5   B   20  10
6   B   30  5
7   B   40  20
8   B   50  10
9   B   20  30

目標

以下是我的預期結果

   ts_code  high low l3_high_low_pct_chg    l4_high_low_pct_chg
    0   A   20  10  NA  NA
    1   A   30  5   NA  NA
    2   A   40  20  0.5 NA
    3   A   50  10  0.8 0.8
    4   A   20  30  0.8 0.8
    5   B   50  10  NA  NA
    6   B   30  5   NA  NA
    7   B   40  20  0.9 NA
    8   B   10  10  0.75    0.9
    9   B   20  30  0.75    0.75

ln_high_low_pct_chg (如l3_high_low_pct_chg )= 1-(峰后low列的最小值)/( high列的最大值),在每組和每一行的最后N行。

嘗試和問題

df['l3_highest']=df.groupby('ts_code')['high'].transform(lambda x: x.rolling(3).max())
df['l3_lowest']=df.groupby('ts_code')['low'].transform(lambda x: x.rolling(3).min())
df['l3_high_low_pct_chg']=1-df['l3_lowest']/df['l3_highest']

但它失敗了,對於第二行, l3_lowest將是 5 而不是 20。我不知道如何計算峰值后的百分比。

對於最后 4 行,在 index=8,low=10,high=50,low=5, l4_high_low_pct_chg =0.9,在 index=9,high=40,low=10, l4_high_low_pct_chg =0.75

另一個測試數據

  • 如果滾動 window 為 52,對於 hy_code 880912組和索引 1252, l52_high_low_pct_chg將為 0.281131 和880301組和索引 1251, l52_high_low_pct_chg將為 0.321471。

按“ts_code”分組只是一個簡單的 groupby() function。DataFrame.rolling() function 適用於單列,因此如果您需要來自多列的數據,應用它會很棘手。 您可以使用“from numpy_ext import rolling_apply as rolling_apply_ext”,如本例所示: Pandas rolling apply using multiple columns 但是,我剛剛創建了一個 function,它手動將 dataframe 分組為 n 個長度的子數據幀,然后應用 function 來計算該值。 idxmax() 找到低列峰值的索引值,然后我們找到后面的值的 min()。 rest 非常簡單。

import numpy as np
import pandas as pd

df = pd.DataFrame([['A', 20, 10],
    ['A', 30, 5],
    ['A', 40, 20],
    ['A', 50, 10],
    ['A', 20, 30],
    ['B', 50, 10],
    ['B', 30, 5],
    ['B', 40, 20],
    ['B', 10, 10],
    ['B', 20, 30]],
    columns=['ts_code', 'high', 'low']
)
    
 
def custom_f(df, n):
    s = pd.Series(np.nan, index=df.index)

    def sub_f(df_):
        high_peak_idx = df_['high'].idxmax()
        min_low_after_peak = df_.loc[high_peak_idx:]['low'].min()
        max_high = df_['high'].max()
        return 1 - min_low_after_peak / max_high

    for i in range(df.shape[0] - n + 1):
        df_ = df.iloc[i:i + n]
        s.iloc[i + n - 1] = sub_f(df_)

    return s


df['l3_high_low_pct_chg'] = df.groupby("ts_code").apply(custom_f, 3).values
df['l4_high_low_pct_chg'] = df.groupby("ts_code").apply(custom_f, 4).values


print(df)

如果您更喜歡使用滾動 function,此方法會給出相同的 output:

def rolling_f(rolling_df):
    df_ = df.loc[rolling_df.index]
    high_peak_idx = df_['high'].idxmax()
    min_low_after_peak = df_.loc[high_peak_idx:]["low"].min()
    max_high = df_['high'].max()
    return 1 - min_low_after_peak / max_high

df['l3_high_low_pct_chg'] = df.groupby("ts_code").rolling(3).apply(rolling_f).values[:, 0]
df['l4_high_low_pct_chg'] = df.groupby("ts_code").rolling(4).apply(rolling_f).values[:, 0]

print(df)

最后,如果你想做一個真正的滾動 window 計算,避免任何索引查找,你可以使用 numpy_ext ( https://pypi.org/project/numpy-ext/ )

from numpy_ext import rolling_apply

def np_ext_f(rolling_df, n):
    def rolling_apply_f(high, low):
        return 1 - low[np.argmax(high):].min() / high.max()
    try:
        return pd.Series(rolling_apply(rolling_apply_f, n, rolling_df['high'].values, rolling_df['low'].values), index=rolling_df.index)
    except ValueError:
        return pd.Series(np.nan, index=rolling_df.index)


df['l3_high_low_pct_chg'] = df.groupby('ts_code').apply(np_ext_f, n=3).sort_index(level=1).values
df['l4_high_low_pct_chg'] = df.groupby('ts_code').apply(np_ext_f, n=4).sort_index(level=1).values

print(df)

output:

  ts_code  high  low  l3_high_low_pct_chg  l4_high_low_pct_chg
0       A    20   10                  NaN                  NaN
1       A    30    5                  NaN                  NaN
2       A    40   20                 0.50                  NaN
3       A    50   10                 0.80                 0.80
4       A    20   30                 0.80                 0.80
5       B    50   10                  NaN                  NaN
6       B    30    5                  NaN                  NaN
7       B    40   20                 0.90                  NaN
8       B    10   10                 0.75                 0.90
9       B    20   30                 0.75                 0.75

對於大型數據集,這些操作的速度成為一個問題。 因此,為了比較這些不同方法的速度,我創建了一個時序 function:

import time

def timeit(f):

    def timed(*args, **kw):
        ts = time.time()
        result = f(*args, **kw)
        te = time.time()
        print ('func:%r took: %2.4f sec' % \
          (f.__name__, te-ts))
        return result

    return timed

接下來,讓我們制作一個大的 DataFrame,只需將現有的 dataframe 復制 500 次即可:

df = pd.concat([df for x in range(500)], axis=0)
df = df.reset_index()

最后,我們在時間 function 下運行三個測試:

@timeit
def method_1():
    df['l52_high_low_pct_chg'] = df.groupby("ts_code").apply(custom_f, 52).values
method_1()

@timeit
def method_2():
    df['l52_high_low_pct_chg'] = df.groupby("ts_code").rolling(52).apply(rolling_f).values[:, 0]
method_2()

@timeit
def method_3():
    df['l52_high_low_pct_chg'] = df.groupby('ts_code').apply(np_ext_f, n=52).sort_index(level=1).values
method_3()

這給了我們這個 output:

func:'method_1' took: 2.5650 sec
func:'method_2' took: 15.1233 sec
func:'method_3' took: 0.1084 sec

因此,最快的方法是使用 numpy_ext,這是有道理的,因為它針對矢量化計算進行了優化。 第二快的方法是我編寫的自定義 function,它比較高效,因為它在進行一些矢量化計算的同時還進行一些 Pandas 查找。 迄今為止最慢的方法是使用 Pandas 滾動 function。

對於我的解決方案,我們將使用.groupby("ts_code")然后使用.rolling來處理特定大小的組和custom_function 這個自定義 function 將獲取每個組,而不是直接在接收到的值上應用 function,我們將使用這些值來查詢原始 dataframe。然后,我們可以通過找到“高”所在的行來計算您期望的值" peak 是,然后查看以下行以找到最小的“低”值,最后使用您的公式計算結果:

def custom_function(group, df):
    # Query the original dataframe using the group values
    group = df.loc[group.values]
    # Calculate your formula
    high_peak_row = group["high"].idxmax()
    min_low_after_peak = group.loc[high_peak_row:, "low"].min()
    return 1 - min_low_after_peak / group.loc[high_peak_row, "high"]


# Reset the index to roll over that column and be able query the original dataframe
df["l3_high_low_pct_chg"] = df.reset_index().groupby("ts_code")["index"].rolling(3).apply(custom_function, args=(df,)).values
df["l4_high_low_pct_chg"] = df.reset_index().groupby("ts_code")["index"].rolling(4).apply(custom_function, args=(df,)).values

Output:

  ts_code  high  low  l3_high_low_pct_chg  l4_high_low_pct_chg
0       A    20   10                  NaN                  NaN
1       A    30    5                  NaN                  NaN
2       A    40   20                 0.50                  NaN
3       A    50   10                 0.80                 0.80
4       A    20   30                 0.80                 0.80
5       B    50   10                  NaN                  NaN
6       B    30    5                  NaN                  NaN
7       B    40   20                 0.90                  NaN
8       B    10   10                 0.75                 0.90
9       B    20   30                 0.75                 0.75

我們可以將這個想法進一步擴展為一個唯一的組:

groups = df.reset_index().groupby("ts_code")["index"]
for n in [3, 4]:
    df[f"l{n}_high_low_pct_chg"] = groups.rolling(n).apply(custom_function, args=(df,)).values

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM