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?
DataFrame effizient befüllen
- __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
-
- 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.
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.
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.
Die json-Datei sieht wie folgt aus.
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
]
]
-
- 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:
Mit folgender `orte.json`:
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.
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)
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
]
]
Als Alternative zur Verwendung von itertuples() könnte `.apply()` mit lambda-Funktion und `axis`-Parameter genutzt werden.