Frage zum Schnellen Abgleichen von Textdateien mit pandas

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
vanKey
User
Beiträge: 18
Registriert: Mittwoch 31. August 2022, 07:14

Hallo zusammen,

ich bin neu in python und pandas und möchte einen möglichst zeiteffizienten Abgleich mit pandas machen, der auf Schleifen verzichtet.
Der Code unten liest 2 Files ein, die folgendermaßen aussehen:

file_1.txt:

Code: Alles auswählen

1
3
4
5
8
9
file_2.txt:

Code: Alles auswählen

1 11
2 12
3 13
4 14
5 15
6 16
7 17
8 18
9 19
Aufgabenstellung:
Immer wenn der Zeileneintrag von file_1.txt dem ersten Zeileneintrag von file_2.txt gleicht, soll der zweite Zeileneintrag von file_2.txt (zweite Spalte) an die betreffende Stelle von file_1.txt geschrieben werden. Das Ergebnis soll also so aussehen:

Code: Alles auswählen

1      11
3      13
4      14
5      15
8      18
9      19
Mein Schleifenbasierter Code sieht so aus:

Code: Alles auswählen

import numpy as np
import pandas as pd

data_file_1 = pd.read_csv('file_1.txt', delim_whitespace=True, header=None, names=["SPALTE_1","SPALTE_2"])
print(data_file_1)
data_file_2 = pd.read_csv('file_2.txt', delim_whitespace=True, header=None, names=["SPALTE_1","SPALTE_2"])
print(data_file_2)

### Wenn SPALTE_1 von file_1.txt gleich SPALTE_1 von file_2.txt, dann schreibe SPALTE_2 von file_2.txt nach data_file_1
for i in range(0,len(data_file_1)):
    for j in range(0,len(data_file_2)):
        if data_file_1["SPALTE_1"][i] == data_file_2["SPALTE_1"][j]:
            data_file_1["SPALTE_2"][i]= data_file_2["SPALTE_2"][j]

print(data_file_1)
Das Problem ist, dass ich bei großen Dateien extrem lange warten muss, bis die Schleife durchgelaufen ist. Daher ist meine Frage, wie ich das mit pandas schneller lösen kann. Hat jemand eine Idee?
VG
vanKey
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

vanKey
User
Beiträge: 18
Registriert: Mittwoch 31. August 2022, 07:14

Vielen Dank,

die Lösung wäre aus meiner Sicht dann:

Code: Alles auswählen

data_file_1.merge(data_file_2, on='SPALTE_1')
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@vanKey: Der schleifenbasierte Code ist sehr ineffizient. Nicht weil er in Python-Schleifen geschrieben ist, sondern weil der Ansatz für jeden Eintrag aus der 1. Datei jeden Eintrag aus der 2. Datei zur durchsuchen. Wenn man das in Python-Code lösen wollen würde, dann würde man die zweite Datei in ein Wörterbuch einlesen, und dann im nächsten Schritt die erste Datei durcharbeiten und für jeden Eintrag den Wert für die zweite Spalte im Wörterbuch nachschlagen.

Numpy wird importiert, in dem Beispiel aber gar nicht verwendet.

Es ist nur ein generisches Beispiel, aber man nummeriert keine Namen. Das betrifft auch die Spaltennamen. Ich würde auch in Beispielen versuchen das zu vermeiden, sonst gewöhnt man sich das auch in Produktivcode eher an.

`data_file_*` ist als Name für einen `DataFrame` irreführend. Bei `*_file` erwartet der Leser ein Objekt das eine Datei repräsentiert und mindestens eine `read()`- oder eine `write()`-Methode, und in der Regel eine `close()`-Methode besitzt. Hinter den Namen verbergen sich nicht die Dateien, sondern deren Inhalt(e).

Kommmentare werden mit einem "#" eingeleitet. Da braucht man keine drei.

0 ist der Default-Startwert von `range()`, den braucht man nicht angeben.

Man macht auch in Python in der Regel etwas falsches oder zumindest komisches wenn man über einen Laufindex iteriert, der dann nur verwendet wird um über die Elemente von einer Sequenz zu iterieren. Man kann in Python *direkt* über die Elemente iterieren, ohne den Umweg über einen Laufindex. ``for i in range(len(sequence)):`` ist in Python ein „anti pattern“. Falls man zusätzlich zu den Elementen noch eine laufende ganze Zahl benötigt, gibt es die `enumerate()`-Funktion. Im konkreten Fall haben wir ein `Series`-Objekt das eine Methode bietet um über Index/Wert-Paare zu iterieren. Der Index von einem `DataFrame` muss ja nicht aus fortlaufenden Nummern mit 0 beginnend bestehen.

Der ursprüngliche, schleifenbasierte Code könnte dann so aussehen:

Code: Alles auswählen

#!/usr/bin/env python3
import pandas as pd


def main():
    data_a, data_b = (
        pd.read_csv(
            filename, delim_whitespace=True, header=None, names=["ID", "Wert"]
        )
        for filename in ["file_1.txt", "file_2.txt"]
    )
    print(data_a)
    print(data_b)

    for index, wanted_id in data_a["ID"].iteritems():
        for id_, value in data_b.itertuples(False):
            if wanted_id == id_:
                data_a.at[index, "Wert"] = value

    print(data_a)


if __name__ == "__main__":
    main()
Eine effiziente Lösung ohne Pandas um die Werte den IDs zuzuordnen könnte so aussehen:

Code: Alles auswählen

#!/usr/bin/env python3
import math


def main():
    with open("file_2.txt", encoding="ascii") as lines:
        id_to_value = dict(tuple(map(int, line.split())) for line in lines)

    with open("file_1.txt", encoding="ascii") as lines:
        for id_ in map(int, lines):
            print(f"ID: {id_}, Wert: {id_to_value.get(id_, math.nan)}")


if __name__ == "__main__":
    main()
Mit Pandas würde es sich auch anbieten die IDs tatsächlich als Index vom `DataFrame` zu verwenden, dann kann man statt `merge()`, `join()` verwenden:

Code: Alles auswählen

#!/usr/bin/env python3
import pandas as pd


def main():
    data_a, data_b = (
        pd.read_csv(
            filename,
            delim_whitespace=True,
            header=None,
            names=column_names,
            index_col="ID",
        )
        for filename, column_names in [
            ("file_1.txt", ["ID"]),
            ("file_2.txt", ["ID", "Wert"]),
        ]
    )
    print(data_a)
    print(data_b)
    print(data_a.join(data_b))


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten