【问题标题】:Add new column to dataframe based on dictionary根据字典向数据框添加新列
【发布时间】:2020-02-24 22:44:42
【问题描述】:

我有一个数据框和一个字典。我需要向数据框添加一个新列并根据字典计算其值。

机器学习,基于某些表格添加新功能:

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

【问题讨论】:

    标签: python pandas dataframe dictionary


    【解决方案1】:

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

    【讨论】:

    • 不错的MultiIIndex之一。备选:df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
    • @ALollz,原谅我,我喜欢你的回答,但是当我看到这么多人对这样的答案表示赞同时,我必须大声说出来。这个答案很好并且聪明。但这不是很好。有太多的活动部件没有很大的收获。在此过程中,您通过set_index 创建了一个新的df,通过构造函数创建了一个新的Series。尽管将索引对齐分配给df['score'] 时会受益。最后,fillna(0, downcast='infer') 完成了工作,但没有人会喜欢这种冗长的解决方案,因为它会不必要地创建许多 pandas 对象。
    • 再次抱歉,您也有我的支持,我只是想引导人们找到更简单的答案。
    • @piRSquared 我去吃午饭了,我很惊讶这会在我回来时引起人们的注意。我同意做一些简单的merge 可以完成的事情有点令人费解。我认为答案会很快发布,所以我选择了替代方案,出于某种原因,我想到了 MultiIndices。我同意,这可能不应该是公认的答案,所以希望这不会发生。
    • 哦,我和你在一起。我已经多次回答过同样的问题。我只是尽我所能为社区服务(-:我相信你明白我的意图。
    【解决方案2】:

    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))...重申一下,这是我的首选方法。
    • df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
    • 最后,我写的大部分内容都是虚张声势,因为1.0 的哈希值与1 的哈希值相同,因此元组查找无论如何都应该得到相同的答案。为这么多 cmets 向@Alexander 道歉,但我只是希望人们对此给予更多支持,因为......他们应该(-:
    • 只要你计时,看看我的建议。有时候.values很贵
    • @AndyL。您甚至可以控制哪些列和顺序:zip(*map(df.get, ['col2', 'col1', 'col5'])) 或获取 df 修改的元组:zip(*map(df.eq(1).get, df))
    【解决方案3】:

    你可以使用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
    • 添加一个你真​​正想要的例子
    • 简单示例:# 这里 40 和 50、10 和 20 是我应该使用的年龄范围 score = 4(or 5) score = {(1, 40, 50, 1, 1) : 4, (0, 10, 20, 1, 3) : 5}
    • @Mikola 你应该让每个人都知道,尽管在这一点上我相信如果你再问一个问题会更好。
    【解决方案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
    

    【讨论】:

      【解决方案5】:

      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
      

      【讨论】:

        【解决方案6】:

        可能是另一种方式是使用.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
        

        【讨论】:

          【解决方案7】:

          简单的一行解决方案,按行使用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's 计时。
          猜你喜欢
          • 2022-12-18
          • 2019-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-29
          • 2017-01-14
          • 2017-04-22
          • 2015-11-19
          相关资源
          最近更新 更多