Tambahkan kolom baru ke bingkai data berdasarkan kamus

23

Saya memiliki kerangka data dan kamus. Saya perlu menambahkan kolom baru ke kerangka data dan menghitung nilainya berdasarkan kamus.

Pembelajaran mesin, menambahkan fitur baru berdasarkan beberapa tabel:

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)

Saya mengharapkan output berikut:

   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
sumber

Jawaban:

13

Karena scoreini adalah kamus (jadi tombolnya unik) kita bisa menggunakan MultiIndexperataan

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
sumber
1
Salah satunya MultiIIndex. Alternatif: df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Quang Hoang
4
@ALollz, maafkan saya, saya suka jawaban Anda, tetapi saya harus angkat bicara ketika saya melihat begitu banyak upvotes pada jawaban seperti ini. Jawaban ini baik dan pintar. Tapi itu tidak bagus. Ada terlalu banyak bagian bergerak tanpa keuntungan besar. Dalam prosesnya, Anda telah membuat baru dfmelalui set_index, baru Seriesmelalui konstruktor. Meskipun Anda mendapatkan manfaat dari penyelarasan indeks saat Anda menugaskannya df['score']. Terakhir, fillna(0, downcast='infer')menyelesaikan pekerjaan tetapi tidak ada yang lebih suka solusi panjang ini dengan membuat banyak objek panda secara tidak perlu.
piRSquared
Sekali lagi, permintaan maaf, Anda juga mendapat dukungan dari saya, saya hanya ingin membimbing orang-orang ke jawaban yang lebih sederhana.
piRSquared
@piRSquared Saya pergi untuk makan siang, dan terkejut ini mendapat perhatian yang dilakukannya ketika saya kembali. Saya setuju bahwa itu semua agak berbelit-belit untuk melakukan sesuatu yang sederhana mergebisa capai. Saya pikir jawaban itu akan diposting dengan cepat jadi saya memilih alternatif dan untuk beberapa alasan ada MultiIndices di pikiran saya. Saya setuju, ini mungkin seharusnya bukan jawaban yang diterima, jadi semoga itu tidak terjadi.
ALollz
1
Oh, aku bersamamu. Saya sudah menjawab berkali-kali yang sama. Saya hanya melakukan yang terbaik untuk melayani masyarakat (-: Saya percaya Anda mendapatkan niat saya.
piRSquared
7

Menggunakan assigndengan pemahaman daftar, mendapatkan tupel nilai (setiap baris) dari scorekamus, default ke nol jika tidak ditemukan.

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

Pengaturan waktu

Mengingat berbagai pendekatan, saya pikir akan menarik untuk membandingkan beberapa timing.

# 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
sumber
Favorit saya sedikit. Namun, hanya untuk memastikan semuanya tetap dengan tipe yang dimaksud saat memproses melalui score.getsaya akan menggunakan itertuplesatau zip(*map(df.get, df))... Untuk mengulangi, ini adalah pendekatan yang saya sukai.
piRSquared
1
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
piRSquared
1
Terakhir, sebagian besar dari apa yang saya tulis adalah bluster karena hash 1.0sama dengan hash karena 1itu tuple look up harus menghasilkan jawaban yang sama. Permintaan maaf @Alexander atas begitu banyak komentar tentang hal ini, tetapi saya hanya ingin orang-orang memuji ini lebih karena ... mereka seharusnya (-:
piRSquared
1
Selama Anda menghitung waktu, lihat saran saya. Ada saat-saat ketika .valuesitu mahal
piRSquared
1
@AndyL. Anda bahkan dapat mengontrol kolom mana dan dalam urutan apa: zip(*map(df.get, ['col2', 'col1', 'col5']))atau mendapatkan tupel modifikasi df:zip(*map(df.eq(1).get, df))
piRquared
4

Anda dapat menggunakan peta , karena skor adalah kamus:

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

Keluaran

   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

Sebagai alternatif, Anda dapat menggunakan pemahaman daftar:

df['score'] = [score.get(t, 0) for t in zip(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)
Dani Mesejo
sumber
Saya ingin memperluas pertanyaan saya. Sungguh saya perlu menambahkan basis kolom pada kisaran nilai kolom. Misalnya, jika 40 <umur <50 maka skor = 4 dll ... Sekarang kamus memetakan pada beberapa nilai. Sama benar dan untuk kunci lainnya ....
Mikola
1
Tambahkan contoh dari apa yang Anda inginkan
Dani Mesejo
Contoh sederhana: # Di sini 40 dan 50, 10 dan 20 adalah rentang usia yang harus saya gunakan skor = 4 (atau 5) skor = {(1, 40, 50, 1, 1): 4, (0, 10, 20 , 1, 3): 5}
Mikola
@ Mikola Jadi jika jenis kelamin = 1 dan 40 <usia <50 dan seterusnya ...
Dani Mesejo
1
@ Mikola Anda harus memberi tahu semua orang, meskipun pada titik ini saya percaya lebih baik jika Anda mengajukan pertanyaan lain.
Dani Mesejo
4

Daftar pemahaman dan peta:

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

Keluaran:

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

Atau 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
sumber
2

Mungkin cara lain akan menggunakan .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
sumber
2

Solusi satu baris sederhana, Gunakan getdan tuplebijaksana untuk baris,

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

Solusi di atas mengasumsikan tidak ada kolom selain yang diinginkan secara berurutan. Jika tidak, cukup gunakan kolom

cols = ['gender','age','cholesterol','smoke']
df['score'] = df[cols].apply(lambda x: score.get(tuple(x), 0), axis=1)
Vishnudev
sumber
Penggunaan score.getyang baik. Namun, Anda harus lebih memilih pemahaman, menurut saya. Lihat @ pengaturan waktu Alexander .
piRSquared
Ok @piSquared. Akan mengingatnya.
Vishnudev