资讯动态

Python数据分析与可视化:从数据清洗到图表交付的完整避坑指南

发布时间:2026/10/15 18:16:07 来源:尧图企业网站定制
简介从Python语言特性出发这份PDF为数据分析与可视化初学者提供了清晰的学习路径。内容在介绍易学、解释型、动态类型等语言特点后系统梳理了两大工具链数据分析侧包含Pandas数据清洗与聚合、NumPy数值计算、SciPy科学计算和StatsModels统计建模可视化侧覆盖Matplotlib基础绘图、Seaborn统计图表、Plotly与Bokeh交互式图形及Cufflinks接口简化。文中更以read_csv读取数据、mean计算均值、plt.bar绘制条形图为例完整演示了从数据导入到图表展示的过程可直接迁移到实际项目里。压缩包为单个PDF文档大小仅114KB知识模块独立且便于零散时间阅读电脑或手机上都可随时查阅。目前已有1848人学习非常适合作为开启数据分析项目前的知识地图也能帮助读者减少选型与排错时间快速产出专业图表或进行初步数据探索。1. Python数据分析与可视化为什么跟教程敲完代码图表还是没法用花了两周时间把pandas和matplotlib的语法背得滚瓜烂熟拿到一份真实业务数据后依然不知道从哪下手这是python数据分析与可视化这条路上最常见的卡点。数据是乱的日期读进来变成字符串订单号被科学计数法吞掉精度两个表关联怎么都对不上好不容易画出一张图中文坐标轴全是方框时间轴上的点按字母顺序排列成诡异曲线。问题不在你不够努力而在大多数教程把数据准备、指标计算和画图拆成了三条独立的学习路线实战时你得把它们当作一条流水线整体配合。我的核心建议是将可视化当作一种数据检查手段而不是最后一步的美化工作。一张图能不能讲清业务问题在数据加载和聚合阶段就已经决定了七八成喂给绘图函数的DataFrame是干净且结构正确的哪怕调用不熟练图也不会翻车得太离谱。这篇笔记按“加载清洗 → 分组聚合 → 绘图 → 避坑 → 进阶”的顺序展开适合准备做数据分析的开发者、被Excel透视表逼到墙角的运营与产品同学以及想给日常报表引入Python环节的工程师。不需要大型集群一台电脑、一个Python环境加两三份样例数据就能把整条链路完整跑通。2. 数据加载与清洗地基歪了后面全是花架子2.1 读文件前先确认三件事编码、列类型、日期解析很多人拿到CSV就一把梭pd.read_csv(data.csv)跑通后立刻开始画图结果图表里出现大量异常值、时间排序错乱、join结果莫名其妙。真正动手时我一般会在读文件这一步就压住三个参数编码encoding、列类型dtype、日期解析parse_dates。import pandas as pd df pd.read_csv( sales_2024.csv, encodingutf-8, # Windows导出的CSV常为gbk报错后改utf-8-sig dtype{order_id: str}, # 长订单号按文本读避免转成浮点丢精度 parse_dates[order_date], # 日期列在读取阶段直接解析成datetime )encoding决定用哪种编码解读文件里的字节读错会直接抛UnicodeDecodeError此时把编码换成gbk或utf-8-sig基本能解决dtype接收一个字典只对列名命中的列强制指定类型适合订单号、身份证号这类长度超过16位的数字parse_dates可以传列名或列名列表把它解析成datetime类型后后续的dt访问器和按时间分组才具备操作前提。除了CSV业务数据经常落在Excel和数据库里read_excel需要另装openpyxl库read_sql则要求先建好SQLAlchemy引擎。代码里的逻辑与CSV保持一致——族长数字列仍要指定为字符串日期列尽早在读取阶段转类型能省掉一整轮清洗工作。import pandas as pd df_excel pd.read_excel( 仓库数据_2024.xlsx, sheet_name月度汇总, # 默认读第一个sheet这里显式指定 skiprows2, # 跳过前两行混合标题和备注 dtype{sku_id: str}, ) from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passlocalhost:3306/orders_db) df_sql pd.read_sql( SELECT order_date, region, amount, quantity FROM orders WHERE pay_status 1, conengine, parse_dates[order_date], )read_excel的sheet_name参数省去读多个工作表时的循环切片skiprows跳过Excel里常见的表头合并单元格和说明行read_sql直接把查询结果灌进DataFrame比手动导出CSV再读取更不容易引入中间态编码问题。注意create_engine要另装pymysql或psycopg2等驱动连接串格式按数据库类型略有差异。2.2 清洗三件套缺失值、重复值与异常值数据读进来后的第一轮清洗我把它压缩成三件事缺失值、重复值、异常值。顺序不能乱——先处理缺失再去重最后过滤异常。每步操作都要先看一眼影响范围切忌上来就df.dropna(inplaceTrue)一把梭。# 1) 缺失值先看分布再决定删还是填 print(df.isna().sum()) # 逐列统计缺失数量 df df.dropna(subset[order_date]) # 日期缺失无法补直接删行 df[amount] df[amount].fillna(0) # 金额缺失按业务规则填0 df[quantity] df[quantity].ffill() # 数量用前向填充适合时序数据 # 2) 重复值按订单维度去重保留第一次出现的记录 df df.drop_duplicates(subset[order_id], keepfirst) # 3) 异常值用1%和99%分位数夹逼而不是凭感觉定死阈值 lo df[amount].quantile(0.01) hi df[amount].quantile(0.99) df df[(df[amount] lo) (df[amount] hi)] print(df.shape) # 清洗结束后务必看一次行列数量变化dropna里的subset只检查指定列避免因为其他列偶尔为空而误删关键记录fillna(0)适合金额、数量这种缺失即视为不存在的字段ffill()是前向填充最近一条有效值向后补齐适合时间序列的连续缺口。drop_duplicates的subset定义“怎么算重复”keepfirst保留第一条日志类数据建议保留first流水号类数据则要业务确认。异常值过滤用分位数而不是硬编码阈值是因为真实业务数据大多有厚尾例如大促当天的销售额本身就该是平日的十倍拍一个3000元上限会把真实峰值杀掉。quantile(0.01)和quantile(0.99)的效果相当于剪掉两端尾部具体分位点可根据数据量调整。务必先打印lo和hi看看边界是否合理再执行过滤。2.3 用四个内置函数摸清数据底细清洗前我总会执行这四个探针型函数把数据“摸”一遍。它们不修改数据只负责暴露问题属于按下去立刻有反馈的探索工具。print(df.shape) # (行数, 列数)一眼确认数据量级 print(df.info()) # 列名、非空数量、类型类型错乱在这里暴露 print(df.describe()) # 数值列的均值、分位数、min/max发现不合理边界 print(df.head(10)) # 查看前10行实际数据长相df.shape返回元组如果读进来只有几十行说明read_csv的参数或文件路径可能有问题df.info()输出的Dtypes列很关键凡是业务上该是日期的列显示为object就该返回2.1节补上解析逻辑describe()对数值列自动计算统计量min或max与业务常识严重偏离时说明读入的数据混入了脏值head(10)看的是真实数据长相——列名是否带空格、数字里是否嵌逗号、日期是否混格式这些问题只有肉眼看一遍才够直观。这轮摸底的产出是一张“数据质量清单”哪些列要补缺失、哪些列类型错了、哪些值超出业务范围。带着这张清单进入下一步聚合计算结构化的数据才经得起后续的透视和绘图操作。3. 分组聚合与透视表把明细数据变成指标矩阵的三种写法3.1 groupby与agg组合一行算出一组指标明细数据一行一行铺开是没法讲问题的分析的第一步通常是把行记录压缩成按维度聚合的指标。groupby负责拆组agg负责对每组执行一组函数两者组合起来能一次算出总额、均值、去重客户数等多个指标而不必写循环。# 按区域聚合一次算出四个指标 region_stats df.groupby(region).agg( total_amount(amount, sum), avg_amount(amount, mean), order_count(quantity, count), customer_count(customer_id, nunique), ).reset_index() print(region_stats.sort_values(total_amount, ascendingFalse))agg括号里的元组写法是常见惯例冒号左侧是新列名右侧元组第一个元素是源列名第二个是聚合函数名。sum和mean作用于数值列count对非空值计数nunique是去重计数——统计“有多少个不重复客户”时务必用它用count会把同一客户多次下单也算进去。reset_index()把分组用的region从索引位置放回普通列方便后续直接merge或绘图时引用。sort_values(total_amount, ascendingFalse)把结果按总金额降序排列排序后的DataFrame直接print前几名区域谁高谁低一目了然。这张表就是后续画条形图、制作Top榜的原材料。3.2 时间分组按年和月切数据别让日期卡住你时间序列是数据分析里最常踩的类型陷阱。若order_date还是字符串groupby只会按字母序排列月份10月会排在2月前面。正确做法是先通过2.1节的parse_dates或pd.to_datetime保证列的类型是datetime再生成周期标签。# 生成“年-月”周期列 df[year_month] df[order_date].dt.to_period(M) # 按年月区域二维分组保留分组列为普通列 monthly_region df.groupby( [year_month, region], as_indexFalse )[amount].sum() print(monthly_region.head())dt.to_period(M)是datetime类型的访问器方法M表示按月切分若需要按周、按季度替换成W或Q即可。groupby接收列名列表即按年月和区域双重维度拆分as_indexFalse让两个分组列保留成普通列省去之后每次reset_index()的重复操作。这步产出的monthly_region是画趋势图的直接输入。务必看一下year_month列的输出格式——Period类型虽然适合分组但matplotlib画图时可能要求转成字符串或时间戳这个转换会在第4章遇到。3.3 pivot_table与crosstab把长表摊平成对比矩阵groupby的结果是长表——每个分组一行。看数据时宽表更直观行是维度列是另一个维度交叉点是指标值。pivot_table干的就是这件事它是Excel透视表在pandas中的等价物。# 行区域列年月值销售额聚合后空值填0 pivot pd.pivot_table( df, valuesamount, indexregion, columnsyear_month, aggfuncsum, fill_value0, ) # 区域×支付方式的交叉表按行归一化成占比 cross pd.crosstab( indexdf[region], columnsdf[pay_method], valuesdf[amount], aggfuncsum, normalizeindex, )pivot_table的参数逻辑是index决定行维度columns决定列维度values是被聚合的值列aggfunc决定怎么聚合。fill_value0把没有数据组合的空洞填成0避免热力图或柱状图渲染时出现NaN缺口。crosstab适合两个离散维度交叉normalizeindex按行归一化每行加起来是1输出变成占比矩阵特别适合看“各区域支付方式偏好”。实际选型时记住一句话后续还要继续计算用groupby维持长表后续要做横向对比或直接喂给热力图用pivot_table换成宽表。长格式和宽格式的转换也是pivot和melt相互切换的基础掌握这两个方向能应对绝大多数表格形态问题。4. matplotlib与seaborn画图先定画布再谈美观4.1 画布与坐标系fig和ax到底在说什么大多数教程会用plt.plot(x, y)演示单图表看起来没问题一旦画子图、双坐标轴或自定义刻度就抓瞎。原因是plt.plot走的是matplotlib的全局隐式状态机所有操作都挂在同一个看不见的画布上。正式项目中应直接创建画布fig和坐标系ax后续所有绘图动作显式发生在ax上。import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(10, 5)) # 创建画布和坐标系 # 先按年月排序再取华东区域的数据 east monthly_region[monthly_region[region] 华东].sort_values(year_month) ax.plot( east[year_month].astype(str), # Period转字符串避免绘图报错 east[amount], markero, linewidth2, label华东, ) ax.set_title(华东月度销售额趋势) ax.set_xlabel(月份) ax.set_ylabel(销售额元) ax.legend() ax.grid(True, linestyle--, alpha0.4) plt.show()plt.subplots(figsize(10, 5))返回两个对象的元组fig是整张画布ax是画布上的坐标系。ax.plot把折线画到这个坐标系上ax.set_title、ax.set_xlabel等系列方法都显式作用于该坐标系多个子图各自独立互不污染。east[year_month].astype(str)这步值得注意——Period类型在旧版本matplotlib里可能报ValueError先转成字符串最保险。单图表这么做看似多写两行收益在后半程要加第二个子图时改成plt.subplots(1, 2, figsize(14, 5))即可要加双坐标轴时基于ax再创建ax2即可。一开始养成显式fig/ax的习惯后面所有进阶操作都会顺畅很多。4.2 seaborn统计图箱线图、密度图和热力图的正确姿势seaborn是建立在matplotlib之上的统计绘图库最大的好处是直接接收DataFrame和列名省去手动拆数组的步骤。它擅长三类图看单个变量分布的kdeplot和histplot看分组对比的boxplot看多变量相关性的heatmap。import seaborn as sns # 箱线图比较各区域金额分布顺带暴露离群点 sns.boxplot(datadf, xregion, yamount) plt.xticks(rotation45) # 密度图按支付方式拆分看金额分布形态 sns.kdeplot(datadf, xamount, huepay_method, fillTrue, alpha0.3) # 热力图多个数值列的相关系数矩阵 corr df[[amount, quantity, discount, unit_price]].corr() sns.heatmap(corr, annotTrue, fmt.2f, cmapRdBu_r, center0)boxplot的数据传参方式与matplotlib完全不同不传x和y数组而是传整个DataFrame加上列名seaborn内部自动完成分组统计kdeplot里的hue参数指定另一个离散列自动按支付方式拆分并着色fillTrue填充密度曲线下方区域alpha控制透明度避免重叠部分完全遮死corr()先计算出相关系数矩阵heatmap的annotTrue在格子里显示数字fmt.2f保留两位小数center0让颜色以零为中心对称过渡。在选图形时我的取舍标准很简单看单变量分布用直方图或密度图看分组对比用箱线图或小提琴图看两个连续变量关系用散点图看多变量相关性只用热力图。下表可以直接照抄。分析目标推荐图型关键参数单变量分布kdeplot / histplothue拆分对比分组对比boxplotx放分组列y放数值列相关性heatmapannotTrue显示相关系数时间趋势lineplotmarkero标数据点4.3 双Y轴与子图让两个量纲在同一张图上对比业务报表常常要同时呈现两个量纲差异巨大的指标销售额是几百万级别订单数是几百级别画在同一坐标系里订单数会被压成一条贴底直线。双Y轴是解决这类问题的标配方案。monthly df.groupby(year_month, as_indexFalse).agg( total_amount(amount, sum), order_count(quantity, count), ) fig, ax1 plt.subplots(figsize(12, 5)) ax2 ax1.twinx() # 创建共享X轴的第二Y轴 ax1.bar(monthly[year_month].astype(str), monthly[total_amount], alpha0.5, label销售额) ax2.plot(monthly[year_month].astype(str), monthly[order_count], colortomato, markero, label订单数) ax1.set_ylabel(销售额元) ax2.set_ylabel(订单数) ax1.legend(locupper left) ax2.legend(locupper right)ax1.twinx()基于ax1创建第二个坐标系共享底部的X轴拥有独立的右侧Y轴。左侧柱状图跟销售额右侧折线图跟订单数两套刻度互不干扰。图例分开设置loc分别放到左上和右上避免重叠。反之如果需要共享Y轴但X轴不同用twiny()。这种双Y轴图最容易出现的误导是两侧刻度的零点不重合看起来销售额和订单数的起伏同步实际比例关系可能被拉平或夸大。使用时务必在图注里说明左侧和右侧分别代表什么。4.4 导出图片清晰度的三个关键参数画图最后一步是导出。很多人在Jupyter里看效果觉得不错插入Word或汇报PPT后糊成一团问题几乎都出在保存参数上。fig.savefig( sales_trend.png, dpi300, # 打印/投影清晰度门槛 bbox_inchestight, # 裁掉周围多余白边 facecolorwhite, # 避免透明背景在PPT深色主题下翻车 )dpi300是清晰度关键屏幕显示用96-150就够打印或大屏投影建议300起步bbox_inchestight自动计算内容边界并裁剪空白导出的图片四周不会出现大片留白facecolorwhite把画布背景固化为白色否则在有透明背景的PPT模板里图表的白色坐标轴和透明底板会显得很突兀。导出后务必打开图片确认一遍中文是否显示正常——这直接关联到下一章的避坑内容。5. 避坑数据分析可视化最常见的5个翻车现场5.1 matplotlib中文变方块负号变成缺字符现象标题、坐标轴标签只要是中文显示出来全是空心方块坐标轴上的负号变成一个不伦不类的短线。自己在Jupyter里盯着代码看毫无问题但在别人电脑上打开图片就翻车。原因matplotlib默认字体列表中不包含中文字形渲染中文时找不到对应字形就画成占位方块负号使用的是特殊数学字体默认配置下与中文字体的字形不兼容。解决通过rcParams显式指定字体并关闭负号的特殊字形import matplotlib.pyplot as plt # SimHei在Windows可用macOS换成PingFang SCLinux换成WenQuanYi Zen Hei plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False这段配置要放在任何绘图代码之前建议集中放在项目入口或专门的配置模块中不要每张图前都写一遍。axes.unicode_minus设为False后负号就用普通连字符渲染不会再出现缺字形问题。注意如果用了seaborn.set_theme()它会重置一部分样式应在它之后再设置rcParams。5.2 SettingWithCopyWarning反复出现赋值结果时灵时不灵现象对一个DataFrame做条件筛选后直接赋值例如df[df[amount]100][tag]high控制台弹出SettingWithCopyWarning。赋值有时生效有时完全不生效代码逻辑看不出问题。原因链式赋值在等号左边执行了两次索引操作第一次选出视图像第二次对该视图做赋值。pandas无法确定你意图修改原DataFrame还是临时副本就发出警告实际执行结果取决于内部视图是引用还是拷贝碰运气。解决要么先显式复制再操作要么用loc一步到位写入原表。# 方案一筛选后copy保证操作的是独立数据 df_filtered df[df[amount] 100].copy() df_filtered[tag] high # 方案二loc定位后原地修改原表 df.loc[df[amount] 100, tag] high先.copy()能斩断与原DataFrame的视图关系后面怎么赋值都不会影响原表loc写法把行条件和列名写在一行清晰表达“筛选行、写入列”的意图。作为习惯只要对筛选结果要新增列或改值一律使用loc警告基本不会出现。5.3 read_csv把长数字读成科学计数法关联账全对不上现象订单号、身份证号这类超出十位的数字读取后显示成6.301939e18打印出来连末尾几位都看不清。拿这个DataFrame去关联另一个表明明在Excel里看起来一样的订单号SQL里却匹配不上。原因read_csv默认对纯数字列做类型推断发现全是数字就自动转成int64当数值超过2的53次方约9千万亿pandas为保留性能会把该列转成float64长整型精度在转换中丢失后面的数字变成近似值或科学计数法。解决必须在读文件阶段用dtype把长数字列指定为字符串事后转换救不回来。df pd.read_csv(orders.csv, dtype{order_id: str})如果数据已经读进来了df[order_id]df[order_id].astype(str)只能把浮点数转换为带科学计数法的字符串丢失的低位数字无法恢复。唯一后悔药就是回到读取参数指定该列以文本形式解析。关联数据表时两边都用字符串比较才不会再出现账对不上的问题。5.4 日期列看起来是日期实际是字符串趋势图全乱套现象从Excel导出的数据里order_date列肉眼看着就是2024-01-15这种标准日期。画月度趋势图时X轴上的月份排成了1月、10月、11月、12月、2月……折线来回乱跳。原因Excel导出CSV后日期列被当成普通文本read_csv未指定parse_dates时它保持为object字符串类型。字符串排序是字典序“10月”自然排在“2月”前面。解决清洗阶段统一用pd.to_datetime做显式转换并指定格式提升解析速度。df[order_date] pd.to_datetime(df[order_date], format%Y-%m-%d) # 若格式不统一用errorscoerce把解析失败的行置为NaT再排查 df[order_date] pd.to_datetime(df[order_date], errorscoerce)format%Y-%m-%d是四位数年份、两位数月份、两位数日期的模板显式给格式后解析更快还能避开欧美日期顺序的歧义。errorscoerce把无法解析的值统一变更为NaT随后用df[df[order_date].isna()]定位哪些行格式异常检查是数据源混入了别的日期格式还是脏数据。5.5 多个筛选条件用and连接直接抛ValueError现象df[(df[amount]100) and (df[region]华东)]执行后报ValueError: The truth value of a Series is ambiguous。代码看起来完全符合Python语法怎么就跑不通原因pandas的条件筛选产生的是布尔Series而Python的and关键字要求对单个布尔值做判断df[amount]100返回的Series有多个元素Python无法决定“整个Series是真是假”只能抛错。数组元素级的逻辑组合要用位运算符且和|或。解决用替代and并且给每个条件套上括号。df df[(df[amount] 100) (df[region] 华东)] # ^ ^ ^ ^ # 每个条件独立成组连接优先级不会被比较运算符抢走括号不能省Python中比较运算符优先级高于位运算符不套括号会先执行再执行结果和期望完全不同。取反条件用~例如df[~(df[region]华东)]。这三件事记住了多条件筛选就能一次性写对。6. 进阶技巧样式统一、封装画图函数与交付检查6.1 用rcParams集中管理全局样式项目里图表风格不一致是返工的高频原因。处理方法是建一个chart_config.py把所有样式参数集中放好任何脚本导入即可。import matplotlib as mpl mpl.rcParams.update({ figure.facecolor: white, axes.edgecolor: #cccccc, axes.grid: True, grid.linestyle: --, grid.alpha: 0.4, font.sans-serif: [SimHei], # 换成你系统里的中文字体 axes.unicode_minus: False, })集中配置后各脚本画图前只要import chart_config颜色、网格、字体全项目统一新同事接手也不用一行行去猜别人图表的字号哪里设置的。6.2 把一张图封装成一个函数重复的画图逻辑应该收进函数参数只暴露数据和保存路径。分析层调用时代码干净改动图表样式时也只需要改一处。def plot_sales_trend(df, save_pathNone): monthly df.groupby(df[order_date].dt.to_period(M))[amount].sum() fig, ax plt.subplots(figsize(12, 5)) ax.plot(monthly.index.to_timestamp(), monthly.values, markero) ax.set_title(月度销售趋势) ax.set_xlabel(月份) ax.set_ylabel(销售额) if save_path: fig.savefig(save_path, dpi300, bbox_inchestight) return fig, ax函数内部分工明确第一行完成聚合中间五行完成绘图末尾按需保存并返回画布对象。return fig, ax保留扩展能力调用方需要加辅助线或标注时可以继续操作坐标系。6.3 交付前检查的三个问题图表交付前我会固定过三关把图调成黑白打印模式看信息是否还完整把标题和坐标轴拿给不看数据的人看是否语言通顺检查Y轴起点是否故意截断夸大趋势。这三关都过了图才算真正可交付。这几年带过的分析项目返工最频繁的几乎都栽在加载与清洗阶段把脏数据直接喂给可视化函数后面调整字体、换配色都是治标。现在我的习惯是每张图能成立首先问DataFrame干不干净其次才问函数调用对不对。这个习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑