Hinzufügen berechneter Spalten zu einem Datenrahmen in Pandas

78

Ich habe einen OHLC-Preisdatensatz, den ich von CSV in einen Pandas-Datenrahmen analysiert und auf 15-Minuten-Balken neu abgetastet habe:

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 500047 entries, 1998-05-04 04:45:00 to 2012-08-07 00:15:00
Freq: 15T
Data columns:
Close    363152  non-null values
High     363152  non-null values
Low      363152  non-null values
Open     363152  non-null values
dtypes: float64(4)

Ich möchte verschiedene berechnete Spalten hinzufügen, beginnend mit einfachen Spalten wie dem Periodenbereich (HL) und dann Booleschen Werten, um das Auftreten von Preismustern anzuzeigen, die ich definieren werde - z. B. ein Hammerkerzenmuster, für das eine Beispieldefinition gilt:

def closed_in_top_half_of_range(h,l,c):
    return c > l + (h-l)/2

def lower_wick(o,l,c):
    return min(o,c)-l

def real_body(o,c):
    return abs(c-o)

def lower_wick_at_least_twice_real_body(o,l,c):
    return lower_wick(o,l,c) >= 2 * real_body(o,c)

def is_hammer(row):
    return lower_wick_at_least_twice_real_body(row["Open"],row["Low"],row["Close"]) \
    and closed_in_top_half_of_range(row["High"],row["Low"],row["Close"])

Grundproblem: Wie ordne ich die Funktion der Spalte zu, insbesondere dort, wo ich auf mehr als eine andere Spalte oder die gesamte Zeile oder was auch immer verweisen möchte?

Dieser Beitrag befasst sich mit dem Hinzufügen von zwei berechneten Spalten aus einer einzelnen Quellenspalte, was nahe ist, aber nicht ganz.

Und etwas weiter fortgeschritten: Wie kann ich bei Preismustern, die mit Bezug auf mehr als einen einzelnen Balken (T) ermittelt werden, innerhalb der Funktionsdefinition auf verschiedene Zeilen (z. B. T-1, T-2 usw.) verweisen?

ultra909
quelle

Antworten:

75

Der genaue Code variiert für jede der Spalten, die Sie ausführen möchten, aber wahrscheinlich möchten Sie die Funktionen mapund applyverwenden. In einigen Fällen können Sie einfach direkt mit den vorhandenen Spalten rechnen, da es sich bei den Spalten um Objekte der Pandas-Serie handelt, die auch als Numpy-Arrays fungieren und für übliche mathematische Operationen automatisch elementweise funktionieren.

>>> d
    A   B  C
0  11  13  5
1   6   7  4
2   8   3  6
3   4   8  7
4   0   1  7
>>> (d.A + d.B) / d.C
0    4.800000
1    3.250000
2    1.833333
3    1.714286
4    0.142857
>>> d.A > d.C
0     True
1     True
2     True
3    False
4    False

Wenn Sie Operationen wie max und min in einer Zeile verwenden müssen, können Sie applymit verwenden axis=1, um eine beliebige Funktion auf jede Zeile anzuwenden. Hier ist ein Beispiel, das berechnet min(A, B)-C, das wie Ihr "unterer Docht" zu sein scheint:

>>> d.apply(lambda row: min([row['A'], row['B']])-row['C'], axis=1)
0    6
1    2
2   -3
3   -3
4   -7

Hoffentlich gibt Ihnen das eine Vorstellung davon, wie Sie vorgehen sollen.

Bearbeiten: Um Zeilen mit benachbarten Zeilen zu vergleichen, besteht der einfachste Ansatz darin, die zu vergleichenden Spalten in Scheiben zu schneiden, den Anfang / das Ende wegzulassen und dann die resultierenden Segmente zu vergleichen. Hier erfahren Sie beispielsweise, für welche Zeilen das Element in Spalte A kleiner ist als das Element der nächsten Zeile in Spalte C:

d['A'][:-1] < d['C'][1:]

und dies geschieht andersherum, indem Sie erfahren, welche Zeilen A kleiner als das C der vorhergehenden Zeile haben:

d['A'][1:] < d['C'][:-1]

Doing ['A"][:-1]Scheiben aus dem letzten Element der Spalte A, und tun ['C'][1:]Scheiben aus dem ersten Element der Spalte C, so dass , wenn Sie diese beiden und vergleichen sie säumen, sind Sie jedes Element in A mit dem C aus der folgenden Zeile zu vergleichen.

BrenBarn
quelle
47

Sie könnten is_hammerin Bezug auf row["Open"]etc. wie folgt haben

def is_hammer(rOpen,rLow,rClose,rHigh):
    return lower_wick_at_least_twice_real_body(rOpen,rLow,rClose) \
       and closed_in_top_half_of_range(rHigh,rLow,rClose)

Dann können Sie map verwenden:

df["isHammer"] = map(is_hammer, df["Open"], df["Low"], df["Close"], df["High"])
Andy Hayden
quelle
2
Auch nützlich, vielen Dank. Viele Möglichkeiten, eine Katze und alle zu häuten. Ich würde Ihnen eine positive Bewertung geben, aber dies ist meine erste Frage zu StackOverflow und ich habe leider nicht genug Wiederholungen. Ich nehme nicht an, dass Sie im zweiten Teil keine Ideen haben, nämlich auf benachbarte Zeilen im Datenrahmen innerhalb der Map / Apply-Funktion zu verweisen. Nochmals Prost.
Ultra909
1
Aus irgendeinem Grund ist die verwendete Methode map(f, col1, col2)viel schneller als df.apply(..., axis=1). Die Karte benötigt 0,35 Sekunden, während für einen Datenrahmen mit 1 Million Zeilen 26 Sekunden benötigt werden. Irgendeine Idee warum? (Python 2.7 und Pandas 0.18.1)
MohamedEzz
Manchmal funktioniert das großartig, aber manchmal erhalte ich die Warnung: "Ein Wert versucht, auf einer Kopie eines Slice aus einem DataFrame festgelegt zu werden. Verwenden Sie stattdessen .loc [row_indexer, col_indexer] = value" ... Irgendwelche Erkenntnisse?
Elomage
1
@elomage es ist unklar ohne weitere Infos. Vielleicht schneiden Sie schon früh einen Teil des Datenrahmens auf und weisen ihn dann in dieser Ansicht zu. Sie können die Ansicht wie df = df.copy()oben beschrieben kopieren ... dies kann jedoch besser als neue Frage beantwortet werden.
Andy Hayden
@AndyHayden df.copy () hat es geschafft, danke. Ich habe vorher etwas geschnitten. Für kleinere Datenrahmen gab es kein Problem, für größere musste ich die copy () machen.
Elomage
1

Die ersten vier Funktionen, die Sie auflisten, funktionieren auch mit Vektoren, mit der Ausnahme, dass lower_wick angepasst werden muss. Etwas wie das,

def lower_wick_vec(o, l, c):
    min_oc = numpy.where(o > c, c, o)
    return min_oc - l

wobei o, l und c Vektoren sind. Sie können dies stattdessen auf diese Weise tun, indem Sie nur den df als Eingabe verwenden und die Verwendung von numpy vermeiden, obwohl dies viel langsamer sein wird:

def lower_wick_df(df):
    min_oc = df[['Open', 'Close']].min(axis=1)
    return min_oc - l

Die anderen drei arbeiten mit Spalten oder Vektoren so wie sie sind. Dann können Sie mit beenden

def is_hammer(df):
    lw = lower_wick_at_least_twice_real_body(df["Open"], df["Low"], df["Close"]) 
    cl = closed_in_top_half_of_range(df["High"], df["Low"], df["Close"])
    return cl & lw

Bit - Operatoren können Satz Logik auf boolean Vektoren durchführen, &für and, |für orusw. genug ist , vollständig die Beispielrechnungen vektorisieren Sie gab und sollte relativ schnell sein. Sie könnten wahrscheinlich noch schneller werden, indem Sie vorübergehend mit den den Daten zugrunde liegenden Numpy-Arrays arbeiten, während Sie diese Berechnungen durchführen.

Für den zweiten Teil würde ich empfehlen, eine Spalte einzuführen, die das Muster für jede Zeile angibt, und eine Familie von Funktionen zu schreiben, die sich mit jedem Muster befassen. Gruppieren Sie dann nach dem Muster und wenden Sie die entsprechende Funktion auf jede Gruppe an.

JoeCondron
quelle