恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python数据分析实战:从CSV清洗到可视化报表的完整工程
首页
资讯中心
/
Python数据分析实战:从CSV清洗到可视化报表的完整工程
Python数据分析实战:从CSV清洗到可视化报表的完整工程
发布时间:2026/10/16 3:27:21
简介一套Python数据分析与可视化课程资源面向数据科学入门者与需要系统提升可视化能力的学员。内容从Python编程基础变量、数据类型、控制结构、函数讲到Pandas数据读写、清洗与预处理再深入Matplotlib/Seaborn统计绘图最后覆盖pyecharts交互式网页图表贯通数据分析全流程。压缩包共含一百六十二个文件、约三十一点二七兆字节以六十七个ipynb代码笔记本、十一个PPT课堂教学课件和十个CSV示例数据集为主体另含xls、txt、pdf、doc等多格式辅助资料教学大纲、进度表和教材大纲文档可辅助规划章节进度常用数据集如鸢尾花、葡萄酒、学生成绩等便于随学随练。目前已有一万八千一百四十九人学习使用适合作为高校课程补充资源或自学实操素材。结合配套源码与数据集可逐步练习数据读取、缺失值处理、分组聚合以及直方图、散点图、热力图等多种图表绘制最终独立完成交互式可视化项目积累完整的数据分析与表达能力。1. 项目定位把脏数据变成会说话的图表很多数据分析的实战项目第一步就卡在一份“不那么干净”的 Excel 或 CSV 上。日期格式乱七八糟金额列里混着汉字备注地区字段有五六种写法还没开始分析就被数据清洗耗掉了大半精力。这份 python 数据分析与可视化 的源代码就是用来解决这个痛点的。它把从 CSV 加载、字段规整、空值处理、分组聚合到输出图表报告的完整流程做成了可运行、可复用的工程。内置一份模拟零售订单数据集clone 下来装好依赖就能跑跑完你会看到一条数据从原始表格变成可视化图表的全链路。适合刚接触 pandas 想知道完整项目长什么样的新手也适合需要快速搭一套分析模板的开发者。2. 工程结构与环境这份源代码是怎么组织起来的2.1 环境准备依赖清单与版本选择项目根目录下的 requirements.txt 已经把依赖锁死了我拆的时候特意看了下选得比较克制没有无关库。pandas2.0.3 numpy1.24.3 matplotlib3.7.2 seaborn0.12.2 pyecharts2.0.5 openpyxl3.1.2pandas 负责数据加载与清洗numpy 做底层数值计算matplotlib 和 seaborn 负责静态图表pyecharts 用来生成可交互的 HTML 图表openpyxl 是 pandas 读写 Excel 文件的桥梁。这个组合是当前数据分析场景下的常见搭配没有引入重量级的大数据框架单机跑这份模拟数据完全够用。建议先创建虚拟环境再装依赖避免和系统全局 Python 包打架python -m venv venv venv\Scripts\activate # Windows 环境 source venv/bin/activate # macOS / Linux 环境 pip install -r requirements.txt如果 pip 下载慢可以在命令后面追加-i https://pypi.tuna.tsinghua.edu.cn/simple切换镜像源。装完之后用python -c import pandas; print(pandas.__version__)验证能输出版本号就说明环境通了。2.2 目录结构src 与 output 怎么分层项目的目录划分很清晰我把完整结构贴出来python-data-analysis/ ├── data/ │ ├── raw/ # 存放原始数据只读不写 │ ├── processed/ # 清洗后的中间数据供分析模块读取 │ └── sample_data.csv # 内置模拟订单数据约 12000 行 ├── notebooks/ │ ├── 01_explore.ipynb # 探索性分析脚本 │ └── 02_analysis.ipynb # 聚合分析脚本 ├── src/ │ ├── __init__.py │ ├── config.py # 路径、字段名、图表参数集中配置 │ ├── data_loader.py # 数据加载模块 │ ├── cleaner.py # 数据清洗模块 │ ├── analyzer.py # 聚合分析模块 │ └── visualizer.py # 图表生成模块 ├── output/ │ ├── charts/ # 静态图表输出目录 │ └── reports/ # HTML 报告输出目录 └── requirements.txt这个分层思路是典型的工程化拆分data 目录把原始数据和加工数据物理隔离raw 下的文件永远保持初始状态这样无论清洗脚本跑多少遍原始数据都不会被污染。src 目录按职责拆成四个模块config.py 统一管理文件和参数避免在代码里到处写死路径。notebooks 目录里两个脚本是给探索阶段用的跑通逻辑后再把函数搬进 src 模块化。output 目录存放所有产出物charts 放 matplotlib 和 seaborn 生成的静态图reports 放 pyecharts 生成的 HTML 报告方便直接用浏览器打开。2.3 数据从哪里来内置模拟数据说明data/sample_data.csv 是一份模拟的零售连锁订单数据字段覆盖了订单维度、商品维度和用户维度我整理成表格方便查看字段名类型说明order_idstring订单编号唯一order_datedatetime下单日期含时分秒categorystring商品类目product_namestring商品名称pricefloat单价单位元quantityint购买数量amountfloat订单金额price 与 quantity 的乘积paymentstring支付方式如支付宝、微信、银行卡regionstring客户地区分为华东、华北、华南等ratingint客户评分范围 1 到 5这份数据是模拟生成的但刻意埋了不少“脏”点order_date 列混合了2024/01/05和20240105两种格式amount 列偶发混入N/A字符串region 列有华东和华东区两种写法rating 列存在超出 1 到 5 范围的异常值。这些坑都是真实数据分析中高频出现的正好用来验证清洗模块是否合格。3. 核心模块拆解数据清洗与聚合分析的完整链路3.1 数据加载read_csv 的参数没那么简单data_loader.py 负责把 CSV 读进 DataFrame看似一行代码的事实际上参数暗藏玄机。我贴出核心实现import pandas as pd from pathlib import Path def load_order_data(file_path: str) - pd.DataFrame: path Path(file_path) if not path.exists(): raise FileNotFoundError(f数据文件不存在: {file_path}) df pd.read_csv( path, encodingutf-8, dtype{ order_id: str, category: str, payment: str, region: str, }, parse_dates[order_date], infer_datetime_formatTrue, keep_default_naTrue, na_values[N/A, NULL, null, ], ) return df这段代码的关键在dtype参数。order_id 虽然看起来是数字开头但实际是字符串类型如果不显式指定pandas 会把类似000123的订单号读成整数 123后续关联时前缀丢失。category 和 region 指定为 str是因为这些列后期要做分组聚合保持字符串类型可以避免数字和文本混排带来的类型推断问题。parse_dates[order_date]告诉 pandas 把该列解析成 datetime 类型。infer_datetime_formatTrue是提速选项pandas 会尝试推断日期格式但如果数据里同时存在多种日期格式这个推断可能失效。na_values参数很实用它扩展了缺失值的识别范围把N/A、NULL、空字符串都视为 NaN这样后续清洗时统一处理。3.2 空值处理先想清楚删除还是填充cleaner.py 里的清洗逻辑是整个项目的核心之一。拿到 DataFrame 后第一步是df.info()和df.isnull().sum()摸清缺失情况然后分列处理。我贴出这段清洗代码def clean_orders(df: pd.DataFrame) - pd.DataFrame: # 删除订单号和金额完全为空的记录无法修复的信息直接丢弃 df df.dropna(subset[order_id, amount]) # 金额列可能混入非数值字符串强制转 numeric转换失败置为 NaN df[amount] pd.to_numeric(df[amount], errorscoerce) # 金额为空时按单价乘数量补算单价也没有则删除 mask df[amount].isna() df.loc[mask, amount] ( df.loc[mask, price] * df.loc[mask, quantity] ) df df.dropna(subset[amount]) # 地区列清洗统一字段写法去掉区字后缀 df[region] df[region].str.replace(区, , regexFalse) # 评分列的异常值处理不在 1-5 区间内的按众数填充 valid_score df[rating].between(1, 5) if (~valid_score).any(): mode_score df.loc[valid_score, rating].mode()[0] df.loc[~valid_score, rating] mode_score # 商品类目缺失时填充为未分类 df[category] df[category].fillna(未分类) return df.drop_duplicates()这段代码体现了一个重要的处理原则不同列的缺失值处理策略完全不同。order_id 和 amount 同时为空说明这条订单记录已经无法信任直接删除比硬填更有价值而 amount 为空但 price 和 quantity 都在可以用业务规则补算这就是典型的字段间推导。pd.to_numeric搭配errorscoerce是个高频用法它会把123转成数字 123把N/A转成 NaN转换失败不再报错。region 列的清洗看似简单str.replace(区, )把“华东区”变成“华东”但如果数据里有“内蒙古自治区”这类词就会误伤所以这个操作只适用于已知的枚举值列表。rating 列先用between(1, 5)判断合法性再用众数填充异常值这是统计学上处理异常评分的常见做法比用均值填充更能保留评分的整数特征。3.3 分组聚合groupby 与透视表配合使用analyzer.py 里封装了两个分析函数一个用 groupby 做分组统计一个用 pivot_table 做多维透视。核心代码如下def monthly_category_stats(df: pd.DataFrame) - pd.DataFrame: # 按订单日期重采样到月份再按商品类目分组聚合 df df.copy() df[order_month] df[order_date].dt.to_period(M) stats df.groupby([order_month, category]).agg( total_amount(amount, sum), order_count(order_id, count), avg_price(price, mean), ).reset_index() return stats.sort_values([order_month, total_amount], ascending[True, False]) def region_payment_pivot(df: pd.DataFrame) - pd.DataFrame: pivot pd.pivot_table( df, valuesamount, indexregion, columnspayment, aggfuncsum, fill_value0, ) return pivotdt.to_period(M)把日期列转换成月度周期对象这样同一自然月的所有订单归到同一个键下后续 groupby 才能按月分组。agg函数接收一个字典键是目标列名值是元组元组第一个元素是源列名第二个是聚合方式。这里的order_count(order_id, count)用了 count 而不是 sizecount 会忽略 NaNsize 不会统计订单数时 count 更准确。pivot_table 的行索引是地区列索引是支付方式值是对应金额总和。fill_value0把没有发生的交叉组合填充为 0避免图表中留下难看的空缺点。这段分析模块的输出会直接交给可视化层使用groupby 结果画出月度趋势折线图透视表结果画出区域支付方式热力图。3.4 时间序列重采样按月汇总注意索引陷阱重采样在 pandas 中是个容易踩坑的操作但也是时间序列分析绕不开的环节。项目里有一段按季度汇总的示例代码def quarterly_sales(df: pd.DataFrame) - pd.DataFrame: df df.set_index(order_date).sort_index() quarterly df[amount].resample(QE).agg([sum, mean, count]) # resample 会生成 DatetimeIndex直接 reset_index 丢掉索引层级 quarterly quarterly.reset_index() quarterly.columns [quarter_end, total_amount, avg_amount, order_count] return quarterlyresample(QE)表示按季度末聚合Q 是季度E 表示右对齐到期末。如果不 reset_index索引会变成季度结束日期下游图表模块读取时可能因为索引类型不匹配碰壁。重采样前一定要先set_index否则 pandas 会直接报错。这段代码展示了时间聚合的完整套路设置时间索引、排序、重采样聚合、重置索引、重命名列。4. 可视化实战从 matplotlib 到 pyecharts 的图表工厂4.1 matplotlib 画布中文乱码是绕不过去的坎visualizer.py 的第一个函数是设置全局画布风格。中文乱码在 Windows 环境下几乎必现代码里提前做了处理import matplotlib.pyplot as plt def setup_plot_style(): plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, PingFang SC] plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块的问题 plt.style.use(seaborn-v0_8-whitegrid) plt.rcParams[figure.figsize] (10, 6) plt.rcParams[figure.dpi] 100font.sans-serif把默认字体改成中文字体列表从左到右依次尝试Windows 优先用微软雅黑macOS 用苹方。axes.unicode_minus设为 False 是关键细节否则坐标轴负号会显示成竖线方块。plt.style.use直接套用了 seaborn 的白色网格风格省去手动设置刻度线和网格的繁琐。4.2 seaborn 统计图相关性热力图与分析结论结合项目用 seaborn 画了两类图评分与金额的相关性热力图以及支付方式的分布柱状图。相关代码如下import seaborn as sns import matplotlib.pyplot as plt def plot_correlation(df: pd.DataFrame, save_path: str): corr_cols [price, quantity, amount, rating] corr_matrix df[corr_cols].corr() # 皮尔逊相关系数矩阵 fig, ax plt.subplots(figsize(8, 6)) sns.heatmap( corr_matrix, annotTrue, # 在格子中显示数值 fmt.2f, # 数值保留两位小数 cmapRdBu_r, # 红蓝色系正相关红负相关蓝 center0, # 颜色中心对齐到 0 vmin-1, vmax1, # 相关系数取值范围 cbar_kws{shrink: 0.8}, # 色条长度缩到画布的 80% axax, ) ax.set_title(订单价格、数量、金额与评分相关性, fontsize14) fig.tight_layout() fig.savefig(save_path, dpi150) plt.close(fig)df[corr_cols].corr()默认计算皮尔逊相关系数只对数值列生效。热力图的annotTrue显示数值观察时能直接读出相关系数。center0让 0 相关系数对应色带中间位置正负相关通过颜色冷暖区分比默认色带更直观。plt.close(fig)是画完必须做的清理动作否则在 Jupyter 里反复执行会累积大量图形对象占用内存在脚本循环中也容易引发报错。4.3 pyecharts 交互式图表给非技术同事看报告pyecharts 生成的 HTML 图表支持鼠标悬浮提示和缩放适合放进周报。项目里封装了一个月度销售趋势柱状图from pyecharts.charts import Bar from pyecharts import options as opts def plot_monthly_trend(stats_df: pd.DataFrame, save_path: str): months [str(m) for m in stats_df[order_month].tolist()] amounts [float(v) for v in stats_df[total_amount].tolist()] bar ( Bar() .add_xaxis(months) .add_yaxis( 月度销售额(元), amounts, label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( title_optsopts.TitleOpts(title月度销售额趋势), yaxis_optsopts.AxisOpts(name销售额), xaxis_optsopts.AxisOpts(name月份), ) ) bar.render(save_path)LabelOpts(is_showFalse)隐去柱顶的数值标签避免数据点密集时文字重叠。TitleOpts设置标题AxisOpts设置坐标轴名称。最后一句bar.render(save_path)会生成一个完整的 HTML 文件默认模板包含 echarts 的 CDN 资源双击即可在浏览器打开。这里要注意离线的内网环境需要单独下载 echarts 本地资源否则图表无法渲染这是 pyecharts 在受限网络下最常见的翻车点。4.4 图表落地多图排版与报告导出最后一步是把多张图表汇总到一张画布上做对比。项目里用 GridSpec 完成了四宫格排版from matplotlib.gridspec import GridSpec def build_report_figure(df: pd.DataFrame): fig plt.figure(figsize(16, 12)) gs GridSpec(2, 2, figurefig, wspace0.3, hspace0.35) ax1 fig.add_subplot(gs[0, 0]) ax1.bar(df[region].value_counts().index, df[region].value_counts().values) ax1.set_title(各地区订单量分布) ax2 fig.add_subplot(gs[0, 1]) average df.groupby(payment)[amount].mean() ax2.plot(average.index, average.values, markero) ax2.set_title(各支付方式平均订单金额) ax3 fig.add_subplot(gs[1, :]) # 占用底部整行的位置 ax3.hist(df[amount], bins50, edgecolorwhite) ax3.set_title(订单金额直方图) fig.savefig(output/reports/overview.png, dpi120, bbox_inchestight)GridSpec(2, 2)把画布分成两行两列gs[1, :]表示第二行横跨两列这样能做出上两图、下一长图的布局。bbox_inchestight保存时自动去掉留白报告配图不会出现大块白边。wspace和hspace控制子图间距0.3 和 0.35 是经验值视觉上不松不挤。5. 避坑指南跑这份代码最常见的五个坑5.1 Windows 下 CSV 乱码现象用pd.read_csv读入数据后打印 DataFrame 所有中文都显示成乱码或者读文件直接抛出UnicodeDecodeError。原因数据文件本身是 GBK 编码而 pandas 默认按 UTF-8 解码两者不匹配导致乱码或解码失败。这种情况在这类直接下载下来的 CSV 里非常常见。解决加载时显式指定encodinggbk或encodinggb18030也可以先用文本编辑器打开文件另存为 UTF-8。项目里 loader 默认走 UTF-8如果遇到乱码优先检查文件真实编码而不是在 data_loader.py 里盲目换配置。5.2 groupby 之后分组列神秘消失现象对 DataFrame 执行groupby([region, category]).agg(...)后打印结果发现 region 和 category 列不见了只留下聚合后的数值列和索引。原因pandas 的 groupby 默认把分组列提升为索引。不熟悉这个行为的人后续想用df[region]就会直接报 KeyError。解决在 agg 之后加一行.reset_index()或者分组时传入as_indexFalse。我在 analyzer.py 里已经处理过这个问题如果你自己写分组逻辑记得补上这一步。5.3 日期解析后类型还是 object现象代码里明明加了parse_dates[order_date]但df[order_date].dtype返回的还是dtype(O)而不是 datetime 类型。原因数据列里混入了多个日期格式pandas 的默认日期推断没能统一识别要么全列变成字符串。更隐蔽的情况是列中有空字符串会导致整列推断失败。解决先用pd.to_datetime(df[order_date], formatmixed)手动转换formatmixed 是 pandas 2.0 之后支持的模式。如果混入的格式太多可以用 error 参数排查具体行pd.to_datetime(col, errorscoerce)然后看哪一行转换后变成 NaT。5.4 pyecharts 图表渲染出来是空白现象HTML 文件生成了浏览器打开后图表区域一片空白控制台报 echarts 相关的加载错误。原因pyecharts 生成的 HTML 默认从 CDN 引入 echarts 脚本内网环境或离线状态下资源加载失败图表自然渲染不出来。解决下载 echarts.min.js 放到本地在Bar()实例化前执行bar Bar(init_optsopts.InitOpts(js_host./static/))指定本地资源目录。如果你只是本地跑通演示直接联外网打开 HTML 也没问题。5.5 保存的图片模糊不清现象图表保存到 output 目录后放大看文字边缘发虚线条也是糊的。原因savefig默认 dpi 沿用画布设置通常只有 100。如果画布尺寸较大但文字偏小导出后分辨率不够。解决在每张图的 savefig 调用中显式指定dpi150或更高。这个项目里我已经在关键保存点加了 dpi 参数你扩展新图表时记得带上。发布报告用的图建议 200 dpi屏幕上预览 120 足够。6. 二次开发把固定流程改造成可配置的数据看板这套工程最值得改的地方是把写死的 CSV 读取改成参数化入口让数据源可切换。我建议从 data_loader.py 下手给它加一个简单的工厂函数def load_from_source(source_type: str, source_path: str): if source_type csv: return load_order_data(source_path) elif source_type excel: return pd.read_excel(source_path, sheet_nameSheet1) elif source_type sql: import sqlite3 conn sqlite3.connect(source_path) return pd.read_sql(SELECT * FROM orders, conn) else: raise ValueError(f不支持的来源: {source_type})改完 loader再去 analyzer.py 里把聚合字段也参数化。比如把 groupby 的键和 agg 字典都提取成配置项放在 config.py 里这样换一批数据只需要改配置不用动核心代码。这是工程化思维和数据脚本思维的分水岭也是你从“能跑”走向“能维护”的一步。如果你想让图表更贴近汇报场景还可以把 output 目录做成时间戳快照每次运行拷贝一份带日期的报告。改动不大但团队协作时能省很多沟通成本。我习惯用datetime.now().strftime(%Y%m%d_%H%M%S)拼一个子目录老报告归档不覆盖。这套项目我拆完之后最大的感受是数据分析求的不是花哨的模型而是把每一步做扎实。从那以后我每次接手新数据集都强制自己走一遍 loader → cleaner → analyzer → visualizer 的标准流程先把数据摸透再做结论翻车的概率确实低了很多。希望这份解析能帮到你少走几步我曾经绕过的弯路。本文还有配套的精品资源点击获取