简介:这份资源面向希望系统掌握Python数据分析与可视化的学习者,覆盖从编程基础到实战项目的完整技能链路,适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件,约31.27MB,以67个ipynb实验代码、11个pptx课件、10个csv与6个xls数据集为主,辅以tif、jpg图表素材及doc、pdf教学文档,兼顾理论讲解与动手实践。内容从数据分析与可视化概述切入,依次讲解Python编程基础、Pandas统计分析、数据载入与预处理,再深入Matplotlib、Seaborn绘图及pyecharts交互式图表,并配套iris、红酒品质、学生成绩等真实数据集供练习。已有18154人学习下载,读者可借助课件与源码复现数据清洗、统计分析与多库可视化全流程,积累可迁移的项目经验。1. 从一份能跑的数据分析源码说起:为什么我建议你从拆项目开始学很多人学 Python 数据分析,卡在同一个地方:pandas 的 API 背了一堆,matplotlib 的参数也查了不少,但真给一份 CSV,还是不知道从哪下手。问题不在语法,在于你从来没有完整地跑通过一个真实项目。这份「Python 数据分析与可视化」源码包,就是冲着这个痛点来的——它把数据读取、清洗、统计分析、可视化出图串成了一条完整链路,每个环节都有可运行的代码,不是零散的知识点拼盘。适合谁?刚学完 pandas 基础想找个练手项目的,或者工作中需要快速搭一套数据分析脚本、不想从零造轮子的开发者。你拿到手就能跑,跑完就能改,改完就能用到自己的数据上。2. 项目结构拆解:从数据文件到图表输出的完整目录2.1 目录布局与模块职责拿到源码包,第一件事不是急着跑,而是先看目录结构。这个项目的组织方式很典型,适合作为数据分析类项目的模板来理解。常见做法是按功能分层,大致长这样:data_analysis_project/ ├── data/ # 原始数据与清洗后数据 │ ├── raw/ # 原始 CSV、Excel 文件 │ └── processed/ # 清洗后的中间数据 ├── src/ # 核心代码 │ ├── data_loader.py # 数据读取与初步检查 │ ├── data_cleaner.py # 缺失值、异常值、类型转换 │ ├── analyzer.py # 统计分析与聚合计算 │ └── visualizer.py # 图表绘制与导出 ├── output/ # 图表、报告输出目录 │ ├── figures/ # PNG、HTML 图表 │ └── reports/ # 导出的统计结果 ├── config.py # 路径、参数、常量配置 ├── main.py # 主流程入口 └── requirements.txt # 依赖清单这个结构的好处是职责清晰:data_loader只管读进来,data_cleaner只管洗干净,analyzer只管算,visualizer只管画。你改任何一个环节,不会牵连其他模块。我一般会建议新手先把main.py从头到尾读一遍,看清楚数据是怎么从data/raw流到output/figures的,这条链路理解了,整个项目就通了。2.2 依赖清单与运行环境requirements.txt里通常包含这几个核心库:pandas做数据处理,numpy做数值计算,matplotlib和seaborn做静态图表,plotly做交互式图表。版本方面,pandas 建议 1.5 以上,matplotlib 3.5 以上,避免一些旧版本的 API 兼容问题。安装依赖的命令很直接:# 创建虚拟环境(推荐,避免污染全局包) python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 安装依赖 pip install -r requirements.txt这里有个细节值得注意:如果你用的是 macOS 的 M 系列芯片,某些旧版本的 numpy 可能需要 Rosetta 才能装,直接pip install报错的话,先升级 pip 再试。另外,plotly如果只是导出静态图片,还需要额外装kaleido,这个在 requirements 里不一定有,跑的时候报错再补装就行。2.3 配置文件的设计思路config.py这个文件容易被忽略,但它决定了项目能不能灵活复用到你自己的数据上。典型内容长这样:# config.py - 全局配置 import os # 基础路径 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_RAW_DIR = os.path.join(BASE_DIR, 'data', 'raw') DATA_PROCESSED_DIR = os.path.join(BASE_DIR, 'data', 'processed') OUTPUT_FIGURES_DIR = os.path.join(BASE_DIR, 'output', 'figures') OUTPUT_REPORTS_DIR = os.path.join(BASE_DIR, 'output', 'reports') # 数据文件配置 RAW_DATA_FILE = 'sales_data.csv' # 原始数据文件名 ENCODING = 'utf-8' # 文件编码,中文数据常用 gbk DATE_COLUMNS = ['order_date', 'ship_date'] # 需要解析为日期的列 # 可视化配置 FIGURE_DPI = 150 # 图片分辨率 FIGURE_SIZE = (12, 6) # 默认画布尺寸 COLOR_PALETTE = 'viridis' # 配色方案把路径和参数抽到配置文件里,好处是你换一份数据只需要改RAW_DATA_FILE和DATE_COLUMNS,不用去翻每个模块的代码。我见过太多人把路径硬编码在函数里,换个环境就跑不起来,这种坑提前避开。3. 核心模块逐段拆:数据读取、清洗、分析与可视化3.1 数据读取与初步体检data_loader.py是整个流程的入口,它的任务不只是pd.read_csv()一行搞定,还要做初步的数据体检。常见写法:# src/data_loader.py import pandas as pd import config def load_data(file_path=None): """读取原始数据,返回 DataFrame""" if file_path is None: file_path = f"{config.DATA_RAW_DIR}/{config.RAW_DATA_FILE}" # 读取 CSV,指定编码 df = pd.read_csv(file_path, encoding=config.ENCODING) # 日期列自动解析 for col in config.DATE_COLUMNS: if col in df.columns: df[col] = pd.to_datetime(df[col], errors='coerce') return df def inspect_data(df): """数据体检:形状、类型、缺失、重复""" print(f"数据形状: {df.shape}") print(f"\n列类型:\n{df.dtypes}") print(f"\n缺失值统计:\n{df.isnull().sum()}") print(f"\n重复行数: {df.duplicated().sum()}") print(f"\n前5行预览:\n{df.head()}") return { 'shape': df.shape, 'missing': df.isnull().sum().to_dict(), 'duplicates': df.duplicated().sum() }这段代码的关键点:pd.to_datetime加了errors='coerce',遇到无法解析的日期不会直接报错,而是转成NaT,后续清洗时统一处理。inspect_data返回一个字典,方便在main.py里做判断——比如缺失率超过 50% 的列,可能直接考虑丢弃。参数说明:encoding这个参数很关键,中文数据用utf-8读出来乱码的话,换成gbk或gb18030试试。DATE_COLUMNS在 config 里配置,不同数据集的日期列名不一样,改配置就行。3.2 数据清洗:缺失值、异常值与类型转换data_cleaner.py是工作量最大的模块,也是最能体现分析水平的地方。清洗策略没有标准答案,取决于业务场景。常见做法:# src/data_cleaner.py import pandas as pd import numpy as np def handle_missing(df, strategy='auto'): """缺失值处理""" df_clean = df.copy() for col in df_clean.columns: missing_rate = df_clean[col].isnull().mean() if missing_rate == 0: continue elif missing_rate > 0.5: # 缺失超过一半,直接丢弃该列 df_clean.drop(columns=[col], inplace=True) print(f"丢弃列 {col},缺失率 {missing_rate:.2%}") elif df_clean[col].dtype in ['float64', 'int64']: # 数值列用中位数填充 median_val = df_clean[col].median() df_clean[col].fillna(median_val, inplace=True) else: # 类别列用众数填充 mode_val = df_clean[col].mode()[0] df_clean[col].fillna(mode_val, inplace=True) return df_clean def remove_outliers(df, columns, method='iqr'): """异常值处理:IQR 法""" df_clean = df.copy() for col in columns: if method == 'iqr': Q1 = df_clean[col].quantile(0.25) Q3 = df_clean[col].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR # 用边界值截断,而不是直接删除 df_clean[col] = df_clean[col].clip(lower, upper) return df_clean逻辑说明:缺失值处理分三种情况——缺失率过高直接丢列,数值列用中位数填充(比均值更抗异常值),类别列用众数填充。异常值处理用 IQR 法,但注意这里用的是clip截断而不是删除行,因为删除行可能导致样本量骤减,截断保留了样本但限制了极端值的影响。参数说明:strategy='auto'是预留的扩展点,你可以改成'drop'直接删缺失行,或者'interpolate'做插值。columns参数指定对哪些列做异常值处理,不是所有数值列都需要,比如 ID 列就不需要。3.3 统计分析:分组聚合与透视表analyzer.py负责从清洗后的数据里提取业务洞察。最常用的两个工具是groupby和pivot_table:# src/analyzer.py import pandas as pd def group_summary(df, group_col, value_col, agg_funcs=None): """分组聚合统计""" if agg_funcs is None: agg_funcs = ['sum', 'mean', 'count', 'std'] result = df.groupby(group_col)[value_col].agg(agg_funcs) result.columns = [f"{value_col}_{func}" for func in agg_funcs] return result.reset_index() def pivot_analysis(df, index, columns, values, aggfunc='sum'): """透视表分析""" pivot = pd.pivot_table( df, index=index, columns=columns, values=values, aggfunc=aggfunc, fill_value=0, margins=True, # 添加合计行/列 margins_name='合计' ) return pivot def time_series_trend(df, date_col, value_col, freq='M'): """时间趋势分析""" df_ts = df.set_index(date_col) trend = df_ts[value_col].resample(freq).sum() return trend.reset_index()逻辑说明:group_summary一次性输出多个统计量,避免反复 groupby。pivot_analysis加了margins=True自动算合计,fill_value=0处理透视后的空值。time_series_trend用resample做时间重采样,freq='M'按月,改成'W'按周,'Q'按季度。参数说明:agg_funcs可以自定义,比如只关心['sum', 'mean']就传这个列表。freq参数遵循 pandas 的 offset 别名,常用'D'(日)、'W'(周)、'M'(月)、'Q'(季)、'Y'(年)。3.4 可视化:从静态图到交互式图表visualizer.py是最终出成果的地方。这个项目通常同时支持 matplotlib/seaborn 静态图和 plotly 交互图:# src/visualizer.py import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import config # 设置中文字体,避免中文乱码 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False def plot_bar(df, x_col, y_col, title='柱状图', save=True): """柱状图""" fig, ax = plt.subplots(figsize=config.FIGURE_SIZE) sns.barplot(data=df, x=x_col, y=y_col, ax=ax, palette=config.COLOR_PALETTE) ax.set_title(title, fontsize=14) ax.set_xlabel(x_col) ax.set_ylabel(y_col) plt.xticks(rotation=45, ha='right') plt.tight_layout() if save: path = f"{config.OUTPUT_FIGURES_DIR}/{title}.png" plt.savefig(path, dpi=config.FIGURE_DPI, bbox_inches='tight') print(f"图表已保存: {path}") plt.close() return fig def plot_interactive_trend(df, date_col, value_col, title='趋势图'): """交互式趋势图(plotly)""" fig = px.line( df, x=date_col, y=value_col, title=title, markers=True, template='plotly_white' ) fig.update_layout( font=dict(size=12), hovermode='x unified' ) # 导出为 HTML path = f"{config.OUTPUT_FIGURES_DIR}/{title}.html" fig.write_html(path) print(f"交互图表已保存: {path}") return fig逻辑说明:matplotlib 部分设置了中文字体,这是中文数据可视化的必备步骤,不设置的话标题和标签全是方块。plot_bar用 seaborn 封装,代码更简洁。plot_interactive_trend用 plotly 生成 HTML,可以直接在浏览器里缩放、悬停查看数值。参数说明:palette控制配色,seaborn 支持的方案有'viridis'、'Set2'、'coolwarm'等。bbox_inches='tight'防止保存时标签被截断。hovermode='x unified'让悬停时同时显示同一 X 轴上的所有数值。4. 避坑与排查:跑这份源码时最容易翻车的几个地方4.1 中文乱码:从数据读取到图表显示现象:CSV 读进来列名或内容全是乱码,或者图表标题显示为方块。原因:数据文件编码不是 UTF-8,或者 matplotlib 没有配置中文字体。解决:读取时指定encoding='gbk'或encoding='gb18030'。图表乱码则在visualizer.py开头加plt.rcParams['font.sans-serif'] = ['SimHei'],macOS 上换成['Arial Unicode MS']。如果还不行,检查系统是否装了对应字体。4.2 日期解析失败导致时间序列断裂现象:pd.to_datetime报错,或者解析后大量NaT,时间趋势图出现断点。原因:日期格式不统一,比如有的写2024-01-01,有的写01/01/2024。解决:先用pd.to_datetime(df[col], errors='coerce')强制转换,然后检查NaT的比例。如果比例高,用format参数指定格式,或者写自定义解析函数逐条处理。我一般会先把日期列的前 20 个唯一值打印出来看一眼,确认格式再批量转。4.3 内存溢出:大文件读取的正确姿势现象:读一个几百 MB 的 CSV 直接卡死或报MemoryError。原因:默认pd.read_csv会把整个文件加载到内存,且自动推断类型可能把整数读成 float64,内存翻倍。解决:用chunksize分块读取,或者指定dtype参数。常见做法是:# 分块读取,每块 10 万行 chunks = pd.read_csv('big_file.csv', chunksize=100000) result = pd.concat([chunk.groupby('category')['value'].sum() for chunk in chunks])另外,只读需要的列:pd.read_csv('file.csv', usecols=['col1', 'col2']),能省不少内存。4.4 图表保存空白或尺寸不对现象:plt.savefig()保存出来的图片是空白的,或者尺寸和预期不符。原因:在plt.show()之后调用savefig,此时画布已被清空;或者figsize设置后被tight_layout覆盖。解决:先savefig再show,或者直接用fig.savefig()。尺寸问题检查figsize和dpi的乘积是否合理,bbox_inches='tight'会自动裁剪空白边缘,但可能导致尺寸略小于设定值。4.5 plotly 导出静态图片报错现象:fig.write_image()报错提示缺少 kaleido。原因:plotly 导出 PNG 需要 kaleido 引擎,requirements 里不一定包含。解决:pip install kaleido。如果还报错,检查 plotly 版本,旧版本可能需要降级 kaleido。实在不行就导出 HTML,用浏览器打开后截图,虽然土但管用。5. 二次开发:把这套源码改成你自己的分析工具5.1 替换数据源的最小改动路径拿到这份源码后,最直接的改造就是换成你自己的数据。改动集中在三个地方:config.py里的RAW_DATA_FILE和DATE_COLUMNS,data_cleaner.py里的清洗策略,以及analyzer.py里的分组字段和聚合函数。我一般会先跑一遍原始数据,看inspect_data的输出,根据缺失率和列类型决定清洗方案,而不是一上来就套模板。5.2 扩展新的分析维度如果你想加一个同比/环比计算,可以在analyzer.py里加一个函数:def add_yoy_mom(df, date_col, value_col): """添加同比(YoY)和环比(MoM)列""" df = df.sort_values(date_col).copy() df['MoM'] = df[value_col].pct_change(periods=1) # 环比 df['YoY'] = df[value_col].pct_change(periods=12) # 同比(假设月度数据) return df这个函数的periods参数取决于数据频率,月度数据同比是 12,季度数据是 4,日度数据是 365。改的时候注意对齐。5.3 把分析流程封装成命令行工具如果想让非技术同事也能用,可以用argparse把main.py包装成命令行工具:# main.py 改造示例 import argparse from src.data_loader import load_data, inspect_data from src.data_cleaner import handle_missing, remove_outliers from src.analyzer import group_summary from src.visualizer import plot_bar def main(): parser = argparse.ArgumentParser(description='数据分析工具') parser.add_argument('--file', type=str, help='数据文件路径') parser.add_argument('--group', type=str, default='category', help='分组列') parser.add_argument('--value', type=str, default='amount', help='数值列') args = parser.parse_args() df = load_data(args.file) inspect_data(df) df = handle_missing(df) df = remove_outliers(df, [args.value]) summary = group_summary(df, args.group, args.value) plot_bar(summary, args.group, f"{args.value}_sum", title='分组汇总') print(summary) if __name__ == '__main__': main()这样同事只需要跑python main.py --file data/raw/sales.csv --group region --value revenue就能出图,不用改代码。5.4 一个我踩过的坑:别在清洗阶段丢原始数据最后说一个血泪教训。我早期做数据分析时,习惯在handle_missing里直接inplace=True修改原始 DataFrame,结果后面想换个填充策略重新跑,发现原始数据已经被覆盖了,只能重新读文件。从那以后我每次做数据清洗都强制走一遍「先 copy 再操作」的流程,df_clean = df.copy()这行代码从来不省。另外,清洗后的数据我会单独存一份到data/processed/,而不是每次从原始数据重新跑一遍清洗,这样调试分析逻辑时能省不少时间。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源面向希望系统掌握Python数据分析与可视化的学习者,覆盖从编程基础到实战项目的完整技能链路,适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件,约31.27MB,以67个ipynb实验代码、11个pptx课件、10个csv与6个xls数据集为主,辅以tif、jpg图表素材及doc、pdf教学文档,兼顾理论讲解与动手实践。内容从数据分析与可视化概述切入,依次讲解Python编程基础、Pandas统计分析、数据载入与预处理,再深入Matplotlib、Seaborn绘图及pyecharts交互式图表,并配套iris、红酒品质、学生成绩等真实数据集供练习。已有18154人学习下载,读者可借助课件与源码复现数据清洗、统计分析与多库可视化全流程,积累可迁移的项目经验。1. 从一份能跑的数据分析源码说起:为什么我建议你从拆项目开始学很多人学 Python 数据分析,卡在同一个地方:pandas 的 API 背了一堆,matplotlib 的参数也查了不少,但真给一份 CSV,还是不知道从哪下手。问题不在语法,在于你从来没有完整地跑通过一个真实项目。这份「Python 数据分析与可视化」源码包,就是冲着这个痛点来的——它把数据读取、清洗、统计分析、可视化出图串成了一条完整链路,每个环节都有可运行的代码,不是零散的知识点拼盘。适合谁?刚学完 pandas 基础想找个练手项目的,或者工作中需要快速搭一套数据分析脚本、不想从零造轮子的开发者。你拿到手就能跑,跑完就能改,改完就能用到自己的数据上。2. 项目结构拆解:从数据文件到图表输出的完整目录2.1 目录布局与模块职责拿到源码包,第一件事不是急着跑,而是先看目录结构。这个项目的组织方式很典型,适合作为数据分析类项目的模板来理解。常见做法是按功能分层,大致长这样:data_analysis_project/ ├── data/ # 原始数据与清洗后数据 │ ├── raw/ # 原始 CSV、Excel 文件 │ └── processed/ # 清洗后的中间数据 ├── src/ # 核心代码 │ ├── data_loader.py # 数据读取与初步检查 │ ├── data_cleaner.py # 缺失值、异常值、类型转换 │ ├── analyzer.py # 统计分析与聚合计算 │ └── visualizer.py # 图表绘制与导出 ├── output/ # 图表、报告输出目录 │ ├── figures/ # PNG、HTML 图表 │ └── reports/ # 导出的统计结果 ├── config.py # 路径、参数、常量配置 ├── main.py # 主流程入口 └── requirements.txt # 依赖清单这个结构的好处是职责清晰:data_loader只管读进来,data_cleaner只管洗干净,analyzer只管算,visualizer只管画。你改任何一个环节,不会牵连其他模块。我一般会建议新手先把main.py从头到尾读一遍,看清楚数据是怎么从data/raw流到output/figures的,这条链路理解了,整个项目就通了。2.2 依赖清单与运行环境requirements.txt里通常包含这几个核心库:pandas做数据处理,numpy做数值计算,matplotlib和seaborn做静态图表,plotly做交互式图表。版本方面,pandas 建议 1.5 以上,matplotlib 3.5 以上,避免一些旧版本的 API 兼容问题。安装依赖的命令很直接:# 创建虚拟环境(推荐,避免污染全局包) python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 安装依赖 pip install -r requirements.txt这里有个细节值得注意:如果你用的是 macOS 的 M 系列芯片,某些旧版本的 numpy 可能需要 Rosetta 才能装,直接pip install报错的话,先升级 pip 再试。另外,plotly如果只是导出静态图片,还需要额外装kaleido,这个在 requirements 里不一定有,跑的时候报错再补装就行。2.3 配置文件的设计思路config.py这个文件容易被忽略,但它决定了项目能不能灵活复用到你自己的数据上。典型内容长这样:# config.py - 全局配置 import os # 基础路径 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_RAW_DIR = os.path.join(BASE_DIR, 'data', 'raw') DATA_PROCESSED_DIR = os.path.join(BASE_DIR, 'data', 'processed') OUTPUT_FIGURES_DIR = os.path.join(BASE_DIR, 'output', 'figures') OUTPUT_REPORTS_DIR = os.path.join(BASE_DIR, 'output', 'reports') # 数据文件配置 RAW_DATA_FILE = 'sales_data.csv' # 原始数据文件名 ENCODING = 'utf-8' # 文件编码,中文数据常用 gbk DATE_COLUMNS = ['order_date', 'ship_date'] # 需要解析为日期的列 # 可视化配置 FIGURE_DPI = 150 # 图片分辨率 FIGURE_SIZE = (12, 6) # 默认画布尺寸 COLOR_PALETTE = 'viridis' # 配色方案把路径和参数抽到配置文件里,好处是你换一份数据只需要改RAW_DATA_FILE和DATE_COLUMNS,不用去翻每个模块的代码。我见过太多人把路径硬编码在函数里,换个环境就跑不起来,这种坑提前避开。3. 核心模块逐段拆:数据读取、清洗、分析与可视化3.1 数据读取与初步体检data_loader.py是整个流程的入口,它的任务不只是pd.read_csv()一行搞定,还要做初步的数据体检。常见写法:# src/data_loader.py import pandas as pd import config def load_data(file_path=None): """读取原始数据,返回 DataFrame""" if file_path is None: file_path = f"{config.DATA_RAW_DIR}/{config.RAW_DATA_FILE}" # 读取 CSV,指定编码 df = pd.read_csv(file_path, encoding=config.ENCODING) # 日期列自动解析 for col in config.DATE_COLUMNS: if col in df.columns: df[col] = pd.to_datetime(df[col], errors='coerce') return df def inspect_data(df): """数据体检:形状、类型、缺失、重复""" print(f"数据形状: {df.shape}") print(f"\n列类型:\n{df.dtypes}") print(f"\n缺失值统计:\n{df.isnull().sum()}") print(f"\n重复行数: {df.duplicated().sum()}") print(f"\n前5行预览:\n{df.head()}") return { 'shape': df.shape, 'missing': df.isnull().sum().to_dict(), 'duplicates': df.duplicated().sum() }这段代码的关键点:pd.to_datetime加了errors='coerce',遇到无法解析的日期不会直接报错,而是转成NaT,后续清洗时统一处理。inspect_data返回一个字典,方便在main.py里做判断——比如缺失率超过 50% 的列,可能直接考虑丢弃。参数说明:encoding这个参数很关键,中文数据用utf-8读出来乱码的话,换成gbk或gb18030试试。DATE_COLUMNS在 config 里配置,不同数据集的日期列名不一样,改配置就行。3.2 数据清洗:缺失值、异常值与类型转换data_cleaner.py是工作量最大的模块,也是最能体现分析水平的地方。清洗策略没有标准答案,取决于业务场景。常见做法:# src/data_cleaner.py import pandas as pd import numpy as np def handle_missing(df, strategy='auto'): """缺失值处理""" df_clean = df.copy() for col in df_clean.columns: missing_rate = df_clean[col].isnull().mean() if missing_rate == 0: continue elif missing_rate > 0.5: # 缺失超过一半,直接丢弃该列 df_clean.drop(columns=[col], inplace=True) print(f"丢弃列 {col},缺失率 {missing_rate:.2%}") elif df_clean[col].dtype in ['float64', 'int64']: # 数值列用中位数填充 median_val = df_clean[col].median() df_clean[col].fillna(median_val, inplace=True) else: # 类别列用众数填充 mode_val = df_clean[col].mode()[0] df_clean[col].fillna(mode_val, inplace=True) return df_clean def remove_outliers(df, columns, method='iqr'): """异常值处理:IQR 法""" df_clean = df.copy() for col in columns: if method == 'iqr': Q1 = df_clean[col].quantile(0.25) Q3 = df_clean[col].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR # 用边界值截断,而不是直接删除 df_clean[col] = df_clean[col].clip(lower, upper) return df_clean逻辑说明:缺失值处理分三种情况——缺失率过高直接丢列,数值列用中位数填充(比均值更抗异常值),类别列用众数填充。异常值处理用 IQR 法,但注意这里用的是clip截断而不是删除行,因为删除行可能导致样本量骤减,截断保留了样本但限制了极端值的影响。参数说明:strategy='auto'是预留的扩展点,你可以改成'drop'直接删缺失行,或者'interpolate'做插值。columns参数指定对哪些列做异常值处理,不是所有数值列都需要,比如 ID 列就不需要。3.3 统计分析:分组聚合与透视表analyzer.py负责从清洗后的数据里提取业务洞察。最常用的两个工具是groupby和pivot_table:# src/analyzer.py import pandas as pd def group_summary(df, group_col, value_col, agg_funcs=None): """分组聚合统计""" if agg_funcs is None: agg_funcs = ['sum', 'mean', 'count', 'std'] result = df.groupby(group_col)[value_col].agg(agg_funcs) result.columns = [f"{value_col}_{func}" for func in agg_funcs] return result.reset_index() def pivot_analysis(df, index, columns, values, aggfunc='sum'): """透视表分析""" pivot = pd.pivot_table( df, index=index, columns=columns, values=values, aggfunc=aggfunc, fill_value=0, margins=True, # 添加合计行/列 margins_name='合计' ) return pivot def time_series_trend(df, date_col, value_col, freq='M'): """时间趋势分析""" df_ts = df.set_index(date_col) trend = df_ts[value_col].resample(freq).sum() return trend.reset_index()逻辑说明:group_summary一次性输出多个统计量,避免反复 groupby。pivot_analysis加了margins=True自动算合计,fill_value=0处理透视后的空值。time_series_trend用resample做时间重采样,freq='M'按月,改成'W'按周,'Q'按季度。参数说明:agg_funcs可以自定义,比如只关心['sum', 'mean']就传这个列表。freq参数遵循 pandas 的 offset 别名,常用'D'(日)、'W'(周)、'M'(月)、'Q'(季)、'Y'(年)。3.4 可视化:从静态图到交互式图表visualizer.py是最终出成果的地方。这个项目通常同时支持 matplotlib/seaborn 静态图和 plotly 交互图:# src/visualizer.py import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import config # 设置中文字体,避免中文乱码 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False def plot_bar(df, x_col, y_col, title='柱状图', save=True): """柱状图""" fig, ax = plt.subplots(figsize=config.FIGURE_SIZE) sns.barplot(data=df, x=x_col, y=y_col, ax=ax, palette=config.COLOR_PALETTE) ax.set_title(title, fontsize=14) ax.set_xlabel(x_col) ax.set_ylabel(y_col) plt.xticks(rotation=45, ha='right') plt.tight_layout() if save: path = f"{config.OUTPUT_FIGURES_DIR}/{title}.png" plt.savefig(path, dpi=config.FIGURE_DPI, bbox_inches='tight') print(f"图表已保存: {path}") plt.close() return fig def plot_interactive_trend(df, date_col, value_col, title='趋势图'): """交互式趋势图(plotly)""" fig = px.line( df, x=date_col, y=value_col, title=title, markers=True, template='plotly_white' ) fig.update_layout( font=dict(size=12), hovermode='x unified' ) # 导出为 HTML path = f"{config.OUTPUT_FIGURES_DIR}/{title}.html" fig.write_html(path) print(f"交互图表已保存: {path}") return fig逻辑说明:matplotlib 部分设置了中文字体,这是中文数据可视化的必备步骤,不设置的话标题和标签全是方块。plot_bar用 seaborn 封装,代码更简洁。plot_interactive_trend用 plotly 生成 HTML,可以直接在浏览器里缩放、悬停查看数值。参数说明:palette控制配色,seaborn 支持的方案有'viridis'、'Set2'、'coolwarm'等。bbox_inches='tight'防止保存时标签被截断。hovermode='x unified'让悬停时同时显示同一 X 轴上的所有数值。4. 避坑与排查:跑这份源码时最容易翻车的几个地方4.1 中文乱码:从数据读取到图表显示现象:CSV 读进来列名或内容全是乱码,或者图表标题显示为方块。原因:数据文件编码不是 UTF-8,或者 matplotlib 没有配置中文字体。解决:读取时指定encoding='gbk'或encoding='gb18030'。图表乱码则在visualizer.py开头加plt.rcParams['font.sans-serif'] = ['SimHei'],macOS 上换成['Arial Unicode MS']。如果还不行,检查系统是否装了对应字体。4.2 日期解析失败导致时间序列断裂现象:pd.to_datetime报错,或者解析后大量NaT,时间趋势图出现断点。原因:日期格式不统一,比如有的写2024-01-01,有的写01/01/2024。解决:先用pd.to_datetime(df[col], errors='coerce')强制转换,然后检查NaT的比例。如果比例高,用format参数指定格式,或者写自定义解析函数逐条处理。我一般会先把日期列的前 20 个唯一值打印出来看一眼,确认格式再批量转。4.3 内存溢出:大文件读取的正确姿势现象:读一个几百 MB 的 CSV 直接卡死或报MemoryError。原因:默认pd.read_csv会把整个文件加载到内存,且自动推断类型可能把整数读成 float64,内存翻倍。解决:用chunksize分块读取,或者指定dtype参数。常见做法是:# 分块读取,每块 10 万行 chunks = pd.read_csv('big_file.csv', chunksize=100000) result = pd.concat([chunk.groupby('category')['value'].sum() for chunk in chunks])另外,只读需要的列:pd.read_csv('file.csv', usecols=['col1', 'col2']),能省不少内存。4.4 图表保存空白或尺寸不对现象:plt.savefig()保存出来的图片是空白的,或者尺寸和预期不符。原因:在plt.show()之后调用savefig,此时画布已被清空;或者figsize设置后被tight_layout覆盖。解决:先savefig再show,或者直接用fig.savefig()。尺寸问题检查figsize和dpi的乘积是否合理,bbox_inches='tight'会自动裁剪空白边缘,但可能导致尺寸略小于设定值。4.5 plotly 导出静态图片报错现象:fig.write_image()报错提示缺少 kaleido。原因:plotly 导出 PNG 需要 kaleido 引擎,requirements 里不一定包含。解决:pip install kaleido。如果还报错,检查 plotly 版本,旧版本可能需要降级 kaleido。实在不行就导出 HTML,用浏览器打开后截图,虽然土但管用。5. 二次开发:把这套源码改成你自己的分析工具5.1 替换数据源的最小改动路径拿到这份源码后,最直接的改造就是换成你自己的数据。改动集中在三个地方:config.py里的RAW_DATA_FILE和DATE_COLUMNS,data_cleaner.py里的清洗策略,以及analyzer.py里的分组字段和聚合函数。我一般会先跑一遍原始数据,看inspect_data的输出,根据缺失率和列类型决定清洗方案,而不是一上来就套模板。5.2 扩展新的分析维度如果你想加一个同比/环比计算,可以在analyzer.py里加一个函数:def add_yoy_mom(df, date_col, value_col): """添加同比(YoY)和环比(MoM)列""" df = df.sort_values(date_col).copy() df['MoM'] = df[value_col].pct_change(periods=1) # 环比 df['YoY'] = df[value_col].pct_change(periods=12) # 同比(假设月度数据) return df这个函数的periods参数取决于数据频率,月度数据同比是 12,季度数据是 4,日度数据是 365。改的时候注意对齐。5.3 把分析流程封装成命令行工具如果想让非技术同事也能用,可以用argparse把main.py包装成命令行工具:# main.py 改造示例 import argparse from src.data_loader import load_data, inspect_data from src.data_cleaner import handle_missing, remove_outliers from src.analyzer import group_summary from src.visualizer import plot_bar def main(): parser = argparse.ArgumentParser(description='数据分析工具') parser.add_argument('--file', type=str, help='数据文件路径') parser.add_argument('--group', type=str, default='category', help='分组列') parser.add_argument('--value', type=str, default='amount', help='数值列') args = parser.parse_args() df = load_data(args.file) inspect_data(df) df = handle_missing(df) df = remove_outliers(df, [args.value]) summary = group_summary(df, args.group, args.value) plot_bar(summary, args.group, f"{args.value}_sum", title='分组汇总') print(summary) if __name__ == '__main__': main()这样同事只需要跑python main.py --file data/raw/sales.csv --group region --value revenue就能出图,不用改代码。5.4 一个我踩过的坑:别在清洗阶段丢原始数据最后说一个血泪教训。我早期做数据分析时,习惯在handle_missing里直接inplace=True修改原始 DataFrame,结果后面想换个填充策略重新跑,发现原始数据已经被覆盖了,只能重新读文件。从那以后我每次做数据清洗都强制走一遍「先 copy 再操作」的流程,df_clean = df.copy()这行代码从来不省。另外,清洗后的数据我会单独存一份到data/processed/,而不是每次从原始数据重新跑一遍清洗,这样调试分析逻辑时能省不少时间。希望帮到你。本文还有配套的精品资源,点击获取