拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Matplotlib绘图线从入门到实战:线型样式、坐标轴与子图全解析

用Python做科学计算和数据可视化绕不开Matplotlib而Matplotlib里打交道最多的图形对象就是绘图线。折线图、趋势曲线、频谱包络、模型误差曲线、训练损失曲线——这些看似不同的图落到代码上都是在画一条线。很多同学一开始都是plt.plot(x, y)一把梭画完才发现线型不协调、坐标轴不对、中文乱码、图例挤成一团。这篇我把Matplotlib绘图线从头到尾捋一遍线型样式怎么选、坐标轴尤其是对数坐标轴怎么控制、多个子图怎么编排、中文显示和全局配置怎么一劳永逸最后再给一套和numpy、pandas、scikit-learn联动时的完整示例。适合刚摸到Matplotlib门的新手也适合画过不少图但总在细节上卡壳的同学。1. 绘图线的基础认知与设计思路1.1 为什么说线条是可视化的“第一公民”数据可视化本质上是在做一件事把数据里的规律转化成眼睛能快速识别的视觉信号。点能表达“某个时刻有多高”但只有线能表达“从高到低是怎么变化的”。日活曲线、股价走势、模型loss下降、传感器读数波动一旦被连成线趋势、周期、异常点、收敛速度都变得一目了然这就是线条在数据图里的地位。画线这件事很多人以为只要把数据点连起来就行但实际项目里“能不能看懂”很大程度取决于线条的视觉设计。两条不同含义的线都是黑色实线读者会盯着图发愣线宽太细投到屏幕上根本看不清对数坐标下该用直线趋势表达结果用线性轴画出一团压缩在左下角的曲线。说白了绘图线不只是一个“画线函数”它是一套视觉语法的载体。理解这一点后面所有参数的学习都是在往这套语法里补词汇。1.2 两种APIplt.plot() 与 ax.plot()Matplotlib画线有两条路。第一种是Pyplot状态机风格直接调用plt.plot(x, y)简单直观import matplotlib.pyplot as plt import numpy as np x np.linspace(0, 10, 100) y np.sin(x) plt.plot(x, y) plt.xlabel(x) plt.ylabel(y) plt.title(sin(x)) plt.show()第二种是面向对象风格先创建Figure和Axes再调用ax.plot(x, y)fig, ax plt.subplots(figsize(8, 4)) ax.plot(x, y) ax.set_xlabel(x) ax.set_ylabel(y) ax.set_title(sin(x)) fig.tight_layout() plt.show()两种写法在简单场景下结果一样但实际操作里我强烈建议养成用Axes的习惯。原因是Pyplot风格每次调用plt.xlabel()时Matplotlib都要通过plt.gca()去猜“当前正在操作哪个坐标系”。画单张图的时候无所谓一旦代码变长、出现多个子图、双Y轴、多次循环画线这个“隐形状态”就成了最大的坑——你明明想改第二个子图的标题结果改到了最近一次激活的图上。用ax.xxx()则所有操作都显式绑定到某个Axes上谁是谁、改哪里一眼就清楚。尤其配合subplots()创建子图后axes[i, j]直接定位到具体位置代码可维护性完全不是一个量级。对比项plt.plot() 状态机ax.plot() 面向对象上手难度低适合交互式探索略高但结构清晰多子图时容易搞混当前轴每个Axes独立显式控制代码复用较差适合封装成函数实际项目很少用推荐主力使用1.3 画线前把数据整理成Matplotlib喜欢的样子不管用哪种APIplot()接收的数据本质上都是三类numpy数组、Python列表、pandas的Series或DataFrame列。画线时x和y必须长度一致这是最基础也是新手最容易踩的坑——DataFrame里有几行NaN或者groupby之后索引对不齐画出来就是一条断掉的线或者直接报错。import pandas as pd # 从DataFrame取两列直接画线 df pd.DataFrame({ time: pd.date_range(2024-01-01, periods100, freqD), value: np.random.randn(100).cumsum() }) fig, ax plt.subplots() ax.plot(df[time], df[value]) plt.show()pandas的日期列可以直接作为x轴但有个常见的细节如果日期列是字符串类型Matplotlib会把它当成一堆离散类别横轴刻度会非常拥挤。建议先pd.to_datetime()转成datetime类型再画。数值列如果有NaN线会在NaN处断开有时候这恰恰是好事——能直观看到数据缺口在哪有时候不是需要先fillna()或用插值补上。这是画线前必须想清楚的一步数据的预处理质量直接决定线的可信度。2. 核心细节线条的样式体系2.1 线型与颜色先定“性格”再调“皮肤”Matplotlib线型就那么几种但选错很容易让图变得很“廉价”。最常用的有四种实线-、虚线--、点划线-.、点线:。一张图里如果有多条含义并列的线我的习惯是先区分颜色再在关键的一条比如基线、参考线上用虚线或点划线做二次区分而不是每条线都用不同线型那样视觉上会很花。颜色的写法有四种单字符颜色r、十六进制#1f77b4、RGB元组(0.2, 0.4, 0.6)、颜色名称orange。实际项目里我几乎不用单字符颜色因为可选范围太小、颜色太“生”。更推荐十六进制既能精确控制色值也和设计稿、品牌色的风格容易对齐。fig, ax plt.subplots(figsize(9, 4)) x np.linspace(0, 2 * np.pi, 200) ax.plot(x, np.sin(x), linestyle-, color#1f77b4, label实线) ax.plot(x, np.sin(x 1), linestyle--, color#ff7f0e, label虚线) ax.plot(x, np.sin(x 2), linestyle-., color#2ca02c, label点划线) ax.plot(x, np.sin(x 3), linestyle:, color#d62728, label点线) ax.set_xlabel(x) ax.set_ylabel(y) ax.legend() fig.tight_layout() plt.show()另一个实用技巧是使用fmt参数快速组合ax.plot(x, y, r--)表示红色虚线。这个写法适合快速探索阶段正式出图时我会写成带linestyle和color的完整形式可读性更好。2.2 线宽、标记点和透明度细节决定质感linewidth缩写lw控制线条粗细。这看起来是小事但项目交付的图一旦投到大屏或者印到PDF里太细的线会直接“消失”。我的经验值是一般展示曲线用1.5~2主视觉曲线用2.5辅助参考线用0.8~1。数据量大的曲线如果线太粗会糊成一团黑色这时反而要降线宽同时配合透明度。标记点marker用于标出离散数据的位置可选的形状很多o圆点、s方块、^三角、d菱形等。什么时候该用标记我总结的规律是点少几十个以内且需要精确读数值时用标记点多几百上千时用线就够了硬加标记只是一片墨点。折中方案是markevery参数它控制每隔多少个点画一个标记x np.linspace(0, 50, 200) y np.random.randn(200).cumsum() fig, ax plt.subplots(figsize(8, 3.5)) ax.plot(x, y, linewidth1.2, markero, markersize5, markevery20) plt.show()这样线是完整的标记只是每隔20个点出现一次既保留了线的连续感又能看清楚取样位置。这是我用了几年之后才发现的参数以前都是手动切片数据来实现代码又长又容易错。透明度用alpha控制取值0到1之间1为完全不透明。多条线叠在同一个坐标里时把每条线的alpha都设成0.5~0.7能明显减轻互相遮挡带来的视觉压力还能让重叠区域的密度透出来这对探索性数据分析图特别有用。2.3 图例、坐标标签和标题让线条能被读懂线画出来只是半成品没有图例、标签和标题的图就算样式再精致别人也看不懂。图例要依赖label参数每个plot()里传了label最后调用一次ax.legend()才会显示图例。这个顺序经常有人搞反——画了线但不传label然后问为什么图例是空的。图例的位置和排列也有讲究。默认locbest是让Matplotlib自己找空白处大多数情况靠谱但有时它会挡住关键曲线尤其是曲线密集的时候。我会用手动方式ax.legend(locupper right)或者干脆把图例放到图外面ax.legend(locupper left, bbox_to_anchor(1.02, 1), borderaxespad0)这句配合fig.tight_layout()或bbox_inchestight保存时图例会显示在图的右侧不占绘图区域。多序列图例如果条目很多用ncol2或ncol3排成两列三列比一列到底清爽很多。还有一个高阶技巧用Line2D手造图例句柄可以在图例里加入图上并不存在的数据线用来表达“理想趋势”或“参考范围”这类概念from matplotlib.lines import Line2D legend_items [ Line2D([0], [0], color#1f77b4, lw2, label实测值), Line2D([0], [0], color#2ca02c, lw2, ls--, label目标趋势) ] ax.legend(handleslegend_items, locbest)3. 坐标轴控制让线在正确的尺度上讲故事3.1 对数坐标轴什么时候用、为什么用、怎么用「对数坐标轴」这个名字听起来吓人其实想解决的事情很简单当数据跨越好几个数量级时线性轴会把小数值挤在底部肉眼完全看不出结构。比如网络请求延迟从1ms到1000ms价格从0.01到10000神经网络训练早期loss从5.0降到0.005这种数据用线性轴画前段走势被压缩成一条陡降到地平线的曲线后面几乎是一条平线什么都看不清。解决办法就是对数刻度。Matplotlib里用两行代码ax.set_xscale(log) ax.set_yscale(log)如果x和y都要对数可以直接ax.loglog(x, y)。绝大多数情况下我们更常遇到的是只对y轴做对数处理比如观察loss下降、响应耗时分布这时用ax.set_yscale(log)就够了。对数坐标最大的隐藏坑是数据必须全为正数。0和负数在数学上没法取对数Matplotlib遇到会直接跳过或画不出期望的效果。如果数据里混了0值先过滤x np.array([0, 1, 2, 4, 8, 16, 32, 64]) y np.array([0, 1, 4, 16, 64, 256, 1024, 4096]) mask (x 0) (y 0) ax.loglog(x[mask], y[mask], -o)如果负值也必须展示比如误差可以是负的可以用symlog刻度它在0附近用线性、两侧用对数ax.set_yscale(symlog, linthresh1)对数坐标还有个很妙的理解幂律关系y C * x^k画在对数坐标下会变成一条直线。以前做性能分析时我经常用loglog坐标确认数据是否符合幂律分布——只要点在双对数图上排成直线斜率就是幂指数k一眼就能读出规律不需要拟合。x np.logspace(-2, 2, 100) y 3 * x ** 1.5 fig, ax plt.subplots(figsize(8, 4)) ax.loglog(x, y, linewidth2) ax.set_xlabel(x对数坐标) ax.set_ylabel(y对数坐标) ax.set_title(幂律关系在对数坐标下呈直线) plt.show()3.2 坐标范围与刻度不要让默认值拖后腿Matplotlib默认会根据数据范围自动设坐标轴边界还会在两端各留5%的空白。大多数时候这很友好但有几种情况要手动干预想从0开始展示基线、想去掉空白让曲线占满画布、想锁定坐标范围方便多图对比。ax.set_xlim(0, 100) ax.set_ylim(0, 1)多图对比时最怕每张图的坐标范围不一样读者会误读曲线波动大小。如果是不同类别的数据做横向对比一定要手动固定x轴范围如果是同一接口不同时段的指标y轴也最好统一。刻度间隔方面MultipleLocator是我最常用的工具from matplotlib.ticker import MultipleLocator ax.xaxis.set_major_locator(MultipleLocator(5)) # x轴主刻度每隔5显示一个 ax.yaxis.set_major_locator(MultipleLocator(0.2))配合FuncFormatter可以美化刻度标签比如把y轴显示成百分比from matplotlib.ticker import PercentFormatter ax.yaxis.set_major_formatter(PercentFormatter(1))把刻度控制权从默认手里拿回来之后整张图会一下子变得专业很多。3.3 双Y轴两种量纲同框要克制有时候两条线的量纲完全不一样比如“温度”和“湿度”一个在0~40一个在20%~80%直接画在同一个y轴上必有一条被压平。这时可以用twinx()创建共享x轴的第二个y轴fig, ax1 plt.subplots() t np.arange(0, 24, 0.1) temp 25 8 * np.sin(2 * np.pi * t / 24) humidity 55 20 * np.sin(2 * np.pi * t / 24 2) ax1.plot(t, temp, colortab:red, label温度) ax1.set_ylabel(温度 (°C), colortab:red) ax1.tick_params(axisy, labelcolortab:red) ax2 ax1.twinx() ax2.plot(t, humidity, colortab:blue, linestyle--, label湿度) ax2.set_ylabel(湿度 (%), colortab:blue) ax2.tick_params(axisy, labelcolortab:blue) plt.show()注意双Y轴图有个天然视觉陷阱两条线的交点会被读者误读成“数值相等”但它们的刻度根本不通用。所以我在实际项目里对双Y轴非常克制能用上下两个子图同一个x轴替代就尽量不用双Y轴尤其当目标读者不熟悉这个图表的语境时。两条曲线的纵向对比关系用共享x轴的两个独立坐标区来表达更不容易产生误读。4. 子图使用一次生成多张“子线图”4.1 六张图合在一个面上的核心方法搜“六张图合在一个图上”这个需求的人多半是想把多个相关的指标曲线汇总到一张大图里方便整体对比。Matplotlib实现这点最直接的方式就是plt.subplots()fig, axes plt.subplots(2, 3, figsize(15, 8))这句创建了一个2行3列的Axes数组总共6个子图。axes的形状是(2, 3)通过axes[0, 0]、axes[1, 2]这样的索引可以精确定位每个子图。如果要批量设置直接用axes.flat遍历所有子图for ax in axes.flat: ax.plot(x, y) ax.grid(alpha0.3)当6个子图共享同一个x轴比如都是时间序列时可以通过sharexTrue省掉大量冗余的横轴刻度fig, axes plt.subplots(2, 3, figsize(15, 8), sharexTrue)这样最后一行的子图会显示x轴刻度上面一行的x轴自动隐藏图面瞬间干净不少。同理如果6个子图的y轴尺度一致也可以shareyTrue如果不一致千万别共享否则数值差别大的指标会被压成一条直线。4.2 同一坐标内叠加多条线子图之外的另一种“多线”场景是在同一个坐标区里叠加多条线用于对比不同分组、不同时间窗口、不同模型输出的变化。最简单的写法是多次调用plot()每次传不同label最后统一legend()。但如果线条数量多到要靠循环生成就要注意控制颜色循环和标签fig, ax plt.subplots(figsize(9, 5)) cols [#1f77b4, #ff7f0e, #2ca02c, #d62728] for i, col in enumerate(cols): y np.sin(x i * 0.5) ax.plot(x, y, colorcol, linewidth1.5, alpha0.8, labelf相位偏移 {i * 0.5}) ax.legend(locupper right, ncol2) plt.show()在循环里画线有几个心得。第一不要每次都调用plt.plot()再plt.legend()那样图例范围会越来越乱正确做法是循环里只传label循环结束后统一调用一次legend。第二颜色尽量用色觉友好、色相差异大的组合别用红绿这种色盲用户看起来几乎一样的搭配。第三如果线实在太多用alpha降透明度比硬塞不同颜色更实用。4.3 布局与尺寸别让子图挤成一团六个子图画在同一张图上最常遇到的问题就是“挤成一团”标题和坐标标签互相重叠、日期刻度横竖交错。解决方案有三板斧。第一是constrained_layoutTrue在创建fig时直接设置让Matplotlib自动为子图之间留出空间fig, axes plt.subplots(2, 3, figsize(15, 8), constrained_layoutTrue)第二是fig.tight_layout()在画完内容后调用一次自动调整子图间距。第三是fig.subplots_adjust(hspace0.3, wspace0.3)手动控制子图之间的垂直和水平间距。这三种方式不冲突可以按场景选择。保存时bbox_inchestight是个必杀技。它会自动裁掉图片周围多余的空白避免明明画了标题却因为边距太紧被截掉一半fig.savefig(six_panels.png, dpi150, bbox_inchestight)dpi参数决定图片清晰度默认100偏糊交付用的图我一般用150~300。如果后续要放大或出版印刷还可以直接存成矢量格式fig.savefig(six_panels.svg)无限放大都不会糊。5. 中文显示、字体与全局配置5.1 中文乱码几行代码解决的事Matplotlib画图遇到中文默认情况下是一排方格子这是新手最常遇到的劝退问题。原因是Matplotlib的默认字体DejaVu Sans不支持中文字形遇到中文只能渲染成占位方块。解决方案很明确手动指定一个系统中文字体。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False第一行指定字体候选列表Matplotlib会从左到右找第一个系统里存在的字体。Windows一般有SimHei黑体和Microsoft YaHei微软雅黑macOS和Linux常见的是Noto Sans CJK SC或Source Han Sans SC。如果你的脚本要跨系统跑直接把这几个都写进列表。第二行axes.unicode_minus False解决的是另一个隐藏问题设置中文字体后数值轴的负号偶尔会变成方块这是因为默认负号字符用的也是DejaVu字体。关掉unicode_minus后负号会用ASCII风格的减号渲染干干净净。如果你不确定系统里有哪些可用字体可以列出来看from matplotlib import font_manager names sorted({f.name for f in font_manager.fontManager.ttflist}) print([n for n in names if Hei in n or YaHei in n or CJK in n or SimSun in n])5.2 全局配置rcParams一次设置处处生效中文字体只是rcParams这个全局配置字典里的一个字段。实际项目里我更推荐把所有通用配置集中到一个代码块画每张图之前先执行一次直接形成统一的风格基调plt.rcParams.update({ figure.figsize: (10, 5), figure.dpi: 100, savefig.dpi: 150, font.sans-serif: [Microsoft YaHei, SimHei, Noto Sans CJK SC], axes.unicode_minus: False, axes.grid: True, grid.alpha: 0.3, lines.linewidth: 1.8, legend.fontsize: 10, axes.titlesize: 13, axes.labelsize: 11, })这几个字段覆盖了最常用的需求图片尺寸、保存清晰度、中文字体、网格线、线条默认粗细、字号。配置一次之后后面所有画的图都继承这些设置不用每张图重复写。如果某张图想临时覆盖单图内显式传参就行优先级高于全局配置。这里有个实操中的坑要注意rcParams修改的是当前代码进程里的全局状态。Notebook里如果反复执行不同配置的代码块后执行的会覆盖先执行的容易让前面的图“风格突变”。我的习惯是每个Notebook开头只做一次配置后续所有绘图逻辑都依赖这同一份配置。5.3 样式表一行代码切换整体风格除了手动改rcParamsMatplotlib还内置了多套样式表一行代码就能切换整体外观plt.style.use(ggplot)常用样式包括ggplot模仿R语言ggplot2风格、bmh来自Bayes Methods Hackathon、fivethirtyeight新闻数据风格、seaborn-v0_8-whitegrid浅灰网格风格。注意Matplotlib 3.6之后seaborn样式统一改名成seaborn-v0_8-*直接写seaborn会飘红甚至报错。样式表在Notebook里更适合用上下文管理器做局部生效不影响后续画图with plt.style.context(ggplot): fig, ax plt.subplots() ax.plot(x, y) plt.show()6. 实战演练从原始数据到可发布的线图6.1 先造一份“像真实业务”的模拟数据空讲参数不够直观我模拟一份内容平台的运营数据用6个指标分别展示不同类型的线图。时间跨度120天包含DAU、新增用户、内容发布量、评论量、接口P95耗时和模型训练loss。import numpy as np import pandas as pd import matplotlib.pyplot as plt np.random.seed(42) days pd.date_range(2024-01-01, periods120, freqD) df pd.DataFrame(indexdays) # DAU线性上升 7天周期性波动 噪声 df[dau] 10000 60 * np.arange(120) 1500 * np.sin(2 * np.pi * np.arange(120) / 7) np.random.normal(0, 200, 120) # 新增用户平稳上升 df[new_user] 300 2.5 * np.arange(120) np.random.normal(0, 20, 120) # 内容发布量随机波动再做7日移动平均 publish np.random.randint(800, 1500, 120) df[publish] publish df[publish_ma7] pd.Series(publish).rolling(7, min_periods1).mean() # 评论量工作日和周末差异明显 weekend (days.dayofweek 5).astype(int) df[comment] 5000 3000 * weekend np.random.normal(0, 300, 120) # P95耗时逐渐劣化适合用对数轴观察 df[p95] 80 8 * np.arange(120) ** 1.5 / 20 np.random.normal(0, 5, 120) # 训练loss模拟指数衰减 df[loss] 3.0 * np.exp(-0.04 * np.arange(120)) 0.005数据生成逻辑里我刻意安排了不同形态有的是线性趋势叠加周期有的是随机毛刺有的是指数衰减。这样画出来的6张子图才能覆盖多种线条样式的使用场景。6.2 一张大图覆盖多种线条形态下面这段代码把6个指标用2行3列子图汇总到一张图上每张子图用不同的线型、标记和坐标轴设置同时处理了中文显示、日期倾斜、对数轴和全局网格fig, axes plt.subplots(2, 3, figsize(16, 8), constrained_layoutTrue) axes[0, 0].plot(df.index, df[dau], linewidth1.6, color#1f77b4) axes[0, 0].set_title(DAU 趋势) axes[0, 1].plot(df.index, df[new_user], linestyle--, markero, markersize4, markevery10, linewidth1.4, color#ff7f0e) axes[0, 1].set_title(新增用户) axes[0, 2].plot(df.index, df[publish_ma7], linestyle-., linewidth1.6, color#2ca02c) axes[0, 2].set_title(内容发布量 7 日均线) axes[1, 0].plot(df.index, df[comment], alpha0.75, linewidth1.2, color#d62728) std df[comment].std() axes[1, 0].fill_between(df.index, df[comment] - std, df[comment] std, alpha0.15, color#d62728) axes[1, 0].set_title(评论量及波动区间) axes[1, 1].plot(df.index, df[p95], linewidth1.5, color#9467bd) axes[1, 1].set_yscale(log) axes[1, 1].set_title(接口 P95 耗时对数 Y 轴) axes[1, 2].plot(df.index, df[loss], linewidth1.8, color#8c564b) axes[1, 2].set_yscale(log) axes[1, 2].set_title(训练 Loss对数 Y 轴) for ax in axes.flat: ax.tick_params(axisx, rotation30) ax.grid(alpha0.3) fig.suptitle(内容平台核心指标线图汇总, fontsize16) fig.savefig(core_metrics.png, dpi150, bbox_inchestight)这段代码值得注意的地方有几个。markevery10让新增用户的标记不会糊成一团评论量那张图用fill_between画出了波动区间两处对数轴分别用在耗时和loss上因为这两个指标动态范围大、线性轴看不清楚。最后统一grid和日期旋转整张图的风格就整齐了。6.3 与scikit-learn联动画模型的学习曲线绘图线不只用在业务指标上模型评估里也很常见。比如scikit-learn的learning_curve可以返回不同训练集大小下的训练分数和验证分数把这几个数组画出来就能直观判断模型是过拟合还是欠拟合。from sklearn.datasets import make_regression from sklearn.model_selection import learning_curve from sklearn.ensemble import RandomForestRegressor X, y make_regression(n_samples500, n_features8, noise0.3, random_state42) train_sizes, train_scores, valid_scores learning_curve( RandomForestRegressor(n_estimators100, random_state42), X, y, train_sizesnp.linspace(0.1, 1.0, 8), cv5, scoringr2 ) fig, ax plt.subplots(figsize(8, 5)) ax.plot(train_sizes, train_scores.mean(axis1), o-, label训练集得分) ax.plot(train_sizes, valid_scores.mean(axis1), s--, label验证集得分) ax.fill_between(train_sizes, valid_scores.mean(axis1) - valid_scores.std(axis1), valid_scores.mean(axis1) valid_scores.std(axis1), alpha0.2) ax.set_xlabel(训练样本数) ax.set_ylabel(R² 得分) ax.set_title(随机森林回归的学习曲线) ax.legend() fig.tight_layout() plt.show()这里线条的作用一眼就能看出来训练集和验证集两条线的距离、走势以及验证曲线的波动带都在传达模型泛化能力的信息。画这种图时o-和s--这种带标记的组合特别合适因为样本点本身就代表“训练了几次”不是连续过程标记点能帮读者精确对应横坐标。7. 常见问题与排查技巧7.1 高频问题速查表画线过程里我踩过的坑、读者问过我的问题整理成一张表遇到问题时按图索骥。现象可能原因解决方法图上没有线数据里有NaN或x/y长度不一致用np.isnan()检查数据过滤或填充NaN图例不显示每个plot没传label每个plot传入label最后调一次ax.legend()中文显示为方块默认字体不含中文字形设置plt.rcParams[font.sans-serif]为系统中文字体负号显示为方块中文字体设置后负号字符异常同时设axes.unicode_minusFalse保存的图很模糊dpi太低savefig(dpi150)或dpi300图的标题/坐标标签被截断边距不够savefig时加bbox_inchestight多个子图重叠子图间距没调整用constrained_layoutTrue或tight_layout()对数轴上有0值/负值log定义域不允许非正数过滤非正数或改用symlog日期刻度挤成一团x轴是字符串类型转成datetime后设置刻度角度7.2 我踩过的几个坑和现在的处理习惯第一个坑是我刚开始用循环画图时踩的。那时候在for循环里直接写plt.plot()又写plt.xlabel()结果所有线都画到了同一张图上标题还一个盖一个。现在我的习惯是每次循环开始前创建独立的fig, ax plt.subplots()循环里只用ax.xxx()循环结束后统一设置标题和保存顺序清晰、完全不会串图。第二个坑和时间序列有关。早期我拿着订单数据的日期列直接画横坐标确实显示出日期但刻度密到粘连整条x轴像一坨黑色横杠。后面改成pd.to_datetime()转类型再用ax.tick_params(axisx, rotation30)把刻度旋转30度才彻底解决。第三个坑更隐蔽发生在Notebook里。同一个cell重复执行了多次plt.plot()结果旧图一直“残留”在内存里画出来的图看起来好像数据变了但像素没刷新。如果遇到这种诡异情况通常是执行环境缓存了之前的绘图状态手动plt.close(fig)或者从头重启内核就能解决。另外想特别提醒一句画完图尤其是给同事或客户看的交付图一定要主动savefig输出成PNG或PDF文件而不是只在Notebook里show()一下。Notebook里的图渲染效果和最终文件经常有细微差别只有保存出来的文件才是别人真正看到的东西。我现在的固定流程是数据整理完先固定在代码开头统一配置rcParams再按“创建fig → 画线 → 设标签 → 调图例 → 保存高dpi”的顺序来跑完不会漏掉任何一张图。Matplotlib画线的这些年我最大的体会是图是给人看的不是给代码跑的。线型、颜色、坐标尺度、图例摆放每一个细节都在影响读者读取信息的效率。与其指望默认设置不如自己把关键参数掌握住哪怕只是记住markevery、set_yscale(log)、bbox_inchestight这三个小东西出图质量也能立刻上一个台阶。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门