Pandas一行绘图12类图全解:从DataFrame到快速可视化
如果你经常用 Pandas 做数据清洗一定遇到过这种尴尬数据整理好了想快速看一眼分布和趋势却要临时去翻 Matplotlib 或 Seaborn 的文档。其实 Pandas 自带一套完整的绘图接口一个.plot()就能在 12 类基础图之间任意切换覆盖当前原生支持的所有绘图类型。这篇文章不打算做文档翻译而是把我平时最常用的 Pandas 绘图姿势从数据准备、参数调优到踩坑记录一次性整理出来。无论你是刚接触 Pandas 的新手还是天天和 DataFrame 打交道的分析岗都应该能从中找到能直接抄走的东西。1. Pandas 绘图到底能画什么12 类图形全景与适用场景1.1 一张表看懂 12 类基础图先给结论。Pandas 的DataFrame.plot()和Series.plot()可以看成是“绘图路由”通过传kind参数切换图形类型。从实用性角度我会把 Pandas 原生能输出的绘图形式归纳为下面 12 类图类型kind 取值一句话适用场景典型输入折线图line展示连续时间序列或数值趋势一列连续 X 一列或多列 Y面积图area展示累积量或部分与整体的变化同上尤其适合堆叠柱状图bar类别之间的离散对比类别 X 数值 Y水平柱状图barh类别名较长、需要排名展示类别 X 数值 Y直方图hist查看单变量数值分布单列数值核密度图kde平滑分布估计比直方图更连续单列或多列数值密度图density与kde同义的别名单列数值箱线图box查看分位数、离群值、分布形态一列或多列数值散点图scatter探索两个数值变量关系X 列 Y 列六边形分箱图hexbin大数据量下的二维密度分布X 列 Y 列饼图pie展示简单占比类别汇总值表格图table()把原始数据以表格形式贴在图上DataFrame / Series严格来说table并不是kind参数的值而是 Pandas 提供的df.plot.table()原生方法。把它算进“12 类”是因为它同样是 Pandas 画出来的一种独立输出形式。如果你去翻官方文档会发现kind实际只有 11 个取值所以标题里说的“覆盖所有原生绘图类型”本质是“11 个 kind 表格图”这样理解不会和文档打架。1.2 为什么说“一键绘制”而不是“模板绘制”很多人第一次接触 Pandas 绘图时会觉得奇怪同样是画折线图Matplotlib 要先创建画布、生成坐标轴、逐条plot()最后还要写legend()和title()而 Pandas 里面一行df.plot(kindline)就结束了。这是因为 Pandas 把“把 DataFrame 映射到 Matplotlib 的 Axes 对象”这套逻辑封死了。举例来说Matplotlib 画一组多序列折线图你得写import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(8, 5)) ax.plot(df[日期], df[销售额], label销售额) ax.plot(df[日期], df[订单量], label订单量) ax.set_xlabel(日期) ax.set_ylabel(数值) ax.set_title(销售趋势) ax.legend()Pandas 只需要df.set_index(日期)[[销售额, 订单量]].plot(kindline, figsize(8, 5), title销售趋势)它自动识别“DataFrame 的每一列是一组数据”自动添加图例自动把索引作为 X 轴。这个“自动识别”就是kind一键切换的底层逻辑。你在做探索性数据分析时最宝贵的是时间不是绘图代码重写的能力。2. 绘图前的准备让 Pandas 画得又稳又好看2.1 数据准备宽表才是绘图最佳姿势Pandas 绘图的第一个隐藏要求是最好准备成宽表。宽表的意思是“每一列是一个指标每一行是一个观测样本”。比如下面这个销售模拟数据就是典型的宽表import pandas as pd import numpy as np rng np.random.default_rng(42) df pd.DataFrame({ 日期: pd.date_range(2024-01-01, periods30, freqD), 销售额: rng.integers(80, 200, 30), 订单量: rng.integers(20, 60, 30), 客户数: rng.integers(15, 45, 30), 品类: rng.choice([数码, 服饰, 家居, 食品], 30), 退货率: rng.uniform(0.01, 0.15, 30).round(4), })后面的所有图我都基于这份数据。它包含日期、多个数值指标、一个类别列足够覆盖 12 类图的输入要求。为什么宽表合适因为df.plot()默认会把所有数值列都当成 Y 变量。如果你手里是长表比如每一行是“日期 指标名 值”画图前通常需要pivot或者pivot_table转成宽表否则 Pandas 没法一次性把多条序列画到同一个图里。日期类列最好转成datetime64类型或直接设为索引。虽然 Pandas 有时能自动解析但遇到字符串日期画出来刻度会非常杂乱。最稳妥的写法是df[日期] pd.to_datetime(df[日期]) df df.set_index(日期).sort_index()2.2 后端选择默认 Matplotlib想交互就换 PlotlyPandas 绘图默认后端是 Matplotlib。这也是绝大多数分析场景够用的方案输出是静态图适合放进 PPT 和周报。但如果你希望图能缩放、悬停看数据可以把后端切成 Plotlypd.options.plotting.backend plotly切换之后df.plot(kindline)返回的就不是 Axes而是 Plotly Figure 对象。有一点必须提醒不是所有 kind 都能在 Plotly 后端下正常工作。比如hexbin和table在 Plotly 后端下就不是默认支持项。我的建议是日常探索用 Matplotlib需要做可交互的报告时再切 Plotly并且先验证你要用的 kind 在当前后端下能不能跑。2.3 高频参数这些配置直接决定图好不好看很多人看到df.plot()就只传kind结果出来的图连坐标轴标签都被截掉一半。这里整理几个最高频的参数参数作用示例figsize画布大小单位英寸figsize(10, 5)title图标题title2024年销售趋势grid是否显示网格线gridTruesubplots多列数值拆成多个子图subplotsTruelayout子图排列方式layout(2, 2)sharex/sharey子图是否共享坐标轴sharexTruelegend是否显示图例legendFalserotX 轴刻度旋转角度rot45style折线图的线型/颜色style--stacked堆叠柱状图/面积图stackedTruecolormap/cmap颜色映射cmapYlGnBu这些参数会被 Pandas 透传到底层 Matplotlib 方法所以如果你遇到某个参数不确定直接看看 Matplotlib 的对应函数是否支持同名参数即可。3. 12 类基础图逐个拆解代码、参数与踩坑点3.1 折线图 line时间序列的第一选择折线图是 Pandas 绘图中使用频率最高的一种。直接在这份数据上画销售额和订单量df[[销售额, 订单量]].plot(kindline, figsize(10, 5), title销售额与订单量趋势)这里不需要指定x因为 DataFrame 的索引就是日期。如果你不想把日期设为索引也可以这样df.plot(kindline, x日期, y[销售额, 订单量])Pandas 会自动处理 X 轴的数据类型只要日期列是datetime64刻度就会自动按时间间隔排布。折线图最常见的坑是数据列数太多画在一起变成毛线团。解决办法是加subplotsTrue每个指标独占一个子图或者只画你真正关心的两三条线。另外style参数可以在折线图中快速修改线型比如style{销售额: -, 订单量: --}比画完再去ax.lines[1].set_linestyle()方便得多。3.2 柱状图 bar / 水平柱状图 barh类别对比和排名柱状图适合展示类别汇总。先把数据按品类聚合category_sum df.groupby(品类)[销售额].sum().sort_values() category_sum.plot(kindbar, figsize(8, 5), rot0, title各品类销售额)rot0是为了让 X 轴标签水平显示否则品类名称斜着叠在一起。柱状图有一个容易被忽略的细节Pandas 默认把索引当作类别顺序。如果你不排序就会按照字母顺序或原始出现顺序画很容易误导读者。建议聚合后立刻sort_values()让柱状图从高到低排列视觉上更符合“条形图排行榜”的阅读习惯。水平柱状图barh只是把bar换个方向category_sum.plot(kindbarh, figsize(8, 5), title各品类销售额排行)它特别适合品类名称很长的情况。名称是斜着放不下水平放就没有这个问题。这里有个小技巧barh画出来之后Y 轴标签顺序是从下往上走的。想让最大值出现在顶部排序时用sort_values(ascendingTrue)想让最大值在底部就反着来。实际做报告时我通常会让最大的排在最上面也就是sort_values()默认的升序结果。3.3 直方图 hist / 核密度图 kde 与 density分布形状直方图用来观察单变量分布df[销售额].plot(kindhist, bins12, edgecolorwhite, figsize(8, 5))bins是最重要的参数。太小会掩盖分布细节太大又会变成锯齿状。一个比较实用的经验先用默认bins10画一版再根据数据量调整通常样本量几百时bins10~20够用样本量几万时甚至可以用bins50。直方图是连续数值的探索工具如果你发现图形是一坨集中在左边的长尾说明数据偏态明显后续建模时可能需要做对数变换或Box-Cox变换。核密度图是直方图的平滑版本df[销售额].plot(kindkde, figsize(8, 5)) df[销售额].plot(kinddensity, figsize(8, 5))这两行代码画出来的东西在底层完全一致。density只是kde的别名因为kde全称是 Kernel Density Estimation而有些人更习惯叫“密度图”。如果你把两个 kind 分别画在同一张图上会看到两条完全重合的曲线。Pandas 文档里把kde和density作为两个 kind 列出所以我在统计 12 类时把它们分开看实际使用时选一个就行。核密度图容易受带宽影响数据量少时会画出诡异的凸起或凹陷建议与直方图配合着看不要只画密度曲线就下结论。3.4 箱线图 box离群值和分位数一眼看穿箱线图是我的“体检工具”。它把中位数、四分位数和离群值压缩在一张图里特别适合同时对比多个数值列df[[销售额, 订单量, 客户数]].plot(kindbox, figsize(8, 5))Pandas 会自动把这三列分别画成三个箱体。箱体中间的线是中位数箱体上下边界是 Q1 和 Q3上下须是 1.5 倍四分位距范围内的最大值和最小值超过这个范围的点会以“小圆圈”形式显示那就是离群值。很多人第一次看箱线图会疑惑为什么那根中位数线不在箱子正中间这是好事说明数据偏态。偏得越厉害你需要警惕的情况越多。箱线图还有一个隐藏功能当你有几十个数值列时可以用df.select_dtypes(number).plot(kindbox, subplotsTrue)把每一列拆成单独子图快速扫描哪些列离群值多。3.5 面积图 area累计贡献与趋势结合的视图面积图是从折线图演化出来的它对折线下方做了颜色填充df[[销售额, 订单量]].plot(kindarea, stackedTrue, figsize(10, 5), alpha0.7)stackedTrue时下一列的面积会堆叠在上一列之上顶部的高度表示总和。这个图很适合回答“总量是多少、各组成部分如何变化”这类问题。比如你想看“销售额 订单量”随时间的变化趋势同时又要分辨两个指标各自的贡献堆叠面积图就非常直观。面积图有个必须注意的地方如果不同列的数值量级差别太大小数值的序列几乎会被压成一条线看不见变化。这时候要么拆到子图里要么先做标准化要么就用折线图而不是面积图。我踩过不少次坑拿着两个量级相差 10 倍的指标直接画面积图结果发现底下的序列完全没有信息量。3.6 散点图 scatter / 六边形分箱图 hexbin两变量相关关系散点图用于观察两个连续变量之间的关系df.plot(kindscatter, x订单量, y销售额, figsize(8, 5), alpha0.7)这里一定要显式指定x和y因为它不是按列全部画出而是从 DataFrame 里选两列做映射。如果还想看第三个变量可以用c和sdf.plot( kindscatter, x订单量, y销售额, c客户数, colormapviridis, sdf[客户数] * 3, alpha0.7, )c指定颜色映射的数值列s指定散点大小。这种“气泡散点图”能让一张图承载三维信息。但要注意如果数据点太多比如几万条普通散点图会糊成一片。这个时候建议使用hexbindf.plot(kindhexbin, x订单量, y销售额, gridsize15, cmapYlGnBu, figsize(8, 5))六边形分箱图会把二维平面切成一个个六边形格子用颜色深浅表示落在格子里的样本数量。gridsize越大格子越密图形越精细但也越容易抖动。几百上千的样本量用散点图就够几万以上我直接用 hexbin避免“黑压压一片”的问题。3.7 饼图 pie / 表格图 table占比展示与原始数据附录饼图用来展示占比只适合“类别数量比较少”的情况。品类一共 4 个可以画category_sum.plot(kindpie, autopct%.1f%%, figsize(7, 7), ylabel)autopct会直接在扇区里写出百分比ylabel是去掉饼图左侧多余的轴标签。饼图最大的问题是类别一多扇区角度太小标签挤在一起没法看。超过 6 个类别的时候我基本不会选饼图宁可使用柱状图。表格图稍微特殊一点它适合作为主图的附件。比如你想把某几个异常日的原始数据一起展示import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(9, 4)) ax.axis(off) df.head(10).plot.table(axax, loccenter)这样做出来的是一张纯表格图可以直接保存成图片放进报告附件。也可以画完折线图之后把表格放在折线图下方做成“图表 数据明细”的复合图但我建议只在数据量小的时候用数据一多表格图就会非常丑陋。4. 完整实战从一个业务 DataFrame 一口气画出 12 类图4.1 造一份接近真实业务的销售数据为了让上面的内容能直接落地我把所有 12 类图串成一个完整脚本。假设你已经拿到了前面那份df并且执行过df df.set_index(日期).sort_index() category_sum df.groupby(品类)[销售额].sum().sort_values() numeric_cols [销售额, 订单量, 客户数]现在我们手上有三块数据带日期索引的全量宽表、品类汇总表、多个数值列。这三块基本覆盖了 12 类图的所有输入需求。4.2 12 类图全量脚本与展示要点下面是一段可以一次性跑完的脚本。我只省略了plt.show()你在 Jupyter 里可以直接看到每一张图import matplotlib.pyplot as plt # 1. 折线图 df[[销售额, 订单量]].plot(kindline, figsize(10, 5), title1. 折线图) # 2. 面积图 df[[销售额, 订单量]].plot(kindarea, stackedTrue, figsize(10, 5), alpha0.7, title2. 面积图) # 3. 柱状图 category_sum.plot(kindbar, figsize(8, 5), rot0, title3. 柱状图) # 4. 水平柱状图 category_sum.plot(kindbarh, figsize(8, 5), title4. 水平柱状图) # 5. 直方图 df[销售额].plot(kindhist, bins12, edgecolorwhite, figsize(8, 5), title5. 直方图) # 6. 核密度图 df[销售额].plot(kindkde, figsize(8, 5), title6. 核密度图) # 7. 密度图 df[销售额].plot(kinddensity, figsize(8, 5), title7. 密度图) # 8. 箱线图 df[numeric_cols].plot(kindbox, figsize(8, 5), title8. 箱线图) # 9. 散点图 df.plot(kindscatter, x订单量, y销售额, alpha0.7, figsize(8, 5), title9. 散点图) # 10. 六边形分箱图当前这份数据点少实际效果演示为主 df.plot(kindhexbin, x订单量, y销售额, gridsize8, cmapYlGnBu, figsize(8, 5), title10. 六边形分箱图) # 11. 饼图 category_sum.plot(kindpie, autopct%.1f%%, figsize(7, 7), ylabel, title11. 饼图) # 12. 表格图 fig, ax plt.subplots(figsize(9, 4)) ax.axis(off) df.head(10).plot.table(axax, loccenter) fig.suptitle(12. 表格图)如果是在脚本环境下运行建议把每张图保存下来再统一查看for i, fig in enumerate(plt.get_fignums()): plt.figure(fig).savefig(fchart_{i}.png, dpi150, bbox_inchestight)这里用plt.get_fignums()拿到所有的画布编号再逐张保存省去为每张图单独命名保存的麻烦。4.3 出图前的最后把关中文字体、DPI 与裁剪许多人的图不是画不出来而是画出来没法用。最常见的是中文显示为方框。解决办法是在绘图前统一设置 Matplotlib 的全局配置plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] Falseaxes.unicode_minus是第二个关键设置。如果不把它设为False坐标轴上出现的负号可能显示成方块尤其是在 Linux 环境下。另一个常见问题是保存图片时边缘被裁剪尤其是标题或图例超出画布边界。解决办法是保存时加上bbox_inchestightfig df[[销售额, 订单量]].plot(kindline).get_figure() fig.savefig(trend.png, dpi150, bbox_inchestight)dpi150是折中值平时屏幕上看够了如果要打印或者放进 PPT我一般用dpi200甚至dpi300不然放大后文字边缘会发虚。5. 常见问题与排查技巧实录5.1 三类最常报错类型错误、索引问题、空数据Pandas 绘图报错并不神秘绝大多数跑不出图是因为数据本身有问题。我遇到的频率最高的有三类第一类是ValueError: could not convert string to float。这就是你试图把包含字符串的列画成数值图比如df.plot(kindline)时 Pandas 默认把所有列都画一遍而品类列是文本。解决办法是只选择数值列df.select_dtypes(number).plot(kindline, subplotsTrue)第二类是和索引有关最常见的是画散点图用了不存在的列名报KeyError。这个报错通常在x或y里写错列名多用df.columns检查一下。第三类是空值问题。如果 DataFrame 里全为空或某列全为空Pandas 有时会画出空白图不报错但也没有信息量。可以用df.isna().sum()先看一下缺失值情况。绘图前要不要删空值取决于场景折线图可以保留空值Pandas 会自动断开该区间但回归分析和相关性分析前空值最好先处理掉。5.2 中文乱码与负号方块两句代码救回来中文乱码几乎每台机器都会遇到一次尤其是直接用df.plot(title销售额)的时候。如果还没用上面那两句rcParams输出图片里的中文全是方框。这里有一个细节不同系统的中文字体名字不一样Windows 常见的是SimHei和Microsoft YaHeimacOS 常见的是PingFang SCLinux 要看是否安装了wqy-zenhei等字体。你可以一次多传几个plt.rcParams[font.sans-serif] [ SimHei, Microsoft YaHei, PingFang SC, WenQuanYi Zen Hei, Arial Unicode MS, ]这样从左到右找第一个可用的字体兼容性会高很多。如果设置了字体还是方块大概率是当前环境没有安装对应字体那就需要手动安装字体文件或者换一个确认存在的字体名字。5.3 数据太多画成一坨黑用 hexbin 和采样代替 scatter一万条以内的散点图通常还能看但到了几万条、几十万条普通散点图会变成一团黑墨。因为 Matplotlib 要把每个点的形状都画出来Windows 上渲染慢视觉上又看不出密度。我常用的两个替代方案一是hexbin用颜色深浅替代点的密集程度画几十万条数据也没压力。二是对原始数据随机采样。做探索性分析时如果只是想看趋势和相关性df.sample(n5000)往往就够了。采样后画散点图速度更快图形也更清爽。不要一听见“采样”就觉得是在偷懒做初步可视化时采样是合理的但要记住采样后图中看到的密度分布可能与真实分布有偏差。5.4 图例重复、日期刻度过密、柱状图宽度刺眼图例重复通常发生在df.plot(kindline, y[销售额, 订单量], legendTrue)且多次执行的时候因为 Pandas 可能在同一个 Axes 上反复添加 label。此时先plt.cla()清空当前轴或者新建 Figure 再画图可以避免同一个图例重复叠加。另外如果一张图里画了好几个指标但图例标签一样说明列名重复建议先检查列名是否有重复或者用df.columns.duplicated()。日期刻度过密也是一个高频问题。30 天可能还好但如果数据跨度是 300 天X 轴刻度会挤成一团。一个快速解决办法是ax df[[销售额]].plot(kindline) ax.xaxis.set_major_locator(md.DayLocator(interval10)) ax.xaxis.set_major_formatter(md.DateFormatter(%m-%d))这里interval10表示每隔 10 天显示一个刻度。如果不想引入matplotlib.dates也可以简单地对索引做切片只画一部分但那样会丢失全局趋势所以还是用刻度定位器更合理。柱状图宽度刺眼通常是因为类别太少或太多。类别少时柱子会特别粗类别多时柱子会变得像缝衣针。可以用width参数调节category_sum.plot(kindbar, width0.6)类别很多时我不建议硬调宽度而是考虑水平柱状图或者只展示 Top N 并加上“其余”归为其他类。6. 我踩过几次坑之后的选图方法论6.1 按变量类型选图形虽然 Pandas 提供了 12 类图但选错图是很多分析报告看起来别扭的根源。我自己的选图逻辑很简单两个变量都是连续数值且想看到“随着 X 增大Y 怎么变”先试折线图如果 X 是时间或散点图。一个变量是类别一个变量是数值用柱状图或水平柱状图。一个连续数值变量看分布用直方图、核密度图或箱线图。一个变量是类别且想显示占比用饼图但类别不要超过 6 个。多个连续数值变量要对比分布直接用箱线图信息密度最高。两个连续变量且数据量很大优先 hexbin而不是散点图。这张表不用背关键原则是“你想让读者对比什么”。对比趋势选折线对比大小选柱状对比分布选直方图和箱线图对比关系选散点。想清楚这一点kind参数其实很好填。6.2 按数据量级选图形选图还有一个经常被忽略的维度数据量级。几十到几百行几乎什么图都能画优先散点图和柱状图。几千到几万行散点图还可以但要注意点之间的覆盖折线图如果每条线几百个点线条会非常曲折可以先按天/月聚合再画。十万行以上别再画普通散点图直接 hexbin或者先聚合再画。折线图如果时间跨度很长最好做重采样例如df[销售额].resample(M).mean()画月度趋势而不是直接拿日度数据画一条锯齿线。我见过不少人拿着实时采样的百万行数据硬画普通散点图最后图没出来电脑先卡住。Pandas 绘图再方便底层也要经过 Matplotlib数据量一大必须做聚合或降维。6.3 进一步扩展pandas.plotting 模块的高级原生绘图12 类基础图之外Pandas 其实还有一个容易被忽略的pandas.plotting模块它提供了更高级的原生绘图函数比如from pandas.plotting import scatter_matrix, parallel_coordinates, radviz, lag_plot, autocorrelation_plot scatter_matrix(df[numeric_cols], figsize(10, 10), alpha0.7) lag_plot(df[销售额]) autocorrelation_plot(df[销售额])scatter_matrix能快速生成一个多变量两两散点矩阵是我做特征筛选时的常用工具。parallel_coordinates适合展示多个类别在多维数值上的模式。lag_plot和autocorrelation_plot则更适合时间序列分析看自相关性时非常方便。这些函数不是kind参数能直接切换的所以我在标题里没有把它们算进“12 类基础图”。但如果你经常用 Pandas不要只盯着df.plot()from pandas.plotting import ...这一行会让你的可视化武器库瞬间扩大一倍。6.4 最后说一点个人体会接触 Pandas 绘图这么多年最大的感受是它不是为了取代专业可视化工具而是为了让数据分析师在“分析过程中”快速获得反馈。你不需要把图画得多么精致重要的是画出来的图能及时告诉你“数据是对的、趋势是存在的、异常点是值得关注的”。我到现在依然会在建模前先用 Pandas 画一堆箱线图和散点图筛选掉明显有问题的特征再进入正式建模流程。每次有人问我 Pandas 绘图值不值得学我都会说先把kind这 11 个参数玩熟再学其他可视化库你的效率会提升一大截。另外如果你做的不是一次性探索而是要重复生成周报我建议把这些绘图代码封装到一个函数里。比如输入一个 DataFrame自动按列的类型选择合适图形并保存成文件这样后续换数据只需要重新跑一遍脚本不需要再重复调参。这也是把 Pandas 绘图用得“融会贯通”之后真正省时间的地方。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →