Array in Pandas DataFrame

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

hallo zusammen,

ich bin neu in diesem Forum und auch ein Python Novize. Ich versuche derzeit mit kleineren Projekten in diese Sprache hineinzufinden.
Zu mir: ich bewege mich beruflich primär im objektorientierten Bereich.

Bei folgendem Problem bin ich nun ein wenig ratlos, denn das was man "objektorientiert" erwarten würde, scheint auf den ersten Blick nicht zu klappen.

Ich habe ein Array (Liste), welches in einem Dataframe steckt. Und ich möchte nun eine Spalte einfügen, in der steht, wieviele Objekte in der Liste sind.

Code: Alles auswählen

import pandas as pd
data = [['Ada', 30, 'a, b, c, d, e'],['Bob', 32, 'a, b, c'], ['Clarke', 33, 'a, c'], ['Dylan', 33, 'a, c']]
df = pd.DataFrame(data, columns=['Name', 'Age', 'Skills'])

print(df)

df["SkillArray"] = df["Skills"].str.split(",")
df['SkillCount'] = len(df['SkillArray'])
print(df)
Leider liefert "len(df["SkillArray"]) nicht die länge des Inhaltes in df["SkillArray"] sondern die Länge des Dataframes.

Ich habe auch df["SkillArray"].size ausprobiert. Aber auch hier erhalt ich jeweils nur die Länge des Dataframes.

Ich freue mich über alle Hinweise, die mir hier weiterhelfen!
Ggf. steht mir hier die "Objektorientierte Denke" im Weg... Oder ich habe ggf. was ganz Grundlegendes an den Dataframes nicht verstanden.
Ich habe auch gegooglet und auch hier im Forum geschaut, ob es ggf. irgendwo ein ähnliches Problem gibt, aber leider nichts gefunden.
Evtl. würde mir auch helfen, wenn ihr mir ein Stichwort sagt, was mir hier weiterhilft.

(Natürlich könnte man ggf. über das df iterieren und das einzeln Zeile für Zeile ausrechnen. Aber das erscheint mir bei dem was die Dataframes leisten können, irgendwie falsch)

Dankeschön!
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Buchfink,

willkommen im Forum!
Bei deiner Vorgehensweise wendest du "len" auf die gesamte Spalte an.

Ich denke du suchst aber das:

Code: Alles auswählen

df["SkillArray"].map(len)
"map" wendet die Funktion "len" Zeile für Zeile auf df["SkillArray"] an.
Ich bin aber kein Pandas-Wizzard, daher gibt es vielleicht noch bessere Möglichkeiten.

Dieses Konzept kommt einem vielleicht etwas ungewöhnlich vor, verlagert das Iterieren aber in den zugrundeliegenden C-Code, der damit schneller umgehen kann.
Daher war deine Annahme, dass man nicht selber iterieren soll genau richtig.
Pandas ist sehr mächtig und ein tieferes Einlesen lohnt sich auf jeden Fall.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@rodgerb

vielen herzlichen Dank für die schnelle Antwort und das freundliche Willkommen.

Kaum macht man's richtig, schon geht's :)

Die Methode "map" werde ich mir auf jeden Fall nun etwas genauer ansehen, denn ich glaube, die brauche ich noch öfter.

Und ja: das Iterieren erschien mir bei dem, was ich von den DataFrames bisher so gesehen habe eher "unelegant" zu sein.

Wenn die Lösung komplexer ist als das Problem, ist die Lösung in der Regel schlecht.

Mit "map" ist das Ziel imho bestens erreicht :) Danke!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Wobei ich nicht so ganz sehe was das mit objektorientiert oder nicht zu tun hat. Das ist hier ja alles ziemlich objektorientiert. Alles was man in Python an einen Namen binden kann ist ein Objekt. Und ob man nun ``len(irgendwas)`` oder ``irgendwas.len()`` schreiben muss, ändert in diesem Punkt ja nicht wirklich ob das objektorientiert ist oder nicht. Man hätte ja sogar tatsächlich ``df["SkillArray"].__len__()`` schreiben können, nur das man die ”magischen” Methoden in aller Regel nicht direkt aufruft, wenn es dafür einen anderen vorgesehenen Weg gibt. Guido hat `len()` eher als Operator gesehen, statt als Funktion, aber keine extra Syntax dafür eingeführt.

Man sollte übrigens mit den Begriffen Array und Liste sauber umgehen, weil das in Python zwei verschiedene Dinge sind. Und gerade wenn man Pandas und damit indirekt Numpy benutzt, dann verwendet man auch *beide* Datentypen, und Python-Programmierer haben gewisse erwartungen an etwas das Array genannt wird. Eben dass es sich wie ein Array verhält. Und sind dann überrascht wenn es eigentlich eine Liste ist.

Zumindest die konkreten Grunddatentypen sollten auch eher nicht in Namen stehen, denn das ändert man ab und zu mal während der Programmentwicklung, und dann hat man falsche/irreführende Namen im Programm stehen, oder muss die überall anpassen.

Gibt es denn einen Grund die Skills redundant in zwei verschiedenen Formaten im DataFrame zu haben?

Die `repr()`-Darstellung von dem `split()`-Ergebnis ist ein bisschen ungünstig, weil man da nicht so leicht sieht, dass da wirklich nur an dem "," getrennt wurde und alle bis auf den ersten Eintrag mit dem Leerzeichen anfangen, das nach dem Komma stand. Je nach dem wie sauber die Eingabedaten an der Stelle sind, möchte man vielleicht an ", " auftrennen, oder gar beliebig viele Leerzeichen vor und nach dem Komma erlauben:

Code: Alles auswählen

In [235]: df["Skills"].str.split(",")                                           
Out[235]: 
0    [a,  b,  c,  d,  e]
1            [a,  b,  c]
2                [a,  c]
3                [a,  c]
Name: Skills, dtype: object

In [236]: df["Skills"].str.split(", ")                                          
Out[236]: 
0    [a, b, c, d, e]
1          [a, b, c]
2             [a, c]
3             [a, c]
Name: Skills, dtype: object

In [237]: df["Skills"].str.split(r"\s*,\s*")                                    
Out[237]: 
0    [a, b, c, d, e]
1          [a, b, c]
2             [a, c]
3             [a, c]
Name: Skills, dtype: object
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_blackjack_
Man sollte übrigens mit den Begriffen Array und Liste sauber umgehen, weil das in Python zwei verschiedene Dinge sind. Und gerade wenn man Pandas und damit indirekt Numpy benutzt, dann verwendet man auch *beide* Datentypen, und Python-Programmierer haben gewisse erwartungen an etwas das Array genannt wird. Eben dass es sich wie ein Array verhält. Und sind dann überrascht wenn es eigentlich eine Liste ist.
ja, da hast Du absolut Recht. Das war mir leider so nicht bewusst. Wieder was gelernt! Danke!
Wäre es aus Deiner Sicht sinnvoll, die Überschrift des Beitrags dahingehend nochmals anzupassen?

(vermutlich habe ich diesen Begriff unbewusst einfach von meiner "alten" Programmiersprache übernommen)
Zumindest die konkreten Grunddatentypen sollten auch eher nicht in Namen stehen, denn das ändert man ab und zu mal während der Programmentwicklung, und dann hat man falsche/irreführende Namen im Programm stehen, oder muss die überall anpassen.
Auch hiermit hast Du vollkommen Recht. Ich habe mein konkretes Beispiel lediglich soweit abstrahieren wollen, dass man schnell sieht, was ich eigentlich tun möchte.
Es handelt sich auch nicht um "Produktiv-Code". Da würden mein "innerer Monk" auch rebellieren, wenn der Typ in den Spaltennamen eincodiert ist.
Gibt es denn einen Grund die Skills redundant in zwei verschiedenen Formaten im DataFrame zu haben?
nein. Es handelt sich lediglich um ein Fragment meiner eigenen Versuche, dem Problem auf die Spur zu kommen.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_blackjack_

Dankeschön :) auch für den Hinweis zu "split". Auch hier muss ich Dir beipflichten, dass man da sicherheitshalber auch mehr als ein Leerzeichen erlauben sollte.

so hier nochmal die Lösung "in schön"

Code: Alles auswählen

import pandas as pd
data = [['Ada', 30, 'a, b, c, d, e'],['Bob', 32, 'a, b, c'], ['Clarke', 33, 'a, c'], ['Dylan', 33, 'a, c']]
df = pd.DataFrame(data, columns=['Name', 'Age', 'Skills'])
df["SkillCount"] = df["Skills"].str.split(r"\s*,\s*").map(len)
print(df)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Schau Dir mal den Randfall an wenn jemand gar keine Skills hat:

Code: Alles auswählen

In [241]: df                                                                    
Out[241]: 
     Name  Age         Skills
0     Ada   30  a, b, c, d, e
1     Bob   32        a, b, c
2  Clarke   33           a, c
3   Dylan   33           a, c
4   Edith    3               

In [242]: df["Skills"].str.split(r"\s*,\s*").map(len)                           
Out[242]: 
0    5
1    3
2    2
3    2
4    1
Name: Skills, dtype: int64
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_blackjack_
ich sehe zwei interessante Aspekte:

1) nun, in meinen "Realdaten" tauchte die Problematik nicht auf, weil ich mit folgender Zeile "Problemwerte" bereits entfernt hatte. Bzw. ich habe geglaubt, sie hiermit ausgeschlossen zu haben.

Code: Alles auswählen

df = df.dropna(subset=["Skills"])
Wenn ich das jedoch analog für die synthetischen Daten aus dem Beispiel oben mache, so scheint das gar keine Auswirkungen zu haben. Ich bin irritiert.

Die Doku (https://pandas.pydata.org/pandas-docs/s ... ght=dropna) lese ich so, dass damit Zeilen mit fehlenden Werten entfernt werden sollten.
Bin ich da auf dem Holzweg?

2) len ermittelt den Wert 1 für die leere Liste.

Ich schätze, da muss ich nochmal forschen, woran das wohl liegt. Bzw. überlegen was hier mein Denkfehler ist.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_blackjack_
ich sehe zwei interessante Aspekte:

1) nun, in meinen "Realdaten" tauchte die Problematik nicht auf, weil ich mit folgender Zeile "Problemwerte" bereits entfernt hatte. Bzw. ich habe geglaubt, sie hiermit ausgeschlossen zu haben.

Code: Alles auswählen

df = df.dropna(subset=["Skills"])
Wenn ich das jedoch analog für die synthetischen Daten aus dem Beispiel oben mache, so scheint das gar keine Auswirkungen zu haben. Ich bin irritiert.

Die Doku (https://pandas.pydata.org/pandas-docs/s ... ght=dropna) lese ich so, dass damit Zeilen mit fehlenden Werten entfernt werden sollten.
Bin ich da auf dem Holzweg?

2) len ermittelt den Wert 1 für die leere Liste.

Ich schätze, da muss ich nochmal forschen, woran das wohl liegt. Bzw. überlegen was hier mein Denkfehler ist.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Ergänzung

wenn man "echte" NaN-Werte hätte, dann tut dropna() auch das was ich möchte.

Code: Alles auswählen

import numpy as np
import pandas as pd
data = [['Ada', 30, 'a, b, c, d, e'], ['Bob', 32, 'a, b, c'], ['Clarke', 33, 'a, c'], ['Dylan', 33, 'a, c'],
        ['Ernie', 3, np.nan]]
df = pd.DataFrame(data, columns=['Name', 'Age', 'Skills'])
df = df.dropna(subset=["Skills"])
print(df)
Es scheint also einen Unterschied zwischen Leerstring und Leer geben. Und deshalb hat es auch mit meinem Echtdaten funktioniert. Denn das Einlesen aus einer CSV führt wohl dazu, dass Leerstrings in ein NaN "konvertiert" werden. Damit greift dropna und der von Dir beschriebene Fall trat daher auch in meinen Realdaten nicht auf.
Toll, dass der Algorithmus zum Einlesen einer CSV da so intelligent ist :)

Da hab ich direkt schon wieder was gelernt. :)

Aber - jetzt bleibt aber immer noch die Frage: Wie entferne ich Zeilen, die Leerstrings enthalten?

Mein erster Ansatz wäre zu schauen, ob es eine Methode gibt, die pauschal Leerstrings in NaN wandelt. Denn ich habe bei dropna in der Doku keinen Parameter gesehen, der mein Vorhaben ermöglichen würde.

Oder ist mein Ansatz im Kern schon falsch?
Wie würdest Du hier herangehen?

LG
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

oh.... ich sehe, dass ich meinen ersten Beitrag doppelt gepostet habe. Das war keine Absicht!

Liebe Admins: könnt ihr den ggf. entfernen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Vorgehen ist immer das selbe, man sucht sich die Zeilen mit einer bestimmten Eigenschaft und maskiert sie dann.

Code: Alles auswählen

mask =  df['Skills'].str.len() != 0
df = df[mask].copy()
Wenn Du dann mit den Listen gar nichts machst, außer deren Länge zu bestimmen, dann brauchst Du kein split:

Code: Alles auswählen

 df["SkillCount"] = df['Skills'].str.count(',') + 1
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei auch das nur funktioniert wenn man keine "Skills" hat wo nichts drin steht, die müssen da also auch vorher ausgefiltert werden.

@Buchfink: `len()` ergibt bei einer leeren Liste 0. 1 wäre ja sehr komisch. Das Problem ist `split()` auf einer leeren Zeichenkette verhält sich so wie es sich auf einer nicht-leeren Zeichenkette ohne den Trenner verhält:

Code: Alles auswählen

In [250]: "".split(",")                                                         
Out[250]: ['']

In [251]: "kein Komma".split(",")                                               
Out[251]: ['kein Komma']
Ich habe hier jetzt `str.split()` gezeigt, aber das `split()` auf dem Pandas-DataFrame verhält sich analog.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@Sirius3
Dankeschön für diesen alternativen Lösungsansatz.
Ich vermute, dass er auch ggf. etwas performanter als "map" ist, denn ich brauche die Zerlegung in eine Liste tatsächlich nicht zwingend.
Von der Lesbarkeit finde ich im Moment "map" etwas eingängiger.
Für was man sich in der Praxis entscheidet, hängt natürlich davon ab, was im gegebenen Fall tatsächlich geboten ist. (In meinen Realdaten geht es um 200 Datensätze... Da dürfte Performance im Messfehlerbereich verschmerzbar sein)

Angenommen ich wollte hier zu Lernzwecken die Performance mal messen, was würdet ihr mir raten? Gibt's Dinge, die man unter Python beim Messen der Performance beachten sollte?
Wäre ich hiermit gut beraten?

Code: Alles auswählen

import timeit
....
@_blackjack_
Ach Dir vielen Dank für den Hinweis. Ich habe meine Testdaten mal um Leerstring und NaN erweitert, so dass beide Fälle enthalten sind.
Wenn ich Dich richtig verstanden habe, müsste man beide Fälle herausfiltern.

Heraus käme nun folgendes:

Code: Alles auswählen

import numpy as np
import pandas as pd
data = [['Ada', 30, 'a, b, c, d, e'], ['Bob', 32, 'a, b, c'], ['Clarke', 33, 'a, c'], ['Dylan', 33, 'a, c'],
        ['Ernie', 3, np.nan], ['Fred', 2, '']]
df = pd.DataFrame(data, columns=['Name', 'Age', 'Skills'])
df = df.dropna(subset=["Skills"])
df = df[df['Skills'].str.len() != 0].copy()
df["SkillCount"] = df['Skills'].str.count(',') + 1
print(df)
Vielen Dank für Eure hilfreichen Verbesserungsvorschläge. Ich merke schon, dass ich hier viel lernen kann.

LG
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_blackjack_
`len()` ergibt bei einer leeren Liste 0. 1 wäre ja sehr komisch
ja, das meinte ich, als ich schrieb, dass ich hier ggf. noch einen Denkfehler habe. Jetzt wird es aber klar. Er zählt den Leerstring als Element. Wenn man drüber nachdenkt, ist es logisch. :)
Antworten