DataFrame effizient befüllen

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
tmessers
User
Beiträge: 28
Registriert: Dienstag 30. Oktober 2018, 21:08

Hallo liebe Gemeinde,

ich möchte ein Pandas Dataframe effizient befüllen und bitte Euch, mich zu beraten, welche der mir
vorschwebenden Wege der bessere ist oder welchen Weg ich einschlagen sollte.

Grundsätzlich habe ich eine Excel-Liste, die ich in einen DataFrame (df1) einlese.
Diese Daten bilden die Grundlage für meine weiteren Berechnungen.
Aus den Daten in df1 berechne ich weitere Werte und möchte das df1 um diese Werte erweitern. Die Werte werden aus den Indexwerten i und i +1 berechnet.

Bisher mache ich das wie folgt:
Beim Einlesen der Excel-Liste in df1 erweitere ich den header der Excel-Liste um die columns, in die ich die berechneten Daten schreiben möchte. Mit einer For-Schleife berechne ich die Daten und schriebe sie in das df1.
Das funktioniert. Ich habe aber gelesen, das For-Schleifen iZm DataFrames ineffizient sein sollen.

Das habe ich probiert:
Ich passe die columns des df1 dem header der Excel-Liste an. Mit einer For-Schleife berechne ich die Daten und schreibe sie in ein df2 und hänge df2 an df1 an. Funktioniert noch nicht. Ich bekomme den Fehler ------ TypeError: cannot unpack non-iterable float object. Meine Fehlersuche ist diesbezüglich noch nicht abgeschlossen. Diese Variante hat auch ein For-Schleife.

Eine andere Lösung wäre, dass ich die columns des df1 dem header der Excel-Liste entnehme.
Mit einer For-Schleife berechne ich die Daten füge sie in eine Series ein. Diese Series füge ich dann an df1 an.
Das habe ich noch nicht umgesetzt.

Welchen Rat könnt Ihr mir geben.
Gibt es noch weitere Möglichkeiten?
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tmessers: Alles mit einer ``for``-Schleife um die Werte zu berechnen klingt falsch, denn dann braucht man kein Pandas verwenden. Was willst Du denn da berechnen?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Bitte gibt uns ein konkretes Beispiel mit echten Daten. Input, Berechnung, jetziger Output und erwarteter Output - idealerweise als Code, damit man damit selbst rumprobieren kann. Das macht es gleich sehr viel einfacher hier zielführende Tipps zu geben.
Grundsätzlich vermute ich, dass du dir das Erweitern der Excel-Liste sparen kannst und die neuen Spalten in einer Art `df1['neue Spalte'] = berechnungsergebnisse` erstellen kannst. Wie genau `berechnungsergebnisse` aussehen kann, hängt davon ab, was du genau vorhast/berechnest.
tmessers
User
Beiträge: 28
Registriert: Dienstag 30. Oktober 2018, 21:08

Danke für die Antworten.
Ich berechne aus Geokoordinaten die Entfernung zwischen Punkten, die Richtung, den Verbrauch und die Flugzeit.
Ich werde später den Code hier einfügen.
tmessers
User
Beiträge: 28
Registriert: Dienstag 30. Oktober 2018, 21:08

Wie gewünscht mein Code

Hier ist schon eine Version, die die Daten für den DataFrame aus einem json-File ausliest.
Zudem wird das finale DataFrame in ein GUI ausgeben. An der Ausgabe arbeite ich noch.
Die ist noch nicht so schick.

Code: Alles auswählen

import json
import numpy as np
import math
import geopy.distance
import pandas as pd
from geopy.distance import geodesic
from haversine import haversine, Unit
import PySimpleGUI as sg

with open( "orte3.json" ) as j:
    data = json.load( j )
    print( data )

flug_gesch = int( input( "Bitte geben Sie die Flugeschwindigkeit (km/min)an: " ) )
verb_min = int( input( "Bitte geben Sie den Verbrauch in l/min an: " ) )

laenge_liste = len( data )
# print ("Länge der Liste: %s" %laenge_liste)
# Erzeugt das DataFrame aus den Daten der Datei Orte
df = pd.DataFrame( data, columns=["Nummer", "Beschreibung", "Name", "Nordwert", "Ostwert", "Höhe/m", "Höhe/ft",
                                  "Distanz/km", "Heading/°", "Flugzeit/min", "Verbrauch/l"] )

# Werte für Comboboxen ermitteln
#werte_beschreibung = pd.unique( df['Beschreibung'] ).tolist()

# print(df) #Kontrolle
df1 = df
# Auswahl nach der Beschreibung
df_neu = df1[df1['Beschreibung'].eq( 'Landeplatz' )]
# print(df_neu)

laenge_df = len( df_neu )
# print(laenge_df)
# Kontrolle
# ---Berechnungsteil
for i in range( 0, laenge_df - 1 ):
    coords_1 = (df_neu.at[df_neu.index[i], df_neu.columns[3]]), (df_neu.at[df_neu.index[i], df_neu.columns[4]])
    coords_2 = (df_neu.at[df_neu.index[i + 1], df_neu.columns[3]]), (df_neu.at[df_neu.index[i + 1], df_neu.columns[4]])

    # Berechnung der Distanz zwischen den Koordinaten mit verschiedenen Methoden
    ergebnis_1 = geopy.distance.distance( coords_1, coords_2 ).km
    ergebnis_2 = geodesic( coords_1, coords_2 ).kilometers
    ergebnis_3 = haversine( coords_1, coords_2, unit=Unit.KILOMETERS )

    # Berechnung der Heading
    lon1 = df_neu.at[df_neu.index[i], df_neu.columns[4]]
    lat1 = df_neu.at[df_neu.index[i], df_neu.columns[3]]
    lon2 = df_neu.at[df_neu.index[i + 1], df_neu.columns[4]]
    lat2 = df_neu.at[df_neu.index[i + 1], df_neu.columns[3]]

    dLon = lon2 - lon1
    y = math.sin( dLon ) * math.cos( lat2 )
    x = math.cos( lat1 ) * math.sin( lat2 ) - math.sin( lat1 ) * math.cos( lat2 ) * math.cos( dLon )
    brng = np.rad2deg( math.atan2( y, x ) )
    if brng < 0:
        brng += 360

    # Ausgabe der Werte zur Kontrolle
    #   print ("geopy.distance.distance: %s km" %round(ergebnis_1,2))
    #   print ("geopy.distance.geodesic: %s km" %round(ergebnis_2,2))
    #   print ("haversine: %s km" %round(ergebnis_3,2))
    #   print ("Heading : %s Grad" %round(brng, 0))

    # Eintragen der Werte "Distanz" und "Heading" in den DataFrame
    df_neu.at[df_neu.index[i], df_neu.columns[7]] = round( ergebnis_1, 2 )
    df_neu.at[df_neu.index[i], df_neu.columns[8]] = round( brng, 0 )
    # print(data [i+1] [8]) #Kontrolle

    # Berechung
    flugzeit = round( ergebnis_1, 2 ) / flug_gesch
    df_neu.at[df_neu.index[i], df_neu.columns[9]] = round( flugzeit, 0 )
    verb = round( (flugzeit * verb_min), 0 )
    df_neu.at[df_neu.index[i], df_neu.columns[10]] = verb

    df2 = pd.DataFrame(df_neu, columns=["Nummer", "Beschreibung", "Name", "Nordwert", "Ostwert", "Höhe/m", "Höhe/ft",
                                         "Distanz/km", "Heading/°", "Flugzeit/min", "Verbrauch/l"])

    print(df_neu)
# x = int(len(df2))
# print ("Länge df2: %s" %x)
# data1 = df2[0:].values.tolist()
data1 = df2.values.tolist()
# print(data1)
layout = [

    [sg.Listbox( values=(data1), select_mode='single', bind_return_key=False, change_submits=True, size=(100, 10),
                 auto_size_text=True, key='_lb1_' )],
    [sg.Submit(), sg.Cancel()]
]

window = sg.Window( 'Örtlichkeiten' ).Layout( layout ).Finalize()

button, values = window.Read()

while True:  # Event loop
    button, values = window.Read()
    # print(button,values)
    if button == 'Cancel':
        break
        window.close()
    elif button == 'Submit':
        window.FindElement( '_lb1_' ).Update( scroll_to_index=4 )
        window.FindElement( '_lb1_' ).Update( set_to_index=4 )

print( "ende" )

Die json-Datei sieht wie folgt aus.

Code: Alles auswählen

[
  [
    1,
    "Landeplatz",
    "Großrasen",
    48.123456,
    7.123456,
    123,
    456,
    0,
    0,
    0,
    0
  ],
  [
    2,
    "Flugplatz",
    "Lahr",
    48.999999,
    8.111111,
    987,
    321,
    0,
    0,
    0,
    0
  ],
  [
    3,
    "Landeplatz",
    "Mittelwiese",
    47.345678,
    8.111111,
    987,
    321,
    0,
    0,
    0,
    0
  ],
  [
    4,
    "Landeplatz",
    "Kleinwiese",
    48.345678,
    7.342134,
    987,
    321,
    0,
    0,
    0,
    0
  ]
]
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Grundsätzliches zum Code:
Zwischen Klammer und Ausdruck kommt kein Leerzeichen. Du musst nicht ständig neue DataFrames erzeugen, sondern kannst mit dem bestehenden DataFrame arbeiten. Statt `.at` wäre hier die Verwendung von `.iloc` die richtige Wahl, da du dann das `.index[index]`und `.columns[nr]` durch `.iloc[i, nr]` ersetzen kannst. Das ist aber fast nicht erforderlich. Durch die Verwendung der externen Module, die keine Verarbeitung von Array-like-Objekten vorsehen, sehe ich auch nur die Möglichkeit im weitesten Sinne über die Reihen zu iterieren. Verwende keine Abkürzungen wie `flug_gesch`, `verb_min` oder `brng`. Die 0-Werte in der JSON/dem DataFrame sind nicht erforderlich. Du musst die Werte nicht initialisieren, sondern kannst dann setzen, wenn du sie berechnet hast. Gerundet wird erst bei der Ausgabe, um Rundungsfehler in Folgeberechnungen zu vermeiden.
In meinem Beispiel habe ich flug_geschwindigkeit und verbrauch_min festgesetzt. Richtigerweise müssten sie, weil hier konstant, GROSS geschrieben werden. Aber du wirst sie ja sicher wieder durch den Input ersetzen. So könnte es dann aussehen:

Code: Alles auswählen

import numpy as np
import geopy.distance
import pandas as pd
from geopy.distance import geodesic
from haversine import haversine, Unit

flug_geschwindigkeit = 23
verbrauch_min = 100

df = pd.read_json("orte.json", orient="records")
df.columns = ["Nummer", "Beschreibung", "Name", "Nordwert", "Ostwert", "Höhe_m", "Höhe_ft"]
df = df.set_index("Nummer")
df["Koordinate_N_O"] = df[["Nordwert", "Ostwert"]].values.tolist()
df["Koordinate_N_O_next"] =  df[["Nordwert", "Ostwert"]].shift(-1).values.tolist()

for row in df.itertuples(index=True):
    if not pd.isna(row.Koordinate_N_O_next).any():
        df.at[df.index[row.Index], "Geopy"] = geopy.distance.distance(row.Koordinate_N_O, row.Koordinate_N_O_next).km
        df.at[df.index[row.Index], "geodesic"] = geodesic(row.Koordinate_N_O, row.Koordinate_N_O_next).kilometers
        df.at[df.index[row.Index], "haversine"] = haversine(row.Koordinate_N_O, row.Koordinate_N_O_next, unit=Unit.KILOMETERS)

diff_longitude = df.Ostwert.shift(-1) - df.Ostwert
y = np.sin(diff_longitude) - np.cos(df.Nordwert.shift(-1))
x = np.cos(df.Nordwert) * np.sin(df.Nordwert.shift(-1)) - np.sin(df.Nordwert) * np.cos(df.Nordwert.shift(-1)) * np.cos(diff_longitude)
df["brng"] = np.rad2deg(np.arctan2(y, x))
df.loc[df["brng"] < 0, "brng"] += 360
df["Flugzeit"] = df["Geopy"] / flug_geschwindigkeit
df["verb"] = df["Flugzeit"] * verbrauch_min
print(df)
Mit folgender `orte.json`:

Code: Alles auswählen

[
  [
    1,
    "Landeplatz",
    "Großrasen",
    48.123456,
    7.123456,
    123,
    456
  ],
  [
    2,
    "Flugplatz",
    "Lahr",
    48.999999,
    8.111111,
    987,
    321
  ],
  [
    3,
    "Landeplatz",
    "Mittelwiese",
    47.345678,
    8.111111,
    987,
    321
  ],
  [
    4,
    "Landeplatz",
    "Kleinwiese",
    48.345678,
    7.342134,
    987,
    321
  ]
]
Du solltest dir allerdings mal GeoPandas ansehen. Das scheint für solche Berechnungen in Verbindung mit DataFrames gemacht zu sein - ich kenne es allerdings nicht.
Als Alternative zur Verwendung von itertuples() könnte `.apply()` mit lambda-Funktion und `axis`-Parameter genutzt werden.
tmessers
User
Beiträge: 28
Registriert: Dienstag 30. Oktober 2018, 21:08

Danke für Eure Antworten. Ich werde sie mal durcharbeiten.
Antworten