根据字典将新列添加到数据框


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

Answers:


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,请原谅我,我喜欢您的回答,但是当我看到如此多的人对这样的回答表示赞同时,我必须大声说出来。这个答案是罚款聪明。但这不是很好。有太多的活动部件,无济于事。在此过程中,您创建了一个新的dfvia set_index,一个新的Seriesvia构造函数。将索引分配给时,虽然可以获得索引对齐的好处df['score']。最后,fillna(0, downcast='infer')完成工作,但是没有人会因为不必要的创建许多熊猫对象而喜欢这种冗长的解决方案。
piRSquared '19

再次道歉,您也有我的支持,我只想引导人们获得更简单的答案。
piRSquared '19

@piRSquared我去吃午饭了,很惊讶,当我回来时,它受到了关注。我同意,做一个简单的事情merge可能会有点麻烦。我认为答案会很快发布,所以我选择了其他选择,由于某种原因,我想到了MultiIndices。我同意,这可能不应该被接受,因此希望不会发生。
ALollz

1
哦,我和你在一起。我已经回答了很多次。我正在尽力为社区服务(-:我相信您会得到我的
帮助。– piRSquared

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使用itertupleszip(*map(df.get, df))... 处理时所有内容都保持预期的类型,重申一下,这是我的首选方法。
piRSquared

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

1
最后,我写的大部分内容都是模糊的,因为的哈希值1.0与的哈希值相同1因此无论如何,元组查找都应得出相同的答案。抱歉@Alexander对此发表了很多评论,但我只希望人们对此表示赞同,因为...他们应该(-:
piRSquared

1
只要您有时间,请看看我的建议。在某些情况.values下价格昂贵
海盗

1
@AndyL。您甚至可以控制哪些列和顺序:zip(*map(df.get, ['col2', 'col1', 'col5']))或获得对以下内容的修改的元组dfzip(*map(df.eq(1).get, df))
piRSquared

4

您可以使用map,因为score是字典:

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 <age <50,则得分= 4,依此类推。。。现在字典将精确地映射为某个值。同一真正的和其他键....
米高拉

1
添加您真正想要的示例
Dani Mesejo '19

简单的例子:#这里40和50、10和20是年龄范围,我应该使用score = 4(or 5)score = {((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

简单的单行解决方案,按需使用gettuple逐行

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

好的@piSquared。将牢记这一点。
Vishnudev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.