如何用 Python 将模糊文本描述解析为结构化时间序列数据?
建议让 LLM 提取原始短语,再用 Python 按规则处理数字、单位和修饰词,将区间策略设为可配置项,最后用 pandas 整理结果。题目中的这类表达用正则表达式就能解析,也方便核查每个结果的处理过程。
just under 200 million 没有给出下限,仅凭字面无法推导出 195。要把它转换成 195~200,需要先约定业务规则,例如“比目标值低不超过 2.5%”。
先定义模糊表达的处理规则
要得到题目中的结果,可以采用以下约定,所有数值统一以“百万订阅用户”为单位。
| 表达类型 | 示例 | 处理规则 |
|---|---|---|
| 近似值 | roughly 30 million | 保留中心值,输出 30, 30 |
| 近似值 | hovered around 220 million | 保留中心值,输出 220, 220 |
| 略低于 | just under 200 million | 下限取 200 × (1 - 0.025),上限取 200 |
| 无修饰词 | 200 million | 保留数值,输出 200, 200 |
使用这些规则时,需要区分中心值和区间边界的含义:
roughly 30 million的上下限都输出为30,只是暂时保留近似中心值,不能据此认为实际人数恰好为 3000 万。just under 200 million严格来说不包含200。目标表没有表示开闭区间的字段,建议在底层数据中额外保存upper_inclusive=False。
如果分析需要近似区间,也可以把 roughly x 约定为 [x × 0.95, x × 1.05]。这个比例同样是人为设定的,应根据数据来源和分析用途确定。
Python 实现
下面的解析器支持题目中的英文表达,以及 thousand、million 和 billion 三种单位。它使用完整匹配,遇到规则之外的文本会报错,避免仅提取到其中一个数字就判定解析成功。
代码依赖 pandas,正则表达式和 Decimal 都来自 Python 标准库。
import re
from decimal import Decimal
import pandas as pd
# 示例策略:just under 的下限比目标值低 2.5%
JUST_UNDER_MARGIN = Decimal("0.025")
# 统一转换为“百万订阅用户”
UNIT_TO_MILLIONS = {
"thousand": Decimal("0.001"),
"million": Decimal("1"),
"billion": Decimal("1000"),
}
PATTERN = re.compile(
r"(?:(?P<qualifier>"
r"roughly|about|approximately|around|hovered around|just under"
r")\s+)?"
r"(?P<value>[0-9]+(?:\.[0-9]+)?)\s+"
r"(?P<unit>thousand|million|billion)"
r"(?:\s+subscribers)?"
r"(?:\s+worldwide)?"
)
def parse_subscribers(text):
if not isinstance(text, str):
raise ValueError("订阅人数描述必须是字符串")
normalized = " ".join(text.casefold().split())
match = PATTERN.fullmatch(normalized)
if match is None:
raise ValueError(f"不支持的订阅人数描述:{text!r}")
qualifier = match.group("qualifier") or "exact"
value = (
Decimal(match.group("value"))
* UNIT_TO_MILLIONS[match.group("unit")]
)
lower = upper = value
upper_inclusive = True
if qualifier == "just under":
lower = value * (Decimal("1") - JUST_UNDER_MARGIN)
upper_inclusive = False
return {
"subscribers_min": float(lower),
"subscribers_max": float(upper),
"confidence": "high" if qualifier == "exact" else "medium",
"qualifier": qualifier,
"upper_inclusive": upper_inclusive,
}
data = {
"raw_extractions": [
{
"platform_mention": "Netflix",
"year_mention": "2012",
"subscriber_mention":
"roughly 30 million subscribers worldwide",
},
{
"platform_mention": "Netflix",
"year_mention": "2020",
"subscriber_mention": "just under 200 million",
},
{
"platform_mention": "Netflix",
"year_mention": "2022",
"subscriber_mention":
"hovered around 220 million subscribers",
},
]
}
records = []
errors = []
for index, item in enumerate(data["raw_extractions"]):
try:
year_text = str(item["year_mention"]).strip()
if re.fullmatch(r"[0-9]{4}", year_text) is None:
raise ValueError("年份必须是四位数字")
platform = item["platform_mention"]
if not isinstance(platform, str) or not platform.strip():
raise ValueError("平台名称必须是非空字符串")
platform = platform.strip()
# 按需要扩充平台别名表
platform = {"netflix": "Netflix"}.get(
platform.casefold(), platform
)
parsed = parse_subscribers(item["subscriber_mention"])
records.append({
"year": int(year_text),
"platform": platform,
**parsed,
"raw_subscriber_mention": item["subscriber_mention"],
})
except (KeyError, ValueError) as exc:
errors.append({
"index": index,
"error": str(exc),
"raw": item,
})
columns = [
"year",
"platform",
"subscribers_min",
"subscribers_max",
"confidence",
]
# audit_df 保留修饰词、原文和区间边界信息
audit_df = pd.DataFrame(records)
df = (
audit_df.reindex(columns=columns)
.sort_values(["platform", "year"])
.reset_index(drop=True)
)
print(df.to_string(index=False))
if errors:
print("待复核记录:", errors)
按上述规则,结果为:
| year | platform | subscribers_min | subscribers_max | confidence |
|---|---|---|---|---|
| 2012 | Netflix | 30.0 | 30.0 | medium |
| 2020 | Netflix | 195.0 | 200.0 | medium |
| 2022 | Netflix | 220.0 | 220.0 | medium |
保留小数便于处理 30.5 million 等表达,不建议仅为显示整齐就把整列转换为整数。
如何理解 confidence
示例中的 confidence 是自定义的解析标签:
high:短语没有近似修饰词,并且完整命中规则。medium:包含模糊修饰词,需要保留近似含义或应用区间策略。
这些标签未经概率校准,也不能保证原始数据正确。即使成功解析了 200 million,LLM 仍可能提取错平台、年份或统计口径。
如果需要评估抽取质量,可以建立人工标注样本,分别检查数字、单位、时间和平台是否提取正确,再确定置信度的计算方式。LLM 自报的置信度不能直接当作可靠概率。
扩展规则时要注意什么
正则表达式适合处理格式相对固定的短语,以下表达需要增加专门规则:
| 表达 | 需要处理的信息 |
|---|---|
between 195 and 200 million | 两个端点共享 million 单位 |
more than 200 million | 只有下限,上限未知 |
at most 200 million | 只有上限,且包含端点 |
nearly doubled to 200 million | 同时包含变化关系和最终值 |
对于单侧界限,未知端点应保存为缺失值。例如,more than 200 million 无法推出有限的上限。数值解析库可以帮助识别数字和单位,模糊词对应的区间宽度仍需由业务规则确定。
句式增多后,可以让 LLM 先提取中间结构,例如:
{
"value": 200,
"unit": "million",
"relation": "just_under",
"evidence": "just under 200 million"
}
再由 Python 校验字段、统一单位并计算区间。以后调整 just_under 的处理比例,就可以重新计算已有记录,同时保留原始证据供复核。
时间序列也需要统一统计口径。年末订阅数、全年平均订阅数和某季度订阅数,不能仅凭年份相同就合并;付费订阅账户与用户人数也应分别记录。题目中的数据只有年份,还不足以判断这些差异。
备注:内容仅供参考。
