2017-05-04 37 views
11

編列分配:熊貓:有多個條件和日期門檻

我有一個熊貓數據幀DF,其中指數是日期金融投資組合,我有每個日期多金融股。

如數據框:

Date Stock Weight Percentile Final weight 
1/1/2000 Apple 0.010 0.75 0.010 
1/1/2000 IBM 0.011 0.4  0 
1/1/2000 Google 0.012 0.45 0 
1/1/2000 Nokia 0.022 0.81 0.022 
2/1/2000 Apple 0.014 0.56 0 
2/1/2000 Google 0.015 0.45 0 
2/1/2000 Nokia 0.016 0.55 0 
3/1/2000 Apple 0.020 0.52 0 
3/1/2000 Google 0.030 0.51 0 
3/1/2000 Nokia 0.040 0.47 0 

我做的Weight分配值,每當Percentile大於0.7

現在我希望這是一個有點複雜的創建Final_weight,我還是想Weight是分配到Final_weightPercentile is > 0.7,但是在此日期之後(在未來的任何時間點),而不是在不是>0.7時變爲0,我們仍然會得到一個稱重t只要股票Percentile高於0.5(即持倉超過一天)。

然後如果股票跌破0.5(不久的將來)然後Final_weight would become 0

如修改數據幀從上面:

Date Stock Weight Percentile Final weight 
1/1/2000 Apple 0.010 0.75 0.010 
1/1/2000 IBM  0.011 0.4  0 
1/1/2000 Google 0.012 0.45 0 
1/1/2000 Nokia 0.022 0.81 0.022 
2/1/2000 Apple 0.014 0.56 0.014 
2/1/2000 Google 0.015 0.45 0 
2/1/2000 Nokia 0.016 0.55 0.016 
3/1/2000 Apple 0.020 0.52 0.020 
3/1/2000 Google 0.030 0.51 0 
3/1/2000 Nokia 0.040 0.47 0 

日常的組合是不同的並不總是具有相同的股票評級從之前的一天。

+0

你有沒有嘗試過的代碼向我們展示? –

+0

我寫的代碼如果與PiRSquaredes的答案非常相似,但是這只是在前一天看到的,我想要一個很好的熊貓方法來完成它,因爲數據集很大,所以最好不要使用循環 – MysterioProgrammer91

回答

4

該解決方案更加明確且較少熊貓式,但它只涉及一次遍歷所有行而不創建大量臨時列,因此可能會更快。它需要一個額外的狀態變量,我將它封裝到一個閉包中,不必上課。

def closure(): 
    cur_weight = {} 
    def func(x): 
     if x["Percentile"] > 0.7: 
      next_weight = x["Weight"] 
     elif x["Percentile"] < 0.5 : 
      next_weight = 0 
     else: 
      next_weight = x["Weight"] if cur_weight.get(x["Stock"], 0) > 0 else 0 
     cur_weight[x["Stock"]] = next_weight 
     return next_weight 
    return func 

df["FinalWeight"] = df.apply(closure(), axis=1) 
+0

很好的回答....太快了! – MysterioProgrammer91

+0

@ MysterioProgrammer91這對整個數據集來說快多少? (爲此你說,其他答案需要3天左右)。 –

+0

@cronos除非這是我提交的相同問題的犧牲品,它可能會改變'closure'只給出指標變量,然後添加'df ['Final Weight'] = df ['Final Weight'] *在應用之後,df ['Weight']'應該會更快,當我測試它時,對我來說應該是10%左右。 – EFT

3
  • 我會首先把'Stock'到索引
  • 然後unstack將它們放到列
  • 我再拆爲百分
  • w的權重和 p
  • 然後用一系列的操作where

d1 = df.set_index('Stock', append=True) 

d2 = d1.unstack() 

w, p = d2.Weight, d2.Percentile 

d1.join(w.where(p > .7, w.where((p.shift() > .7) & (p > .5), 0)).stack().rename('Final Weight')) 

        Weight Percentile Final Weight 
Date  Stock         
2000-01-01 Apple 0.010  0.75   0.010 
      IBM  0.011  0.40   0.000 
      Google 0.012  0.45   0.000 
      Nokia 0.022  0.81   0.022 
2000-02-01 Apple 0.014  0.56   0.014 
      Google 0.015  0.45   0.000 
      Nokia 0.016  0.55   0.016 
+0

您好,非常感謝您的答案。我已經修改了問題中的示例數據框。實際上,我不僅看到一個轉變,即在我們購買股票後,因爲它處於前30個百分點,我們將擁有它,只要它保持在0.5以上,例如甚至可以持續10天。我仍然會分配一個權重,但是一旦Percentile低於0.5,它就不會將權重分配給Final Weight,並且會等到它再次超過0.7百分點。 – MysterioProgrammer91

1

我想你可能要使用pandas.Series rolling窗口方法。

也許是這樣的:

import pandas as pd 

grouped = df.groupby('Stock') 

df['MaxPercentileToDate'] = np.NaN 
df.index = df['Date'] 

for name, group in grouped: 
    df.loc[df.Stock==name, 'MaxPercentileToDate'] = group['Percentile'].rolling(min_periods=0, window=4).max() 

# Mask selects rows that have ever been greater than 0.75 (including current row in max) 
# and are currently greater than 0.5 
mask = ((df['MaxPercentileToDate'] > 0.75) & (df['Percentile'] > 0.5)) 
df.loc[mask, 'Finalweight'] = df.loc[mask, 'Weight'] 

我相信這個假設值是按日期(其中您最初的數據集似乎有)排序,你也將不得不調整min_periods參數是最大數量每個股票的條目。

2

一種避免循環和限制回溯期的方法。

使用你的例子:

import pandas as pd 
import numpy as np 


>>>df = pd.DataFrame([['1/1/2000', 'Apple', 0.010, 0.75], 
         ['1/1/2000', 'IBM',  0.011, 0.4], 
         ['1/1/2000', 'Google', 0.012, 0.45], 
         ['1/1/2000', 'Nokia', 0.022, 0.81], 
         ['2/1/2000', 'Apple', 0.014, 0.56], 
         ['2/1/2000', 'Google', 0.015, 0.45], 
         ['2/1/2000', 'Nokia', 0.016, 0.55], 
         ['3/1/2000', 'Apple', 0.020, 0.52], 
         ['3/1/2000', 'Google', 0.030, 0.51], 
         ['3/1/2000', 'Nokia', 0.040, 0.47]], 
        columns=['Date', 'Stock', 'Weight', 'Percentile']) 

首先,被跟蹤的最終質量鑑定時,股票將開始或停止:

>>>df['bought'] = np.where(df['Percentile'] >= 0.7, 1, np.nan) 
>>>df['bought or sold'] = np.where(df['Percentile'] < 0.5, 0, df['bought']) 

'1',表明該股買入和「0 '一個賣,如果擁有。

由此,您可以識別股票是否擁有。注意,這需要數據幀已經按時間順序排序,如果您在任何地點使用它在一個數據幀不注日期的指標:

>>>df['own'] = df.groupby('Stock')['bought or sold'].fillna(method='ffill').fillna(0) 

'ffill'向前填補,從買入傳播權屬狀況正向和銷售日期。 .fillna(0)可捕獲整個數據框中保持在.5和.7之間的任何股票。 然後,計算出最終重量

>>>df['Final Weight'] = df['own']*df['Weight'] 

乘法,與df['own']是身份或零,比另一個np.where快一點,並給出了相同的結果。

編輯:

由於速度是一個問題,在一列中所做的一切,通過@cronos的建議,確實在我的測試中20行提供一個速度提升,未來在周圍37%的提高,或2,000,000的18%。如果存儲中間列是跨越某種內存使用閾值或者還有其他涉及我沒有體驗過的系統細節的東西,我可以想象後者會更大。

本想看看:

>>>df['Final Weight'] = np.where(df['Percentile'] >= 0.7, 1, np.nan) 
>>>df['Final Weight'] = np.where(df['Percentile'] < 0.5, 0, df['Final Weight']) 
>>>df['Final Weight'] = df.groupby('Stock')['Final Weight'].fillna(method='ffill').fillna(0) 
>>>df['Final Weight'] = df['Final Weight']*df['Weight'] 

或者使用此方法或刪除中間領域會給結果:

>>>df 
     Date Stock Weight Percentile Final Weight 
0 1/1/2000 Apple 0.010  0.75   0.010 
1 1/1/2000  IBM 0.011  0.40   0.000 
2 1/1/2000 Google 0.012  0.45   0.000 
3 1/1/2000 Nokia 0.022  0.81   0.022 
4 2/1/2000 Apple 0.014  0.56   0.014 
5 2/1/2000 Google 0.015  0.45   0.000 
6 2/1/2000 Nokia 0.016  0.55   0.016 
7 3/1/2000 Apple 0.020  0.52   0.020 
8 3/1/2000 Google 0.030  0.51   0.000 
9 3/1/2000 Nokia 0.040  0.47   0.000 

進一步改進,我想看看添加一種方法來設置一個擁有股票的初始條件,其次是打破數據框以查看較小的時間表。這可以通過添加一個初始條件由這些較小dataframes之一所覆蓋的時間段,然後改變

>>>df['Final Weight'] = np.where(df['Percentile'] >= 0.7, 1, np.nan) 

喜歡的東西

>>>df['Final Weight'] = np.where((df['Percentile'] >= 0.7) | (df['Final Weight'] != 0), 1, np.nan) 

允許要識別和傳播來完成。

+0

這樣做可行,但由於我投資組合中的股票數量和大數據性質,需要運行大約3天。任何方式使其更快? – MysterioProgrammer91

+1

很好的答案。但是,您可以通過從一開始就使用單個「FinalWeight」列加快速度,並加以處理。不需要有3個臨時列。 – cronos

+0

@ MysterioProgrammer91你能描述一下你的數據集(#行,#個股票)的內部結構和你運行它的環境嗎?我在構建一個場景時遇到了一些麻煩,在這種場景中,我的任何地方都可以在很長的時間內運行,而運行的數據幀適合內存,而且這些信息對於確定我目前所用的技術在哪裏以及如何執行失敗規模。 – EFT

2

設置

Dataframe: 

      Stock Weight Percentile Finalweight 
Date            
2000-01-01 Apple 0.010  0.75   0 
2000-01-01  IBM 0.011  0.40   0 
2000-01-01 Google 0.012  0.45   0 
2000-01-01 Nokia 0.022  0.81   0 
2000-02-01 Apple 0.014  0.56   0 
2000-02-01 Google 0.015  0.45   0 
2000-02-01 Nokia 0.016  0.55   0 
2000-03-01 Apple 0.020  0.52   0 
2000-03-01 Google 0.030  0.51   0 
2000-03-01 Nokia 0.040  0.57   0 

解決方案

df = df.reset_index() 
#find historical max percentile for a Stock 
df['max_percentile'] = df.apply(lambda x: df[df.Stock==x.Stock].iloc[:x.name].Percentile.max() if x.name>0 else x.Percentile, axis=1) 
#set weight according to max_percentile and the current percentile 
df['Finalweight'] = df.apply(lambda x: x.Weight if (x.Percentile>0.7) or (x.Percentile>0.5 and x.max_percentile>0.7) else 0, axis=1) 

Out[1041]: 
     Date Stock Weight Percentile Finalweight max_percentile 
0 2000-01-01 Apple 0.010  0.75  0.010   0.75 
1 2000-01-01  IBM 0.011  0.40  0.000   0.40 
2 2000-01-01 Google 0.012  0.45  0.000   0.45 
3 2000-01-01 Nokia 0.022  0.81  0.022   0.81 
4 2000-02-01 Apple 0.014  0.56  0.014   0.75 
5 2000-02-01 Google 0.015  0.45  0.000   0.51 
6 2000-02-01 Nokia 0.016  0.55  0.016   0.81 
7 2000-03-01 Apple 0.020  0.52  0.020   0.75 
8 2000-03-01 Google 0.030  0.51  0.000   0.51 
9 2000-03-01 Nokia 0.040  0.57  0.040   0.81 

注意

在您的示例數據的最後一排,諾基亞的百分位數爲0.57,而在你的結果就變成0.47。在這個例子中,我使用了0.57,所以輸出與最後一行的輸出有些不同。