程序员的数学(十三)数据处理与分析中的数学思维:从清洗到可视化的全流程应用

举报
倔强的石头_ 发表于 2025/12/21 09:25:37 2025/12/21
【摘要】 本文介绍了如何运用数学思维解决数据处理与分析中的核心问题。通过数据清洗、特征提取、统计分析、可视化四个场景,展示了逻辑判断、余数分组、概率统计和线性代数等数学工具的实际应用。在数据清洗环节,利用逻辑判断识别异常值,余数分组规整时间序列;特征提取阶段采用线性代数进行向量编码,排列组合生成复合特征。文章强调数据处理不是简单调用API,而是通过数学规律挖掘数据价值,帮助程序员从数据搬运工转变为数据分析师

在这里插入图片描述

文章目录


欢迎回到 “程序员的数学” 系列第十三篇。在前十二篇内容中,我们从 0 的基础逻辑讲到算法优化、数据结构设计,构建了一套 “数学 + 编程” 的实战体系。今天,我们聚焦程序员高频需求之一 ——数据处理与分析,通过 “数据清洗、特征提取、统计分析、可视化” 四个核心场景,展示如何用前面学过的数学知识(逻辑判断、余数分组、概率统计、线性代数)解决实际数据问题,让 “杂乱数据” 变成 “有价值的 insights”。


数据处理不是 “机械地调用 API”,而是 “用数学规律挖掘数据背后的逻辑”:比如用逻辑判断识别异常值,用余数分组处理时间序列,用概率统计分析数据分布,用线性代数将文本转为可计算的向量。掌握这些思维,能让你从 “数据搬运工” 变成 “数据分析师”。

一、为什么数据处理需要数学思维?

先看一个真实场景:某电商平台收集了 10 万条用户购物数据,包含 “用户 ID、购买时间、商品类别、支付金额” 等字段,但数据中存在 “缺失的支付金额”“异常的购买时间(如 2099 年)”“重复的订单记录”—— 如果直接用这些数据分析 “用户偏好”,结果会严重失真。

此时,数学思维就是 “数据的过滤器和转换器”:

  • 逻辑判断:识别 “购买时间> 当前时间” 的异常值,“支付金额为负数” 的错误值;
  • 余数分组:将 “购买时间” 按 “星期几” 分组(用时间戳 mod 7),分析用户周末购物习惯;
  • 概率统计:计算 “支付金额” 的均值和方差,用 3σ 原则判断异常值(如支付金额 > 均值 + 3σ);
  • 线性代数:将 “商品类别” 转为 one-hot 向量,用于后续的偏好分析。

没有数学思维,数据处理会变成 “碰运气”;而用对数学工具,能让数据处理既 “准确” 又 “高效”。

二、场景 1:数据清洗 —— 逻辑与余数的 “数据过滤术”

数据清洗是数据处理的第一步,核心是 “去除脏数据,保留有效数据”,用到逻辑判断(识别脏数据)和余数分组(规整结构化数据)。

1. 数学原理:逻辑判断与余数分组的应用

  • 逻辑判断:用 “与 / 或 / 非” 规则识别脏数据,比如 “支付金额≤0 → 异常值”“购买时间为空且支付金额不为空 → 缺失值”;
  • 余数分组:对时间、ID 等结构化数据分组,比如 “时间戳 mod 86400 → 按天分组”“用户 ID mod 100 → 分 100 组抽样验证”。

2. 实战:清洗电商用户购物数据

假设我们有一份电商购物数据(shopping_data.csv),包含 “user_id(用户 ID)、buy_time(购买时间,时间戳)、category(商品类别)、amount(支付金额)” 字段,存在以下问题:

  1. 缺失值:部分buy_timeamount为空;
  2. 异常值:amount≤0buy_time超出 “2023-01-01 至 2024-01-01” 范围;
  3. 重复值:同一user_id在同一buy_time的重复订单。

我们用 Python 的pandas处理,结合逻辑判断和余数分组:

python

import pandas as pd
import numpy as np
from datetime import datetime

# 1. 读取数据
df = pd.read_csv("shopping_data.csv")
print(f"原始数据形状:{df.shape}")  # 输出:(10000, 4)
print("原始数据前5行:")
print(df.head())

# 2. 处理缺失值(逻辑判断:识别空值)
# 规则:buy_time或amount为空的行删除(逻辑判断)
df_clean = df.dropna(subset=["buy_time", "amount"])
print(f"\n删除缺失值后形状:{df_clean.shape}")  # 示例:(9800, 4)

# 3. 处理异常值(逻辑判断+时间转换)
# 3.1 转换buy_time为datetime(便于判断范围)
df_clean["buy_time"] = pd.to_datetime(df_clean["buy_time"], unit="s")  # 时间戳转datetime
# 3.2 定义时间范围:2023-01-01 至 2024-01-01(逻辑判断)
start_time = datetime(2023, 1, 1)
end_time = datetime(2024, 1, 1)
# 规则1:buy_time在范围内;规则2:amount>0(逻辑与)
df_clean = df_clean[
    (df_clean["buy_time"] >= start_time) & 
    (df_clean["buy_time"] <= end_time) & 
    (df_clean["amount"] > 0)
]
print(f"删除异常值后形状:{df_clean.shape}")  # 示例:(9500, 4)

# 4. 处理重复值(逻辑判断:同一用户同一时间的订单去重)
# 按user_id和buy_time去重,保留第一行
df_clean = df_clean.drop_duplicates(subset=["user_id", "buy_time"], keep="first")
print(f"删除重复值后形状:{df_clean.shape}")  # 示例:(9400, 4)

# 5. 余数分组:按星期几分组(分析周末购物习惯)
# 用dt.dayofweek获取星期(0=周一,6=周日),mod 7确保范围0-6
df_clean["weekday"] = df_clean["buy_time"].dt.dayofweek % 7
# 新增周末标记(逻辑判断:6=周日,5=周六)
df_clean["is_weekend"] = df_clean["weekday"].apply(lambda x: 1 if x in [5,6] else 0)

print("\n清洗后数据前5行:")
print(df_clean[["user_id", "buy_time", "category", "amount", "weekday", "is_weekend"]].head())

3. 关联知识点

  • 逻辑判断:用&(与)、>=(大于等于)识别缺失值、异常值、重复值;
  • 余数分组:weekday % 7确保星期几在 0-6 范围,buy_time mod 86400可按天分组;
  • 数据类型转换:时间戳转datetime是 “抽象化”(0 的简化作用)的体现,将数字转为人类可理解的时间格式。

三、场景 2:特征提取 —— 线性代数与排列组合的 “数据编码术”

特征提取是将 “原始数据” 转为 “可计算的特征”,核心用到线性代数的向量表示和排列组合的特征组合,比如将文本、类别数据转为数值向量,将连续数据分桶组合。

1. 数学原理

  • 线性代数:将非数值数据(如商品类别、文本)转为向量,比如 “商品类别 = 衣服”→[1,0,0],“类别 = 食品”→[0,1,0](one-hot 编码,本质是线性代数的单位向量);
  • 排列组合:计算特征的组合数,比如 “价格区间(3 个)× 类别(2 个)” 有 3×2=6 种组合特征;
  • 余数分组:将连续数据分桶,比如 “amount mod 100 → 价格区间(0-99, 100-199, …)”。

2. 实战:提取商品特征用于推荐

基于清洗后的购物数据,提取商品的特征(价格区间、类别编码、周末销量占比),为后续推荐算法准备输入:

python

import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder

# 基于清洗后的df_clean数据
df = df_clean.copy()

# 1. 特征1:价格区间(余数分组)
# 规则:amount mod 200 分桶(0-199, 200-399, ..., 800+)
def get_price_range(amount):
    bucket = amount // 200  # 整除200,得到桶编号(0,1,2,...)
    return f"{bucket*200}-{(bucket+1)*200-1}" if bucket < 4 else "800+"

df["price_range"] = df["amount"].apply(get_price_range)
print("价格区间分布:")
print(df["price_range"].value_counts())

# 2. 特征2:商品类别编码(线性代数:one-hot向量)
# 初始化OneHotEncoder(处理类别数据)
encoder = OneHotEncoder(sparse_output=False, drop="first")  # drop避免多重共线性
# 对category编码,得到one-hot向量
category_encoded = encoder.fit_transform(df[["category"]])
# 转为DataFrame,列名为类别名称
category_df = pd.DataFrame(
    category_encoded,
    columns=[f"category_{cat}" for cat in encoder.categories_[0][1:]],  # 跳过第一个类别(drop=True)
    index=df.index  # 对齐索引
)
# 合并到原数据
df = pd.concat([df, category_df], axis=1)
print("\n类别编码后数据前5行(部分列):")
print(df[["category", "category_食品", "category_家电"]].head())

# 3. 特征3:周末销量占比(排列组合:计算每组的比例)
# 按category和price_range分组,计算周末销量占比
group_stats = df.groupby(["category", "price_range"]).agg(
    total_sales=("amount", "count"),  # 总销量
    weekend_sales=("is_weekend", "sum")  # 周末销量
).reset_index()
# 计算占比(概率统计:频率近似概率)
group_stats["weekend_ratio"] = group_stats["weekend_sales"] / group_stats["total_sales"]
print("\n各品类-价格区间的周末销量占比:")
print(group_stats[["category", "price_range", "total_sales", "weekend_ratio"]].head())

# 4. 最终特征集(线性代数:特征向量)
# 选择数值型特征,组成特征矩阵(n_samples × n_features)
feature_cols = ["amount", "is_weekend"] + list(category_df.columns)
feature_matrix = df[feature_cols].values  # 转为numpy数组(线性代数的矩阵表示)
print(f"\n特征矩阵形状:{feature_matrix.shape}")  # 示例:(9400, 5)(5个特征)
print("前2个样本的特征向量:")
print(feature_matrix[:2])

3. 关联知识点

  • 线性代数:feature_matrix是特征矩阵,每个样本是一行向量,每个特征是一列,符合线性代数的矩阵定义;
  • 排列组合:groupby(["category", "price_range"])是 “类别 × 价格区间” 的组合,总组合数 = 类别数 × 价格区间数;
  • 概率统计:weekend_ratio是周末销量的频率,近似周末购买的概率,用到 “频率与概率” 关系。

四、场景 3:统计分析 —— 概率统计与期望方差的 “数据解读术”

统计分析是从数据中提取规律,核心用到第 2 篇的概率统计知识(期望、方差、概率分布),比如计算数据的集中趋势、离散程度,判断数据是否符合特定分布(如正态分布)。

1. 数学原理

  • 期望与均值(第 2 篇):均值是 “样本的期望”,反映数据的集中趋势,如 “用户平均购买金额 = 总金额 / 订单数”;
  • 方差与标准差(第 2 篇):反映数据的离散程度,如 “购买金额的标准差大,说明用户消费差距大”;
  • 正态分布(第 2 篇):许多自然数据(如用户登录频率)符合正态分布,可用均值±2×标准差识别异常值。

2. 实战:分析用户购物行为的统计规律

基于清洗后的用户数据,分析用户的购物频率、消费金额的统计特征,判断数据分布:

python

import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt

# 基于清洗后的df_clean数据
df = df_clean.copy()

# 1. 计算用户级统计量(期望与均值,第2篇)
# 按user_id分组,计算每个用户的购物统计
user_stats = df.groupby("user_id").agg(
    order_count=("amount", "count"),  # 订单数(购物频率)
    total_amount=("amount", "sum"),  # 总消费金额
    avg_amount=("amount", "mean"),  # 平均消费金额(均值≈期望)
    std_amount=("amount", "std")    # 消费金额标准差(离散程度)
).reset_index()
# 填充std_amount的NaN(只有1个订单的用户,标准差为0)
user_stats["std_amount"] = user_stats["std_amount"].fillna(0)

print("用户购物行为统计(前5行):")
print(user_stats.head())
print(f"\n整体统计描述:")
print(user_stats[["order_count", "avg_amount", "std_amount"]].describe())

# 2. 分析购物频率的分布(概率分布,第2篇)
# 统计订单数的分布(多少用户有1个订单,多少有2个,...)
order_dist = user_stats["order_count"].value_counts().sort_index()
print(f"\n用户订单数分布:")
print(order_dist.head(10))  # 显示前10个订单数的用户数量

# 3. 检验平均消费金额是否符合正态分布(第2篇)
# 取平均消费金额(过滤极端值,避免影响检验)
avg_amount_clean = user_stats[user_stats["avg_amount"] < user_stats["avg_amount"].quantile(0.95)]["avg_amount"]
# Shapiro-Wilk正态性检验(H0:数据符合正态分布)
stat, p_value = stats.shapiro(avg_amount_clean)
print(f"\n正态性检验(Shapiro-Wilk):")
print(f"统计量:{stat:.4f},p值:{p_value:.4f}")
if p_value > 0.05:
    print("结论:p>0.05,无法拒绝H0,数据近似符合正态分布")
else:
    print("结论:p≤0.05,拒绝H0,数据不符合正态分布")

# 4. 计算用户活跃度的分位数(概率分位数,第2篇)
# 按订单数将用户分为高、中、低活跃三组(33%、66%分位数)
user_stats["activity_level"] = pd.qcut(
    user_stats["order_count"],
    q=[0, 0.33, 0.66, 1],
    labels=["低活跃", "中活跃", "高活跃"]
)
print(f"\n用户活跃度分布:")
print(user_stats["activity_level"].value_counts())

# 5. 可视化平均消费金额的分布(直方图)
plt.figure(figsize=(10, 6))
plt.hist(avg_amount_clean, bins=30, edgecolor="black", alpha=0.7)
plt.axvline(avg_amount_clean.mean(), color="red", linestyle="--", label=f"均值:{avg_amount_clean.mean():.2f}")
plt.axvline(avg_amount_clean.median(), color="green", linestyle="--", label=f"中位数:{avg_amount_clean.median():.2f}")
plt.xlabel("用户平均消费金额(元)")
plt.ylabel("用户数量")
plt.title("用户平均消费金额分布")
plt.legend()
plt.show()

3. 关联知识点

  • 期望与均值:avg_amount是用户消费金额的均值,近似 “用户单次消费的期望”;
  • 方差与标准差:std_amount反映用户消费的稳定性,标准差大说明用户消费波动大;
  • 概率分布:Shapiro-Wilk 检验用于判断数据是否符合正态分布,用到 “正态分布” 概念;
  • 分位数:pd.qcut按概率分位数分组,用到 “概率分位数” 定义。

五、场景 4:数据可视化 —— 坐标变换与周期刻度的 “数据呈现术”

数据可视化是将统计结果直观展示,核心用到线性代数的坐标变换和余数的周期刻度,比如调整图表的坐标比例、处理时间序列的周期刻度。

1. 数学原理

  • 线性代数:图表的坐标变换(如 x 轴缩放、y 轴平移)是线性变换,比如将 “金额(元)” 转为 “金额(千元)” 是y = 0.001x的线性变换;
  • 余数:时间序列的 x 轴刻度(如月份、星期)是周期的,用时间 mod 周期确定刻度位置,比如 “月份 mod 12” 确保刻度在 1-12 范围;
  • 逻辑判断:图表的条件高亮(如 “周末数据标红”)用逻辑判断实现。

2. 实战:可视化电商月度销售趋势

基于清洗后的购物数据,绘制 2023 年每月的销售额趋势图,高亮周末销量:

python

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime

# 基于清洗后的df_clean数据
df = df_clean.copy()

# 1. 按“年-月”和“是否周末”分组,计算销售额
# 新增“年-月”字段(周期刻度:按月份分组)
df["year_month"] = df["buy_time"].dt.strftime("%Y-%m")
# 分组统计:每月周末/非周末的销售额
monthly_sales = df.groupby(["year_month", "is_weekend"]).agg(
    sales_amount=("amount", "sum"),  # 销售额
    sales_count=("amount", "count")   # 订单数
).reset_index()

# 2.  pivot表格:将周末/非周末转为列(便于绘图)
monthly_sales_pivot = monthly_sales.pivot(
    index="year_month",
    columns="is_weekend",
    values="sales_amount"
).fillna(0)
# 重命名列(0=非周末,1=周末)
monthly_sales_pivot.columns = ["非周末销售额", "周末销售额"]
# 计算每月总销售额
monthly_sales_pivot["总销售额"] = monthly_sales_pivot["非周末销售额"] + monthly_sales_pivot["周末销售额"]

# 3. 坐标变换:销售额单位转为“万元”(线性代数:y=0.0001x)
monthly_sales_pivot = monthly_sales_pivot / 10000  # 元→万元

print("2023年每月销售额(万元):")
print(monthly_sales_pivot)

# 4. 绘制趋势图(周期刻度+坐标变换)
plt.figure(figsize=(12, 7))
# 设置中文字体
plt.rcParams["font.sans-serif"] = ["WenQuanYi Zen Hei"]
plt.rcParams["axes.unicode_minus"] = False

# 绘制堆叠柱状图(非周末+周末)
x = range(len(monthly_sales_pivot.index))
width = 0.6
plt.bar(
    x,
    monthly_sales_pivot["非周末销售额"],
    width,
    label="非周末销售额",
    color="#3498db"
)
plt.bar(
    x,
    monthly_sales_pivot["周末销售额"],
    width,
    bottom=monthly_sales_pivot["非周末销售额"],
    label="周末销售额",
    color="#e74c3c"
)

# 绘制总销售额折线(高亮趋势)
plt.plot(
    x,
    monthly_sales_pivot["总销售额"],
    color="#2c3e50",
    marker="o",
    linewidth=2,
    label="总销售额"
)

# 调整x轴刻度(周期刻度:每月一个刻度)
plt.xticks(x, monthly_sales_pivot.index, rotation=45)
# 调整y轴标签(坐标变换后的单位)
plt.ylabel("销售额(万元)")
plt.xlabel("月份")
plt.title("2023年电商平台月度销售额趋势(含周末/非周末拆分)")
plt.legend()
plt.tight_layout()  # 自动调整布局,避免标签截断
plt.show()

# 5. 绘制周末销售额占比饼图(逻辑分组)
# 计算2023年整体周末/非周末销售额占比
total_weekend = monthly_sales_pivot["周末销售额"].sum()
total_weekday = monthly_sales_pivot["非周末销售额"].sum()
labels = ["非周末销售额", "周末销售额"]
sizes = [total_weekday, total_weekend]
colors = ["#3498db", "#e74c3c"]
explode = (0, 0.1)  # 突出周末部分

plt.figure(figsize=(8, 8))
plt.pie(
    sizes,
    explode=explode,
    labels=labels,
    colors=colors,
    autopct="%1.1f%%",
    shadow=True,
    startangle=90
)
plt.axis("equal")  # 保证饼图是圆形
plt.title("2023年电商平台周末/非周末销售额占比")
plt.show()

3. 关联知识点

  • 线性代数:销售额 “元→万元” 是线性变换y=0.0001x,属于 “向量数乘”;
  • 余数:“年 - 月” 分组是 “时间戳 mod 2592000”(每月约 2592000 秒)的简化,确保按周期分组;
  • 逻辑判断:饼图的 “突出周末部分” 用explode实现,本质是逻辑上的 “重点标记”。

六、综合思维框架与后续学习

1. 数据处理的数学思维框架

通过四个场景,可总结出 “数据处理与分析的数学思维框架”,串联前面的所有章节:

数据处理环节 核心数学知识点
数据清洗 逻辑判断、余数分组
特征提取 线性代数(向量)、排列组合
统计分析 期望方差、概率分布
数据可视化 坐标变换、周期刻度

2. 后续学习方向

若想深化 “数据处理中的数学思维”,可探索:

  • 机器学习基础:用线性回归预测销售额,用决策树(逻辑判断 + 排列组合)分类用户;
  • 深度学习入门:用神经网络(多层线性变换)处理图像数据;
  • 时间序列分析:用 ARIMA 模型(余数周期 + 概率统计)预测未来销量。

七、小结:数学是数据处理的 “翻译器”

今天的四个数据处理场景,展示了数学思维如何将 “杂乱数据” 翻译为 “有价值的信息”:

  • 数据清洗用逻辑和余数 “过滤噪声”;
  • 特征提取用线性代数和排列组合 “编码数据”;
  • 统计分析用概率统计 “解读规律”;
  • 数据可视化用坐标变换和周期刻度 “呈现结果”。

对程序员而言,数据处理不是 “调用 pandas API” 的机械操作,而是 “用数学思维理解数据本质” 的过程 —— 当你能将数据问题转化为数学模型(如向量、概率分布、周期分组),就能轻松应对任何数据场景。

如果你在数据处理中用过类似的数学思维,或者有其他想深入的场景(如时序预测、机器学习),欢迎在评论区分享!

下篇预告

数据处理与分析让我们从数据中看到了规律,而要让这些规律真正服务于预测和决策,就需要进入机器学习的世界。在下一篇《机器学习入门中的数学思维:从数据到模型的数学桥梁》中,我们将探讨如何用数学构建模型,让程序具备“自我学习”的能力。敬请期待!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。