使用嵌套字典根据条件映射数据帧中的值

Map values in dataframe based on condition using a nested dictionary

提问人:Fazli 提问时间:10/21/2021 最后编辑:Fazli 更新时间:10/22/2021 访问量:798

问:

我有以下词典

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

最终结果应如下所示

enter image description here

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

Python Pandas DataFrame numpy 数据操作

评论


答:

1赞 jezrael 10/21/2021 #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

评论

0赞 Fazli 10/21/2021
谢谢,但是“Undef”应该只在第 5 行,因为只有该行在“药物”列中具有“D”,在列名称中具有“X”,而“Anti”必须位于第 0、1、2 行中,因为它满足字典中指定的第一个条件
0赞 jezrael 10/21/2021
@Fazli - 很遗憾不明白为什么。
0赞 jezrael 10/21/2021
@Fazli - 第一、第二和最后一行匹配两个条件(药物、名称)、第三、第五马赫、第四匹配。为什么会有?NameDrugNaN
0赞 Fazli 10/21/2021
因此,如果它满足第一个条件并被映射,则无需检查其他条件(这可以通过使用 nans 创建新列并在 for 循环中使用 fillna 来完成),并且第 4 行有 NaN,因为如果 Drug 是 D 且 Name 是 “”,则没有应该映射它的情况
0赞 jezrael 10/21/2021
@Fazli 选中 EDIT。在 Result1 中被映射,所以只有 NaN,如果映射得到 NaN。那么为什么在最后一列中替换了 index=4 呢?Drugindex=3NameResult22,4NaN
0赞 Tranbi 10/21/2021 #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

评论

0赞 Fazli 10/21/2021
如果有的话,我可以在不对列名进行硬编码的情况下做到这一点,而是从列表中获取感兴趣的列cols_to_map?因为可能存在涉及 2 个以上变量的情况
0赞 Tranbi 10/21/2021
你能举一个第三列的例子吗?包括对字典和预期输出的更改?
0赞 Fazli 10/21/2021
我已经更新了上面的问题
0赞 Tranbi 10/22/2021
好的,我不确定我是否完全掌握了您的要求,但我更新了我的答案。一探究竟!