项目背景
在数据分析工作中,数据准备环节通常是最耗时的阶段,包括加载、清理、转换和异常处理等任务,通常占用数据分析工作者50%到80%的时间。以下是对这一过程的详细解释:
数据异常问题:
数据采集异常数据传输异常数据录入异常在数据集中数据可能存在明显错误,这些错误数据被称为“脏”数据。
异常数据的产生原因多种多样,主要包括:
这些异常数据会对后续的数据分析、模型搭建和参数调优产生负面影响。
数据清洗的目的:
数据清洗的核心目标是将原始数据集转换为高质量的数据集。
清洗后的数据支持后续的统计分析和机器学习模型的建立,确保分析结果的准确性和可靠性。
数据可视化的重要性:
数据可视化利用图形技术有效地解读和传达分析结果信息。
通过可视化,从不同维度深入观察和分析数据,有助于揭示数据中的趋势和模式,为决策提供支持。
通过系统化的清洗和可视化,数据分析工作可以变得更加高效和可靠,为统计分析和机器学习提供坚实的基础。
4.2 技能图谱
4.3 数据清洗
数据分析与数据质量
优良的数据分析结果依赖于其输入数据集的高质量。未经处理的实际数据集通常存在各种问题,这些问题会影响数据分析的准确性和可靠性。
常见数据问题
数据缺失: 数据集中缺少必要的信息,例如某些列或行中的值为空。
数据格式不统一: 数据的格式不一致,例如日期格式不同、文本大小写不统一等。
数据单位不统一: 数据使用不同的单位表示,例如温度可能同时使用摄氏度和华氏度。
数据错误: 数据中存在明显的错误或不合理的值,例如负的年龄或超出合理范围的温度。
这些问题会导致数据集被称为“脏”数据。清洗这些“脏”数据是数据预处理中的一个重要步骤。
数据清洗功能
数据清洗是提高数据质量的重要步骤,主要包括以下功能:
缺失值处理: 填充或删除缺失的数据,使数据集更完整。
异常值处理: 识别并修正数据中的异常值或错误值。
数据转换:
下面是一个关于数据离散化的简化例子。设想我们有一组人的年龄数据如下:
年龄:[22, 45, 19, 34, 26, 59, 38, 15, 67, 43]我们需要将这些年龄数据分类为三个类别:“青年”、“中年”和“老年”。采用等宽离散化方法,我们将年龄区间(15岁到70岁)划分为三个等宽区间:
应用这个方法,年龄数据的分类结果是:
这样,连续的年龄数据就被简单地转换成了三个明确的年龄段类别。
为什么要“离散化”
原因 说明 1. 简化模型 连续数值太多不好分析,分成几类更容易看规律。 2. 便于统计 比如计算“各年龄段人数”、“各分数段占比”。 3. 便于可视化 离散的类别可以直接做柱状图、饼图等。 4. 提升算法效果 有些算法(如决策树)更适合处理离散特征。 [青年, 中年, 青年, 中年, 青年, 老年, 中年, 青年, 老年, 中年]
“青年”:15-33岁
“中年”:34-52岁
“老年”:53-70岁
“男” → 1
“女” → 0
数据替换: 将数据中的特定值替换为其他值。
假设我们有一列性别数据:
["男", "女", "男", "女", "女"]为了方便分析或建模,我们想把文字改成数字(机器更容易理解):
替换后得到:
[1, 0, 1, 0, 0]其他常见替换场景
场景 原始数据 替换规则 结果 缺失值替换 [89, 76, None, 91] None → 0 [89, 76, 0, 91] 异常值替换 [12, 999, 35, 40] 999 → 平均值(29) [12, 29, 35, 40] 标准化文本 ["北京 ", "北京市", "Beijing"] 都替换为 “北京” ["北京", "北京", "北京"] 数据离散化: 把一串连续的数字数据,分成几个“区间”,并给每个区间贴上一个标签,让数字变成分类。
4.3.1 处理缺失值
一、缺失值处理概述
在实际数据集中,常常会遇到某些字段的数据缺失。Pandas 提供了专门的方式来识别、处理和填补这些缺失值,从而保证后续分析的准确性。
二、NaN 的表示与原因
表示方式: 在 Pandas 中,缺失值使用浮点类型的
NaN(Not a Number)表示。这是因为NaN专门用于表示未定义或不可用的数值。类型转换: 当一个列或数组中存在缺失值时,Pandas 会自动将该列的数据类型转换为浮点数,以便能够表示
NaN。即使数据原本是整数类型,包含NaN后也会转为浮点类型。
三、缺失值检测
| 方法 | 作用 |
|---|---|
df.isnull() | 检查每个位置是否为缺失值,返回布尔 DataFrame |
df.isna() 【常用】 | 同上,两者功能完全一致 |
df.notnull() / df.notna() | 返回非缺失值位置的布尔值 |
四、缺失值处理方法
删除缺失值 dropna():
df.dropna():默认删除含缺失值的行;df.dropna(axis=1):删除含缺失值的列;可通过
thresh参数保留至少有 n 个非空值的行/列。
填充缺失值 fillna():
用固定值:
df.fillna(0);用列均值:
df["age"].fillna(df["age"].mean());前向/后向填充:
df.fillna(method="ffill")/df.fillna(method="bfill")。
判断缺失值数量:
df.isnull().sum():每列缺失值数量统计;df.isnull().any():判断每列是否含有缺失值。
五、常见处理策略建议
| 情况 | 建议 |
|---|---|
| 少量缺失 | 可考虑删除行或列 |
| 缺失值较集中 | 使用均值、中位数、众数等填充 |
| 时间序列数据 | 使用前向或后向填充(ffill/bfill) |
| 特殊业务逻辑 | 可设定默认值如 0、"未知" 等 |
实验任务:处理用户信息表中的缺失值(users_dirty.csv)
users_dirty.csv 是某电商平台的用户信息表,包含部分缺失字段。为了后续做用户画像、行为建模等工作,需先进行数据清洗,处理缺失值。
数据说明:
缺失值(字段 age/city/register_date):随机抽样产生空值,模拟用户未填写或系统漏录。
异常值(age 超出合理范围):注入 -10、130、180 等不合理年龄,模拟录入或采集错误。
未知值(gender = "未知"):固定 10 行性别标记为“未知”,模拟模糊性数据。
格式异常(register_date 非标准格式,未开启):注入中文日期、顺序颠倒或非法字符串,模拟不同系统或人工输入错误。
重复记录(约 0.3%):复制部分行形成重复,模拟用户重复注册或系统重复采集。
乱序数据:随机打乱数据行顺序,模拟实际业务中的无序输入。
原始数据读取
import pandas as pd
# 读取用户信息表
users = pd.read_csv("users_dirty.csv")
# 显示前5行查看结构
users.head()【例4-1】检测哪些列存在缺失值
users.isnull().any()
# 等同于 users.isna().any()【例4-2】统计每列缺失值数量
users.isnull().sum()
# 等同于 users.isna().sum()【例4-3】输出含缺失值的前10行记录
# users.isnull().any(axis=1) 用来判断每一行是否存在缺失值,返回一个布尔序列(True 表示该行有缺失值)。
# axis=0 表示竖向操作(从上往下) → 针对“行”进行处理;
# axis=1 表示横向操作(从左到右) → 针对“列”进行处理。
users[users.isnull().any(axis=1)].head(10)【例4-4】将缺失的年龄(age)填充为所有非缺失年龄的平均值
# 1.查看年龄列有缺失的行
users[users["age"].isnull()]
# 2.替换(在原表上进行修改)
users["age"].fillna(users["age"].mean(), inplace=True)【例4-5】将 city 列缺失值填为 “默认城市”
# 1.查看有缺失的行
users[users["city"].isnull()]
# 2.替换
users["city"].fillna("默认城市", inplace=True)【例4-6】删除注册时间(register_date)为空的用户
# 1.统计有多少个缺失值
users["register_date"].isnull().sum()
# 2.删除:dropna(subset=[...])
users = users.dropna(subset=["register_date"])【例4-7】删除所有含缺失值的用户记录
# 1.重新读取用户信息表
users = pd.read_csv("users_dirty.csv")
# 2.统计每列缺失值数量
users.isnull().sum()
# 3.删除所有包含缺失值的行,返回一个新的“干净”表格
users_cleaned = users.dropna()【例4-8】处理重复记录
# 1.查看是否有重复行(完全相同)
users.duplicated().sum()
# 2.查看重复行的具体内容
users[users.duplicated(keep=False)].sort_values(by="user_id")
# duplicated(keep=False):表示保留所有重复的行(不只显示后面的)。
# 如果不加 keep=False,默认只标记重复项的后面那一条。
# 3.去重
# 方法一:删除重复行,保留第一次出现的。(最常用)
users_clean = users.drop_duplicates(keep="first")
# 方法二:删除重复行,保留最后一次出现的。
users_clean = users.drop_duplicates(keep="last")
# 方法三:去重删除所有重复的(不保留任何一条)
users_clean = users.drop_duplicates(keep=False)【例4-9】按用户ID排序,恢复顺序
# 把用户表按照 user_id 排序,并重建连续的行号索引,得到一个有序的新表 users_sorted。
# reset_index(drop=True) 的作用是——重建连续行号,丢弃旧索引,让表格更整洁。
users_sorted = users.sort_values(by="user_id").reset_index(drop=True)4.3.2 处理异常值
处理被识别的异常值时,主要有两种方法:删除法和填充法。
删除法:当异常值的数量较少或其删除对数据分布的影响较小时,删除异常值是一个较好的选择。
填充法:如果异常值数量较多,或主要由读数错误、输入错误导致,采用填充法更为合适。填充方法包括:
后向填充:使用前一个有效值填充缺失值,
前向填充:使用后一个有效值填充缺失值,
平均值填充
中位数填充
最大值填充
最小值填充
零值填充
随机选择填充
根据具体情况选择合适的方法,以确保数据处理的准确性和完整性。
以下是结合 电商数据集(如 users_dirty.csv 中的用户年龄)设计的异常值处理练习题
【例4-10】使用箱型图方法,识别users表中的 age 字段中的异常值。
箱型图(boxplot),专门用来看一组数据的分布:中间集中在哪儿、上下边界大概在哪儿、有没有异常值。
import matplotlib.pyplot as plt
# 正确显示中文与负号
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
plt.rcParams['axes.unicode_minus'] = False # 显示负号
# 绘制年龄的箱型图
plt.boxplot(users["age"].dropna())
plt.title("年龄字段的箱型图")
plt.ylabel("Age")
plt.show()
结合结果
在箱型图中:
| 名称 | 位置 | 含义 |
|---|---|---|
| 下边缘(Q1) | 箱子的底边 | 表示 25% 的人年龄低于这个值 |
| 中间线(Q2)[中位线] | 箱子中间的黄线 | 表示 50% 的人年龄低于这个值(中位数) |
| 上边缘(Q3) | 箱子的顶边 | 表示 75% 的人年龄低于这个值 |
| 上下“胡须”线 | 延伸线 | 表示正常范围的最大/最小值(非异常点) |
| 上方几个黑点、空心点 | 上下 | 异常值,说明确实有极端年龄被识别出来。 |
图里箱子的主体在 30~45 岁之间(说明大多数用户年龄在这个区间)。
中位数大约在 35 岁左右。
上方几个黑点、空心点 = 异常值,说明确实有极端年龄被识别出来。
【例4-11】读取用户表 users_dirty.csv,查看 age 字段中是否存在明显异常值(如小于 0 或大于 120)。
import pandas as pd
users = pd.read_csv("users_dirty.csv")
# 查看年龄分布的基本信息
print(users["age"].describe())
# 筛选出异常年龄数据
users[(users["age"] < 0) | (users["age"] > 120)]
# 等同于 users.query("age < 0 or age > 120")【例4-12】删除所有年龄不合理(<0 或 >120)的用户数据。
# 删除异常年龄数据,users_cleaned = users.query("0 <= age <= 120")
users_cleaned = users[(users["age"] >= 0) & (users["age"] <= 120)]
users_cleaned.shape【例4-13】用中位数填充所有不合理年龄(<0 或 >120)为正常范围的值。
中位数是描述数据集中间位置的统计量 ,把一组数据按大小排序后,处于中间位置的数值即为中位数。
# (1)先找出“正常年龄”的数据,并计算中位数 /miːdiən/
# 条件:年龄在 0~120 岁之间
valid_median_age = users.query("0 <= age <= 120")["age"].median()
# (2)用中位数去替换掉异常的年龄
# 如果年龄小于0 或 大于120,就用中位数代替,否则保留原值
users["age"] = users["age"].apply(
lambda x: valid_median_age if (x < 0 or x > 120) else x
)
# apply() 会让每个年龄都交给后面的函数(lambda x: ...)去处理。
# valid_median_age if (x < 0 or x > 120) else x
# 若不合理:替换为中位数(valid_median_age)
# 若合理:保留原来的年龄 x【例4-14】处理性别的异常值
# 查看性别列的分组及数量
users.groupby("gender").size()
# 或者:df_dirty["gender"].value_counts()
# 方法一:替换
# 把性别列中的“未知”替换为“男”
users["gender"] = users["gender"].replace("未知", "男")
# 方法二:删除
# 删除掉 gender 等于 "未知" 的行。
users = users[usres["gender"] != "未知"]
# 或者
users = users[users["gender"].isin(["男", "女"])]4.4 数据可视化
一、Matplotlib 简介
Matplotlib 是 Python 最常用的数据可视化库,能轻松绘制折线图、柱状图、饼图、散点图等。 它常与 Pandas 搭配使用,用于把数据分析结果“画出来”,让信息更直观。
二、基本使用步骤
| 步骤 | 操作 | 示例 |
|---|---|---|
| ① 导入库 | import matplotlib.pyplot as plt | plt 是绘图的核心对象 |
| ② 准备数据 | 用列表、数组或 Pandas 序列 | x = [1,2,3]; y = [5,8,6] |
| ③ 绘制图形 | 调用绘图函数 | plt.plot(x, y) |
| ④ 设置标题与坐标轴 | plt.title("标题")、plt.xlabel("X轴")、plt.ylabel("Y轴") | 增强可读性 |
| ⑤ 显示图形 | plt.show() | 必须写,否则图形不显示 |
三、常用图形类型
| 图形类型 | 函数 | 适用场景 | 关键参数 |
|---|---|---|---|
| 折线图 | plt.plot(x, y) | 变化趋势 | color, marker, linestyle |
| 柱状图 | plt.bar(x, y) | 类别对比(垂直) | color, width |
| 水平条形图 | plt.barh(x, y) | 类别较多时展示 | color |
| 饼图 | plt.pie(values, labels=labels, autopct='%1.1f%%') | 占比分析 | startangle, colors |
| 散点图 | plt.scatter(x, y) | 两变量关系 | s(点大小), c(颜色) |
四、图表美化常用语法
| 功能 | 语法 | 示例说明 |
|---|---|---|
| 中文显示........ | plt.rcParams['font.sans-serif']=['SimHei']``plt.rcParams['axes.unicode_minus']=False | 显示中文与负号 |
| 图形尺寸 | plt.figure(figsize=(8,6)) | 控制宽高 |
| 标题 | plt.title("销售趋势", fontsize=16) | 设置字体大小 |
| 坐标轴标签 | plt.xlabel("月份"); plt.ylabel("销量") | |
| 网格线 | plt.grid(axis='y', linestyle='--', alpha=0.6) | 更易读 |
| 添加数值 | plt.text(x, y, str(value)) | 条形图上标数字 |
| 自动布局 | plt.tight_layout() | 防止遮挡 |
4.4.1 绘制条形图

4.4.2 绘制柱状图


