4. 数据清洗与预处理实战
在主题热度与市场关注度量化项目中,原始数据往往来自多个渠道:搜索引擎指数、社交平台话题量、新闻转载数、电商评论、论坛帖子等。这些数据在进入指标计算之前,几乎不可能直接使用。缺失值会让趋势线断裂,异常值会把均值拉偏,重复记录会虚增热度,文本噪声则会让分词结果失去意义。本节围绕一个可复现的清洗流程展开,目标是把一份"脏"的原始热度数据整理成可直接用于统计与建模的干净数据集。
先看清数据问题,再动手清洗
假设我们采集到某主题连续 30 天的日度热度记录,字段包括:日期、平台、关键词、讨论量、阅读量、情感倾向文本。加载后先做一次体检,常见问题会集中暴露出来:某些日期讨论量缺失;个别平台出现阅读量为 0 但讨论量上千的矛盾记录;同一日期同一平台因多次抓取产生重复行;文本字段里混入 HTML 标签、表情符号、全角空格和"转发微博"之类的模板话术。清洗不是盲目套函数,而是先定位问题类型,再选择对应处理策略。
import pandas as pd
df = pd.read_csv("topic_heat_raw.csv", parse_dates=["date"])
print(df.shape)
print(df.isna().sum())
print(df.duplicated(subset=["date","platform","keyword"]).sum())
print(df.describe())
这段代码给出三个关键信号:各字段缺失数量、按"日期+平台+关键词"判定的重复行数、数值字段的分布范围。分布范围尤其重要,它能帮助判断异常值是录入错误还是真实爆发。
缺失值:区分"没有发生"与"没有采到"
缺失值不能一律删除或一律填充。讨论量缺失若发生在采集故障日,属于"没有采到",可用前后日插值或同平台同期均值填补;若某平台当天确实没有讨论,缺失应填 0。判断依据可以结合阅读量:阅读量正常而讨论量缺失,多半是采集问题;两者同时缺失,则更可能是该平台当日无数据。对于连续缺失超过 3 天的区段,插值会制造虚假平滑,建议标记为缺失并在后续计算中排除,而不是强行补齐。
df["discuss"] = df.groupby("platform")["discuss"] \
.transform(lambda s: s.interpolate(limit=3, limit_direction="both"))
df.loc[df["read"] == 0, "discuss"] = df.loc[df["read"] == 0, "discuss"].fillna(0)
这里用分组插值保证不同平台各自补齐,limit=3 限制连续填补长度,避免长段缺失被过度平滑。
异常值:用业务规则加统计边界双重判断
异常值处理最忌只看统计量。某天讨论量突然是平日的 20 倍,可能是真实热点事件,也可能是爬虫重复计数。实践中采用双重判断:先用 IQR 或 3σ 找出统计离群点,再结合业务规则复核。例如"阅读量小于讨论量"在多数平台不合理,属于逻辑异常;"单日讨论量超过该平台月均值的 10 倍且无对应新闻事件"则需人工确认。确认是录入错误的直接修正或置空,确认是真实爆发的保留,但在计算均值类指标时考虑使用中位数或截尾均值。
q1, q3 = df["discuss"].quantile([0.25, 0.75])
iqr = q3 - q1
upper = q3 + 1.5 * iqr
df["is_outlier"] = df["discuss"] > upper
df.loc[df["read"] < df["discuss"], "discuss"] = df["read"]
把逻辑矛盾记录按"讨论量不应大于阅读量"修正,是比直接删除更稳妥的做法,因为它保留了该日期的存在性,不影响时间序列连续性。
重复记录与标准化
重复记录常因多次抓取或跨表合并产生。去重键应包含业务主键,即日期、平台、关键词三者的组合,保留最新抓取时间的那一条。标准化则包括:平台名称统一("微博""新浪微博"合并为"微博")、关键词统一大小写与全半角、日期统一为 ISO 格式、数值字段统一为数值类型。标准化做在去重之前还是之后会影响结果,建议先去重再标准化,避免因名称不一致而漏判重复。
df = df.sort_values("crawl_time").drop_duplicates(
subset=["date","platform","keyword"], keep="last")
df["platform"] = df["platform"].replace({"新浪微博":"微博","weibo":"微博"})
df["keyword"] = df["keyword"].str.strip().str.lower()
文本噪声与分词
情感倾向文本字段是噪声重灾区。清洗顺序为:去除 HTML 标签与 URL、去除 @用户与话题符号、统一全半角、去除模板话术、再分词。中文分词可选用 jieba,并加载自定义词典加入主题相关专有名词,避免"市场关注度"被切成无意义碎片。分词后过滤停用词与单字,保留有情感或主题指向的词,供后续情感打分或词频统计使用。
import re, jieba
def clean_text(t):
t = re.sub(r"<[^>]+>|http\S+", "", str(t))
t = re.sub(r"@\S+|#\S+#", "", t)
t = t.replace("\u3000", " ").strip()
t = re.sub(r"转发微博|分享图片", "", t)
return t
df["text_clean"] = df["text"].map(clean_text)
df["tokens"] = df["text_clean"].map(lambda x: [w for w in jieba.cut(x) if len(w) > 1])
下图概括了本节从原始数据到干净数据集的完整处理链路,以及各环节的判断分支:
清洗结果如何验收
清洗完成后不能只看代码跑通,要用一组检查项确认数据可用:缺失率是否降到可接受范围(例如关键字段低于 2%);去重后行数是否与"日期×平台×关键词"的理论组合数接近;数值字段是否满足"阅读量≥讨论量≥0";分词结果中是否仍残留明显噪声词。下表给出一份可直接套用的验收清单示例,数值为示例假设,实际以项目数据为准。
| 检查项 | 检查方法 | 示例阈值 | 不通过时的处理 |
|---|---|---|---|
| 关键字段缺失率 | isna().mean() | ≤ 2% | 回查采集日志,补采或标记排除 |
| 重复记录 | 按业务主键 duplicated | = 0 | 保留最新抓取时间记录 |
| 逻辑一致性 | 阅读量 < 讨论量 的行数 | = 0 | 按阅读量修正讨论量 |
| 异常值占比 | IQR 标记比例 | ≤ 3% | 逐条业务复核,区分错误与真实爆发 |
| 文本噪声残留 | 抽样检查分词结果 | 无明显模板词 | 补充正则与停用词表 |
完成上述步骤后,得到的数据集应满足:时间序列连续、平台口径统一、数值逻辑自洽、文本可分词且噪声可控。这份干净数据集才是后续热度指数计算、趋势对比与关注度建模的可靠输入。清洗环节投入的时间,往往直接决定最终量化结论的可信度。