商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何从 DataFrame 列中智能提取章节编号并映射到新列

如何从 DataFrame 列中智能提取章节编号并映射到新列

  发布于2026-07-20 阅读(0)

扫一扫,手机访问

本文介绍一种基于正则匹配与上下文记忆的稳健方法,用于从 section_name 中提取结构化章节 ID(如 1.1、1.3.1),并将其填充至 section_id 列;对无编号条目(如 Synopsis)自动关联最近匹配的编号,空值保持不变。

在处理结构化文档类数据——比如测试用例、标准规范,或者从PDF表格里解析出来的结果——时,经常会遇到一个头疼的问题:数据框里的 `section_name` 列,既包含了带编号的标题(比如 "1.2.Schema12"),也混着纯内容标题(比如 "Synopsis")。目标很明确:为每一行数据准确推断出它所属的逻辑章节ID,然后填到新列 `section_id` 里。这可不是简单的字符串切分,背后其实是上下文语义映射的活儿。

先说核心思路,其实就两步:

  1. 正则识别编号前缀:用 `re.match(r'(\d+(?:\.\d+)*)(.*)', s)` 这种模式,精确捕获由连续数字和点号组成的层级ID(支持 `1`、`1.1`、`1.3.1` 这种任意深度),剩下的部分就作为“语义标识符”存进一个映射字典里。
  2. 模糊反向匹配:对于那些没有编号的行(比如 "Synopsis"),就去遍历已经记录下来的语义标识符,检查当前名称是不是某个标识符的子串(条件就是 `if name.strip() in known_semantic_key`),从而确定它的归属。

下面是一段可以直接运行的完整代码,已经处理了空值、None和空白字符串这些边界情况:

import pandas as pd
import numpy as np
import re

# 构造示例数据(含空值与 None)
data = {
    'section_name': [
        '1.Test Summary9',
        '1.1.Synopsis9',
        '1.2.Schema12',
        '1.3.1.Test Period  I - Screening13',
        '1.3.2.Period II - obes-Treatment 15',
        'Synopsis',
        'Test Period  I - Screening',
        None,
        ''
    ]
}
df = pd.DataFrame(data)

def extract_section_id(row, memo_dict):
    name = row['section_name']
    # 跳过 None 和纯空白
    if pd.isna(name) or str(name).strip() == '':
        return ''  # 或返回 np.nan,按需选择
    s = str(name).strip()
    # 步骤1:尝试匹配编号前缀(如 1.3.1)
    match = re.match(r'^(\d+(?:\.\d+)*)\.(.*)$', s)
    if match:
        sec_id, semantic_part = match.groups()
        # 记录该语义片段对应的 ID(用于后续匹配)
        memo_dict[semantic_part] = sec_id
        return sec_id
    else:
        # 步骤2:对无编号行,在历史语义片段中查找包含关系
        for semantic_key, sec_id in memo_dict.items():
            if s in semantic_key or semantic_key in s:
                return sec_id
    return ''  # 未匹配时返回空字符串

# 初始化记忆字典
section_map = {}
df['section_id'] = df.apply(lambda x: extract_section_id(x, section_map), axis=1)
print(df)

输出结果是这样的:

                         section_name section_id
0                      1.Test Summary9          1
1                        1.1.Synopsis9        1.1
2                         1.2.Schema12        1.2
3   1.3.1.Test Period  I - Screening13      1.3.1
4  1.3.2.Period II - obes-Treatment 15      1.3.2
5                             Synopsis        1.1
6           Test Period  I - Screening      1.3.1
7                                None           
8                                           

这套方法有几处关键优势:

  • 鲁棒性高:显式处理了 None、空字符串和前后空格,不会因为这些边缘情况崩掉。
  • 语义感知:通过 `s in semantic_key` 这种条件,实现了“Synopsis 属于 1.1.Synopsis9”的合理映射,而不是简单粗暴的前缀匹配。
  • 状态可维护:`memo_dict` 字典在 `apply` 过程中不断累积已识别的模式,天然支持多层级文档的流式处理。
  • 正则精准:`^(\d+(?:\.\d+)*)\.` 这个模式,确保只匹配开头的合法编号加一个点号,避免了误把 `Schema12` 里的 `12` 当成编号截取出来。

不过,有几个细节需要注意:

  • 如果存在歧义语义,比如 "Summary" 同时出现在 `1.Test Summary9` 和 `2.Summary Notes` 里,就需要增强匹配逻辑了。可以考虑改成 `semantic_key.startswith(s)`,或者引入编辑距离来辅助判断。
  • 生产环境里,建议把函数封装成独立模块,并加上日志记录,把那些匹配不上的项记下来,方便人工复核。
  • 对于特别大的 DataFrame,可以改用 `itertuples()` 来提升性能,但需要注意的是,`memo_dict` 字典仍然需要跨行共享,确保状态一致。

总的来说,这个方法在准确性、可读性和工程实用性之间找到了一个不错的平衡点,很适合用来处理各种嵌套编号文档的结构化解析任务。

本文转载于:https://www.php.cn/faq/2324472.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注