เพิ่มคอลัมน์ใหม่ไปยัง dataframe ตามพจนานุกรม


23

ฉันมีชื่อไฟล์และพจนานุกรม ฉันต้องการเพิ่มคอลัมน์ใหม่ไปยัง dataframe และคำนวณค่าตามพจนานุกรม

การเรียนรู้ของเครื่องเพิ่มคุณสมบัติใหม่ตามตารางบางส่วน:

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
MultiIIndexหนึ่งที่ดีของ ทางเลือก: df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Quang Hoang

4
@Alollz ยกโทษให้ฉันฉันรักคำตอบของคุณ แต่ฉันต้องพูดเมื่อฉันเห็น upvotes มากมายในคำตอบเช่นนี้ คำตอบนี้ใช้ได้และฉลาด แต่มันก็ไม่ได้ยอดเยี่ยม มีชิ้นส่วนที่เคลื่อนไหวมากเกินไปซึ่งไม่ได้ผลดี ในกระบวนการนี้คุณได้สร้างใหม่dfผ่านทางset_indexใหม่Seriesผ่านตัวสร้าง df['score']แม้ว่าคุณจะได้รับประโยชน์จากการจัดตำแหน่งดัชนีเมื่อคุณกำหนดให้ ท้ายที่สุดfillna(0, downcast='infer')ทำงานให้เสร็จ แต่ไม่มีใครควรจะชอบวิธีแก้ปัญหาที่ยืดยาวนี้ด้วยการสร้างวัตถุแพนด้าจำนวนมากโดยไม่จำเป็น
piRSquared

อีกครั้งขอโทษคุณมี upvote ของฉันเช่นกันฉันแค่ต้องการแนะนำคนที่จะตอบง่ายขึ้น
piRSquared

@piRSquared ฉันไปทานอาหารกลางวันและรู้สึกประหลาดใจที่สิ่งนี้ได้รับความสนใจเมื่อฉันกลับมา ฉันเห็นด้วยว่ามันเป็นเรื่องที่ซับซ้อนพอmergeสมควรที่จะทำสิ่งที่เรียบง่ายสามารถทำได้ ฉันคิดว่าคำตอบนั้นจะได้รับการโพสต์อย่างรวดเร็วดังนั้นฉันจึงเลือกใช้วิธีอื่นและด้วยเหตุผลบางประการที่ MultiIndices อยู่ในใจของฉัน ฉันเห็นด้วยนี่อาจจะไม่เป็นคำตอบที่ยอมรับดังนั้นหวังว่าจะไม่เกิดขึ้น
ALollz

1
โอ้ฉันอยู่กับคุณ ฉันตอบไปหลายครั้งเหมือนกัน ฉันแค่พยายามทำให้ดีที่สุดเพื่อรับใช้ชุมชน (-: ฉันเชื่อว่าคุณจะได้รับความตั้งใจของฉัน
piRSquared

7

ใช้assignกับรายการความเข้าใจรับ tuple ของค่า (แต่ละแถว) จาก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ดังนั้นการค้นหา tuple ควรส่งผลให้ได้คำตอบเดียวกันโดยไม่คำนึงถึง ขอโทษ @Alexander สำหรับความคิดเห็นมากมายเกี่ยวกับเรื่องนี้ แต่ฉันแค่อยากให้คนโหวตมากกว่านี้เพราะ ... พวกเขาควรจะ (-:
piRSquared

1
ตราบใดที่คุณยังมีเวลาดูข้อเสนอแนะของฉัน มีหลายครั้งที่.valuesราคาแพง
piRSquared

1
@AndyL คุณสามารถควบคุมได้ว่าคอลัมน์ใดและเรียงตามลำดับ: zip(*map(df.get, ['col2', 'col1', 'col5']))หรือรับค่าการเปลี่ยนแปลงdf:zip(*map(df.eq(1).get, df))
piRSquared

4

คุณสามารถใช้แผนที่ได้เนื่องจากคะแนนเป็นพจนานุกรม:

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

เป็นอีกทางเลือกหนึ่งที่คุณสามารถใช้ list comprehension:

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

ฉันต้องการ จำกัด ขอบเขตคำถามของฉัน ที่จริงฉันต้องเพิ่มคอลัมน์ฐานในช่วงของค่าคอลัมน์ ตัวอย่างเช่นถ้า 40 <อายุ <50 ดังนั้นคะแนน = 4 ฯลฯ ... ตอนนี้พจนานุกรมแมปค่าที่แน่นอนบางอย่าง เหมือนกันจริงและสำหรับกุญแจอื่น ๆ ....
Mikola

1
เพิ่มตัวอย่างของสิ่งที่คุณต้องการจริงๆ
Dani Mesejo

ตัวอย่างง่ายๆ: # ที่นี่ 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
@ Mikola คุณควรแจ้งให้ทุก ๆ คนรู้ถึงจุดนี้ฉันเชื่อว่าจะดีกว่าถ้าคุณถามคำถามอื่น
Dani 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

Simple one line solution, การใช้งาน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.