Funktion auf DataFrame-Index anwenden

79

Was ist der beste Weg, um eine Funktion auf den Index eines Pandas anzuwenden DataFrame? Derzeit verwende ich diesen ausführlichen Ansatz:

pd.DataFrame({"Month": df.reset_index().Date.apply(foo)})

Wo Dateist der Name des Index und fooist der Name der Funktion, die ich anwende.

Alex Rothberg
quelle
6
funktioniert df.index.map(foo)?
HYRY
1
Es "funktioniert", gibt aber eher ein numpy-Array als eine Pandas-Serie zurück.
Alex Rothberg
1
Was ist dein Endziel? Sie können das Array an den DataFrame-Konstruktor übergeben. Oder machen Sie etwas wiepd.Series(df.index).apply(foo)
Roman Pekar
Es kommt ganz darauf an, was die Funktion ist ...
Andy Hayden
1
Wenn Sie von @HYRY aus den Index eines vorhandenen DataFrame ändern möchten, können Sie dies tundf.index = df.index.map(foo)
Ben

Antworten:

95

Wie bereits von HYRY in den Kommentaren vorgeschlagen, ist Series.map der richtige Weg. Stellen Sie einfach den Index auf die resultierende Reihe ein.

Einfaches Beispiel:

df = pd.DataFrame({'d': [1, 2, 3]}, index=['FOO', 'BAR', 'BAZ'])
df
        d
FOO     1
BAR     2
BAZ     3

df.index = df.index.map(str.lower)
df
        d
foo     1
bar     2
baz     3

Index! = Serie

Wie von @OP hervorgehoben. Der df.index.map(str.lower)Aufruf gibt ein numpy-Array zurück. Dies liegt daran , Dataframe - Indizes werden auf numpy Arrays basieren, nicht - Serie.

Die einzige Möglichkeit, den Index in eine Serie umzuwandeln, besteht darin, daraus eine Serie zu erstellen.

pd.Series(df.index.map(str.lower))

Vorbehalt

Die IndexKlasse unterteilt jetzt die StringAccessorMixin, was bedeutet, dass Sie die obige Operation wie folgt ausführen können

df.index.str.lower()

Dies erzeugt immer noch ein Indexobjekt, keine Serie.

Firelynx
quelle
1
Mit einem Multi-Index können Sie Slicing verwenden, wenn Sie beide Elemente in Ihrer Funktion verwenden möchten, z . B. x[0]und x[1].
Elliott
3
Ein bisschen kürzer Wegdf.index.map(str.lower)
Null
1
@ JohnGalt Danke, dass du darauf hingewiesen hast. Es ist nicht nur kürzer, sondern auch schneller, da str.lower eine kompilierte Cython-Funktion ist und die von mir geschriebene Lambda-Funktion nicht.
Firelynx
12

Angenommen, Sie möchten eine Spalte in Ihrem aktuellen DataFrame erstellen, indem Sie Ihre Funktion "foo" auf den Index anwenden. Du könntest schreiben ...

df['Month'] = df.index.map(foo)

Um die Serie alleine zu generieren, könnten Sie stattdessen ...

pd.Series({x: foo(x) for x in foo.index})
suraj747
quelle
1
Von der Verwendung von for-Schleifen im Pandas / Numpy-Echosystem wird dringend abgeraten. Es ist sehr speichereffizient und stürzt bei größeren Datenmengen leicht ab.
Firelynx
3

Viele Antworten geben den Index als Array zurück, wodurch Informationen über den Indexnamen usw. verloren gehen (obwohl Sie dies tun könnten pd.Series(index.map(myfunc), name=index.name) ). Es funktioniert auch nicht für einen MultiIndex.

Die Art und Weise, wie ich damit gearbeitet habe, ist die Verwendung von "Umbenennen":

mix = pd.MultiIndex.from_tuples([[1, 'hi'], [2, 'there'], [3, 'dude']], names=['num', 'name'])
data = np.random.randn(3)
df = pd.Series(data, index=mix)
print(df)
num  name 
1    hi       1.249914
2    there   -0.414358
3    dude     0.987852
dtype: float64

# Define a few dictionaries to denote the mapping
rename_dict = {i: i*100 for i in df.index.get_level_values('num')}
rename_dict.update({i: i+'_yeah!' for i in df.index.get_level_values('name')})
df = df.rename(index=rename_dict)
print(df)
num  name       
100  hi_yeah!       1.249914
200  there_yeah!   -0.414358
300  dude_yeah!     0.987852
dtype: float64

Der einzige Trick dabei ist, dass Ihr Index eindeutige Bezeichnungen mit verschiedenen Multiindex-Ebenen haben muss, aber vielleicht weiß jemand, der klüger als ich ist, wie man das umgeht. Für meine Zwecke funktioniert dies in 95% der Fälle.

Choldgraf
quelle
2

Sie können einen Index jederzeit mit seiner to_series()Methode konvertieren und dann entweder applyoder mapentsprechend Ihren Vorlieben / Anforderungen.

ret = df.index.map(foo)                # Returns pd.Index
ret = df.index.to_series().map(foo)    # Returns pd.Series
ret = df.index.to_series().apply(foo)  # Returns pd.Series

Alle oben genannten Punkte können direkt einer neuen oder vorhandenen Spalte zugeordnet werden df:

df["column"] = ret

Nur der Vollständigkeit halber: pd.Index.map, pd.Series.mapund pd.Series.applyalle arbeiten elementweise. Ich verwende oft mapLookups, die durch dictsoder dargestellt werden pd.Series. applyist allgemeiner, weil Sie jede Funktion zusammen mit zusätzlichen argsoder übergeben können kwargs. Die Unterschiede zwischen applyund mapwerden in diesem SO-Thread weiter erläutert . Ich weiß nicht, warum pd.Index.applyweggelassen wurde.

normanius
quelle