辞書に基づいてデータフレームに新しい列を追加する


23

データフレームと辞書があります。新しい列をデータフレームに追加し、辞書に基づいてその値を計算する必要があります。

機械学習、いくつかのテーブルに基づく新しい機能の追加:

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)

私は次の出力を期待します:

   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

回答:


13

以来score辞書です(キーはユニークであるので)私たちは使用することができMultiIndex、アライメントを

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

1
素敵な1つMultiIIndex。代替:df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy()
Quang Hoang

4
@ALollz、許してください、私はあなたの答えが大好きですが、このような答えに対する非常に多くの賛成票を見たとき、私は声を上げなければなりません。この答えは結構です賢いです。しかし、それは素晴らしいことではありません。可動部品が多すぎて、大きなメリットはありません。その過程で、新しいdfvia set_index、新しいSeriesviaコンストラクタを作成しました。ただし、に割り当てると、インデックスアライメントのメリットが得られますdf['score']。最後にfillna(0, downcast='infer')、仕事を完了しますが、不必要に多くのパンダオブジェクトを作成するこの長い解決策を好む人はいません。
piRSquared

繰り返しますが、申し訳ありませんが、あなたも私の賛成票を持っています。私は人々をより簡単な答えに導きたいと思っています。
piRSquared

@piRSquaredランチに行ったところ、戻ってきたときに注目を集めたことに驚きました。シンプルでmerge達成できることをするのは少し複雑だということに同意します。私は答えがすぐに投稿されるだろうと思ったので、代替案を選び、何らかの理由でMultiIndicesを思いついた。私は同意する、これはおそらく受け入れられる答えではないはずなので、うまくいけばそれは起こらない。
ALollz

1
ああ、私はあなたと一緒です。私は何度も同じように答えました。私はコミュニティにサービスを提供するために全力を尽くしています(-:私はあなたが私の意図を
理解

7

assignリスト内包表記とともに使用して、score辞書から値のタプル(各行)を取得し、見つからない場合はデフォルトでゼロになります。

>>> 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

タイミング

さまざまなアプローチを考えると、いくつかのタイミングを比較することは興味深いことですが。

# 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)

私のお気に入りは少しです。しかし、念のすべての滞在を介して処理意図タイプにするためにscore.get、私が使用したいitertuplesか、zip(*map(df.get, df))繰り返しになるが...、これは私の好ましい方法です。
piRSquared

1
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
piRSquared

1
最後に、私が書いているもののほとんどは、ハッシュが1.0同じである1ため、タプルのルックアップは同じ回答になるはずです。謝罪@Alexanderこの上のように多くのコメントが、私はちょうど人々がより多くのために...彼らは(はずこれをupvoteしたい- :
piRSquared

1
あなたがタイミングを取っている限り、私の提案を見てください。.values高価な場合があります
piRSquared

1
@AndyL。あなたも、どの列とどの順で制御することができますzip(*map(df.get, ['col2', 'col1', 'col5']))かの修正のタプルを取得dfzip(*map(df.eq(1).get, df))
piRSquared

4

スコアは辞書なので、mapを使用できます。

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

出力

   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

別の方法として、リスト内包表記を使用できます。

df['score'] = [score.get(t, 0) for t in zip(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

質問を広げたいのですが。本当に私は列値の範囲に基づいて列ベースを追加する必要があります。たとえば、40 <年齢<50の場合、スコア= 4などです。辞書は正確な値にマップされます。同じことが他のキーにも当てはまります...
ミコラ

1
あなたが本当に欲しいものの例を追加してください
ダニ・メセホ

簡単な例:#ここで、40と50、10と20は、私が使用する年齢範囲です。スコア= 4(または5)スコア= {(1、40、50、1、1):4、(0、10、20 、1、3):5}
Mikola

@Mikolaしたがって、性別= 1および40 <年齢<50などの場合...
Dani Mesejo

1
@ミコラあなたはすべての体を知らせるべきですが、この時点で私はあなたが別の質問をする方が良いと信じています。
ダニMesejo

4

リストの理解度とマップ:

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

出力:

   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

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

または 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

2

別の方法かもしれません.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

2

シンプルな1行のソリューション、使用getおよびtuple行ごとの、

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

上記の解決策は、目的の列以外の列がないことを前提としています。そうでない場合は、列を使用してください

cols = ['gender','age','cholesterol','smoke']
df['score'] = df[cols].apply(lambda x: score.get(tuple(x), 0), axis=1)

の使用score.getは良いです。ただし、私の意見では、理解力を優先する必要があります。@Alexander タイミングを参照してください。
piRSquared

@piSquared。それを覚えておいてください。
Vishnudev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.