简介:这份实验报告面向学习大数据分析、希望系统掌握Pandas库核心用法的数据科学初学者,完整呈现了统计分析基础与数据预处理全流程。内容涵盖read_table、read_csv、read_excel等数据存取方法,ndim、shape、memory_usage等常用属性,以及to_datetime时间处理、groupby与agg分组聚合、pivot_table和crosstab长宽表转换等关键技能。报告深入演示了缺失值拉格朗日插值、基于id和date的主键内连接整合、标准差标准化、主成分分析(PCA)降维,以及通过pymysql和sqlalchemy实现MySQL数据库的数据导入导出操作,每一步都附有详细代码与运行说明。资源为单个doc文档,大小3.56MB,既适合课程实验和毕业设计参考,也可作为企业数据分析人员的自学模板。已有387人学习下载,是快速掌握Pandas实操的实用资料。
1. Pandas统计分析基础与数据预处理要做的事
拿到一份从业务库导出的 CSV,几万行、二十多列,里面混着空值、重复记录、格式不统一的日期和带货币符号的金额——这种场景下,大多数人第一反应是打开 Excel 手工筛,但数据量一旦过了十万行,Excel 就开始卡顿,更别提把同样的清洗逻辑复用到下周的增量数据上。Pandas 解决的就是这个问题:它把统计分析的基础操作和数据预处理放在同一个 DataFrame 模型里,从读取原始文件到输出统计结果,中间不需要切换任何工具。这篇文章围绕大数据分析技术里最常用的 Pandas 统计分析基础与数据预处理展开,适合正在学数据分析、或者刚把 Pandas 引入报表流程的工程师,新手能照着命令跑通,熟手也能在分组聚合和清洗顺序上找到可以优化的细节。
2. 描述性统计与分组聚合:先摸清数据再谈分析
2.1 用 describe() 快速摸清数值列的底细
Pandas 做统计分析的第一步不是写复杂公式,而是让数据自己说话。describe()会一次性输出计数、均值、标准差、最小值、四分位数和最大值,这几项足以判断数据有没有明显的离群点、量纲是否一致、是否有缺失。默认情况下它只统计数值列,但通过include参数可以纳入对象列或布尔列。
import pandas as pd df = pd.read_csv("sales.csv", encoding="utf-8") desc = df.describe(percentiles=[.25, .5, .75, .9]) print(desc) # 把对象列也纳入统计,看非空个数和唯一值数量 obj_desc = df.describe(include=["object"])percentiles参数控制输出的分位点,默认是 25%、50%、75%,加上 90% 可以观察尾部数据,比如大额订单是否集中在后 10%。如果count数量明显小于总行数,这一列就有缺失值,后面第 3 章的处理就要优先安排。include=["object"]对文本列输出的是非空个数、唯一值个数和出现频率最高的值,这个信息在检查“城市”这类业务字段时很有效——如果唯一值数量异常少,说明数据录入可能有默认值污染。
对于任何一张新接手的数据表,先跑一次describe()再决定后续步骤,比直接进入清洗要稳得多。因为统计结果会直接暴露两个问题:各列的量纲差异是否大得需要标准化,以及是否存在明显偏离业务范围的值。这两个问题不解决,后面的分组聚合和建模都会受到干扰。
2.2 groupby() 与 agg() 搭起分组统计的骨架
分组统计是数据分析里最常用的操作,业务上的“按地区看销售额”“按品类看平均折扣”都对应groupby()加聚合函数。agg()的价值在于可以在一次分组里同时计算多个统计量,而且允许为每个结果指定列名,避免生成一堆层级索引后再去 rename。
result = ( df.groupby("region", as_index=False) .agg( total_revenue=("amount", "sum"), avg_order=("amount", "mean"), order_cnt=("order_id", "nunique"), ) )as_index=False让分组列保留为普通列而不是索引,这样导出的结果可以直接给报表系统用。agg()内的写法是“新列名=(原列名, 聚合函数)”的元组形式,sum和mean后面不加括号,因为它们是传给 Pandas 的回调函数,不是立即执行。nunique统计唯一订单数而不只是行数,因为同一订单可能拆成多行明细,直接count会把订单数算大。
更复杂的需求可以传入字典或函数列表。比如同时计算销售额和销量,并对金额列取中位数:
result = df.groupby("category").agg( revenue_sum=("amount", "sum"), revenue_median=("amount", "median"), quantity_sum=("qty", "sum"), ).reset_index()分组聚合的性能问题在高基数分组列上会暴露,比如按“订单号”分组有几万个组,这时可以先把该列转为category类型再分组,速度会有明显提升。
2.3 透视表 pivot_table 与逆透视 melt:长表和宽表的转换
统计分析和数据预处理里,长表和宽表的转换是躲不掉的场景。宽表是每一行一个样本、每一列一个属性,适合建模;长表是每一行一个观测,适合数据库存储和时序分析。业务系统导出的数据往往是长表,但数据透视需求要的是宽表,pivot_table()就是为此设计的。
pivot = pd.pivot_table( df, values="amount", index="region", columns="category", aggfunc="sum", fill_value=0, )aggfunc默认是mean,如果不指定,金额列会被当作均值而不是总和,这是最常见的误用。fill_value=0把透视后缺失的组合填零,避免输出里出现大量 NaN。逆透视用melt(),把多列合并成一列:
melted = pivot.reset_index().melt( id_vars="region", value_vars=["电子产品", "家居用品"], var_name="category", value_name="amount", )表格对比两者的适用场景:
| 操作 | 输入结构 | 输出结构 | 典型场景 |
|---|---|---|---|
| pivot_table | 长表 | 宽表 | 按维度展开指标,做交叉分析 |
| melt | 宽表 | 长表 | 多列指标合并,适配数据库导入 |
| stack / unstack | 多层索引 | 行列互换 | 索引层级的透视操作 |
3. 数据清洗:缺失值、重复值与数据类型修正
3.1 缺失值处理的三个方向:丢弃、填充、插值
缺失值处理是数据预处理的第一个硬骨头,但很多教程一上来就强调dropna()有多方便,实际上无脑删行是风险最高的做法。删除行会让样本量缩水,更严重的是,如果缺失发生在特定业务条件下,删除后剩下的数据就带上了选择性偏差。处理缺失值的第一步不是选方法,而是搞清楚三个问题:哪些列有缺失、缺失率多高、缺失是否与业务含义关联。
| 缺失情况 | 推荐做法 | 理由 |
|---|---|---|
| 单列缺失率低于 5%,且与其他列无关 | 直接删除该行 | 对整体分布影响可以忽略 |
| 关键列缺失,比如客户 ID | 删除该行 | 缺失后无法关联业务实体 |
| 时间序列的中间空洞 | 插值填充 | 保留时序连续性 |
| 分类特征的缺失 | 填充“未知”类别 | 缺失本身可能代表一种状态 |
3.2 用 isnull() 和 dropna() 定位与删除缺失
正式动手前要先量化缺失情况,isnull()配合sum()和mean()能同时得到缺失的数量和比例。
missing_cnt = df.isnull().sum() missing_ratio = df.isnull().mean().round(4) print(missing_cnt[missing_cnt > 0]) print(missing_ratio[missing_ratio > 0])mean()在这里的语义是缺失比例,因为布尔值求和后除以行数。筛选出有缺失的列后,再决定是删除还是填充。
df_dropped = df.dropna(subset=["customer_id"], how="any")subset指定判断缺失时只看哪些列,避免无关列的空值牵连整行被删。how参数有两个取值:any表示只要有缺失就删,all表示整行全空才删。如果处理包含大量空列的数据,还可以用thresh参数设置保留条件,比如某行至少有 15 个非空值才保留。
3.3 fillna() 的参数细节与填充边界
fillna()是最常用的填充方法,但参数用不好会造成数据污染。常见的低级错误是不区分业务场景,对所有列统一填充 0 或统一填充均值。数值型连续变量用均值或中位数填充需要先看分布——偏态明显的用中位数更稳,正态分布的用均值更合理。分类变量则建议单独填充一个“unknown”值。
df["score"] = df["score"].fillna(df["score"].median()) df["category"] = df["category"].fillna("unknown") df["last_login"] = pd.to_datetime(df["last_login"]).fillna(pd.NaT)时间列填充pd.NaT而不是字符串,目的是保持时间类型的一致性。另一个容易忽略的点是fillna()默认返回新对象,需要赋值回原变量,除非传入inplace=True——而inplace=True在链式操作里经常失效,所以习惯上用赋值方式更安全。
3.4 重复值处理:duplicated() 与 drop_duplicates() 的参数陷阱
重复值判断不等于简单的整行去重。业务上,同一订单在不同时间点被更新,整行数据可能只有更新时间不同;同一用户重复注册,手机号相同但用户名不同。这些情况都要通过subset指定判断重复的字段集。
# 找出重复的订单明细 dup_mask = df.duplicated(subset=["order_id", "sku"], keep=False) # 查看重复部分 print(df[dup_mask].sort_values("order_id")) # 保留第一条,删除其余 df_clean = df[~dup_mask].drop_duplicates( subset=["order_id", "sku"], keep="first" )keep参数有三个选项:first保留第一条、last保留最后一条、False标记所有重复行。这里先通过dup_mask查看重复数据,确认业务上是否真的只该保留一条,避免盲目删除。如果重复行的其他列有不同的值,删除前需要决定保留哪一条,或者干脆用groupby()把多条记录聚合成一条。
4. 数据转换:类型修正、编码处理与标准化
4.1 结合数据类型转换修正隐藏的脏数据
数据类型转换可能是数据预处理里最枯燥但价值最高的环节。业务系统导出的数据经常把数值列做成字符串,日期列做成object类型,金额列里混着“¥1,200”这样的格式。这些问题在统计分析时不会报错,但结果悄然出错——把字符串列做sum()会得到一个拼接的字符串,把日期当字符串排序会得到错误的时间顺序。
# 查看每列类型 print(df.dtypes) # 金额列清洗后转 float df["amount"] = ( df["amount"] .astype(str) .str.replace("¥", "", regex=False) .str.replace(",", "", regex=False) .astype(float) ) # 日期列统一格式 df["order_date"] = pd.to_datetime( df["order_date"], format="%Y-%m-%d", errors="coerce" )errors="coerce"是关键参数,它在格式不匹配时把该值置为NaT而不是抛异常终止整个转换,之后再结合缺失值处理统一收口。astype(str)前置转换是为了防止某些列本身就是浮点型,直接调用.str方法会报错。类型转换的顺序也有讲究,如果金额列清洗和日期解析同时进行,先做字符串清洗再做类型转换更稳,因为清洗过程本身依赖字符串方法。
4.2 类别型变量的编码转换:map、replace 与 get_dummies
统计分析中经常遇到文字类别列,比如“省份”“商品分类”“支付方式”。Pandas 提供了多个转换工具,但各自适用场景明显不同。map()适合用字典做一一映射,比如把“男”“女”映射为 0、1;replace()适合多对一替换,比如把“北京”“上海”统一替换为“一线城市”;get_dummies()则生成独热编码,把类别展开成多列 0/1 特征。
# 有序类别用 map 编码 df["gender_code"] = df["gender"].map({"男": 1, "女": 0}) # 无序类别用 get_dummies 展开 region_dummies = pd.get_dummies( df["region"], prefix="region", dummy_na=True ) df = pd.concat([df, region_dummies], axis=1)dummy_na=True会为缺失值单独生成一列,避免编码过程中丢失缺失信息。map()遇到字典中不存在的键会返回 NaN,所以使用前需要确认类别的实际取值,可以通过df["gender"].value_counts()先查看。类别编码的方向取决于后续用途:做回归或树模型,有序类别用数值映射更合适;做逻辑回归或距离类模型,独热编码是必须的,否则类别之间的数值差会被模型误解。
4.3 数值型变量的量纲统一:标准化与归一化
统计分析里经常出现“销售额”和“订单数量”两个列,数量级相差几百倍。虽然 Pandas 本身的统计函数不受影响,但后续如果接入机器学习模型,量纲差异会影响梯度下降的收敛速度。标准化和归一化是两个不同概念:标准化(StandardScaler)把数据变为均值为 0、标准差为 1;归一化(MinMaxScaler)把数据缩放到 [0, 1] 区间。选择标准是看后续算法对分布有没有假设。
from sklearn.preprocessing import StandardScaler, MinMaxScaler scaler = StandardScaler() df["amount_std"] = scaler.fit_transform(df[["amount"]]) minmax = MinMaxScaler() df["amount_norm"] = minmax.fit_transform(df[["amount"]])fit_transform传入的必须是二维结构,所以df[["amount"]]用双层括号。标准化保留异常值的影响,归一化会把异常值压缩到边界附近。如果数据里存在明显离群点,先对离群点做截断处理再归一化,否则大部分数据会被压缩在一个很窄的区间里。两个缩放器都支持inverse_transform还原,这在需要把预测结果映射回原始量纲时很有用。
4.4 数据降维的轻量做法:相关性分析与去冗余
数据预处理的数据降维不必一上来就上 PCA,最常见的做法是先通过相关性矩阵找出高度相关的列组,再决定保留哪一列。corr()输出的是两两相关系数矩阵,对于标准化后的数据,相关系数绝对值大于 0.8 的列组就存在冗余。
corr_matrix = df[["amount", "quantity", "amount_per_unit"]].corr() high_corr = corr_matrix[corr_matrix.abs() > 0.8] print(high_corr)如果amount和amount_per_unit相关度过高,而amount还受quantity影响,业务上更合理的做法是保留amount和quantity,删除amount_per_unit。基于业务判断的多此一举在上线后会很值得,因为冗余特征不仅增加计算量,还可能让模型对特征产生错误的依赖关系。
5. 多列联合清洗的完整流程与结果验证
5.1 把散落的清洗步骤整理成可复用的流程
前面的清洗步骤各管一段,但真实项目里这些操作要按固定顺序串联起来。我一般按照“去全空行 → 类型转换 → 缺失值收口 → 去重 → 异常值校验”的顺序来组织。顺序有讲究:先做类型转换,后面的缺失值填充和去重才能基于正确的类型判断;去重放在缺失值收口之后,是因为一些重复行会在填充后变得完全相同,此时去重更彻底。
df["amount"] = df["amount"].astype(str).str.replace("¥", "", regex=False).astype(float) df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce") df = df.dropna(subset=["order_id", "sku"]) df["amount"] = df["amount"].fillna(df["amount"].median()) df = df.drop_duplicates(subset=["order_id", "sku"], keep="last")5.2 用断言函数验证清洗后的数据质量
清洗做完并不意味着可以进入分析,还差一步验证。Pandas 官方提供了pd.testing.assert_frame_equal()用于验证两个 DataFrame 是否完全相等,但在清洗场景里,更常用的是针对业务规则写自定义断言:缺失率是否已收敛、金额是否全部大于 0、日期是否在合理区间。
assert df["amount"].isnull().sum() == 0 assert df["amount"].min() > 0 assert df["order_date"].max() <= pd.Timestamp.today() assert df.duplicated(subset=["order_id", "sku"]).sum() == 0 summary = pd.DataFrame({ "rows": [len(df)], "cols": [len(df.columns)], "missing_rate": [df.isnull().mean().max()], })断言失败时抛出的异常会直接打断流程,让问题在一次执行里暴露完整,而不是等分析结果出错后再回头排查。把这一段整理成函数,后续每次拿到新数据先执行一次,能得到稳定的数据质量基线。统计分析建立在干净数据之上,预处理的每一步都该留下验证痕迹。
本文还有配套的精品资源,点击获取