2017-05-04 4 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 

私はPercentile0.7

以上である今、私は、これはもう少し洗練されたようにしたい時はいつでもWeightの割り当て値を実行してFinal_weightを作成し、私はまだWeightになりたいですPercentile>0.7ではない場合、Percentile is > 0.7の場合はFinal_weightに割り当てられますが、この日付後(将来の任意の時点)に0になるのではなく、 Percentile0.5を上回っている(つまり、1日以上のポジションを保持している限り)。

次に、株式が0.5(近い将来)になると、Final_weight would become 0となります。

Egは上からデータフレームを変更:

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

このソリューションは、より明示的でパンダ風ではありませんが、一時的な列を作成せずにすべての行を1回だけ通過するため、高速になる可能性があります。追加の状態変数が必要です。これをクラスを作成する必要がないためにクロージャーにラップしました。

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これは、私の提出物が持っていたのと同じ問題に挑戦しない限り、インジケータ変数を与えて 'df ['Final Weight'] = df ['Final Weight'] * df ['Weight'] 'を適用した後はテストしたほうが速く、私の場合は約10%です。 – EFT

3
  • は、私が最初に
  • 列にそれらを置くためにunstack次にインデックス
  • 'Stock'を置くところ私はその後、一連の操作次にパーセンタイル
  • の重みのためwpを分割したいです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

こんにちは。私はこの質問のサンプルデータフレームを修正しました。私は実際には、1つのシフトだけを見ているだけでなく、つまり、30パーセンタイルの上位にあるために株式を購入した後、0.5を超えている限り、所有します。例えば、10日間でも可能です。私はまだ体重を割り当てますが、Percentileが0.5未満になると、最終体重に体重を割り当てず、再び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パラメータを調整しなければならないと仮定し信じます株式1株あたりのエントリ数。

2

ループを回避し、ルックバック期間を制限する1つの方法です。あなたの例を使用して

:購入する株式を示す '1' で

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

、「0:

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['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 

を、私が設定する方法を追加することで、見てねストックが所有されている初期状態であり、続いてデータフレームを破棄してより小さいタイムフレームを見る。これは、それが認識されるようにして伝播する

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

のようなものに

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

を変更し、その後、これらの小さなデータフレームの1でカバー期間の初期条件を追加することによって行うことができます。あなたの例のデータの最後の行で

+0

これはうまくいきますが、ポートフォリオのストック数と大きなデータの性質から、実行には約3日かかります。それをもっと速くする方法はありますか? – MysterioProgrammer91

+1

良い答え。しかし、最初から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を使用したため、出力は最後の行とは少し異なります。