iterrows über mehrere dataframes per loop

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

Hallo,

ich habe mehrere dataframes, die ich nach den gleichen Bedingungen filtern möchte; gerade habe ich 3x den gleichen Code:

Code: Alles auswählen

for pos, d in df1.iterrows():
  if (( dies und jenes )):
    das = etwas
    und nochwas
    
Jetzt will ich das gleiche aber über df1, df2 und df3 machen.
Nah liegt eine Liste:
dfList = [df1, df2, df3] oder ['df1', 'df2', 'df3'], ich habe beides versucht.
und dann:

Code: Alles auswählen

for pos, d in dfList.iterrows():
  .. 
  ..
Hier gibt es dann den Attribute Error, dass das List Object keine iterrows Methode hat.

Wie macht man das denn?

Besten Dank & Grüße,
Chris
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Du brauchst natürlich eine weitere Schleife.

Wenn Du aber iterrows benutzt, machst Du mit großer Wahrscheinlichkeit etwas falsch, denn bei Pandas möchte man die entsprechenden Funktionen auf den gesamten Datenframe anwenden.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ChrisLi: Anmerkung zur Namensgebung: Namen sollten keine kryptischen Abkürzungen enthalten oder nur daraus bestehen. `pos` steht für „positiv“? Positronenanzahl? Point of Sale? Und `d` passt wie genau zu einer Zeile aus der Tabelle? Also selbst wenn man es dort generisch halten will, wäre das eher so etwas wie `index` und `row`.

Namen werden Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Grunddatentypen sollten nicht in Namen stehen. Den Datentyp ändert man ab und zu mal im Laufe der Programmentwicklung, und dann hat man entweder irreführende, falsche Namen, oder muss durch den ganzen Code gehen und alle betroffenen Namen anpassen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

Sirius3 hat geschrieben: Mittwoch 27. September 2023, 08:00 Du brauchst natürlich eine weitere Schleife.

Wenn Du aber iterrows benutzt, machst Du mit großer Wahrscheinlichkeit etwas falsch, denn bei Pandas möchte man die entsprechenden Funktionen auf den gesamten Datenframe anwenden.
Wieso noch eine Schleife? Mit iterrows klappt es, ohne den Loop-Versuch, sehr gut und ich will ja über alle Dataframes komplett iterieren.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

__blackjack__ hat geschrieben: Mittwoch 27. September 2023, 09:32 @ChrisLi: Anmerkung zur Namensgebung: Namen sollten keine kryptischen Abkürzungen enthalten oder nur daraus bestehen. `pos` steht für „positiv“? Positronenanzahl? Point of Sale? Und `d` passt wie genau zu einer Zeile aus der Tabelle? Also selbst wenn man es dort generisch halten will, wäre das eher so etwas wie `index` und `row`.

Namen werden Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Grunddatentypen sollten nicht in Namen stehen. Den Datentyp ändert man ab und zu mal im Laufe der Programmentwicklung, und dann hat man entweder irreführende, falsche Namen, oder muss durch den ganzen Code gehen und alle betroffenen Namen anpassen.
Ich analysiere Finanzdaten. pos steht für Position und d sind die Daten zur Position.
Ja, die Namenskonventionen.. es sind mir Zuviel, so dass ich nie weiß, an welche ich mich halten soll. Ich tanze auf mehreren Hochzeiten und irgendwann bekommt man das alles nicht mehr sauber getrennt.
Aber df brauche ich wirklich nicht im Namen, das habe ich mitgeschleppt von meinen ersten Pandas-Versuchen, das muss ich ändern.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ChrisLi: Noch eine Schleife weil Du mit der Liste eine weitere ”Dimension” hast. Du kannst ja nicht auf der Liste `iterrows()` aufrufen. Das ist eine Methode die kennt `list` nicht. Die kennen die einzelnen Objekte *in* der Liste. Und wenn Du für jedes Objekt *in* der Liste etwas machen willst, dann brauchst Du eine Schleife über die Elemente der Liste.

Wenn Du dann eine Schleife über alle Elemente von den drei Iteratoren haben möchtest, kannst Du die mit `itertools.chain.from_iterables()` verbinden. Das erspart Dir aber nicht in einer Schleife, beziehungsweise einem Generatorausdruck, durch die DataFrames zu iterieren, um `iterrows()` aufzurufen.

Man könnte diese Schleife noch mit `map()` und `operator.methodcaller()` ”verstecken”, aber dann liefe sie *in* der `map()`-Funktion.

Was immer noch bleibt ist die Frage warum überhaupt `iterrows()`. Das ist ein „code smell“.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

__blackjack__ hat geschrieben: Mittwoch 27. September 2023, 14:23 @ChrisLi: Noch eine Schleife weil Du mit der Liste eine weitere ”Dimension” hast. Du kannst ja nicht auf der Liste `iterrows()` aufrufen. Das ist eine Methode die kennt `list` nicht. Die kennen die einzelnen Objekte *in* der Liste. Und wenn Du für jedes Objekt *in* der Liste etwas machen willst, dann brauchst Du eine Schleife über die Elemente der Liste.

Wenn Du dann eine Schleife über alle Elemente von den drei Iteratoren haben möchtest, kannst Du die mit `itertools.chain.from_iterables()` verbinden. Das erspart Dir aber nicht in einer Schleife, beziehungsweise einem Generatorausdruck, durch die DataFrames zu iterieren, um `iterrows()` aufzurufen.

Man könnte diese Schleife noch mit `map()` und `operator.methodcaller()` ”verstecken”, aber dann liefe sie *in* der `map()`-Funktion.

Was immer noch bleibt ist die Frage warum überhaupt `iterrows()`. Das ist ein „code smell“.
@__blackjack__: iterrows() ist das erste, was mir begegnete und es funktioniert, also angenommen.
Ich habe es nun hinbekommen mit der weiteren Schleife, alles klar damit, vielen Dank.
Zum nächsten Problem habe ich gerade lange gesucht und werde nicht findig =( : "Pandas Use variable in df name to Write into multiple dfs in a for loop."

Ich habe nun diese das definiert:

Code: Alles auswählen

# create new dataframes for filtered data
df_M5_h2l = pd.DataFrame(columns=('time', 'high', 'low', 'high2low'))
df_M30_h2l = pd.DataFrame(columns=('time', 'high', 'low', 'high2low'))
df_H1_h2l = pd.DataFrame(columns=('time', 'high', 'low', 'high2low'))
Das kann ja nicht funktionieren, "tf" ist ja der gesamte df, jetzt brauche ich ja nur die Listenelemente..

und will im for loop alle drei füllen:

Code: Alles auswählen

tfList = [df_M5, df_M30, df_H1]
for tf in tfList:
    for pos, d in tf.iterrows():
        if ((d.time.hour >=0 ) and (d.time.hour <=8 )):
          h2l = d.high - d.low
          {tf + '_h2l'}.loc[pos] = [d.time,d.high,d.low,h2l]
Wie bekomme ich den Namen zusammengebastelt aus der Variablen "tf" und dem "_h2l", welches da noch dran kommt?

Code: Alles auswählen

f'{tf} + "_ht2l"'.loc[pos] = [d.time,d.high,d.low,h2l]
tut es auch nicht, dann wird er df zu einem string object and has no Attribute "loc"..


1000 Dank & Grüße
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das funktioniert so auch nicht, weil die Elemente der Liste tflist ja nicht unbedingt vorher schon einen Namen gehabt haben müssen.
Das simpelste ist eine Funktion, die man für jedes Dataframe einmal aufruft:

Code: Alles auswählen

def calculate_high_to_low(data):
    result = data[data.time.dt.hour.between(0, 8, 'both')]
    result['high_to_low'] = result.high - result.low
    return result

df_M5_high_to_low = calculate_high_to_low(df_M5)
df_M30_high_to_low = calculate_high_to_low(df_M30)
df_H1_high_to_low = calculate_high_to_low(df_H1)
Natürlich könnte man die drei Dataframes in ein Wörterbuch packen, oder gleich die M5, M30 oder H1-Information als weiteren Index definieren, damit man nur einen Dataframe hat, weil Du ja scheinbar eh auf allen Daten die selben Operationen anwenden willst.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

Schöner Ansatz; vielen Dank, da muss ich morgen in klarem Kopf überlegen, ob so alles geht, was ich haben will.

Zu meinem Ansatz: Die Elemente in der Liste haben alle vorher einen Namen, weil ich die DFs ja definiere und dann gefiltert befüllen will.

Ich habe es so versucht:

Code: Alles auswählen

tfList = [df_M5, df_M30, df_H1]
tfList_str = ['df_M5', 'df_M30', 'df_H1']
print(tfList_str)
for i in range(len(tfList)):
    df_name = tfList_str[i] + "_h2l"
    print("df_name: ", df_name) # sieht richtig aus
    for tf in tfList:
        for pos, d in tf.iterrows():
            if ((d.time.hour >=0 ) and (d.time.hour <=8 )):
                h2l = d.high - d.low
                df_name.loc[pos] = [d.time,d.high,d.low,h2l]
Aber dann ist ganz unten df_name wieder nur ein String und kein DF.. Ist es nicht möglich, einen DF-Namen zu bauen, der dann auch als DF akzeptiert wird? Also lässt sich ein DF-Name nicht derart generieren? Ich komme von BASH her, alles Strings oder Werte, die ich c[au]tten und tauschen kann, aber so darf ich hier nicht denken?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ChrisLi: `iterrows()` mag funktionieren, aber dann ist die Frage warum Du Pandas verwendest. Du programmierst hier letztlich gegen dieses Rahmenwerk und nicht mit dem Rahmenwerk. Das gleiche bezüglich der Sprache selbst. Wenn Du Bash programmieren willst, dann programmiere in der Bash. Aber versuch nicht Sachen aus Bash in Python zu übernehmen die dort so einfach nicht vorgesehen sind.

Das die Elemente einen, mehrere, oder keinen Namen haben wissen die Elemente selber nicht. Das ist nichts was man als Wert annehmen sollte. Namen sind etwas das der Programmierer fest in den Code schreibt und nicht dynamisch irgendwie zusammenbastelt. Wenn Du eine Abbildung von Zeichenketten auf Werte brauchst, dann ist das Wörterbuch die passende Datenstruktur.

Was auch supergruselig ist, sind leere DataFrame-Objekte die dann Zeile für Zeile mit Werten gefüllt werden. Im schlechtesten Fall wird dabei bei jedem hinzufügen alles was schon in dem DataFrame ist, einmal im Speicher umkopiert, weil DataFrames und Numpy-Arrays, wo das ja drauf aufbaut, nicht in der Grösse veränderbar sind.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@ChrisLi: Um das einmal zu pointieren: Du verwendest Pandas Dataframes wie eine beliebige Python Datenstruktur. Das ist Blödsinn und falsch. Das Besondere an Pandas ist, dass Dataframes eben mehr sind. Und bereits in der ersten Antwort wurdest du darauf hingewiesen, dass die Verwendung von .iterrows() ein deutlicher Hinweis darauf ist, dass du Dinge falsch machst.

Ich empfehle dringend das Pandas Tutorial, damit du ein Gefühl dafür bekommst, was Dataframes eigentlich sind.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

Danke für Eure Antworten und Hinweise. Woher sollte ich wissen, dass es nicht im Sinne Pandas ist, leere frames anzulegen?
Ich war meist hier https://pandas.pydata.org/docs/referenc ... rrows.html
gelandet und war der naiven Ansicht, dass hier bspw. iterrows eine offizielle Methode ist; dazu hat sie bislang für mich das getan, was ich wollte.
Ich werde mir mal Tutorials anderer Quellen reintun.
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Andere Quellen sind gar nicht erforderlich. Pandas hat selbst ganz gute Anleitungen, beginnend mit '10 minutes to pandas' unter: https://pandas.pydata.org/docs/user_guide/index.html.
Bei DataFrames geht es darum die Operationen möglichst auf alle (ggf. selektierten/gefilterten) Werte "gleichzeitig" anzuwenden und nicht die Werte nacheinander durchzugehen.
Beispiel an deinem Ding oben angelehnt:

Code: Alles auswählen

dataframes = [df_M5, df_M30, df_H1]
    for df in dataframes:
        condition = (df.time.hour >= 0) & (df.time.hour <= 8)
        df["hight2low"] = df[condition].high - df[condition].low
Wobei ich ohne Kenntnis der eigentlichen Struktur der Daten nicht ganz beurteilen kann, ob die drei DataFrames überhaupt erforderlich sind oder ob die Daten darin nicht verschiedene Spalten eines DataFrames sein könnten/sollten.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

@einfachTobi: Mal schauen, wann ich dahinter komme, was schlecht an .iterrows() sein soll, wenn es in der offiziellen Doku ohne einen deprecated-Hinweis zu finden ist.
Ich bin jetzt schon in einem Udemy-Kurs.. Pandas.pydata.org ist m.E. kompliziert gestaltet; quasi so wie wenn ein Linux-/Bash-Einsteiger die man pages nutzen soll.. da kommt der erstmal nur sehr schwer weiter.
Aber wie Du zeigst, wie sich conditions anwenden lassen, ist klasse.
Ich will die Werte (highest/lowest price, highest-lowest price, von und zu verschiedenen TimeFrames (5min, 60min, 4hrs, 8hrs..) miteinander vergleichen; wie sich bspw. der höchste Preis zwischen 0:00 - 8:00 zum höchsten Preis der nächsten Stunden verhält und ob und wann der Kurs zurück kam und wenn ja, bis wohin und wenn nicht, wie oft nicht usw..
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

Die Datenstruktur sieht bspw. so aus, pro TimeFrame/DF:

Code: Alles auswählen

          time      high       low  high2low
0   2020-01-05  9,039.75  8,662.88    376.87
1   2020-01-12  9,177.62  8,963.00    214.62
2   2020-01-19  9,277.88  9,099.88    178.00
3   2020-01-26  9,241.25  8,916.62    324.63
4   2020-02-02  9,460.38  8,971.12    489.26
5   2020-02-09  9,650.62  9,320.88    329.74
6   2020-02-16  9,753.38  9,403.50    349.88
7   2020-02-23  9,356.00  8,122.00  1,234.00
8   2020-03-01  9,001.50  8,224.75    776.75
9   2020-03-08  8,384.88  6,945.88  1,439.00
10  2020-03-15  7,908.50  6,830.62  1,077.88
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

ChrisLi: iterrows ist nicht veraltet, sondern nur für Deinen Anwendungsfall nicht richtig. Wie schon geschrieben, die Philosophie von pandas und ähnlichen Bibliotheken ist es, nicht explizit Schleifen zu schreiben, sondern Operationen auf ganze Dataframes anzuwenden.
Das ist nicht nur schneller, sondern auch lesbarer und flexibler.

@einfachTobi: es gibt between, siehe meine Lösung.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@ChrisLi: Ich bin mir, wie andere hier in Thread, ziemlich sicher, dass die Werte in ein DF gehören. Zusammen mit ggf. einem Unterscheidungskriterium.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

sparrow hat geschrieben: Freitag 29. September 2023, 13:29 @ChrisLi: Ich bin mir, wie andere hier in Thread, ziemlich sicher, dass die Werte in ein DF gehören. Zusammen mit ggf. einem Unterscheidungskriterium.
Du meinst, alle Werte aller TimeFrames in einen DF?

Ja, ich bilde mich gerade in Sachen DF-Operationen fort, um von meinem Schleifen-Denken wegzukommen.
Danke für Euren Input.
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Sirius3 hat geschrieben: Freitag 29. September 2023, 13:01 @einfachTobi: es gibt between, siehe meine Lösung.
Hab vollkommen überlesen, dass du es nahezu wie ich (bzw. noch ein bisschen besser) erklärt hast. Danke für den Tipp.
ChrisLi
User
Beiträge: 17
Registriert: Dienstag 26. September 2023, 23:06

einfachTobi hat geschrieben: Freitag 29. September 2023, 10:19 Andere Quellen sind gar nicht erforderlich. Pandas hat selbst ganz gute Anleitungen, beginnend mit '10 minutes to pandas' unter: https://pandas.pydata.org/docs/user_guide/index.html.
Bei DataFrames geht es darum die Operationen möglichst auf alle (ggf. selektierten/gefilterten) Werte "gleichzeitig" anzuwenden und nicht die Werte nacheinander durchzugehen.
Beispiel an deinem Ding oben angelehnt:

Code: Alles auswählen

dataframes = [df_M5, df_M30, df_H1]
    for df in dataframes:
        condition = (df.time.hour >= 0) & (df.time.hour <= 8)
        df["hight2low"] = df[condition].high - df[condition].low
Wobei ich ohne Kenntnis der eigentlichen Struktur der Daten nicht ganz beurteilen kann, ob die drei DataFrames überhaupt erforderlich sind oder ob die Daten darin nicht verschiedene Spalten eines DataFrames sein könnten/sollten.
@einfachTobi: Das funktioniert nicht, weil das datetime in ein string Object umgewandelt wird:

Code: Alles auswählen

AttributeError: 'str' object has no attribute 'time'        
Antworten