【问题标题】:Map values in dataframe based on condition using a nested dictionary使用嵌套字典根据条件映射数据框中的值
【发布时间】:2021-12-08 03:53:50
【问题描述】:

我有以下字典

dict_map = {
    'Anti' : {'Drug':('A','B','C')},
    'Undef': {'Drug':'D','Name':'Type X'},
    'Vit ' : {'Name': 'Vitamin C'},
    'Placebo Effect' : {'Name':'Placebo', 'Batch':'XYZ'},
}

还有数据框

df = pd.DataFrame(
{
        'ID': ['AB01', 'AB02', 'AB03', 'AB04', 'AB05','AB06'],
        'Drug': ["A","B","A",np.nan,"D","D"],
        'Name': ['Placebo', 'Vitamin C', np.nan, 'Placebo', '', 'Type X'],
        'Batch' : ['ABC',np.nan,np.nan,'XYZ',np.nan,np.nan],
        
}

我必须创建一个新列,它将使用列表中指定的列的数据来填充

cols_to_map = ["Drug", "Name", "Batch"]

最终结果应该是这样的

请注意,尽管“维生素 C”和“安慰剂”是“名称”列,但“结果”列的前 3 行填充了“反”,这是因为“反”在字典中排在第一位。如何使用 python 实现这一点? dict_map 无论如何都可以重组以满足这个结果。我不是 python 专业人士,非常感谢您的帮助。

【问题讨论】:

    标签: python pandas dataframe numpy data-manipulation


    【解决方案1】:

    首先为嵌套字典中元组的单独值重塑嵌套字典:

    from collections import defaultdict
    
    d = defaultdict(dict)
    
    for k, v in dict_map.items():
        for k1, v1 in v.items():
            if isinstance(v1, tuple):
                for x in v1:
                    d[k1][x] = k
            else:
                d[k1][v1] = k
    
    print (d)
    defaultdict(<class 'dict'>, {'Drug': {'A': 'Anti', 'B': 'Anti', 
                                          'C': 'Anti', 'D': 'Undef'},
                                 'Name': {'Type X': 'Undef', 'Vitamin C': 'Vit ',
                                          'Placebo': 'PPL'}})
    

    df = pd.DataFrame(
        {
                'ID': ['AB01', 'AB02', 'AB03', 'AB04', 'AB05','AB06'],
                'Drug': ["A","B","A",np.nan,
                         "D","D"],
                'Name': ['Placebo', 'Vitamin C', np.nan, 'Placebo', '', 'Type X']
        }
        )
    

    然后按字典映射,优先级按列表cols_to_map中的列顺序排列:

    cols_to_map = ["Drug", "Name"]
    
    df['Result'] = np.nan
    for col in cols_to_map:
        df['Result'] = df['Result'].combine_first(df[col].map(d[col]))
    
    print (df)
         ID Drug       Name Result
    0  AB01    A    Placebo   Anti
    1  AB02    B  Vitamin C   Anti
    2  AB03    A        NaN   Anti
    3  AB04  NaN    Placebo    PPL
    4  AB05    D             Undef
    5  AB06    D     Type X  Undef
    

    cols_to_map = [ "Name","Drug"]
    
    df['Result'] = np.nan
    for col in cols_to_map:
        df['Result'] = df['Result'].combine_first(df[col].map(d[col]))
    
    print (df)
         ID Drug       Name Result
    0  AB01    A    Placebo    PPL
    1  AB02    B  Vitamin C   Vit 
    2  AB03    A        NaN   Anti
    3  AB04  NaN    Placebo    PPL
    4  AB05    D             Undef
    5  AB06    D     Type X  Undef
    

    编辑:

    df['Result1'] = df['Drug'].map(d['Drug'])
    df['Result2'] = df['Name'].map(d['Name'])
    print (df)
         ID Drug       Name Result1 Result2
    0  AB01    A    Placebo    Anti     PPL
    1  AB02    B  Vitamin C    Anti    Vit 
    2  AB03    A        NaN    Anti     NaN
    3  AB04  NaN    Placebo     NaN     PPL
    4  AB05    D              Undef     NaN
    5  AB06    D     Type X   Undef   Undef
    

    【讨论】:

    • 谢谢,但 'Undef' 应该只在第 5 行,因为只有该行在 Drug 列中有 'D',在 Name 列中有 'Type X',而 'Anti' 必须在第 0 行,1,2 因为它满足字典中指定的第一个条件
    • @Fazli - 不幸的是不明白为什么。
    • @Fazli - 第一行、第二行和最后一行匹配两个条件(药物、名称),第三、第五马赫 Name,第四行匹配 Drug。为什么会有NaN
    • 因此,如果它满足第一个条件并被映射,则无需检查其他条件(可以通过使用 nans 创建新列并在 for 循环中使用 fillna 来完成)并且有 NaN for第 4 行,因为如果 Drug 是 D 并且 Name 是 ""
    • @Fazli 检查编辑。在 Result1 中映射了 Drug,所以只有 index=3 是 NaN,如果将 Name 映射到 Result2,则得到 2,4 的 NaN。那么为什么在最后一列中将 index=4 替换为 NaN 呢?
    【解决方案2】:

    由于 dict 和预期结果之间的关系非常复杂,我会使用一个函数来应用于您的 DataFrame。这使我们免于操作字典:

    def get_result(row):
        result = np.nan
        for k,v in dict_map.items():
            if row['Name'] in v.values():
                result = k
            if row['Name'] and type(row['Drug']) == str and 'Drug' in v.keys() and row['Drug'] in v['Drug']:
                return k
        return result
    
    
    df['Result'] = df.apply(lambda row: get_result(row), axis=1)
    print(df)
    

    输出:

         ID Drug       Name Result
    0  AB01    A    Placebo   Anti
    1  AB02    B  Vitamin C   Anti
    2  AB03    A        NaN   Anti
    3  AB04  NaN    Placebo    PPL
    4  AB05    D               NaN
    5  AB06    D     Type X  Undef
    

    更新您的问题后,我将功能更改为通用功能。我不太确定它是否会涵盖您的所有案例,因为您的输出不会随着新列发生太大变化:

    col_to_maps = ["Drug", "Name", "Batch"]
    
    def get_result(row, dict_map):
        result = np.nan
        for k,v in dict_map.items():
            for i,col in enumerate(col_to_maps[:-1]):    
                if type(v)==dict:
                    if str(row[col]) and \
                     all(str(row[other_col])
                         and (not(str(other_col) in v.keys()) and str(col) in v.keys() and str(row[col]) in v[col]
                              or str(other_col) in v.keys() and str(row[other_col]) in v[other_col]
                              )
                         for other_col in col_to_maps[i+1:]
                        ):
                        return k 
                elif str(row[col]) in v:
                    result = k
        return result
    
    
    df['Result'] = df.apply(lambda row: get_result(row, dict_map), axis=1)
    print(df)
    

    输出:

         ID Drug       Name Batch          Result
    0  AB01    A    Placebo   ABC            Anti
    1  AB02    B  Vitamin C   NaN            Anti
    2  AB03    A        NaN   NaN            Anti
    3  AB04  NaN    Placebo   XYZ  Placebo Effect
    4  AB05    D              NaN             NaN
    5  AB06    D     Type X   NaN           Undef
    

    【讨论】:

    • 如果有的话我可以在不硬编码列名的情况下做到这一点,而是从列表 cols_to_map 中获取感兴趣的列?因为可能存在涉及超过 2 个变量的情况
    • 你能举一个第三列的例子吗?包括对字典和预期输出的更改?
    • 我已经更新了上面的问题
    • 好的,我不确定我是否完全掌握了您的要求,但我更新了我的答案。看看吧!
    猜你喜欢
    • 2015-07-24
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 2020-11-05
    • 1970-01-01
    • 2013-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多