Pandas: Problem beim Vergleichen von 2 Series

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Hallo Leute.

Ich schlage mich seit ein paar Stunden mit dem Vergelichen von 2 pandas Series rum.

Ich möchte 2 Series miteinander vergleichen und wenn diese ungleich sind, dann soll ein bestimmtes Event stattfinden.
Die Series bestehen aus int, float, datetime.date und string Werten.

Beispiel:

Code: Alles auswählen

SeriesA
0 2.4
1 'hallo'
2 5

Code: Alles auswählen

SeriesB
0 2.4
1 'hallo'
2 5
Ich nutze die Funktion pandas.equals:

Code: Alles auswählen

StringA.equals(SeriesB)
Das Problem ist wahrscheinlich, dass in den Series floats enthalten sind und pandas deshalb sagt, dass beide Series unterschiedlich sind, obwohl sie gleich sein sollten.
Lass ich mir SeriesA normal ausgeben, so wird die Series so ausgegeben wie oben beschrieben, auf eine Stelle nach dem Komma. Aber sage ich:

Code: Alles auswählen

for i in SeriesA.index:
    print(SeriesA[i], type(SeriesA[i]), SeriesB[i],type(SeriesB[i])) 
Zeigt er mir fogendes an:

Code: Alles auswählen

2.39999999999765<float> 2.4<float>
'hallo'<string> 'hallo'<string>
5<int> 5<int>
Warum gbt er die 2.4 aus SeriesA mit 15 Nachkommastellen wieder, während er den selben Wert aus SeriesB mit einer Nachkommastelle wiedergibt? Die Datentypen sind auch gleich! Und vorallem, er vergleicht diese unterschiedlichen Werte sogar miteinander. Das Komische ist, dass an einer anderen Stelle des Programms dieser Vergleich funktioniert.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Na offensichtlich sind die Zahlen ja nicht gleich, also sind auch die beiden Reihen nicht gleich.

Kannst Du mal bitte Code zeigen wie Du dieses `Series`-Objekt erzeugst:

Code: Alles auswählen

SeriesB
0 2.4
1 'hallo'
2 5
Ich habe nämlich gerade Schwierigkeiten drei verschiedene Datentypen in ein `Series`-Objekt zu bekommen. :-)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Ja, genau das sag ich ja. Sie sind nicht gleich. Die Frage ist halt nur, warum Pandas die Werte in den Series unterschiedlich bewertet.

SeriesA sind Werte aus einer MySQL Datenbank:

Code: Alles auswählen

import pandas as pd

sql = """SELECT * FROM Tabelle WERE Kennziffer = %s AND Datum = %s ;"""
parameter = (10123, 02-09-2018)
Cursor.execute(sql, parameter)

results = Cursor.fetchall()

SeriesA = Series([x for x in results])
Die Felder der Tabelle sind korrekt "formatiert"
Datum = DATE
Kennzifer = VARCHAR(5)
Werte = FLOAT


Series B sind Werte aus eine csv-Datei:
Zum Beispiel:

Code: Alles auswählen

datei = pd.read_csv(Datei.csv, sep=';', header=0, encoding='iso8859_15')
Ich hole mir dann die entsprechenden Werte, an den entsprechenden Stellen der Datei zusammen:
und mache daraus eine Series:

Code: Alles auswählen

Datum = datei['Datum'][0]
Kennung = datei['Kennung'][0]
Wert1 = datei['Werte'][0]
Wert2 = datei['Werte'][0]
Wert3 = datei['Werte'][0]

SeriesB = ([Datum, Kennung, Wert1, Wert2, Wert3])
Somit erhalte ich 2 Series:
SeriesA:

Code: Alles auswählen

02-09-2018 <datetime.date>
10777 <string>
Wert1 = 4.5 <float>
Wert2 = 5.0 <float>
Wert3 = 7.3 <float>
SeriesB:

Code: Alles auswählen

02-09-2018 <datetime.date>
10777 <string>
Wert1 = 4.5 <float>
Wert2 = 5.0 <float>
Wert3 = 7.3 <float>
Mir ist aber gerade aufgefallen, dass ich an einer anderen Stelle im Programm die Werte nicht über:

Code: Alles auswählen

Cursor.execute(sql,parameter)
beziehe, sondern über:

Code: Alles auswählen

Ergebnisse = pd.read_sql(sql, params=parameter, con=connection)
Die Ergebnisse dieser Abfrage, die ich direkt über Pandas starte, scheinen nicht unter der unterschiedlichen Darstellung und Bewertung der Floats zu leiden.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Woodz: na, die Datenbank speichert die Zahlen binär mit voller Genauigkeit, die csv-Datei wird gerundete Zahlen enthalten, wobei man die Unterschiede immer beachten muß, wenn man mit Fließkommazahlen arbeitet. Exakte Gleichheit gibt es daher meist nicht, nur ungefähre. Das mußt Du bei der Programmierung berücksichtigen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Woodz: Warum erstellst Du eigentlich `Series`-Objekte und nicht einfach Listen? Die Elemente haben ja unterschiedliche Typen und damit ist der `dtype` von dem `Series`-Objekt `object`, also Python-Objekte. Das ist für Pandas-Objekte schon mal nicht so günstig. Wenn schon, würde man eher einen `DataFrame` erstellen, denn dann hätte jede Spalte ihren passenden Typ. Wobei das auch da nicht wirklich sinnvoll für Dataframes mit immer nur einer Zeile ist.

Zum Vergleich ob Gleitkommazahlen *fast* gleich sind, gibt es `math.isclose()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Ok, das mit den Fließkommazahlen weiß ich ja.
Wie könnte ich denn den Vergleich zwischen zwei Series/DataFrames/Listen realisieren, ohne die Elemente Zeilen- oder Spaltenweise mit math.isclose() vergleichen zu müssen (das wäre wohl "stabiler", dauert aber ziemlich lange) immerhin befinden sich ja auch unterschiedliche Datentypen in der Series/DataFrame und ich geh mal fest davon aus, dass es wohl einen Error geben wird, sobald math.isclose() zwei Strings, oder datetime-Objekte vergleichen soll.

Ich hatte es auch schon einmal mit:

Code: Alles auswählen

SeriesA = Series([round(x,1) if x is float else x for x in results])
versucht, leider funktionierte das auch nicht.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Es gibt auch ein `numpy.isclose`. Für Pandas hab ich das jetzt nicht direkt gefunden.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Woodz: Ich würde sagen da gibt's nix und ein `Series`-Objekt zu verwenden ist immer noch *falsch*. Es macht keinen Sinn. Eine Datenreihe besteht aus Werten des *selben* Typs. Wenn Du da Werte rein steckst wie in Deinem Beispiel, dann ist dieser gemeinsame Typ `object`, und damit kann man so gut wie nichts mit dem `Series`-Objekt machen was sinnvoll wäre. Also noch mal die Frage: Warum sind das nicht einfach Listen (oder Tupel)? Oder eventuell sogar Objekte eines eigenen Typs bei dem dann beispielsweise `__eq__()` sinnvoll überschrieben wurde.

Du zeigst hier insgesamt eine Menge Code der fehlerhaft ist oder unsinnig. Dein letzter zum Beispiel: In `results` ist sicher nicht der `float`-Datentyp als Wert enthalten. Und falls doch, dann kracht's beim `round()`, denn den Datentyp `float` kann man nicht runden:

Code: Alles auswählen

In [31]: x = float

In [32]: x is float
Out[32]: True

In [33]: round(float, 1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-33-7d8717bb25d4> in <module>()
----> 1 round(float, 1)

TypeError: type type doesn't define __round__ method

In [34]: 42.23 is float
Out[34]: False
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Genau aus diesem Grund schrieb ich:
Wie könnte ich denn den Vergleich zwischen zwei Series/DataFrames/Listen realisieren?[...]
Der Ansatz mit den Series ist falsch - ok, kann ich mit leben, dass heißt aber nicht, dass ich keinen anderen Ansatz zulasse.

Mache ich das Ganze mit Listen, so erhalte ich folgende Liste:

Code: Alles auswählen

[datetime.date(2018, 9, 5), '10123', 18.600000381469727, 20.5, 17.100000381469727, 0.0, 8.5, 37.0]
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na und fuer die Liste musst du dir nun ein Praedikat zum Vergleich schreiben. Das fuer alles ausser float normal vergleicht, und sonst mit einem epsilon:

Code: Alles auswählen

import datetime

a = [datetime.date(2018, 9, 5), '10123', 18.600000381469727, 20.5, 17.100000381469727, 0.0, 8.5, 37.0]
b = [datetime.date(2018, 9, 5), '10123', 18.600000381469727, 20.5, 17.100000381469727, 0.0, 8.5, 37.2]
c = [datetime.date(2018, 9, 5), '10123', 18.600000381469727, 20.5, 17.100000381469727, 0.0, 8.5, 37.05]

def compare(left, right, epsilon=.1):
    return all( abs(l - r) < epsilon if isinstance(l, float) else l == r for l, r in zip(left, right))


print(compare(a, b))
print(compare(a, c))
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Woodz: Wobei ich mich jetzt natürlich frage ob man Pandas vielleicht auch benutzen kann. Also nicht irgendwas mit einzelnen Zeilen machen, sondern mit `Dataframes` und `Series`. Du willst ja wahrscheinlich nicht nur einen Datensatz vergleichen, dann wäre das mit der Performance ja kein Problem.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Eine vektorisierte Lösung, die einen elementweisen Vergleich auf das ganze Array ausführt, ohne Schleifen durchlaufen zu müssen wäre natürlich das Beste.

Übrigens vielen Dank für das Codebeispiel.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Woodz: also brauchst Du ein Dataframe, was in Deinen oberen Beispielen read_csv oder read_sql auch schon liefern.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Vektorisierung mit heterogenen Datentypen ist halt nicht. Dann musst du dein Problem dafür aufbereiten.
Woodz
User
Beiträge: 16
Registriert: Sonntag 11. Dezember 2016, 18:32

Ok. Ich danke Euch für Eure Hilfe. Mit einem zeilenweisen Vergleich üner Listen, funktioniert die Sache zumindest halbwegs sicher.

Grüße
Antworten