ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python 数据处理进阶(二十三)从 Pandas 到 Seaborn:统计图入门

Python 数据处理进阶(二十三)从 Pandas 到 Seaborn:统计图入门

写在前面

第 11 篇我们用df.plot()把图跑通了;第 21 篇又用imshow手搓了热力图。这一篇补上最后一块:统计图。

区别在于:df.plot()画的是「数据本身」(一条线、一根柱);Seaborn 画的是「数据 + 统计」(均值 + 置信区间、分组分布、相关矩阵)。做汇报时,后者往往更省事——一行sns.boxplot(...)顶你十几行 matplotlib。

先确认示例数据在手边:

importosimportmkdataifnotos.path.exists("data/sales.csv"):mkdata.main()print("示例数据就绪:",os.path.exists("data/sales.csv"))

这段会检查第 1 篇生成的data/目录在不在,不在就自动补一次;已经有数据时只会打印一行示例数据就绪: True,不影响后面的结果。

一、先设主题:Seaborn 的「一键美化」

sn.set_theme()会统一配色、网格、字号。注意中文字体要单独设,否则全是方块。

importmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns# 主题 + 中文字体(这两行必须写在画图之前)sns.set_theme(style="whitegrid",context="notebook")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falseprint("seaborn 版本:",sns.__version__)print("可用主题:",sns.axes_style()["axes.facecolor"],"| 背景色已应用")print("数据加载中……")df=pd.read_csv("data/sales.csv",encoding="utf-8-sig")print("行数:",len(df),"| 列:",df.columns.tolist())
seaborn 版本: 0.13.2 可用主题: white | 背景色已应用 数据加载中…… 行数: 16 | 列: ['订单日期', '城市', '产品', '数量', '单价', '销售员', '金额']

二、barplot:带误差棒的柱状图

groupby().sum()之后再画柱状图,画的是「一个数」;sns.barplot直接吃原始数据,默认画均值 + 95% 置信区间误差棒——一眼能看出「差异是不是稳定」。

importosimportmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns sns.set_theme(style="whitegrid")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedf=pd.read_csv("data/sales.csv",encoding="utf-8-sig")df["金额"]=df["金额"].fillna((df["数量"]*df["单价"]).round(2))clean=df[df["金额"]>0]os.makedirs("output",exist_ok=True)fig,ax=plt.subplots(figsize=(7.6,4.2))sns.barplot(data=clean,x="城市",y="金额",ax=ax,palette="Blues_d")ax.set_title("各城市平均订单金额(误差棒 = 95% 置信区间)")ax.set_xlabel("城市")ax.set_ylabel("平均金额(元)")fig.tight_layout()fig.savefig("output/sns_barplot.png",dpi=200)plt.close(fig)print("已保存 output/sns_barplot.png")print("各城市样本数:",clean["城市"].value_counts().to_dict())
已保存 output/sns_barplot.png 各城市样本数: {'杭州': 4, '上海': 4, '北京': 3, '广州': 3}

误差棒越长,说明该城市的订单金额波动越大、这个均值越「不可信」。只有 4-5 个样本的城市,误差棒会非常夸张——这本身就是结论:样本太少,别急着下判断。

三、boxplot:分组分布对比

柱状图只给一个均值,箱线图给全貌:中位数、四分位、离群点。

importosimportmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns sns.set_theme(style="whitegrid")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedf=pd.read_csv("data/students.csv",encoding="utf-8-sig")long=df.melt(id_vars=["班级"],value_vars=["语文","数学","英语"],var_name="科目",value_name="分数").dropna()os.makedirs("output",exist_ok=True)fig,ax=plt.subplots(figsize=(8,4.2))sns.boxplot(data=long,x="科目",y="分数",hue="班级",ax=ax,palette="Set2")ax.set_title("各班级三科成绩分布")ax.legend(title="班级",frameon=False,fontsize=8,ncol=3,loc="lower right")fig.tight_layout()fig.savefig("output/sns_boxplot.png",dpi=200)plt.close(fig)print("已保存 output/sns_boxplot.png")print("样本数:")print(long.groupby(["科目","班级"],observed=True).size().to_string())
已保存 output/sns_boxplot.png 样本数: 科目 班级 数学 一班 3 三班 4 二班 3 英语 一班 5 三班 4 二班 2 语文 一班 5 三班 4 二班 2

图 1:左图是按班级分组的成绩箱线图(箱体是中间 50%,点是离群值),右图是三科成绩相关系数——seaborn 一行代码就能出这类统计图。

melt这一步是画图的关键:宽表(三列科目)要先转成长表(一列科目 + 一列分数),Seaborn 才能按「科目」分组。这也是pivot_table的反操作——第 13 篇讲stack时是同一个思路。

四、histplot 与 kde:看分布形状

importosimportmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns sns.set_theme(style="whitegrid")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedf=pd.read_csv("data/sales.csv",encoding="utf-8-sig")df["金额"]=df["金额"].fillna((df["数量"]*df["单价"]).round(2))clean=df[df["金额"]>0]os.makedirs("output",exist_ok=True)fig,ax=plt.subplots(figsize=(7.6,4.2))sns.histplot(data=clean,x="金额",bins=8,kde=True,ax=ax,color="#3b82f6")ax.set_title("订单金额分布(直方图 + 核密度曲线)")ax.set_xlabel("金额(元)")fig.tight_layout()fig.savefig("output/sns_histplot.png",dpi=200)plt.close(fig)print("已保存 output/sns_histplot.png")print("金额描述:")print(clean["金额"].describe().round(2).to_string())
已保存 output/sns_histplot.png 金额描述: count 15.00 mean 482.63 std 758.08 min 49.90 25% 149.60 50% 179.40 75% 489.50 max 2995.00

kde=True叠一条核密度曲线,比光看柱子更容易看出「单峰还是双峰、偏左还是偏右」。样本很少时 KDE 会画出奇怪的鼓包,这种数据就老老实实看直方图。

五、heatmap 与 pairplot:关系和全局

importosimportmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns sns.set_theme(style="whitegrid")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedf=pd.read_csv("data/sales.csv",encoding="utf-8-sig")num=df.select_dtypes(include="number")corr=num.corr().round(2)os.makedirs("output",exist_ok=True)fig,ax=plt.subplots(figsize=(6.4,5))sns.heatmap(corr,annot=True,fmt=".2f",cmap="RdBu_r",vmin=-1,vmax=1,linewidths=0.5,ax=ax,cbar_kws={"label":"相关系数"})ax.set_title("数值列相关系数(seaborn heatmap)")fig.tight_layout()fig.savefig("output/sns_heatmap.png",dpi=200)plt.close(fig)print("已保存 output/sns_heatmap.png")print(corr.to_string())
已保存 output/sns_heatmap.png 数量 单价 金额 数量 1.00 -0.04 0.24 单价 -0.04 1.00 0.45 金额 0.24 0.45 1.00
importosimportmatplotlib matplotlib.use("Agg")importmatplotlib.pyplotaspltimportpandasaspdimportseabornassns sns.set_theme(style="whitegrid")plt.rcParams["font.sans-serif"]=["SimHei"]plt.rcParams["axes.unicode_minus"]=Falsedf=pd.read_csv("data/students.csv",encoding="utf-8-sig")sub=df[["语文","数学","英语"]].dropna()os.makedirs("output",exist_ok=True)grid=sns.pairplot(sub,diag_kind="kde",plot_kws={"alpha":0.7,"s":40})grid.figure.suptitle("三科成绩两两关系(pairplot)",y=1.02)grid.figure.savefig("output/sns_pairplot.png",dpi=180,bbox_inches="tight")plt.close("all")print("已保存 output/sns_pairplot.png,参与样本:",len(sub),"行")
已保存 output/sns_pairplot.png,参与样本: 8 行

pairplot一次画出所有两两组合,对角线是各自分布。列多的时候别用——10 列会画出 100 张子图,慢且看不清,先用corr筛出强相关的几列。

六、Seaborn 还是 pandas.plot?

需求推荐
一条时间趋势线df.plot()够了
按类别汇总后的柱状图(只有值)df.plot(kind="bar")
要带误差棒 / 置信区间sns.barplot
分组分布对比(多组箱线/小提琴)sns.boxplot/violinplot
分布 + 密度曲线sns.histplot(kde=True)
相关矩阵带数值sns.heatmap
多列两两关系sns.pairplot(列少时)
要精确控制每个元素(论文级)直接用 Matplotlib

一句话:先想清楚要表达什么(趋势 / 对比 / 分布 / 关系),再选图;图类型定了,pandas.plot 和 seaborn 谁顺手用谁。

常见问题

Q1:sns.barplot报警告说palette没配hue,图还变灰了?

Seaborn 0.13 起,不传hue时palette会被忽略并给提示。改成hue="城市", legend=False或者直接不传palette。

Q2:中文标题变方块。

plt.rcParams["font.sans-serif"] = ["SimHei"]必须在画图之前设置;sns.set_theme()会重置 rcParams,所以顺序是「先 set_theme,再设字体」。Mac 换PingFang SC。

Q3:pairplot跑得很慢 / 图太大。

列多、行多都会慢。先df[["a","b","c"]]选列、df.sample(500)抽样,再画。

小结

Seaborn 的价值是把「统计」画进图里:误差棒、分组分布、密度曲线、相关矩阵,一行代码就有。用它的三步是设主题(含中文字体)→ 宽表转长表(melt)→ 选对应图类型;导出依然是savefig + dpi。别为了好看强行上复杂图,读者能一眼看懂才是目的。

下一篇是第二批最后一篇:把写好的脚本变成能交付的工具——命令行参数、日志、定时运行。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进