使用Pandas解析数据 - 如何将匹配输出为新列

-2 投票
1 回答
130 浏览
提问于 2025-04-14 18:10

我有一个程序可以读取CSV文件,并输出符合特定条件的选定列:

CSV输入文件长这样:

姓名 角色 登录时间
Phil 角色 A | 角色 B 2024/01/01
Bob 角色 A | 角色 B 2024/02/01
Arthur 角色 A | 角色 C 2024/01/04
Jane 角色 B | 角色 C 2024/01/31
Mary 角色 A | 角色 D 2024/02/12
Liz 角色 B | 角色 F 2024/02/21
Phoebe 角色 C | 角色 D 2023/11/21
Mike 角色 E 2024/02/15
Rick 角色 D | 角色 E 2024/01/13
Hilary 角色 F 2024/01/11

我有一段代码可以根据传入的值进行匹配:

# Define function to check if a value matches any of the filter values
def matches_filter(value):
    value_lower = value.lower()
    for filter_value in value_lower.split("|"):
        filter_value_lower = filter_value.lower()
        for fvals in fltr_values:
            if fvals.lower() in filter_value_lower:
                return fvals.lower()
    return None

# Apply filter
# filtered_df = df[df[fltr_field].apply(matches_filter)]
df[fltr_field + "_matched"] = df[fltr_field].apply(matches_filter)

根据传入的“角色 B”和“角色 D”,我想把角色列中的内容替换成过滤后的结果。因此,最终的表格应该看起来像这样:

姓名 角色 登录时间
Phil 角色 B 2024/01/01
Bob 角色 B 2024/01/01
Jane 角色 B 2024/02/03
Mary 角色 D 2024/02/02
Liz 角色 B 2024/02/12
Phoebe 角色 D 2024/02/21
Rick 角色 D 2024/01/31

到目前为止,代码可以过滤出包含“角色 B”或“角色 D”的字符串,但我想把找到的字符串替换成匹配的条件,而不是角色列表。有人能告诉我需要改什么吗?

为了进一步解释,根据目前收到的评论:

  • fltr_field的内容是什么?

fltr_field包含要过滤的列的名称(在这个例子中,我是在过滤名为"角色"的列)。

  • 你能清楚地解释一下你想做什么吗?

我希望把角色列的内容替换成匹配的值。

  • "登录时间"列的性质是什么?

"登录时间"列包含最后一次登录的日期。

1 个回答

1

这里有一个输入数据框生成器:

df = pd.DataFrame({'Name': {0: 'Phil', 1: 'Bob', 2: 'Arthur', 3: 'Jane', 4: 'Mary', 5: 'Liz', 6: 'Phoebe', 7: 'Mike', 8: 'Rick', 9: 'Hilary'}, 
                   'Role': {0: 'Role A | Role B', 1: 'Role A | Role B', 2: 'Role A | Role C', 3: 'Role B | Role C', 4: 'Role A | Role D', 5: 'Role B | Role F', 6: 'Role C | Role D', 7: 'Role E', 8: 'Role D | Role E', 9: 'Role F'}, 
                   'Login': {0: '2024/01/01', 1: '2024/02/01', 2: '2024/01/04', 3: '2024/01/31', 4: '2024/02/12', 5: '2024/02/21', 6: '2023/11/21', 7: '2024/02/15', 8: '2024/01/13', 9: '2024/01/11'}})

     Name             Role       Login
0    Phil  Role A | Role B  2024/01/01
1     Bob  Role A | Role B  2024/02/01
2  Arthur  Role A | Role C  2024/01/04
3    Jane  Role B | Role C  2024/01/31
4    Mary  Role A | Role D  2024/02/12
5     Liz  Role B | Role F  2024/02/21
6  Phoebe  Role C | Role D  2023/11/21
7    Mike           Role E  2024/02/15
8    Rick  Role D | Role E  2024/01/13
9  Hilary           Role F  2024/01/11

使用正则表达式提取看起来是你最好的选择:

# Define search - insert any number of Role letters inside the brackets
role_pattern = "(Role\s[BD])"
# Generate filtered table
df.assign(Role = df['Role'].str.extract(pat=role_pattern)
 ).dropna(subset = ['Role'])

     Name    Role       Login
0    Phil  Role B  2024/01/01
1     Bob  Role B  2024/02/01
3    Jane  Role B  2024/01/31
4    Mary  Role D  2024/02/12
5     Liz  Role B  2024/02/21
6  Phoebe  Role D  2023/11/21
8    Rick  Role D  2024/01/13

我觉得没有必要使用动态列名的复杂性(比如你的 df[fltr_field + "_matched"]),因为你想要的输出中的最后一列本来就叫 "Role"


更新以匹配作为完整单词的各种角色
如果输入不同,

  • 过滤角色 "Alpha" 和 "Role B",
  • 同时存在其他角色,比如 "Role A"、"Role AB"、"Aphal"、"Alpha A"*
df = pd.DataFrame({'Name': {0: 'Phil', 1: 'Bob', 2: 'Arthur', 3: 'Jane', 4: 'Mary', 5: 'Liz', 6: 'Phoebe', 7: 'Mike', 8: 'Rick', 9: 'Hilary'}, 
                   'Role': {0: 'Role A | Role B', 1: 'Role A | Role B', 2: 'Role A | Role C', 3: 'Role B | Role C', 4: 'Role A | Role D', 5: 'Role BA | Role F', 6: 'Role C | Role D', 7: 'Aphal', 8: 'Alpha B | Role E', 9: 'Alpha'},
                   'Login': {0: '2024/01/01', 1: '2024/02/01', 2: '2024/01/04', 3: '2024/01/31', 4: '2024/02/12', 5: '2024/02/21', 6: '2023/11/21', 7: '2024/02/15', 8: '2024/01/13', 9: '2024/01/11'}}

     Name              Role       Login
0    Phil   Role A | Role B  2024/01/01
1     Bob   Role A | Role B  2024/02/01
2  Arthur   Role A | Role C  2024/01/04
3    Jane   Role B | Role C  2024/01/31
4    Mary   Role A | Role D  2024/02/12
5     Liz  Role BA | Role F  2024/02/21
6  Phoebe   Role C | Role D  2023/11/21
7    Mike             Aphal  2024/02/15
8    Rick  Alpha B | Role E  2024/01/13
9  Hilary             Alpha  2024/01/11

# simple variant for whole words - caution: will mistake a "Role BA" for a "Role B":
role_pattern = "(Alpha|Role B)"
df.assign(Role = df['Role'].str.extract(pat=role_pattern)
 ).dropna(subset = ['Role'])

# Safe variant for whole words that prevents this: (with lookahead assertion)
role_pattern = '(Alpha(?=($|\s|))|Role B(?=($|\s|)))'
df.assign(Role = df['Role'].str.extract(pat=role_pattern)[0]
 ).dropna(subset = ['Role'])

     Name    Role       Login
0    Phil  Role B  2024/01/01
1     Bob  Role B  2024/02/01
3    Jane  Role B  2024/01/31
9  Hilary   Alpha  2024/01/11

使用带有前瞻断言的“安全变体”,只要角色之间用 " | " 隔开,就能正常工作。它确保角色名称后面跟着:

  • 要么是字符串的结尾 ("$"),
  • 要么是空格后面的竖线分隔符 ("\s|")。

同样,我们不仅可以“向前看”,还可以“向后看”;你看,正则表达式的复杂性必须跟上预期“角色”的多样性,但没有必要让它变得比需要的更复杂。

参考资料

撰写回答