2017-05-04 3 views
11

Editiert:Pandas: Zuweisen von Spalten mit mehreren Bedingungen und Datum Schwellen

Ich habe ein Finanzportfolio in einem Pandas Datenrahmen df, wobei der Index das Datum ist, und ich habe mehrere Finanzwerte pro Tag.

Eg Datenrahmen:

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 

I erstellt Final_weight durch Zuweisen von Werten von Weight tun, wenn Percentile größer als 0.7

Jetzt möchte ich dies ein wenig anspruchsvoller sein, ich will noch Weight sein zugewiesen zu Final_weight wenn Percentile is > 0.7, jedoch nach diesem Datum (zu einem beliebigen Zeitpunkt in der Zukunft), anstatt 0 zu werden, wenn ein Aktien Percentile nicht >0.7 ist, würden wir immer noch wiegen t solange die Aktien Percentile über 0.5 sind (dh die Position länger als nur einen Tag halten).

Dann, wenn der Bestand unter 0.5 (in naher Zukunft) geht dann Final_weight would become 0.

Eg modifizierten Datenrahmen von oben:

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 

Jeden Tag die Portfolios unterschiedlich sind nicht immer die gleiche Lager, bevor sie aus dem Tag.

+0

Haben Sie einen versuchten Code, um uns zu zeigen? –

+0

Der Code, den ich schrieb, wenn ich sehr ähnlich zu PiRSquaredes unten antworte, aber das sieht nur vor einem Tag, ich wollte eine nette Pandas Weg, es vorzugsweise ohne Schleifen zu tun, weil der Datensatz riesig ist – MysterioProgrammer91

Antwort

4

Diese Lösung ist expliziter und weniger pandas-esque, aber es beinhaltet nur einen einzigen Durchlauf durch alle Zeilen, ohne Tonnen von temporären Spalten zu erstellen, und ist daher möglicherweise schneller. Es benötigt eine zusätzliche Statusvariable, die ich in eine Schließung eingepackt habe, um keine Klasse zu bilden.

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

Große Antwort .... so schnell! – MysterioProgrammer91

+0

@ MysterioProgrammer91 Wie viel schneller ist das für den gesamten Datensatz? (für die Sie gesagt haben, dass es etwa 3 Tage für die andere Antwort dauerte). –

+0

@cronos Es sei denn, dies ist die gleichen Probleme, die meine Einreichung hatte, was es sein könnte, ändern "Schließung", um nur Indikatorvariablen geben und dann hinzufügen 'df ['Final Weight'] * df ['Final Weight'] * df ['Gewicht'] 'nach der Anwendung sollte schneller sein, etwa 10% für mich, als ich es getestet habe. – EFT

3
  • Ich hatte die erste Stelle setzen 'Stock' in den Index
  • unstack dann in die Spalten zu setzen
  • ich dann w spalten würde für Gewichte und p für Perzentile
  • dann mit einer Reihe von manipulieren 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

Hallo vielen Dank für die Antwort. Ich habe den Beispieldatenrahmen in der Frage geändert. Ich schaue tatsächlich nicht nur auf eine Schicht, dh nachdem wir die Aktie gekauft haben, weil sie in den oberen 30 Perzentilen ist, werden wir sie besitzen, solange sie über 0,5 bleibt, zB sogar für 10 Tage. Ich würde immer noch ein Gewicht zuweisen, aber sobald das Perzentil unter 0,5 liegt, würde es dem Gewicht keine Gewichtung zuweisen und würde warten, bis es wieder über 0,7 Prozent ist. – MysterioProgrammer91

1

Ich denke, dass Sie die Fenstermethode pandas.Series rolling verwenden möchten.

Vielleicht so etwas:

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

Ich glaube, diese Werte annimmt, nach Datum sortiert werden (die Ihr erster Datensatz zu haben scheint), und Sie müßten auch die min_periods Parameter einstellen die maximale Zahl sein von Einträgen pro Lager.

2

Eine Methode, Schleifen und begrenzte Rückblickzeiten zu vermeiden.

mit Ihrem Beispiel:

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']) 

Zuerst identifizieren, wenn Bestände beginnen würde oder in Endgewicht verfolgt stoppen wird:

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

Mit '1' eine Aktie angibt, zu kaufen, und ‚0 "Ein zu verkaufen, wenn es im Besitz ist.

Von diesem können Sie identifizieren, ob das Lager gehört. Beachten Sie, dass dies erfordert die Datenrahmen bereits chronologisch sortiert werden, wenn an irgendeiner Stelle Sie es auf einem Datenrahmen ohne Datum Index verwenden:

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

'ffill' nach vorne füllen, Eigentumsverhältnisse ausbreitende uns darauf, von Kauf- und Verkaufsdaten. .fillna(0) fängt alle Bestände ab, die für den gesamten Datenrahmen zwischen 0,5 und 0,7 verblieben sind. Dann Endgewicht

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

Multiplikation berechnen, mit df['own'] die Identität oder gleich Null ist, ist ein wenig schneller als ein anderer np.where und gibt das gleiche Ergebnis.

Edit:

Da Geschwindigkeit ein Anliegen ist, alles in einer Spalte zu tun, wie @cronos vorgeschlagen, einen Geschwindigkeitsschub liefert, kommt in um eine 37% ige Verbesserung bei 20 Zeilen in meinen Tests, oder 18% bei 2.000.000. Ich könnte mir vorstellen, dass Letzteres größer wäre, wenn das Speichern der Zwischenspalten eine Art Speicherverbrauchsschwelle überschreiten würde oder es etwas anderes mit Systemspezifikationen gäbe, die ich nicht erfahren hätte.

Dies würde wie folgt aussehen:

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

Entweder diese Methode oder das Löschen der Zwischen Feldern Ergebnis geben würde:

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

Zur weiteren Verbesserung, ich das Hinzufügen einer Art und Weise aussehen würde zu setzen eine anfängliche Bedingung, bei der sich Aktien im Besitz befinden, gefolgt von einer Zerlegung des Datenrahmens in kleinere Zeitrahmen. Dies könnte durch das Hinzufügen einer Anfangsbedingung für den Zeitraum von einem dieses kleineren Datenrahmen bedeckt getan wird, dann

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

um so etwas wie

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

zu, dass erkannt werden zu lassen und verbreiten.

+0

Dies funktioniert, aber es dauert etwa 3 Tage zu laufen wegen der Anzahl der Aktien in meinem Portfolio und aufgrund der großen Daten Natur. Irgendeine Möglichkeit, es schneller zu machen? – MysterioProgrammer91

+1

Schöne Antwort. Aber Sie können dies schneller machen, indem Sie von Anfang an eine einzelne "FinalWeight" -Spalte verwenden und daran arbeiten. Keine Notwendigkeit, 3 temporäre Spalten zu haben. – cronos

+0

@ MysterioProgrammer91 Können Sie die interne Struktur Ihres Datasets (# Zeilen, # unterschiedliche Bestände) und die Umgebung, in der Sie das ausführen, beschreiben? Ich habe ein paar Probleme damit, ein Szenario zu erstellen, in dem meine Daten annähernd so lang sind, während der Datenrahmen, in dem sie arbeitet, in den Speicher passt, und diese Informationen wären eine große Hilfe, um herauszufinden, wo und wie die Techniken, die ich derzeit anstrebe, fehlschlagen Rahmen. – EFT

2

Setup-

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 

Lösung

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 

Hinweis

In der letzten Zeile der Beispieldaten, Nokias Perzentil ist 0,57, während in den Ergebnissen wird es 0.47. In diesem Beispiel habe ich 0,57 verwendet, daher ist die Ausgabe für die letzte Zeile etwas anders als die Ihre.

Verwandte Themen