Fügen Sie dem Datenrahmen basierend auf dem Wörterbuch eine neue Spalte hinzu

23

Ich habe einen Datenrahmen und ein Wörterbuch. Ich muss dem Datenrahmen eine neue Spalte hinzufügen und seine Werte basierend auf dem Wörterbuch berechnen.

Maschinelles Lernen, Hinzufügen neuer Funktionen basierend auf einer Tabelle:

score = {(1, 45, 1, 1) : 4, (0, 1, 2, 1) : 5}
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0],
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15],
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1],
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2]},
     dtype = np.int64)

print(df, '\n')
df['score'] = 0
df.score = score[(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

Ich erwarte folgende Ausgabe:

   gender  age  cholesterol  smoke    score
0       1   13            1      0      0 
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
Mikola
quelle

Antworten:

13

Da scorees sich um ein Wörterbuch handelt (die Tasten sind also eindeutig), können wir die MultiIndexAusrichtung verwenden

df = df.set_index(['gender', 'age', 'cholesterol', 'smoke'])
df['score'] = pd.Series(score)  # Assign values based on the tuple
df = df.fillna(0, downcast='infer').reset_index()  # Back to columns

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
ALollz
quelle
1
Schön von MultiIIndex. Alternative : df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Quang Hoang
4
@ALollz, vergib mir, ich liebe deine Antworten, aber ich muss mich melden, wenn ich so viele positive Stimmen zu einer solchen Antwort sehe. Diese Antwort ist gut und klug. Aber es ist nicht großartig. Es gibt zu viele bewegliche Teile für keinen großen Gewinn. Dabei haben Sie ein neues dfVia erstellt set_index, ein neues SeriesVia-Konstruktor. Sie profitieren jedoch von der Indexausrichtung, wenn Sie sie zuweisen df['score']. Schließlich fillna(0, downcast='infer')wird die Arbeit erledigt, aber niemand sollte diese langwierige Lösung mit der unnötigen Erstellung vieler Pandas-Objekte bevorzugen.
piRSquared
Nochmals, Entschuldigung, Sie haben auch meine Gegenstimme. Ich möchte die Leute nur zu einfacheren Antworten führen.
piRSquared
@piRSquared Ich ging zum Mittagessen und war überrascht, dass dies die Aufmerksamkeit bekam, die es tat, als ich zurückkam. Ich bin damit einverstanden, dass es ein bisschen kompliziert ist, etwas zu tun, was ein Einfacher erreichen mergekönnte. Ich dachte, dass die Antwort schnell veröffentlicht werden würde, also entschied ich mich für eine Alternative und hatte aus irgendeinem Grund MultiIndices im Kopf. Ich stimme zu, dies sollte wahrscheinlich nicht die akzeptierte Antwort sein, also hoffentlich passiert das nicht.
ALollz
1
Oh, ich bin bei dir. Ich habe das schon oft geantwortet. Ich gebe nur mein Bestes, um der Gemeinschaft zu dienen (-: Ich vertraue darauf, dass Sie meine Absicht bekommen.
piRSquared
7

Verwenden assigneines Listenverständnisses, Abrufen eines Tupels von Werten (jede Zeile) aus dem scoreWörterbuch, standardmäßig Null, wenn es nicht gefunden wird.

>>> df.assign(score=[score.get(tuple(row), 0) for row in df.values])
   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

Timings

Angesichts der Vielfalt der Ansätze halte ich es für interessant, einige der Timings zu vergleichen.

# Initial dataframe 100k rows (10 rows of identical data replicated 10k times).
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0] * 10000,
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15] * 10000,
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1] * 10000,
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2] * 10000},
     dtype = np.int64)

%timeit -n 10 df.assign(score=[score.get(tuple(v), 0) for v in df.values])
# 223 ms ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10 
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
# 76.8 ms ± 2.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=[score.get(v, 0) for v in df.itertuples(index=False)])
# 113 ms ± 2.58 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit -n 10 df.assign(score=df.apply(lambda x: score.get(tuple(x), 0), axis=1))
# 1.84 s ± 77.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
(df
 .set_index(['gender', 'age', 'cholesterol', 'smoke'])
 .assign(score=pd.Series(score))
 .fillna(0, downcast='infer')
 .reset_index()
)
# 138 ms ± 11.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df.merge(s.to_frame('score').reset_index(),how='left').fillna(0).astype(int)
# 24 ms ± 2.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
                .map(score)
                .fillna(0)
                .astype(int))
# 191 ms ± 7.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=df[['gender', 'age', 'cholesterol', 'smoke']]
                .apply(tuple, axis=1)
                .map(score)
                .fillna(0))
# 1.95 s ± 134 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Alexander
quelle
Mein Favorit von ein bisschen. Nur um sicherzustellen, dass bei der Verarbeitung alles den beabsichtigten Typ beibehält, score.getwürde ich itertuplesoder zip(*map(df.get, df))... Um es noch einmal zu wiederholen, ist dies mein bevorzugter Ansatz.
piRSquared
1
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
piRSquared
1
Schließlich ist das meiste, was ich schreibe, bluster, weil der Hash von 1.0der gleiche ist wie der Hash, 1daher sollten die Tupel-Lookups unabhängig davon zu derselben Antwort führen. Entschuldigung @Alexander für so viele Kommentare dazu, aber ich möchte nur, dass die Leute dies mehr befürworten, weil ... sie sollten (-:
piRSquared
1
Schauen Sie sich meinen Vorschlag an, solange Sie das Timing haben. Es gibt .values
Fälle, in
1
@ AndyL. Sie können sogar steuern, welche Spalten und in welcher Reihenfolge: zip(*map(df.get, ['col2', 'col1', 'col5']))oder Tupel einer Modifikation von df:zip(*map(df.eq(1).get, df))
piRSquared
4

Sie könnten eine Karte verwenden , da die Punktzahl ein Wörterbuch ist:

df['score'] = df[['gender', 'age', 'cholesterol', 'smoke']].apply(tuple, axis=1).map(score).fillna(0)
print(df)

Ausgabe

   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0

Alternativ können Sie ein Listenverständnis verwenden:

df['score'] = [score.get(t, 0) for t in zip(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)
Dani Mesejo
quelle
Ich möchte meine Frage erweitern. Wirklich, ich muss eine Spaltenbasis basierend auf dem Bereich des Spaltenwerts hinzufügen. Wenn zum Beispiel 40 <Alter <50, dann Punktzahl = 4 usw. ... Jetzt ordnet das Wörterbuch genau einen bestimmten Wert zu. Gleiches gilt für andere Schlüssel ....
Mikola
1
Fügen Sie ein Beispiel für das hinzu, was Sie wirklich wollen
Dani Mesejo
Einfaches Beispiel: # Hier sind 40 und 50, 10 und 20 Altersgruppen, für die ich Punktzahl = 4 (oder 5) Punktzahl = {(1, 40, 50, 1, 1): 4, (0, 10, 20) verwenden sollte , 1, 3): 5}
Mikola
@ Mikola Also wenn Geschlecht = 1 und 40 <Alter <50 und so weiter ...
Dani Mesejo
1
@Mikola Du solltest jeden Körper wissen lassen, obwohl ich an dieser Stelle glaube, dass es besser ist, wenn du eine andere Frage stellst.
Dani Mesejo
4

Listenverständnis und Karte:

df['score'] = (pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
               .map(score)
               .fillna(0)
               .astype(int)
              )

Ausgabe:

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
9       0   15            1      2    0.0
Quang Hoang
quelle
4

reindex

df['socre']=pd.Series(score).reindex(pd.MultiIndex.from_frame(df),fill_value=0).values
df
Out[173]: 
   gender  age  cholesterol  smoke  socre
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

Oder merge

s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df=df.merge(s.to_frame('score').reset_index(),how='left').fillna(0)
Out[166]: 
   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0
YOBEN_S
quelle
2

Möglicherweise würde ein anderer Weg verwendet werden .loc[]:

m=df.set_index(df.columns.tolist())
m.loc[list(score.keys())].assign(
           score=score.values()).reindex(m.index,fill_value=0).reset_index()

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
anky
quelle
2

Einfache einzeilige Lösung, Verwendung getund tuplezeilenweise,

df['score'] = df.apply(lambda x: score.get(tuple(x), 0), axis=1)

Bei der obigen Lösung wird davon ausgegangen, dass keine anderen Spalten als die gewünschten in der Reihenfolge vorhanden sind. Wenn nicht, verwenden Sie einfach Spalten

cols = ['gender','age','cholesterol','smoke']
df['score'] = df[cols].apply(lambda x: score.get(tuple(x), 0), axis=1)
Vishnudev
quelle
Verwendung score.getist gut. Meiner Meinung nach sollten Sie jedoch ein Verständnis vorziehen. Siehe @ Alexanders Timings.
piRSquared
Ok @piSquared. Werde das im Hinterkopf behalten.
Vishnudev