1. 从技能大赛真题到真实业务场景:为什么数据清洗是数据分析的“地基”

我干了这么多年数据分析,最深的体会就是:数据清洗这事儿,占了整个分析工作70%的时间和精力。你可能会觉得夸张,但等你真正接手一份“原生态”的业务数据就明白了。那些技能大赛里的练习题,比如处理缺失的作者名、修正奇怪的日期格式、转换数据类型,看起来像是为了考试而设计的“刁难”,但实际上,每一道题背后,都是一个你在真实工作中会踩到的“坑”。

就拿这次我们要聊的场景来说吧。想象一下,你刚入职一家电商公司,老板甩给你一份“当当网畅销图书榜单数据.csv”,让你分析一下今年的图书销售趋势。你摩拳擦掌,准备大干一场,结果一打开文件,傻眼了:作者栏里一堆“NaN”(空值),出版日期有的是“2020-05-01”,有的是“2020年5月1日”,甚至还有“20200501”;评论数里混着数字和文本;折扣比例后面带个“折”字,根本没法算实际价格;推荐值明明是百分比,却存成了“100%”这样的字符串,排序都排不了。

这不就是湖南省职业院校技能大赛那道Python数据清洗题的“现实翻版”吗?大赛题目里,要求你处理三本特定书的作者空值,删除缺失严重的列,处理0%推荐值,转换各种数据类型。这些操作,看似琐碎,实则每一环都紧扣业务逻辑。比如,为什么要把那三本书的作者填上?因为它们是知名系列或官方出版物,缺失作者信息会影响后续的“按作者分析销量”这个业务需求。为什么删除“电子书价格”列?因为缺失率太高,强行填充会引入巨大噪声,误导分析结论,在业务上,这相当于承认“这个数据维度目前不可用”,果断舍弃是更专业的选择。

所以,我们今天不单纯讲题目的答案代码怎么写。我要带你走的,是一条 “真题任务 -> 业务逻辑 -> Pandas代码实现 -> 避坑指南” 的完整路径。你会发现,用Pandas做数据清洗,就像用瑞士军刀处理野外生存问题——工具就那些,但知道在什么情况下用什么工具,以及怎么用最顺手,才是老手和新手的区别。接下来,我们就化身那位刚接手混乱数据的数据分析师,一步步把这份“脏数据”盘成光亮可用的“干净数据”。

2. 实战第一步:读取数据与“望闻问切”

拿到数据别急着写代码,先“望闻问切”做个全面体检。这一步在业务里叫数据探索性分析(EDA),目的不是清洗,而是搞清楚“脏”在哪,有多“脏”。

import pandas as pd
import numpy as np

# 读取数据,建议先不进行任何清洗操作
df = pd.read_csv('当当网畅销图书榜单数据.csv')

# 1. 先看个大概:形状、头尾几行
print(f"数据集形状:{df.shape}")  # 比如 (2000, 10),表示2000行,10列
print("\n--- 前5行数据预览 ---")
print(df.head())
print("\n--- 后5行数据预览 ---")
print(df.tail())

# 2. 查看数据基本信息:列名、数据类型、非空计数
print("\n--- 数据基本信息 ---")
print(df.info())

# 3. 统计每列的缺失值情况
print("\n--- 各列缺失值统计 ---")
missing_stats = df.isnull().sum()
print(missing_stats[missing_stats > 0])  # 只显示有缺失的列

# 4. 查看数值型数据的统计描述(能立刻发现异常值苗头)
print("\n--- 数值列统计描述 ---")
print(df.describe())

# 5. 查看文本型数据的唯一值情况(比如作者、书名是否有奇怪字符)
print("\n--- ‘作者’列唯一值示例(前20个)---")
print(df['作者'].dropna().unique()[:20])

运行这段代码,你可能会发现大赛题目里没明说但真实存在的情况:比如“电子书价格”列可能超过90%都是空值;“评论数”里混有“暂无评论”这样的字符串;“出版日期”列可能有多种分隔符格式。这个诊断过程至关重要,它决定了你后续清洗策略是“大手术”还是“微调”。例如,看到“电子书价格”缺失率极高,你就能理解题目中“直接删除这一特征列”的决策依据了——这在业务上叫“评估数据可用性”,不值得花费成本去修复。

3. 核心清洗操作一:精准处理缺失值与业务逻辑填充

缺失值处理是清洗的重头戏,但绝不是简单地fillna(0)dropna()。必须结合业务背景。

3.1 基于已知信息的精准填充

大赛题目给出了三个非常具体的填充规则,这对应着业务中“根据其他信息推断或查找权威资料进行补全”的场景。

# 方法一:使用 loc 进行精准定位和赋值(大赛答案写法,清晰直接)
df.loc[df['书名'] == '一级建造师2020教材2020版一级建造师建筑工程管理与实务', '作者'] = '全国一级建造师执业资格考试用书编写委员会'
df.loc[df['书名'] == '一级建造师2020教材2020版一级建造师建筑工程管理与实务', '出版日期'] = '2020-05-01'

# 方法二:使用 mask 或 where 条件替换(另一种思路)
# df['作者'] = df['作者'].mask(df['书名'] == '中国共产党简史(32开)2021党史学习教育系列读物领导干部学习指', '中国共产党简史编写组')

# 对于多条类似规则,可以构建一个映射字典,更利于维护
book_author_map = {
    '一级建造师2020教材2020版一级建造师建筑工程管理与实务': '全国一级建造师执业资格考试用书编写委员会',
    '中国共产党简史(32开)2021党史学习教育系列读物领导干部学习指': '中国共产党简史编写组',
    '写给青少年的古文观止全套5册正版小古文小学初中高中注音详解注释': '伊泽'
}
for book, author in book_author_map.items():
    df.loc[df['书名'] == book, '作者'] = author

print(df[df['书名'].isin(book_author_map.keys())][['书名', '作者', '出版日期']].head())

业务思考:为什么手动填?因为这类信息具有确定性。一级建造师教材、党史读物的编写单位是公开、权威的信息。在真实业务中,对于核心字段(如SKU编码、品牌名)的缺失,我们经常需要从产品库、官网甚至手动搜索来补全,确保分析的准确性。

3.2 高缺失率特征的决策:果断删除

对于“电子书价格”这种缺失率可能超过80%的列,填充(无论是用均值、中位数还是众数)都会严重扭曲数据分布,产生大量没有意义的“人造数据”。

# 在删除前,最好计算一下缺失比例,形成数据报告
missing_ratio = df['电子书价格'].isnull().sum() / len(df)
print(f“‘电子书价格’列缺失比例:{missing_ratio:.2%}”)

if missing_ratio > 0.7:  # 设定一个阈值,比如70%
    df.drop(columns=['电子书价格'], inplace=True)
    print("已删除‘电子书价格’列。")
else:
    # 如果缺失不多,可以考虑填充策略,比如用纸质书价格折算等(需要业务知识)
    pass

这个操作在业务汇报时可以这样解释:“由于电子书价格字段数据完备性极低,我们判断其当前不具备分析价值,为避免引入偏差,已暂从分析模型中移除该维度。” 这体现了数据驱动的决策过程。

3.3 数值型缺失的合理填充:均值与业务规则

对于“评论数”的缺失,题目要求用平均值填充。这是连续数值变量常见的处理方式。

# 首先,确保‘评论数’是数值类型(如果不是,需要先转换,我们下一步会做)
# 用均值填充前,最好先观察分布,避免极端值影响
mean_comments = df['评论数'].mean()
print(f“评论数平均值为:{mean_comments:.2f}”)
df['评论数'] = df['评论数'].fillna(mean_comments)

而对于“推荐值”为0%的特殊情况,题目要求替换为100%。这背后可能对应业务规则:系统默认值或数据抓取错误。在真实场景中,可能是某些新上架商品尚未积累推荐数据,前端显示为0,但实际不应参与低推荐排序。这种基于业务知识的规则替换,比单纯的统计填充更有意义。

df['推荐值'] = df['推荐值'].replace('0%', '100%')

4. 核心清洗操作二:数据类型转换与字符串规整

原始数据中,数字和字符经常“傻傻分不清”,导致无法计算、排序。这一步是让数据“各归其位”。

4.1 剥离单位与符号,提取纯数值

“折扣比例”列带“折”字,“推荐值”带“%”号,这些都是分析中的“拦路虎”。

# 处理折扣比例:去掉‘折’字,转浮点数,并理解业务含义(8.5折 = 0.85)
df['折扣比例'] = df['折扣比例'].str.replace('折', '').astype(float)
# 如果需要实际折扣乘数,可以除以10(根据业务理解)
# df['折扣乘数'] = df['折扣比例'] / 10.0

# 处理推荐值:去掉‘%’号,转浮点数
df['推荐值'] = df['推荐值'].str.replace('%', '').astype(float)

print(df[['折扣比例', '推荐值']].dtypes)
print(df[['折扣比例', '推荐值']].head())

踩坑提醒str.replace方法要求该列原本是字符串类型(object)。如果数据类型已经是float,再调用.str会报错。所以务必在df.info()阶段就确认好类型。

4.2 日期格式的统一化处理

日期格式混乱是数据源的常态。“2020-05-01”、“2020/05/01”、“20200501”、“2020年5月1日”可能同时存在。Pandas的pd.to_datetime方法非常强大,能自动解析多种常见格式。

# 方法一:让pandas自动推断(推荐首选,成功率很高)
df['出版日期_parsed'] = pd.to_datetime(df['出版日期'], errors='coerce')
# `errors='coerce'`会将解析失败的变成NaT(Not a Time),而不是报错

# 检查有多少行解析失败
failed_dates = df['出版日期_parsed'].isna().sum()
print(f“日期解析失败行数:{failed_dates}”)
if failed_dates > 0:
    print(“解析失败的原始日期样例:”)
    print(df[df['出版日期_parsed'].isna()]['出版日期'].unique()[:5])

# 方法二:如果自动解析失败较多,可能需要先做字符串预处理
# 例如,将‘2020年5月1日’统一替换为‘2020-05-01’
# df['出版日期'] = df['出版日期'].str.replace('年', '-').str.replace('月', '-').str.replace('日', '')
# 然后再用pd.to_datetime解析

# 转换为题目要求的格式‘2019年11月01日’
df['出版日期_格式化'] = df['出版日期_parsed'].dt.strftime('%Y年%m月%d日')
print(df[['出版日期', '出版日期_parsed', '出版日期_格式化']].head())

4.3 文本数字的提取与转换

“排行榜类型”中的“2023年”需要去掉“年”字并转成整数。这里展示了Pandas字符串序列的向量化操作,比用apply循环快得多。

# 高效向量化操作:先替换字符,再转换类型
df['排行榜类型'] = df['排行榜类型'].str.replace('年', '').astype(int)

# 评论数转换为整数(如果已经是数值,astype(int)即可;如果混有文本,需先处理)
# 假设评论数中可能有‘1.2万’这样的文本,需要更复杂的清洗,本题假设已是纯数字或NaN
df['评论数'] = pd.to_numeric(df['评论数'], errors='coerce').astype('Int64')  # 使用可空整数类型
print(df[['排行榜类型', '评论数']].dtypes)

5. 数据整理与业务洞察:排序、重置索引与简单聚合

清洗干净的最终目的是为了分析。最后几步整理工作,能让数据框更规整,并直接产生初步的业务洞察。

5.1 按关键指标排序

排序是分析前的基本操作。例如,按“推荐值”降序排列,可以立刻看到最受推荐的书。

df.sort_values(by='推荐值', ascending=False, inplace=True)
print(“按推荐值降序排列后的前10本书:”)
print(df[['书名', '作者', '推荐值']].head(10))

5.2 重置索引

排序或删除行后,索引会变得混乱,重置索引能让数据框看起来更整洁,也方便后续按位置索引。

df.reset_index(drop=True, inplace=True)  # drop=True表示丢弃旧的索引列
print(df.index)  # 现在索引是整齐的0,1,2,3...

5.3 回答业务问题:简单聚合分析

题目最后两个问题,其实就是最简单的业务分析:“哪本书出版次数最多?”和“一共有多少位作者?”。这用Pandas的聚合函数可以轻松搞定。

# 找出出版次数最多的书及其次数
book_count = df['书名'].value_counts()
most_frequent_book = book_count.idxmax()
most_frequent_count = book_count.max()
print(f“出版次数最多的书是:‘{most_frequent_book}’,出版了 {most_frequent_count} 次。”)

# 统计不重复的作者数量
unique_authors_count = df['作者'].nunique()
print(f“数据集中一共有 {unique_authors_count} 位不重复的作者。”)

# 更进一步:可以看哪位作者的作品上榜最多
author_book_count = df['作者'].value_counts().head(5)
print(“\n作品上榜数量最多的前5位作者:”)
print(author_book_count)

6. 超越真题:真实业务场景中的进阶清洗技巧

大赛题目覆盖了核心操作,但真实业务的数据“脏”法更加五花八门。这里分享几个我常遇到的进阶问题及处理技巧。

6.1 处理重复数据:不仅仅是去重

数据集里可能有完全重复的行,也可能有关键字段重复(比如同一本书因信息源不同录入两次)。

# 1. 检查并删除完全重复的行
initial_rows = len(df)
df.drop_duplicates(inplace=True)
dropped_rows = initial_rows - len(df)
print(f“删除了 {dropped_rows} 行完全重复的数据。”)

# 2. 基于关键字段(如‘书名’和‘作者’)检查重复
duplicate_books = df.duplicated(subset=['书名', '作者'], keep=False)
if duplicate_books.any():
    print(“发现基于书名和作者的重复记录:”)
    print(df[duplicate_books].sort_values(by=['书名', '作者']).head())
    # 业务决策:可能需要根据‘出版日期’或‘价格’保留最新或最便宜的一条
    # df = df.sort_values(by='出版日期', ascending=False).drop_duplicates(subset=['书名', '作者'], keep='first')

6.2 处理异常值:不只是删除

“评论数”里出现一个999999,“价格”出现0或负数,这些异常值会严重扭曲均值等统计量。

# 以‘评论数’为例,使用分位数或标准差法检测异常值
Q1 = df['评论数'].quantile(0.25)
Q3 = df['评论数'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['评论数'] < lower_bound) | (df['评论数'] > upper_bound)]
print(f“基于IQR方法,检测到 {len(outliers)} 条评论数异常记录。”)

# 处理方式1:盖帽法(Capping),将异常值拉回到边界
df['评论数_capped'] = df['评论数'].clip(lower=lower_bound, upper=upper_bound)
# 处理方式2:视为缺失值,然后用中位数填充
# df.loc[outliers.index, '评论数'] = np.nan
# df['评论数'].fillna(df['评论数'].median(), inplace=True)

6.3 文本数据的高级清洗:正则表达式上场

书名或作者名里可能混入多余空格、乱码、无关符号。

import re

# 示例:清理书名中的多余空格和特殊符号
df['书名_清洗'] = df['书名'].str.strip()  # 去除首尾空格
df['书名_清洗'] = df['书名_清洗'].str.replace(r'\s+', ' ', regex=True)  # 将多个空格替换为一个

# 示例:提取括号内的内容(比如版本信息)
df['版本信息'] = df['书名'].str.extract(r'((.*?))')  # 提取中文括号内容
# df['版本信息'] = df['书名'].str.extract(r'\((.*?)\)')  # 提取英文括号内容

print(df[['书名', '书名_清洗', '版本信息']].head())

7. 构建可复用的数据清洗管道

在真实项目中,清洗步骤往往不是一次性的。新数据每月、每周甚至每天都会来。因此,将清洗步骤封装成函数或管道(Pipeline)至关重要。

def clean_dangdang_book_data(raw_df):
    """清洗当当网图书数据的函数"""
    df = raw_df.copy()

    # 1. 处理特定缺失值
    book_author_map = {...}  # 映射字典
    for book, author in book_author_map.items():
        df.loc[df['书名'] == book, '作者'] = author

    # 2. 删除高缺失列
    if df['电子书价格'].isnull().mean() > 0.7:
        df.drop(columns=['电子书价格'], inplace=True)

    # 3. 填充与替换
    df['推荐值'] = df['推荐值'].replace('0%', '100%')
    df['评论数'] = df['评论数'].fillna(df['评论数'].mean())

    # 4. 字符串处理与类型转换
    df['折扣比例'] = df['折扣比例'].str.replace('折', '').astype(float)
    df['推荐值'] = df['推荐值'].str.replace('%', '').astype(float)
    df['排行榜类型'] = df['排行榜类型'].str.replace('年', '').astype(int)
    df['出版日期'] = pd.to_datetime(df['出版日期'], errors='coerce')

    # 5. 排序与重置索引
    df.sort_values(by='推荐值', ascending=False, inplace=True)
    df.reset_index(drop=True, inplace=True)

    return df

# 使用管道
from sklearn.pipeline import Pipeline
# 可以定义多个转换器,串联起来(这里仅示意概念)
# 实际Pandas清洗更适合用自定义函数或Dask

# 每月运行一次
new_data = pd.read_csv('当月新数据.csv')
cleaned_data = clean_dangdang_book_data(new_data)
cleaned_data.to_csv('已清洗_当月数据.csv', index=False)
print(“月度数据清洗完成并已保存。”)

写完这个函数,下次再拿到新的榜单数据,你只需要两行代码就能得到干净的结果。这才是数据分析师效率的体现。数据清洗从来不是炫技,而是为后续的分析模型打下一个坚实、可靠的基础。这份从技能大赛真题出发,贯穿真实业务逻辑的Pandas清洗全流程,希望能帮你下次面对混乱数据时,心里有底,手上有招。记住,干净的数据本身,就是一份宝贵的资产。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐