一直有一个现象让我挺困惑的很多人学 Python 数据分析买书、看视频、收藏一堆教程最后打开电脑还是不知道从哪下手。问题不是出在某个函数不会写而是缺一条完整的链路——从拿到一份原始数据到中间怎么清洗、怎么分析、怎么画图再到最后怎么把结果整理成一份能交出去的报告。这条链路没人带着走一遍知识就永远是散的。这篇内容就是想补上这个缺口。这篇文章特别适合刚学完 Python 基础语法、想往数据分析方向走的新手也适合那些在 Excel 里处理数据已经觉得吃力、想换个工具提高效率的职场人。我会用一套模拟的订单数据带着你完整走一遍“数据 → 清洗 → 分析 → 可视化 → 报告”的流程。每个步骤我都会解释为什么这样做而不只是丢给你一段能跑的代码。1. 开工之前环境装对了后面能少踩一半坑1.1 别盲目装最新版 Python版本选择的现实逻辑很多新手的第一反应是去官网下载最新的 Python 3.13觉得越新越好。我的建议是如果你只是学数据分析装 Python 3.11 或 3.12 就足够别追求最新。原因有两点。第一数据分析生态里最核心的 pandas、numpy、scikit-learn 这些库对新版本 Python 的适配通常有滞后期。你装了 3.13结果某个库还没有对应版本的预编译包pip 安装的时候就会现场编译轻则等半天重则报一堆看不懂的红色错误。第二很多教程和第三方工具默认环境还是 3.10 或者 3.11版本差太多照着敲代码容易踩到莫名其妙的兼容问题。如果你电脑里已经装了新版本也不用推倒重来后面直接用虚拟环境锁定 Python 版本就行。1.2 用 Anaconda 还是纯 Python pip这个选择题我纠结过很久也折腾过两条路。现在给新手的建议非常明确直接装 Anaconda。我知道有人会说 Anaconda 体积大、占用空间多这个批评没错但它给新手带来的好处远大于代价。Anaconda 自带 conda 包管理器可以一句命令创建一个独立的环境把 Python 版本、所有数据分析相关的库一次性装好不用自己一个个 pip install也不会把系统自带的 Python 环境弄乱。更重要的是它自带 Jupyter Notebook这是数据分析场景最好用的交互式编程环境后面几乎天天要用。如果你实在不想用 Anaconda那纯 Python pip 的路子也能走通需要手动安装以下几样东西Python建议 3.11 版本Jupyter Notebook 或 Jupyter Labpandas、numpy、matplotlib、seaborn、openpyxl 这几个核心库但我还是要说一句新手阶段别在环境配置上消耗太多热情。我见过太多人第一天学 Python装了三天环境第四天放弃了。Anaconda 的价值就是帮你把这一步压缩到十分钟以内。1.3 创建独立的分词环境一个值得养成的习惯装好 Anaconda 之后我建议你打开 Anaconda PromptWindows或终端Mac/Linux先创建一个独立环境不要直接在 base 环境里写分析代码。原因和 1.1 说的一样数据分析项目之间经常会有库版本冲突今天这个项目需要 pandas 2.0明天那个项目可能只支持 pandas 1.5分开建环境最省心。conda create -n data_analysis python3.11 conda activate data_analysis conda install pandas numpy matplotlib seaborn jupyter openpyxl等你运行完这几条命令环境就备好了。以后每次做数据分析先打开终端激活环境再启动 Jupyterconda activate data_analysis jupyter notebook这一步很多人容易漏掉直接双击桌面图标打开 Jupyter结果发现自己装的库全都不认识原因就是激活错了环境。2. 拿到数据先别急着跑代码三步看清数据的“长相”2.1 没有真实数据怎么办给自己造一份可复现的样例数据我带过不少新人最常听到的困难是“我手头没有数据练什么”。这里建议自己造一份模拟数据模拟的规则贴近真实业务场景就行。下面这段代码就是我用随机数生成的订单表用来模拟一家小型零售电商的销售记录import pandas as pd import numpy as np np.random.seed(42) n 2000 cities [北京, 上海, 广州, 深圳, 杭州] categories [数码, 服饰, 家居, 食品, 图书] pay_types [支付宝, 微信, 银行卡, 货到付款] df pd.DataFrame({ 订单号: [ORD str(i).zfill(6) for i in range(1, n1)], 日期: pd.date_range(2024-01-01, periodsn, freqh).strftime(%Y-%m-%d %H:%M), 城市: np.random.choice(cities, n), 品类: np.random.choice(categories, n), 销售额: np.round(np.random.uniform(20, 500, n), 2), 成本: np.round(np.random.uniform(10, 300, n), 2), 支付方式: np.random.choice(pay_types, n, p[0.4, 0.3, 0.2, 0.1]) }) df.to_csv(orders.csv, indexFalse, encodingutf-8-sig) print(订单表已生成行数, len(df))造数据这件事本身也有讲究。我不想一开始就弄一个干干净净的完美表格那样你后面学不到清洗的本领。但第一版先让你跑通全流程所以这份模拟数据故意没有做太多污染。等你走完一遍流程我建议你动手往里面塞点重复行、缺失值、格式错乱的字段再重新跑一遍清洗环节那才是真正的实战。2.2 read_csv 没你想象的那么简单编码和路径是两个大坑读取数据是分析的第一步也是新手翻车频率最高的环节。read_csv 第一参数填文件路径这个谁都知道但有两个细节你没注意到就会卡很久。第一个是编码。我生成数据时用了utf-8-sig这是为了考虑 Excel 打开 CSV 不乱码同时也兼容 pandas 读取。但你从其他地方拿到的 CSV 可能是gbk或gb2312编码直接读会报一个让人摸不着头脑的错误UnicodeDecodeError: utf-8 codec cant decode byte。解决办法很简单读取时手动指定编码df pd.read_csv(orders.csv, encodingutf-8-sig)如果报错就换成encodinggbk再试。实在不行用errorsignore先读进来再说df pd.read_csv(orders.csv, encodinggbk, errorsignore)第二个坑是相对路径。新手经常在 Jupyter 里写pd.read_csv(orders.csv)结果报“文件不存在”。Jupyter 的工作目录默认是它启动时所在的文件夹如果你的 CSV 放在别的目录直接写文件名当然找不到。我这里处理的笨办法是把 CSV 和 Notebook 放在同一个目录下省心或者用绝对路径df pd.read_csv(rD:\项目\学习笔记\orders.csv)Windows 路径里的反斜杠要加r前缀表示原始字符串否则会被转义。很多人在这里报错就是漏了这个r。2.3 用三行代码快速摸清数据的长相数据读进来了先别急着print(df)看全部。数据量大时这样刷屏不说你也看不出什么结构。我习惯的节奏是三个函数连用print(df.head()) # 看前5行了解字段长什么样 print(df.info()) # 看字段类型和非空数量判断缺失 print(df.describe()) # 看数值型字段的统计分布这三个函数各自回答一个问题。head()解决“数据大概长什么样”让你直观感受每一列是文本、数字还是日期info()解决“数据有没有缺”行数和非空数量一旦对不上就说明数据存在缺失describe()解决“数值范围是不是正常”比如销售额最小 20、最大 500符合模拟数据的设定如果出现负数或者一个离谱的极大值那你就要留个心眼了。这里我特别强调一个新手容易忽略的点info()里的object类型并不代表数据是“错的”。比如“日期”这一列在模拟数据里读进来是object因为 CSV 里的时间被当成字符串处理了。这部分后面讲类型转换时会专门处理。3. pandas 核心操作清洗、筛选、聚合就是分析的主战场3.1 缺失值和重复值先处理它们而不是急着算平均数很多新手一上来就做平均、求和得到的结果自己都不知道可不可靠。数据有缺失和有重复的时候任何统计指标都是有偏的。所以我的固定顺序是先检查缺失和重复再进入分析。检查缺失用isna()配合sum()看每一列缺几个print(df.isna().sum())处理缺失值的选择不是唯一的取决于业务场景。常见就三种策略删除缺失行df.dropna()适合缺失比例很低、缺了就缺了不影响大局的情况填充固定值df[列名].fillna(未知)适合文本类字段比如支付方式缺失填“未知”比删行合理填充统计值df[列名].fillna(df[列名].median())适合数值型字段用中位数而不建议用平均数是因为平均数容易被极端值带偏。重复值的判断要提醒一句不要用df.duplicated()看到有重复就删。重复分两种一种是整行完全重复这种删除没毛病另一种是关键字段重复比如同一订单号出现两次可能一行的数据是错的这时需要用subset[订单号]指定判断依据再检查df.drop_duplicates(subset[订单号], keepfirst, inplaceTrue)这里inplaceTrue的意思是直接修改原数据框。很多教程不推荐这个参数因为它在某些链式操作里容易出问题但对新手来说直接修改比反复赋值更直观。3.2 类型转换让日期真正变成“日期”而不是一串文本我前面提过CSV 读进来的“日期”列大概率是字符串。字符串状态下的日期没法做按月份筛选、没法算持续天数、没法按照时间先后排序。把字符串转成 pandas 的 datetime 类型是这个环节最核心的一件事df[日期] pd.to_datetime(df[日期]) print(df.dtypes)pd.to_datetime()会智能识别常见的日期格式比如2024-01-01 15:30、2024/01/01、2024年1月1日都能处理。万一遇到特别奇怪的格式它识别不了会报错这时可以加参数format告诉它解析规则。比如df[日期] pd.to_datetime(df[日期], format%Y/%m/%d)这个%Y/%m/%d的写法对应的是“年/月/日”。日期格式里%Y是四位年份%m是两位月份%d是两位日期%H是小时%M是分钟。你手头的数据是什么格式就按顺序把对应的占位符拼出来。转完类型之后就能做一件非常高频的操作从日期里提取年、月、日、周几等维度df[月份] df[日期].dt.month df[小时] df[日期].dt.hour df[星期几] df[日期].dt.dayofweek # 0周一6周日“提取月份”听起来很基础但它直接决定了你能不能做“按月汇总”“按小时分析销售峰值”这类分析。很多看起来复杂的分析需求拆到最后无非就是“把时间字段拆出维度再分组求和”。3.3 条件筛选和分组聚合一个顶 Excel 透视表的组合拳数据清洗完就可以开始真正“问问题”了。比如你想知道“上海的数码类销售额是多少”SQL 是where筛选加group by分组pandas 的逻辑完全一致df_sh df[df[城市] 上海] df_sh_ds df_sh[df_sh[品类] 数码] total df_sh_ds[销售额].sum() print(上海数码类销售额, round(total, 2))这里df[df[城市] 上海]是一个“布尔索引”通过一个真假序列来挑选符合条件的行。多个条件同时满足时注意要用而不是anddf_sh_ds df[(df[城市] 上海) (df[品类] 数码)]新手经常在这里踩坑写成and会报错ValueError: The truth value of a Series is ambiguous。这个报错的本质是pandas 的 Series 是一个包含很多值的数组中间用表示逐个元素进行逻辑运算而and要求的是单一布尔值。接下来出场的是数据分析中使用频率最高的一组方法groupby()加agg()。Excel 里用透视表能做的事情这套组合拳基本都能做而且代码写一次以后跑任何数据都能复用result df.groupby([城市, 品类]).agg({ 订单号: count, # 订单数 销售额: sum, # 总销售额 成本: mean # 平均成本 }).rename(columns{订单号: 订单数}) print(result)这段代码的逻辑按“城市”和“品类”两个字段分组然后在每一个分组内分别对销售额做求和、对成本做平均、对订单号做计数。agg()的操作符和代码一样sum()会按整列将所有值相加这里的“sum”区别于 Python 内置的sum()函数。如果更进一步还可以用agg()在一个字段上同时计算多个统计值result df.groupby(城市)[销售额].agg([sum, mean, count, max]) print(result)这种写法返回的结果里列名就是sum、mean、count、max一目了然。分组聚合这东西看起来简单但它的思路是整个数据分析的核心骨架。后面无论你是做用户分层、销售漏斗还是库存周转分析本质都是这个套路先分组再聚合然后把结果丢给可视化。4. 可视化让结论自己“跳出来”不只是画图4.1 先想清楚要回答什么问题再选图表类型不少人学可视化的误区是把所有图表类型函数背一遍然后拿到数据挨个画看看哪个好看用哪个。这是本末倒置。正确的顺序是你先有一个问题然后根据问题的性质去匹配图表类型。我自己整理了一个对应关系新手可以直接拿来用你想回答的问题合适的图表某种商品的销售额在一年里怎么变化折线图不同城市卖了多少东西柱状图各种支付方式占比多少饼图销售额和成本有没有关系散点图不同品类的销售额分布差异箱线图问题的类型决定了图表的类型而图表类型又决定了你调用哪个库的哪个函数。先有问题后有图表这个习惯越早建立越好。4.2 seaborn 让你少写 80% 的绘图代码matplotlib 功能强大但新手用它画图有个痛苦点代码又长又绕设置标题、设置坐标轴、调整字体每一样都得自己写。我的建议是直接用一个更高级的封装库 seaborn 作为主力它底层本身基于 matplotlib画出来的图默认也更好看。import matplotlib.pyplot as plt import seaborn as sns # 先把字体设置好中文明细见4.3 plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei] plt.rcParams[axes.unicode_minus] False # 柱状图各城市销售额 city_sales df.groupby(城市)[销售额].sum().reset_index() sns.barplot(datacity_sales, x城市, y销售额) plt.title(各城市总销售额对比) plt.show()这个例子只用三行核心代码就完成了从数据到图表的全部工作。相比 matplotlib 原生的写法这已经是相当友好的体验了。再看一个更有代表性的场景按月份和品类画销售额折线图展示趋势变化# 先按月份和品类做聚合 月度数据 df.groupby([月份, 品类])[销售额].sum().reset_index() sns.lineplot(data月度数据, x月份, y销售额, hue品类, markero) plt.title(各品类月度销售趋势) plt.show()hue品类这个参数很重要它让 seaborn 自动用不同颜色区分不同品类并且自动加图例。你不需要自己循环画线一次调用全部搞定。箱线图特别适合看分布情况比如“各品类销售额的分布差异”一句话就能发现问题——哪个品类波动大哪个品类更稳定sns.boxplot(datadf, x品类, y销售额) plt.title(各品类销售额分布对比) plt.show()看到箱体上下沿的距离你就能直观判断品类的离散程度。如果你只看平均数很容易被极端值掩盖箱线图把这个信息补回来了。4.3 中文字体和坐标轴标签新手最容易卡住的细节画图的代码写完了一运行中文全部变成了方块。这是所有中文用户都会遇到的事而且解决方式特别简单就两行配置plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei] plt.rcParams[axes.unicode_minus] False第一行指定了字符的字体第二行解决负号显示的问题。如果你的系统是 macOS字体名需要改成[Arial Unicode MS, Heiti TC]Linux 则可能需要[WenQuanYi Zen Hei, Noto Sans CJK SC]。不确定系统字体叫什么就先跑下面这段代码查看可用字体from matplotlib.font_manager import fontManager print([f.name for f in fontManager.ttflist if Hei in f.name or CJK in f.name])这个坑让我想起一件事有次我在演示环境里画好了图换到客户的电脑上重新跑一遍中文乱码了。原因就是两台机器装的中文字体不一样。所以上半场我建议你在脚本开头统一放这两行配置下半场要部署到别人机器上还得多一手字体检测。另一个细节是横坐标标签太密时容易重叠比如按小时分析销售额x 轴有 24 个刻度会挤成一团。解决办法是旋转角度或设置显示间隔密度plt.xticks(rotation45, haright)这段代码让标签斜着排避免互相遮盖。5. 从分析到报告把代码、图表和结论整理成能交付的东西5.1 Jupyter Notebook 本身就是一份“草稿报告”很多人做分析的习惯是在 Jupyter 里写代码截图几张图表放进 Word 里再写一段文字生成一份报告。这种流程不是说不行但效率太低了。Jupyter Notebook 本身就能把代码、运行结果、图表、文字说明组织在同一个文档里每一段代码前面用 Markdown 写一句分析结论后面直接是图表输出读起来就是一份完完整整的报告。所以我的建议是从一开始就用“报告思维”来写 Notebook每个分析问题用 Markdown 标题写出来# 1. 各城市销售额对比代码块下面紧跟一句“结论”比如“上海的销售额最高占总额的 24.6%”图表不要随手画要补充图注和关键信息这样一来你分析完的那一刻报告已经完成了一半。剩下的工作就是整理和导出。有些数据结论不应该只留在 Notebook 里。如果你要发给完全不懂代码的同事看需要一份“干净”的文档那就要用导出功能。Jupyter 自带导出成 HTML 的功能在界面上选择File → Download as → HTML就能一键生成。如果你在终端环境里也可以直接用命令jupyter nbconvert --to html 数据分析报告.ipynb导出之后有个细节需要注意默认导出的 HTML 包含所有代码块。如果你不想让对方看到代码可以加参数隐藏代码块jupyter nbconvert --to html 数据分析报告.ipynb --no-input--no-input的意思是只保留输出结果也就是图标、表格、文字结论不显示代码。这是交付给业务人员最常见的姿势。如果对方是一般需要 Word 的人HTML 反而是更好用的交付方式因为 Word 直接打开 HTML 也能看排版问题少。PDF 也可以导出但中文支持的问题更复杂新手阶段优先级靠后。5.3 最后一公里写总结而不是写过程报告最后一定要有一个“结论和建议”部分这也是拿数据说话的核心——你前面做了那么多图表目的就是回答一个业务问题。如果你只是把图表贴出来读者还要自己解读那报告就失去了一半的价值。我常用的模板是发现的核心事实哪个品类销售额最高、哪个月增长最快潜在的异常或风险某个城市成本倒挂利润率为负基于数据给建议下个月应该加大哪个品类的库存备货比如你分析完这份模拟数据可以用这段代码计算出按城市和品类的利润然后挑出利润最高的组合df[利润] df[销售额] - df[成本] profit_summary df.groupby([城市, 品类])[利润].sum().reset_index() top3 profit_summary.sort_values(利润, ascendingFalse).head(3) print(top3)这个结果放进去比单纯写“我们分析了各城市的销售情况”有说服力得多。说回开头那个话题。学 Python 数据分析卡住你的往往不是复杂的算法而是缺一条完整的路线。我从环境配置讲到数据读取从清洗讲到聚合从画图讲到导出报告核心就一个想法整个链路走通了你对“数据分析”这四个字的理解才算真正落地。我自己的体会是第一次完整地从一个 CSV 文件做出带图和结论的报告那种成就感比看多少节视频课都来得实在。如果你手头有真实数据哪怕是几十行的表格也建议今晚就按这个流程走一遍。碰到问题很正常也许你会比教程里更容易迷路但迷路之后走通的路才是你自己的地图。