先交代背景再上手操作。我一直觉得 Pandas 和 Matplotlib 是一对黄金搭档前者负责把乱七八糟的原始数据整理成规规矩矩的结构化表格后者负责把这些表格变成能一眼看懂规律的图表。但很多人在实际项目里只停留在“能用”阶段图是画出来了离“能看”“能汇报”还有距离。这篇就把我从数据读取、清洗到出图、美化再到处理各种中文乱码和坐标系坑的完整流程走一遍适合刚接触数据分析、以及已经在用但想提升图表质量的读者。我默认你已经装好了 Python 环境版本建议 3.8 以上。下面所有代码都用 Jupyter Notebook 写但换成 PyCharm、VS Code 甚至命令行脚本都成立只是展示效果略有差别。1. 环境准备先装对库再谈可视化很多人一上来就pip install pandas结果运行时报错找不到模块。这种事我见得太多了十有八九是环境搞混了——电脑里装了多个 Python或者用 IDE 自动创建的虚拟环境和当前终端不是同一个。1.1 Pandas 和 Matplotlib 的安装方式如果你用的是 PyCharm最稳妥的操作顺序是在 PyCharm 右下角查看当前解释器路径确认用的是哪个 Python 环境。打开左下角 Terminal终端输入pip install pandas matplotlib。如果终端里看到的 Python 和解释器不是同一个就切换到解释器对应的Scripts目录再执行。安装完成后在终端里直接执行python -c import pandas; import matplotlib; print(pandas.__version__, matplotlib.__version__)。能打印出版本号说明环境完全没问题。如果你用 Anaconda推荐在 Anaconda Prompt 里执行conda install pandas matplotlib它会自动解决依赖不会出现缺numpy、缺python-dateutil这类连锁问题。国内网络环境下pip下载慢可以用镜像源加速。我常用的命令是pip install pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple-i参数临时指定清华源不用改全局配置安全省事。1.2 import 的规范写法环境装完进入代码环节。Pandas 和 Matplotlib 的组合已经成了业界默认配置别名也基本统一import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib as mplpd、np、plt这三个别名是无数项目沉淀下来的约定俗成没人会直接import pandas然后用pandas.DataFrame()去写太啰嗦。matplotlib单独导入时通常是为了访问rcParams等全局配置。1.3 笔记本环境的两个前置魔法命令如果你在 Jupyter Notebook 里建议先执行两条魔法命令%matplotlib inline plt.rcParams[figure.dpi] 120%matplotlib inline让图表直接嵌入单元格输出区域figure.dpi120能把默认图片分辨率从 72 提高到 120字不会发虚。这两条不写也不影响运行但写了对观看体验影响很大尤其是后续要调整细节时。注意如果你使用 VS Code 或 PyCharm 的 Python 终端%matplotlib inline会报错那说明你没在 Notebook 环境里运行忽略即可。2. 数据入口让结构化数据变成能画图的 DataFrame拿到手的数据十有八九是 Excel、CSV或者几十行对着 API 文档调接口拉下来的 JSON。在画图之前先要把这些数据变成规整的DataFrame——这是 Pandas 的核心数据结构也是后续一切操作的基础。2.1 读取 CSV 和 Excel 的常见坑我先用一份农产品价格数据做演示这种数据很典型有日期、有品种名称、有价格还有产地适合做各种图形展示。df pd.read_csv(vegetable_prices.csv) df.head()第一版代码很容易遇到两类报错UnicodeDecodeError: utf-8 codec cant decode byte 0xd6...文件实际是 GBK 编码但read_csv默认用 UTF-8 解码。解决办法是pd.read_csv(vegetable_prices.csv, encodinggbk)。如果你不确定文件是什么编码可以用记事本打开文件另存为看右下角编码方式或者用chardet库自动检测。ParserError: Error tokenizing data文件里混入了分隔符不一致的行比如某些行多了逗号。解决办法是先pd.read_csv(file, sepNone, enginepython)让 Pandas 自动推断分隔符再人工确认。Excel 文件同理df pd.read_excel(vegetable_prices.xlsx, sheet_nameSheet1)sheet_name参数常被忽略但如果文件是多 Sheet 的不指定就默认读第一个数据往往不是你想要的。建议养成显式指定sheet_name的习惯少踩坑。2.2 数据类型转换2.2 数据类型转换别让看起来是数字骗了你读进来的列不一定是正确的数据类型。最典型的场景价格列在 Excel 里带了个¥符号读进来变成字符串日期列被读成 object 类型数量列里混了空格变成 object。此时如果直接df[price].plot()Pandas 会尝试隐式转换但一旦遇到无法解析的值就报错或者直接忽略。所以画图前的必修课是显式转换# 去掉价格中的货币符号并转为数值 df[price] df[price].astype(str).str.replace(¥, ).astype(float) # 日期列统一成 datetime 类型方便后续按时间筛选和排序 df[date] pd.to_datetime(df[date])这里有个容易懵的点为什么astype(str)在前面因为原始数据可能是 float 或 int直接调用.str.replace会报错AttributeError。先变成字符串再替换再转数字三步走是处理混合类型列的通用套路。pd.to_numeric也是好帮手尤其当列里混入了约 3.5、3.5 kg这类文本时可以配合errorscoerce把解析不了的值变成 NaNdf[price_clean] pd.to_numeric(df[price], errorscoerce)转换完之后养成检查df.info()的习惯看到price是float64、date是datetime64[ns]心里才踏实。这一步没做对后面画什么图都是错的。2.3 数据清洗缺失值和重复值处理结构化数据不一定干净缺失值、重复值、异常值都有可能出现。画图之前不处理图上的折线会出现断点柱状图会突然缺一根散点图会出现离谱的离群点。我的处理顺序一般是这样# 1. 看缺失情况 df.isnull().sum() # 2. 按业务场景决定删还是补时间序列数据用前后均值填充比较自然 df[price] df[price].interpolate() # 3. 去重确保同一个日期、同一种菜只保留一条记录 df.drop_duplicates(subset[date, item], keeplast, inplaceTrue)interpolate()是线性插值适合价格这类连续性数据。如果缺失量太大超过 30%插值就不太靠谱了建议直接把该列或该行删掉否则图表会给人一种虚假的平滑感。到这里DataFrame 里的数据已经可以信任了。接下来才是重头戏——把数据映射到图形上。3. figure、axes、axis这三者的关系决定了你画图时的心态在 Matplotlib 里figure、axes、axis是三个最核心、也最容易混淆的概念。很多人代码写得乱七八糟就是因为没搞清楚这三个东西到底谁管谁。3.1 用画板、画框、坐标轴来理解我习惯用一个类比画家的工作台。figure是整个画板相当于一块空白画布。你可以在这块画布上放一张图也可以放四张图即 2x2 的子图布局。figure管的是整块画布的尺寸、背景色、分辨率。axes是画板上的一块区域相当于一个画框。每个画框里有自己的坐标系、自己的数据。一个figure可以包含多个axes子图布局的每个格子都是一个axes。axis是axes内部的具体坐标轴包含 X 轴、Y 轴以及对应的刻度线、刻度标签。axis负责的是这条轴上数字怎么分布、标签写什么、刻度朝里还是朝外。很多教程里的plt.plot()其实隐藏了这三层关系plt会自动在当前的axes如果没有就创建一个上画图而这个axes又自动放在当前的figure上。对简单脚本来说这很方便但当你需要控制多个子图、统一图例、精细调整单个坐标轴时这种隐式接口就让人抓狂了。3.2 Pyplot 接口和面向对象接口的区别Matplotlib 常见两种写法# 写法一pyplot 风格快捷但控制力弱 plt.plot(df[date], df[price], o-) plt.xlabel(日期) plt.ylabel(价格) plt.title(农产品价格走势) plt.show() # 写法二面向对象风格推荐控制力强 fig, ax plt.subplots(figsize(10, 6)) ax.plot(df[date], df[price], o-) ax.set_xlabel(日期) ax.set_ylabel(价格) ax.set_title(农产品价格走势)两者最终效果可能一样但第二种写法把figure和axes都显式暴露出来了。你在ax上调各种set_*方法可以精准控制这个子图里的 X 轴标签是什么而不是稀里糊涂地改到别的子图上去。3.3 为什么我强烈建议你早点转成面向对象因为真实项目里几乎不可能只画一张孤零零的图。多数时候要画多子图对比要放共享图例要调整某个子图的数据范围。面向对象写法让你能轻松创建 2x2 画布fig, axes plt.subplots(2, 2, figsize(14, 10)) axes[0, 0].plot(...) axes[0, 1].plot(...)当你需要在同一个 figure 上叠加不同数据、在四个子图之间实现相同的轴范围时pyplot 接口会把状态管理搞得一团糟而面向对象接口清晰得多。从小项目开始就用fig, ax plt.subplots()的习惯省得以后转项目时再花时间适应。这也是社区里从新手到进阶的分水岭。一旦理解了这三者的关系很多「奇奇怪怪」的报错就豁然开朗了比如你看到AxesSubplot object has no attribute plot之类的往往是对象没搞清楚。4. 画图实战折线图、散点图、柱状图、雷达图一网打尽理论讲完来点实战。我用同一份模拟数据演示几张最常见的图每张都附上关键参数。你可以在自己电脑上跑一遍然后改参数感受变化。演示用的数据import numpy as np import pandas as pd # 构造模拟数据7天2种蔬菜的价格 dates pd.date_range(2024-01-01, periods7, freqD) data { date: dates, tomato: [5.2, 5.5, 5.8, 6.0, 5.9, 6.3, 6.1], cucumber: [3.1, 3.3, 3.0, 3.4, 3.6, 3.5, 3.8] } df pd.DataFrame(data)4.1 折线图基础中的基础但别只画一根线场景看价格随时间变化的趋势并方便叠加对比两种蔬菜。fig, ax plt.subplots(figsize(10, 6)) ax.plot(df[date], df[tomato], markero, label番茄, linewidth2) ax.plot(df[date], df[cucumber], markers, label黄瓜, linewidth2) ax.set_xlabel(日期) ax.set_ylabel(价格元/kg) ax.set_title(2024年1月第一周易腐价格走势) ax.legend() ax.grid(True, linestyle--, alpha0.6) fig.tight_layout() plt.show()关键点markero给数据点画上圆点否则线段看起来飘忽不定。label参数是给这条线一个名字后续legend()会用到。linewidth2让线宽适中太细会显得没质感太粗会把点线关系压住。grid(True, linestyle--, alpha0.6)加网格但用虚线且半透明避免网格线喧宾夺主。4.2 散点图透明度、颜色映射和网格是关键场景想观察价格和销量之间的关系或者看分钟级别交易价格的分布密度。fig, ax plt.subplots(figsize(8, 6)) sc ax.scatter( df[price], df[sales], cdf[sales], # 用颜色映射 cmapviridis, alpha0.6, s100 ) ax.set_xlabel(价格元/kg) ax.set_ylabel(销量kg) ax.grid(True, whichmajor, linestyle--, linewidth0.5, alpha0.7) fig.colorbar(sc, axax, label销量) plt.show()这里的alpha0.6很关键当散点很多且重叠时不设透明度密集区域就是一团纯色完全看不出分布密度。透明度能让重叠区域颜色更深视觉效果更好。cmapviridis是色盲友好的经典色图适合绝大多数场景。fig.colorbar(sc, axax, label销量)是加一个颜色条让读者知道颜色深浅对应的数值大小。如果你的散点图的c是某个有业务含义的字段务必加色条否则图就是无源之水别人看不懂。4.3 柱状图对比组别差异时最直接场景不同农产品在一段时间里的平均价格对比。fig, ax plt.subplots(figsize(8, 6)) avg_price df.groupby(item)[price].mean().sort_values() ax.barh(avg_price.index, avg_price.values, color#4C72B0) ax.set_xlabel(平均价格元/kg) ax.set_title(各农产品平均价格排名) plt.show()这里用了横向柱状图barh当类别名字较长时比竖着放好读得多。.sort_values()让柱子按数值排序图的信息层次更好。颜色选了一个比较稳的蓝色#4C72B0比默认的橙色系看起来更商务一些。4.4 雷达图多指标维度对比的神器场景比较不同产品在价格、销量、新鲜度、口碑、供应量五个维度的综合表现。雷达图是这类多指标可视化的经典选择。雷达图和前面的折线图不一样它用的是极坐标系polar。在 Matplotlib 里需要显式设置投影。import numpy as np import pandas as pd import matplotlib.pyplot as plt # 模拟5个维度的评分数据0–10分 categories [价格, 销量, 新鲜度, 口碑, 供应量] values_a [8, 7, 9, 6, 5] # 产品A values_b [5, 8, 7, 9, 8] # 产品B # 计算角度把圆平均分成5份并让首尾闭合 angles np.linspace(0, 2 * np.pi, len(categories), endpointFalse).tolist() angles angles[:1] # 闭合 values_a values_a[:1] values_b values_b[:1] fig, ax plt.subplots(figsize(8, 8), subplot_kw{projection: polar}) # 两条雷达图曲线 ax.plot(angles, values_a, o-, linewidth2, label产品A, color#4C72B0) ax.fill(angles, values_a, alpha0.25, color#4C72B0) ax.plot(angles, values_b, o-, linewidth2, label产品B, color#DD8452) ax.fill(angles, values_b, alpha0.25, color#DD8452) # 设置分类标签和坐标轴范围 ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories, fontsize12) ax.set_ylim(0, 10) ax.set_title(产品多维度对比雷达图, fontsize14) # 网格和方向调整 ax.yaxis.set_grid(True) ax.grid(True, linestyle--, alpha0.5) ax.legend(locupper right, bbox_to_anchor(1.2, 1.0)) plt.show()雷达图有几个容易踩的坑angles np.linspace(0, 2 * np.pi, len(categories), endpointFalse)计算角度endpointFalse是为了让最后一个点和第一个点不重叠。画闭环必须把数据首尾相连angles angles[:1]和values_a values_a[:1]就是干这个的。忘了这一步图就只剩一条开口线看着不像雷达图。subplot_kw{projection: polar}告诉 Matplotlib 这个子图是极坐标系ax.set_xticks(angles[:-1])让五天份标签均匀分布在 360 度上。雷达图对量纲敏感不同指标的数值范围如果差异太大比如一个是 0–100一个是 0–1建议预先做归一化或直接映射到 0–10 的评分。把这几张图画出来之后你的「图库」基本覆盖了日常 80% 的需求接下来是让它们变好看。5. 图表美化从能看出规律到看着专业同样的数据Matplotlib 默认配色下和稍加修饰下的感觉完全不同。美化的目标不是花哨而是「一眼看过去重点突出、信息层级分明」。下面是我常用的几个提升质感的手段。5.1 颜色体系别再只用一种默认蓝Matplotlib 默认会把每条线按tab10色板轮换分配颜色但你最好主动指定。好的颜色选择应该符合业务场景和数据属性。连续型数据如价格随时间变化用cmapviridis、cmapplasma这对色盲友好的色图。离散型分类数据用tab10、Set2、Pastel1等定性色板。要有「重点色」意识——比如整个图里只用一种蓝色做主色用橙色做强调色这种「双色法」比七个颜色堆在一起专业得多。如果你对颜色不太敏感推荐记住几个通用色值场景推荐色值主色蓝#4C72B0强调橙#DD8452成功/正向绿#55A868警示/负向红#C44E52中性灰#817B72这几个来自seaborn的配色灵感无论折线图、柱状图、散点图都能用。#开头的十六进制色值在 Matplotlib 里直接当参数传就行。5.2 字体设置中文乱码是新手第一道坎Matplotlib 默认字体是 DejaVu Sans它对中文支持得极差。你只要在图上用了中文就会看到一堆大小不一的方框这就是最经典的「中文乱码」问题。解决办法是在绘图前设置全局字体import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei, Arial Unicode MS] matplotlib.rcParams[axes.unicode_minus] False # 让负号正常显示SimHei是 Windows 下的中文字体Microsoft YaHei是微软雅黑WenQuanYi是 Linux 下的文泉驿Arial Unicode MS是 macOS 下的通用字体。实际生效的按顺序找第一个存在的中文字体。axes.unicode_minusFalse是防止数字里的负号变成乱码比如横轴标签里出现-1时如果不设置这个参数负号可能显示成方块。如果你在Jupyter里执行完这两行之后发现还是乱码大概率是字体缓存没更新。这种情况下可以运行import matplotlib as mpl mpl.font_manager._rebuild()或者直接重启内核。还不行就找到你系统里除了宋体之外的某一款支持中文的字体用下面的代码拿到字体路径再手动指定from matplotlib import font_manager font_path /path/to/your/chinese_font.ttf font_manager.fontManager.addfont(font_path) prop font_manager.FontProperties(fnamefont_path) plt.rcParams[font.family] sans-serif plt.rcParams[font.sans-serif] [prop.get_name()]不过这个属于「救火」操作日常办公环境直接把SimHei写进 rcParams 就够了。5.3 细节打磨网格、边框、刻度、注释当图形主体画好之后真正的差距在细节。我会按这个顺序检查网格默认白底网格对线图很有帮助但要把默认的实线改成虚线并降低透明度像我前面那样grid(True, linestyle--, alpha0.6)。颜色太深会扰乱视觉太浅则失去参考作用。边框默认图表有上、下、左、右四条框线。有时候只想突出左边和底部的轴线可以用ax.spines[top].set_visible(False)和ax.spines[right].set_visible(False)去掉多余的框图表会更干净、现代。刻度如果某个刻度标签太长比如日期可以手动旋转防止重叠ax.tick_params(axisx, rotation45)。注释重要的数据点最高点、最低点、异常点要加箭头或文字注释。ax.annotate()是个好工具max_idx df[tomato].idxmax() ax.annotate( f最高价 {df[tomato][max_idx]:.1f}, xy(df[date][max_idx], df[tomato][max_idx]), xytext(df[date][max_idx], df[tomato][max_idx]0.5), arrowpropsdict(arrowstyle-, colorgray), hacenter )xy是箭头指向的位置xytext是文字放置的位置。最大值的标记能让看图的人一眼抓住故事重点不用自己去找。文字层级标题最大fontsize16轴标签次之fontsize12刻度标签最小fontsize10。没有层级感的图读者不知道先看哪。5.4 布局与保存finished 之前先想好输出多子图一定要用fig.tight_layout()或者constrained_layoutTrue否则子图标题和坐标轴标签会互相挤压。举个例子fig, axes plt.subplots(2, 2, figsize(12, 10), constrained_layoutTrue)constrained_layout是 Matplotlib 3.1 之后引入的新布局引擎比tight_layout更智能。它会在每次绘图时自动调整子图之间的间距省去了手动adjust_subplots的麻烦。多子图的时候优先用它。保存图片时savefig有几个参数值得养成习惯fig.savefig(output.png, dpi200, bbox_inchestight, facecolorwhite)dpi200屏幕显示一般 150–200 就够了印刷要 300。bbox_inchestight自动裁掉图片周围的留白这个在保存整张图时非常实用。如果你觉得留白还不够多可以不加这个参数。facecolorwhite有些环境下保存的图背景是透明的贴到 PPT 里会出现黑色文字看不清的问题显式指定白色背景最稳。如果你想给设计师或前端提供更灵活的格式可以保存为 SVG 矢量图fig.savefig(output.svg)。SVG 无限缩放不糊而且可以直接用代码改颜色、线条宽度适合需要二次处理的场景。6. 踩坑记录最容易让人崩溃的几个报错和解法做数据可视化最折磨人的不是不会画而是画的过程中各种奇奇怪怪的报错。我挑几个高频问题附上排查思路。6.1 中文乱码问题不是代码问题是字体问题报错形式图片里的中文全部变成小方框但终端不报任何错误。原因Matplotlib 默认的字体里没有中文字形。它只是「画不出来」所以不会像FileNotFoundError一样明确告诉你哪里错了。排查链路先执行matplotlib.rcParams[font.sans-serif]看返回的字体列表是什么。查看系统字体目录C:\Windows\FontsWindows或/System/Library/FontsmacOS或/usr/share/fontsLinux确认中文字体是否存在。在代码最开头加上前文的两行rcParams设置再重启内核验证。经验设置中文字体这件事最好写进项目统一的配置文件里而不是每次复制到头部。我这里就维护了一个style.py包含字体、默认颜色、默认 DPI 等所有全局设置每个分析脚本 import 一下就行。6.2 图像显示不出来后端和显示环境的锅报错形式plt.show()执行后屏幕没有任何反应或者 Jupyter 里图片正常但脚本运行不出来。原因Matplotlib 有多个「后端」有的负责生成 PNG 图片有的通过 GUI 窗口实时交互显示。如果你的环境没有安装对应的 GUI 依赖比如 tkinter或者后端设置不对show()就不会弹出窗口。排查链路在脚本里加print(matplotlib.get_backend())看当前用的什么后端。如果是agg它是非交互后端只负责保存图片plt.show()没有窗口效果是正常的。想交互展示可以用plt.switch_backend(TkAgg)或plt.switch_backend(QtAgg)若没有交互需求show()可以省略直接用savefig()保存图片也一样。经验我日常大部分场景在 Jupyter 里%matplotlib inline图直接嵌在输出里只有调试时需要放大看某个细节才会切到交互后端否则不会为「没弹窗」浪费时间。6.3 Pandas 绘图报错KeyError或数据范围不对先查数据类型报错形式画柱状图时报KeyError: sales但明明数据表里有sales列或者图出来了但横轴变成 0、1、2、3… 而不是日期。原因前一种情况是列名大小写、空格或编码问题比如列名是 Sales而你在代码里写的是sales。后者是date列类型不是 datetimePandas 把日期当成普通字符串plot 时排了序但不识别日期格式。排查链路print(df.columns.tolist())看真实列名列表确认拼写和空格。df.info()看date的 dtype如果不是datetime64[ns]用pd.to_datetime()转换。检查是否有重复列名如果有df.columns会同时出现两个相同字段Pandas 会把它们处理成sales、sales.1也会引发 KeyError。经验数据清洗阶段多花 10 分钟检查 dtype 和列名后面画图能省 1 小时。真别嫌麻烦。6.4AttributeError: AxesSubplot object has no attribute plot怎么回事这个报错本质是对象上不存在plot方法。最常见的原因是你把axes和pyplot混用了比如ax plt.subplot()这是返回单一 axes 的用法结果又写了ax.plot(...)但某个环境下plt.subplot返回的是AxesSubplot这是支持的如果你写成plt.subplots(1, 2)返回的是包含两个 axes 的数组自然没有.plot方法。正确做法如果用了subplots(1, 2)就通过索引访问每个子图axes[0].plot(...)如果只画一张图fig, ax plt.subplots()是最保险的。看到AxesSubplot报错先检查变量到底是不是单个对象。6.5 图例位置总是不理想掌握bbox_to_anchorMatplotlib 的legend()有loc参数比如locupper right。但多子图或小图里默认位置经常遮住数据。我常用的补救方法是ax.legend(locupper left, bbox_to_anchor(1.02, 1.0))bbox_to_anchor(1.02, 1.0)把图例放到坐标轴外部的右侧。这样既不会挡住数据也不会和标题挤在一起。当图例里项目比较多时ncol2参数还可以让图例分成两列节省空间。图例是图表的「说明书」不放会让人看不懂放不好会遮住重点。建议每次画完图第一眼先检查图例位置。7. 从单张图到「可视化报告」的工作流你已经能画出一张精美的图了但真实项目往往需要输出一份可视化报告。这时候就不只是画图而是要考虑整体结构。我把日常最常用的工作流做一个总结。7.1 先理清业务问题再选图表类型很多时候选错图不是因为不会画而是不知道这个业务问题该用什么图表达。我的判断逻辑大致如下业务问题适合的图例子随时间变化的趋势折线图价格走势、用户增长各个类别的对比柱状图/条形图不同产品销量对比两个变量之间的关系散点图价格与销量的相关性多指标综合对比雷达图产品多维度评分占比构成饼图慎用/堆叠柱状图各渠道流量占比地理分布地图各城市门店数量饼图我用得比较少因为它不适合表达精确数值差异尤其当份额接近时很难从扇形角度看出大小区别。优先考虑堆叠柱状图或水平条形图更准确。7.2 建立清晰的画布布局一张图一个故事一页多图则要讲究叙事顺序。我习惯先写结论再放图佐证。比如第一张图放整体走势让读者看到大趋势。第二张图放分类对比展示差异。第三张图放细节点如异常值或最高点。代码布局上用constraXed_layoutTrue和fig, axes plt.subplots(2, 2, ...)就能实现多数场景。7.3 把 Pandas 和 Matplotlib 的项目代码组织好一个常见的坏习惯是把所有分析和绘图代码堆在一个单元格或一个.py文件里改一个参数就得翻几百行。我习惯按下面结构组织project/ ├── data/ │ └── prices.csv ├── src/ │ ├── load_data.py # 读取 清洗 │ ├── analysis.py # 数据聚合、透视 │ ├── plot_style.py # 全局样式配置 │ └── make_charts.py # 生成所有图表 └── output/ └── charts/ # 保存图表基本思路是数据加载和清洗归一处样式配置归一处画图归一处。这样当你换了新数据只需要改load_data.py当你想统一换配色只需要改plot_style.py。8. 一个完整案例从原始数据到成品图前面讲了不少零散的技巧最后我用一个完整的示例串起来帮助你把流程理顺。场景是拿到一份过去一年某种蔬菜的日价格数据需要做一张「年度价格走势月度均值异常值标注」的复合图。import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates # 模拟数据 np.random.seed(42) date_range pd.date_range(2023-01-01, periods365, freqD) price 3 np.sin(np.linspace(0, 8*np.pi, 365)) * 0.8 np.random.normal(0, 0.15, 365) df pd.DataFrame({date: date_range, price: price}) # 1. 数据处理添加月份列计算月均 df[month] df[date].dt.to_period(M) monthly df.groupby(month)[price].mean() # 2. 图形布局创建一个 figure 和一个 axes fig, ax plt.subplots(figsize(14, 6), constrained_layoutTrue) # 3. 画日价格折线半透明细线 ax.plot(df[date], df[price], color#4C72B0, alpha0.4, linewidth1, label日价格) # 4. 画月度均值粗线 ax.plot(monthly.index.to_timestamp(), monthly.values, color#4C72B0, linewidth2.5, label月度均值) # 5. 标注年度最高价 max_row df.loc[df[price].idxmax()] ax.annotate( f最高价 {max_row[price]:.2f}, xy(max_row[date], max_row[price]), xytext(max_row[date] pd.Timedelta(days25), max_row[price] 0.4), arrowpropsdict(arrowstyle-, colorgray), fontsize11, hacenter ) # 6. 美化细节 ax.set_title(某蔬菜年度价格走势, fontsize16, pad15) ax.set_xlabel(日期, fontsize12) ax.set_ylabel(价格元/kg, fontsize12) ax.legend(frameonFalse, locupper left, bbox_to_anchor(1.02, 1.0)) # 7. 格式化横轴每季度一个主刻度显示成2023-01的格式 ax.xaxis.set_major_locator(mdates.MonthLocator(interval3)) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) ax.tick_params(axisx, rotation45) # 8. 全局网格和边框 ax.grid(True, linestyle--, alpha0.5) ax.spines[top].set_visible(False) ax.spines[right].set_visible(False) # 9. 保存 fig.savefig(sales_annual.png, dpi200) plt.show()这个例子把整个流程串起来了df[date].dt.to_period(M)在 Pandas 里生成月份周期再用groupby算出月均。monthly.index.to_timestamp()是为了让月度均值能对应到折线图的 X 轴上。ax.xaxis.set_major_locator(mdates.MonthLocator(interval3))让 X 轴每 3 个月显示一个主刻度避免了默认每天一个刻度导致的标签密集重叠问题。DateFormatter(%Y-%m)把日期格式化成2023-01这种形态。整张图一年 365 个点不透明度alpha0.4用细线画在底层月度均值用粗线画在上面里层的趋势一目了然外层的波动细节也保留着。这就是「数据信息分层」的典型做法。最后说一下我的个人体会数据可视化的核心不是工具用得多花哨而是能不能帮自己和读者快速理解数据。Pandas 负责把数据理顺Matplotlib 负责把理顺的数据讲成清楚的故事这两者配合日常 80% 的可视化需求都能覆盖。你在画图时遇到的很多所谓「报错」和「乱码」其实都不是算法问题而是数据格式和字体配置的问题。按照这篇文章里的流程走一遍把数据清洗、类型转换、rcParams 设置这几个习惯养成输出的图表质量会稳定很多。至于配色审美和布局逻辑是另一层功夫多临摹优秀的图表是提高的最快路径。