Thêm cột mới vào dataframe dựa trên từ điển


23

Tôi có một dataframe và một từ điển. Tôi cần thêm một cột mới vào khung dữ liệu và tính toán các giá trị của nó dựa trên từ điển.

Học máy, thêm tính năng mới dựa trên một số bảng:

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)

Tôi mong đợi đầu ra sau đây:

   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

Câu trả lời:


13

scorelà một từ điển (vì vậy các khóa là duy nhất) nên chúng ta có thể sử dụng MultiIndexcăn chỉnh

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
Đẹp một trong những MultiIIndex. Thay thế : df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Quang Hoàng

4
@ALollz, tha lỗi cho tôi, tôi thích câu trả lời của bạn nhưng tôi phải lên tiếng khi thấy rất nhiều câu trả lời về một câu trả lời như thế này. Câu trả lời này là tốt thông minh. Nhưng nó không tuyệt vời. Có quá nhiều bộ phận chuyển động không có lợi lớn. Trong quá trình này, bạn đã tạo mới dfthông qua set_index, mới Seriesthông qua hàm tạo. Mặc dù bạn có được lợi ích của việc căn chỉnh chỉ mục khi bạn gán nó cho df['score']. Cuối cùng, fillna(0, downcast='infer')hoàn thành công việc nhưng không ai nên thích giải pháp dài dòng này với việc tạo ra nhiều đối tượng gấu trúc không cần thiết.
piRSquared

Một lần nữa, xin lỗi, bạn cũng có upvote của tôi, tôi chỉ muốn hướng dẫn mọi người câu trả lời đơn giản hơn.
piRSquared

@piRSquared Tôi đã đi ăn trưa, và thật ngạc nhiên khi điều này nhận được sự chú ý khi tôi quay lại. Tôi đồng ý rằng đó là một chút phức tạp để làm một cái gì đó mà đơn giản mergecó thể thực hiện. Tôi đoán rằng câu trả lời sẽ được đăng nhanh chóng vì vậy tôi đã chọn một giải pháp thay thế và vì một lý do nào đó có nhiều suy nghĩ trong đầu tôi. Tôi đồng ý, đây có lẽ không nên là câu trả lời được chấp nhận, vì vậy hy vọng điều đó không xảy ra.
ALollz

1
Oh tôi với bạn. Tôi đã trả lời tương tự nhiều lần. Tôi chỉ làm hết sức mình để phục vụ cộng đồng (-: Tôi tin rằng bạn hiểu ý của tôi.
piRSquared

7

Sử dụng assignvới khả năng hiểu danh sách, nhận một bộ giá trị (mỗi hàng) từ từ scoređiển, mặc định là 0 nếu không tìm thấy.

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

Thời gian

Với nhiều cách tiếp cận khác nhau, tôi sẽ rất thú vị khi so sánh một số thời gian.

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

Yêu thích của tôi một chút. Tuy nhiên, chỉ để đảm bảo mọi thứ vẫn ở dạng dự định khi xử lý thông qua score.gettôi sử dụng itertupleshoặc zip(*map(df.get, df))... Để nhắc lại, đây là cách tiếp cận ưa thích của tôi.
piRSquared

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

1
Cuối cùng, hầu hết những gì tôi đang viết là bluster bởi vì hàm băm 1.0giống như hàm băm vì 1vậy việc tra cứu tuple sẽ dẫn đến cùng một câu trả lời. Xin lỗi @Alexander vì rất nhiều bình luận về điều này nhưng tôi chỉ muốn mọi người nâng cao điều này hơn vì ... họ nên (-:
piRSquared

1
Miễn là bạn đúng lúc, hãy nhìn vào gợi ý của tôi. Có những dịp .valuesđắt đỏ
piRSquared

1
@AndyL. bạn thậm chí có thể kiểm soát các cột nào và theo thứ tự nào: zip(*map(df.get, ['col2', 'col1', 'col5']))hoặc nhận các bộ sửa đổi của df:zip(*map(df.eq(1).get, df))
piRSquared

4

Bạn có thể sử dụng bản đồ , vì điểm số là một từ điển:

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

Đầu ra

   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

Để thay thế, bạn có thể sử dụng một danh sách hiểu:

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

Tôi muốn mở rộng câu hỏi của tôi. Thực sự tôi cần thêm cơ sở cột trên phạm vi giá trị cột. Ví dụ: nếu 40 <tuổi <50 thì điểm = 4, v.v ... Bây giờ từ điển ánh xạ vào một số giá trị chính xác. Tương tự đúng và đối với các khóa khác ....
Mikola

1
Thêm một ví dụ về những gì bạn thực sự muốn
Dani Mesejo

Ví dụ đơn giản: # Ở đây 40 và 50, 10 và 20 là độ tuổi mà tôi nên sử dụng điểm = 4 (hoặc 5) điểm = {(1, 40, 50, 1, 1): 4, (0, 10, 20 , 1, 3): 5}
Mikola

@Mikola Vì vậy, nếu giới tính = 1 và 40 <tuổi <50 và cứ thế ...
Dani Mesejo

1
@Mikola Bạn nên cho mọi người biết, mặc dù tại thời điểm này tôi tin là tốt hơn nếu bạn hỏi một câu hỏi khác.
Dani Mesejo

4

Danh sách hiểu và bản đồ:

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

Đầu ra:

   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

Hoặc là 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

Có thể là một cách khác sẽ được sử dụng .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

Giải pháp một dòng đơn giản, Sử dụng gettuplehàng khôn ngoan,

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

Giải pháp trên là giả sử không có cột nào ngoài các cột mong muốn theo thứ tự. Nếu không, chỉ cần sử dụng cột

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

Sử dụng score.getlà tốt. Tuy nhiên, theo ý kiến ​​của tôi, bạn nên thích một sự hiểu biết. Xem thời gian của @ Alexander .
piRSquared

Ok @piSquared. Sẽ ghi nhớ điều đó.
Vishnudev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.