Python Excel Vergleich Werte

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Hi Leute,

ich würde euch gerne die Fragestellung erklären und hoffe auf einige Ansätze 😅

Fragestellung:
Ich muss eine Excel-Datei einlesen und dann die Werte einer Spalte vergleichen.
Zum Beispiel:
-Ich lese die Spalte "Marken" ein. Beim einlesen der Marken kommt es vor, dass anstatt "Nike" wir "Nik e" oder "Nik" steht. Meine Aufgabe ist es, dass das Programm trotzdem "Nike" erkennt und den String von "Nik" zu "Nike" ändert.

(Ich hoffe, dass ich das Problem verständlich erklären konnte)



Ich habe mir die Methode "replace()" angeschaut, jedoch macht es hier keinen Sinn. Da ich bspw. auch den Fall haben könnte:

- Wir haben als Input "B0sch AG" und in der Liste haben wir "bosch ag" und "bosch ag gmbh" und das Programm entscheidet sich für letzteres. Dann würde ein replace vieles verfälschen.

Bin etwas verzweifelt :(

LG
Sirius3
User
Beiträge: 17740
Registriert: Sonntag 21. Oktober 2012, 17:20

Welches Programm? Was hast Du versucht? Nach welchen Regeln soll denn die Ersetzung erfolgen?
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

servus_97 hat geschrieben: Montag 12. Juli 2021, 17:38 Ich muss eine Excel-Datei einlesen und dann die Werte einer Spalte vergleichen.

-Ich lese die Spalte "Marken" ein. Beim einlesen der Marken kommt es vor, dass anstatt "Nike" wir "Nik e" oder "Nik" steht. Meine Aufgabe ist es, dass das Programm trotzdem "Nike" erkennt und den String von "Nik" zu "Nike" ändert.

- Wir haben als Input "B0sch AG" und in der Liste haben wir "bosch ag" und "bosch ag gmbh" und das Programm entscheidet sich für letzteres. Dann würde ein replace vieles verfälschen.
Das, was Du da beschreibst, läuft im Endeffekt auf einen unscharfen Zeichenkettenvergleich hinaus. Das Thema ist nicht ganz trivial, aber es gibt zum Glück mehrere Lösungsansätze.

Einer dieser Lösungsansätze könnte sein, eine entsprechende Funktion zu benutzen, die die Ähnlichkeit zwischen zwei Zeichenketten berechnet. Die Levenshtein- und die Damerau-Levenshtein-Distanz sind dazu sehr beliebt, siehe dazu [1]. Das ist recht einfach zu programmieren, denn dafür gibt es entsprechende Python-Bibliotheken, aber die Verarbeitung kann in Abhängigkeit von der Datenmenge relativ rechen- und zeitaufwändig werden.

Eine andere Alternative könnte es sein, die gewünschten korrekten Namen in eine Suchmaschine wie Pythons "Woosh"- [2] zu füttern und Deine Einträge einfach darin zu suchen. Das kann ganz gut funktionieren, aber ich habe damit keine eigenen Erfahrungen und kann daher nicht viel dazu sagen -- vielleicht weiß jemand anders hier ja mehr.

Ein dritter Ansatz und vermutlich der, mit dem ich persönlich anfangen würde, wäre es, einen einfachen, schnellen MachineLearning-Klassifizierer wie den NaiveBayesClassifier [3] aus dem Python-Paket NLTK mit entsprechend aufbereiteten Daten (N-Gramme auf Buchstaben?) zu trainieren und Deine Einträge dann davon klassifizieren zu lassen. Sowas ist auch für größere Datenmengen sehr schnell und im Kern auch recht leicht zu implementieren. Zudem können mit der Methore "prob_classify()" des NaiveBayesClassifier auch Wahrscheinlichkeiten für die gefundenen Treffer zurückgegeben werden. Dadurch könnest Du das Originalfeld zunächst behalten, in Deinen Pandas-DataFrame zwei neue Spalten mit der gefundenen Klasse ("Bosch AG") und der Trefferwahrscheinlichkeit hinzufügen, und dann die Treffer mit der geringsten Wahrscheinlichkeit noch einmal manuell prüfen. Als Klassen würde ich direkt die gesuchten Namen benutzen, als Features bei diesen kurzen Textfetzen bieten sich n-Gramme [4] (Unigramme, Bigramme und Trigramme) aus Buchstaben an. Nach meinen Erfahrungen funktioniert so etwas erstaunlich gut.

Ganz grundsätzlich wäre es allerdings womöglich sinnvoller, direkt an der Datenquelle für eine bessere Datenqualität zu sorgen... ;-)

[1] https://de.wikipedia.org/wiki/Levenshtein-Distanz
[2] https://whoosh.readthedocs.io/en/latest/intro.html
[3] https://www.nltk.org/book/ch06.html
[4] http://textmining.wp.hs-hannover.de/Spr ... mmung.html
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Wow :D Danke dir für die Ansätze.
Der dritte Ansatz spricht mich auch am ehesten an.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

servus_97 hat geschrieben: Montag 12. Juli 2021, 19:52 Wow :D Danke dir für die Ansätze.
Der dritte Ansatz spricht mich auch am ehesten an.
Cool, das freut mich.. und wenn Du am Ende vielleicht kurz etwas dazu sagen magst, ob und wie gut es funktioniert hat, freut es mich noch mehr! ;-)
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Okay, muss gestehen, dass es doch etwas komplizierter ist als ich gedacht habe.

Jetzt habe ich, wie in [4] die Spaltenwerte in n-gramme aufgeteilt über die Funktion xgram. Diese Werte würde ich dann als "feature_probdist" in der Klasse NaiveBayesClassifier verwenden und die einzelnen Markennamen aus der Spalte dann als "Label_probdist"

Mir stellen sich da echt viele Fragen 😅😅

Dann wüsste ich auch nicht, worauf ich die Methode "prob_classify()" anwenden soll.

Wäre echt sehr lieb, wenn du mir da ein wenig helfen könntest. Hab mir echt einiges angeschaut.

LG
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Würde ich jedoch den ersten Ansatz nehmen, so könnte man auch "nltk.edit_distance()" verwenden.
Hierfür würde ich einfach ein Duplikat von dem eigentlichen Datensatz erstellen, mit einigen Fehlern und die Spalte mit den Marken beider Datensätze in die Methode eingeben.

Bspw.:
data1 = Echte Datensatz --> Spalte s1
data2 = Fehler Datensatz --> Spalte s2

=> nltk.edit_distance(s1,s2) ?
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

servus_97 hat geschrieben: Dienstag 13. Juli 2021, 09:42 Mir stellen sich da echt viele Fragen 😅😅
Frag' ruhig. ;-)
servus_97 hat geschrieben: Dienstag 13. Juli 2021, 09:42 Wäre echt sehr lieb, wenn du mir da ein wenig helfen könntest. Hab mir echt einiges angeschaut.
Also, die von Dir genannten korrekten Namen stehen bei mir zeilenweise in der "correct.txt", die defekten Namen genauso in der "failure.txt".

Code: Alles auswählen

#!/usr/bin/env python
FILE_CORRECT = 'correct.txt'
FILE_FAILURE = 'failure.txt'

from collections import defaultdict

import nltk


def ngrams(text, n):
    liste = []
    if n < len(text):
        for p in range(len(text) - n + 1) :
            tg = text[p:p+n]
            liste.append(tg)
    return liste


def xgrams(text, maxlen=3):
    return [w for n in range(1, maxlen+1) for w in ngrams(text.lower(), n)]


def extract_features(text):
    rv = defaultdict(lambda: 0)
    for w in xgrams(text.lower()): rv[w] += 1
    return dict(rv)
    

if __name__ == '__main__':
    with open(FILE_CORRECT, 'r') as ifh: data_correct = ifh.read().splitlines()
    with open(FILE_FAILURE, 'r') as ifh: data_failure = ifh.read().splitlines()
    
    classifier = nltk.NaiveBayesClassifier.train([(extract_features(line), line) for line in data_correct])
    for line in data_failure:
        classified = classifier.classify(extract_features(line))
        probdist = classifier.prob_classify(extract_features(line))
        print('{:10s} | {:0.2f} | {}'.format(classified, probdist.prob(classified), line))
Ausgabe bei mir:

Code: Alles auswählen

Nike       | 1.00 | Nik e
Nike       | 1.00 | Nik
Bosch AG   | 1.00 | bosch ag
Bosch AG   | 1.00 | bosch ag gmbh
Bosch AG   | 1.00 | B0sch AG
Also ich würde sagen: funktioniert prima.
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

DANKEEEEEEEEEE :D
Ich gehe dein Code später durch und bei fragen komme ich auf dich zurück. Bist mein HELD ! ✌️
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

servus_97 hat geschrieben: Dienstag 13. Juli 2021, 16:44 DANKEEEEEEEEEE :D
Ich gehe dein Code später durch und bei fragen komme ich auf dich zurück. Bist mein HELD ! ✌️
Oh, dankeschön, aber... da nicht für, sehr gerne. Eigentlich ist das gar nicht schwierig, wenn man das einmal gemacht hat, und je nachdem, wie man seine Features aufbereitet, ist es auch ziemlich vielseitig nutzbar. Hoffentlich funktioniert es auch auf Deinen übrigen Daten so gut, viel Spaß und Erfolg damit! ;-)
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@LukeNukem,

schönes Beispiel, hätte nicht gedacht, dass das so einfach ist.
Bisher hatte ich mich ehrlich gesagt nicht sonderlich für ML interessiert.
Bei kurzen Worten merkt man schon wie es ungenauer mit der Erkennung wird.
Für einen OCR-Text sollte das aber schon eine Menge bringen.

Man kann sich die n-grams anscheinend auch sehr gut vom NLTK als Tuple erstellen lassen:

Code: Alles auswählen

#!/usr/bin/env python
from collections import defaultdict

import nltk
from nltk.util import ngrams


FILE_CORRECT = "correct.txt"
FILE_FAILURE = "failure.txt"


def extract_features(text):
    features = defaultdict(int)
    for count in range(1, 4):
        for ngram in ngrams(text, count):
            features[ngram] += 1
    return features


if __name__ == "__main__":
    with open(FILE_CORRECT, "r") as text_file:
        data_correct = text_file.read().splitlines()
    with open(FILE_FAILURE, "r") as text_file:
        data_failure = text_file.read().splitlines()

    classifier = nltk.NaiveBayesClassifier.train(
        ((extract_features(line.lower()), line) for line in data_correct)
    )
    for line in data_failure:
        classified = classifier.classify(extract_features(line))
        probdist = classifier.prob_classify(extract_features(line))
        print(f"{classified:10s} | {probdist.prob(classified):0.2f} | {line}")
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

rogerb hat geschrieben: Mittwoch 14. Juli 2021, 00:35 schönes Beispiel, hätte nicht gedacht, dass das so einfach ist.
Bisher hatte ich mich ehrlich gesagt nicht sonderlich für ML interessiert.
Naja, sagen wir so: ich hab's einfach gemacht. Man kann da schon wesentlich mehr Arbeit hineinstecken... Der größte Aufwand ist meist, verschiedene Kombinationen von Datenaufbereitung, Merkmalsextraktion und ML-Algorithmen mit randomisierten Trainingsdaten zu füttern und zu schauen, wie gut diese Kombinationen dann jeweils funktionieren...
rogerb hat geschrieben: Mittwoch 14. Juli 2021, 00:35 Bei kurzen Worten merkt man schon wie es ungenauer mit der Erkennung wird.
Naja, Garbage in, Garbage out... Ich mein', so ein Zeichen hat ja nur einen sehr begrenzten Informationsgehalt, und wenn die Zeichenkette kurz genug ist... dann ist halt naturgemäß wenig Information drin. Man kann die Ergebnisse sehr deutlich beeinflussen, je nachdem, wie man die Merkmale konstruiert, wie groß die Trainingsdaten sind, und welchen Algorithmus man wählt. Naive Bayes ist eben einer der bekanntesten und performantesten, aber im Kern ziemlich einfach und nicht für alle Anwendungsfälle geeignet. Und das von mir gezeigte ist ja auch alles andere als ein typischer Anwendungsfall, ganz im Gegenteil ist diese Art der Nutzung durchaus ein bisschen... kreativ, diplomatisch gesagt. Aber für den Fall des TO und die von ihm genannten Beispieldaten funktioniert der Ansatz ziemlich gut, würde ich sagen. ;-)
rogerb hat geschrieben: Mittwoch 14. Juli 2021, 00:35 Für einen OCR-Text sollte das aber schon eine Menge bringen.
Probier's halt aus... obwohl, in dem Fall würde ich es vermutlich eher erst einmal mit Bag-Of-Words, Wort-Ngrammen und Wörterbüchern anfangen. Durch die Accuracy-Funktion von NLTK lassen sich die Ergebnisse zum Glück recht einfach überprüfen.
rogerb hat geschrieben: Mittwoch 14. Juli 2021, 00:35 Man kann sich die n-grams anscheinend auch sehr gut vom NLTK als Tuple erstellen lassen:
Die hab' ich vor einigen Jahren mal ausprobiert, und sie war mir damals leider zu langsam. Keine Ahnung, ob das immer noch so ist... muß ich mir wohl mal wieder angucken... ;-)
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Hi Leute,

habe erst heute Zeit gefunden mir es ein wenig genauer anzuschauen :) Danke euch.

Also die Excel Dateien die ich einlesen will und deren Spalten ich dann Vergleiche haben jeweils 5 Spalten.

Ich habe zuerst die Excel Dateien über

Code: Alles auswählen

pd.read_excel()
eingelesen.

Danach habe ich

Code: Alles auswählen

FILE_CORRECT =df1['Name'] 
FILE_FAILURE= df2['Name'] 
und den Part des Codes

Code: Alles auswählen

if __name__ == "__main__":
    with open(FILE_CORRECT, "r") as text_file:
        data_correct = text_file.read().splitlines()
    with open(FILE_FAILURE, "r") as text_file:
        data_failure = text_file.read().splitlines()
weggelassen.

Und bekomme nun den Fehler
AttributeError: 'float' object has no attribute 'lower'
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Was ich halt nicht verstehe, woher dieses float object kommt. Das sollten doch eigentlich strings sein :roll:
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Pandas konvertiert Datentypen.
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Also schlägst du vor mit einer anderen Bibliothek die Daten einzulesen ?

Wie bspw. openpyxl
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Also schlägst du vor mit einer anderen Bibliothek die Daten einzulesen ?

Wie bspw. openpyxl
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

servus_97 hat geschrieben: Dienstag 13. Juli 2021, 10:50 Würde ich jedoch den ersten Ansatz nehmen, so könnte man auch "nltk.edit_distance()" verwenden.
Du hast dich jetzt schon für einen anderen Ansatz entschieden, wenn ich das korrekt verstanden habe, aber mal der Vollständigkeit halber:

'nltk.edit_distance' ist nur mit Einschränkung zu empfehlen. Das verwendet, soweit ich das sehe, die "Lehrbuchimplementierung" mit Dynamic Programming und ist in reinem Python geschrieben. Für kleine Mengen und zum Rumspielen ist das okay. Aber wenn Performance wichtig ist, würde ich das nicht empfehlen. In der Fachliteratur wurden diverse Verbesserungen der Zeichenkettendistanz beschrieben. Auf Pypi findet man eine handvoll Implementierungen, die in C, C++ und/oder Cython implementiert sind und zumindest teilweise einige dieser optimierten Algorithmen einsetzen. Wenn es um größere Datenmengen, d.h. viele Vergleiche geht, würde ich daher dort schauen und dann die Implementierung nehmen, die für dich am besten funktioniert.
servus_97
User
Beiträge: 24
Registriert: Donnerstag 3. September 2020, 12:01

Habe es doch geschafft, indem ich einfach die zu betrachtende Spalte ausgewählt und dann habe ich einfach

Code: Alles auswählen

x = df_correct['name'].to_string().splitlines()
in eine Variable gespeichert. Auch für den zweiten Excel Sheet.

Jetzt hatte ich extra einige Buchstaben für Testzwecke entfernt und habe den Code abgespielt.

Jetzt bekommen ich überall
Nike | 1 | Ni
die Wahrscheinlichkeit 1.
D.h. der Algorithmus erkennt Ni mit 100% WK als Nike an ?

Und wie könnte ich jetzt einfach, in dem Fall, Ni als Nike ändern ?
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

servus_97 hat geschrieben: Sonntag 18. Juli 2021, 16:25 Jetzt bekommen ich überall
Nike | 1 | Ni
die Wahrscheinlichkeit 1.
D.h. der Algorithmus erkennt Ni mit 100% WK als Nike an ?
...unter den gegebenen Auswahlmöglichkeiten, genau. Wenn Du meinen Code benutzt hast, sind das natürlich 100%, denn die Anzahl der korrekten Möglichkeiten ist mit 2 ja sehr überschaubar, und die beiden Namen "Nike" und "Bosch AG" unterscheiden sich ja dann doch sehr deutlich.
servus_97 hat geschrieben: Sonntag 18. Juli 2021, 16:25 Und wie könnte ich jetzt einfach, in dem Fall, Ni als Nike ändern ?
Ich würde dem DataFrame zu Kontrollzwecken zwei neue Spalten hinzufügen, so im Kern (ungetestet):

Code: Alles auswählen

def predict_name(val): 
    # Korrekten Namen 'name' und Wahrscheinlichkeit 'prob' berechnen
    return pd.Series({'name': name, 'prob': prob})

df[['name', 'prob']] = df.Marken.apply(predict_name, axis=1)
Danach würde ich mir die drei Spalten mal nebeneinander ausgeben lassen und das Ergebnis manuell überprüfen. HTH, have fun! ;-)
Antworten