绘图和可视化

绘图和可视化

matplotlib API入门

import matplotlib.pyplot as plt

图和子图

# matplotlib的图像位于Figure对象中。你可以用plt.figure创建一张新图
# plt.figure有一些选项,特别是figsize,用于当图片保存到磁盘时,确保图片具有一定的大小和宽高比
fig = plt.figure()

# 不能在空Figure对象上绘图。必须用add_subplot创建一个或多个subplot才能真正绘图:
# 下面代码指定,图像应该是2×2的(即最多4张图),且当前选中的是4张子图中的第1张(编号从1开始
ax1 = fig.add_subplot(2,2,1)

# 再创建两张子图
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,3)

<img src="https://huihuiteresa.github.io/image/youdao/20250512001.png" width="400">

# 使用plot方法绘制线形图
ax3.plot(np.random.standard_normal(50).cumsum(),color="black",linestyle="dashed")
ax1.hist(np.random.standard_normal(100),bins=20,color="black",alpha=0.3)
ax2.scatter(np.arange(30),np.arange(30)+3*np.random.standard_normal(30))

<img src="https://huihuiteresa.github.io/image/youdao/20250512002.png" width="400">

# matplotlib提供了一个更便捷的方法plt.subplots,它可以创建一个新的图形对象,并返回一个NumPy数组,其中包含创建好的子图对象
# axes[0,1]引用的是位于第1行、第2列的子图。你还可以通过sharex和sharey指定子图应该具有相同的x轴或y轴。
fig,axes = plt.subplots(2,3)

matplotlib.pyplot.subplots的选项:

  • nrows:子图的行数
  • ncols:子图的列数
  • sharex:所有子图应该使用相同的x轴刻度(调整xlim将会影响所有子图)
  • sharey:所有子图应该使用相同的y轴刻度(调整ylim将会影响所有子图)
  • subplot_kw:传入add_subplot的关键字参数字典,用于创建子图
  • **fig kw:创建图形时,subplots的额外关键字参数,比如plt.subplots(2,2,figsize=(8, 6))
调整子图周围的间距

利用Figure对象的subplots_adjust方法可以便捷地调整间距:

fig,axes = plt.subplots(2,2,sharex=True,sharey=True)
for i in range(2):
    for j in range(2):
        axes[i,j].hist(np.random.standard_normal(500),bins=50,color="black",alpha=0.5)
fig.subplots_adjust(wspace=0,hspace=0) # 各子图之间间距为零

<img src="https://huihuiteresa.github.io/image/youdao/20250520001.png" width="400">

不难看出,其中的轴标签重叠了。matplotlib不会检查标签是否重叠,所以对于这种情况,你只能自己设定刻度位置和刻度标签。

颜色、标记和线型

matplotlib的线型plot函数接收一组x和y坐标,还可以接收一个表示颜色和线型的参数。

# 要根据x和y绘制绿色虚线
ax.plot(x, y, linestyle="--", color="green")

# 将标记放到额外的格式选项中
ax = fig.add_subplot()
ax.plot(np.random.standard_normal(30).cumsum(),color="black",linestyle="dashed",marker="o")

在线形图中,你会注意到插值数据点默认是按线性方式插入的。可以通过drawstyle选项修改:

fig = plt.figure()
ax = fig.add_subplot()
data = np.random.standard_normal(30).cumsum()
ax.plot(data,color="black",linestyle="dashed",label="Default")
ax.plot(data,color="black",linestyle="dashed", drawstyle="steps-post",label="steps-post")

<img src="https://huihuiteresa.github.io/image/youdao/20250520002.png" width="400">

由于我们传递了label参数给plot,因此可以使用ax.legend创建一个图例来区分每条线。

刻度、标签和图例

对于大多数的图表装饰项,可以通过matplotlib轴对象的方法访问。主要方法有xlim、xticks和xticklabels,它们分别控制图像范围、刻度位置和刻度标签。

  • 调用时不带参数,则返回当前的参数值(例如,ax.xlim()返回当前的x轴绘图范围)。
  • 调用时带参数,则设置参数值(例如,ax.xlim([0,10])会将x轴的范围设置为0~10)。
设置标题、轴标签、刻度以及刻度标签
fig,ax = plt.subplots()
ax.plot(np.random.standard_normal(1000).cumsum())

# 要修改x轴刻度,最简单的办法是使用set_xticks和set_xticklabels。
# y轴的修改方式与此类似,只需将上述代码中的x替换为y即可
ticks = ax.set_xticks([0,250,500,750,1000])
labels = ax.set_xticklabels(["one","two","three","four","five"],rotation=30,fontsize=8)
# set_xlabel为x轴设置一个名称
ax.set_xlabel("stages")

# 轴的类有set方法,可以批量设置绘图选项
ax.set(title="My first matplotlib plot",xlabel="stages")

<img src="https://huihuiteresa.github.io/image/youdao/20250520003.png" width="400">

添加图例

图例是另一种用于标识图表元素的重要内容。

要从图例中去除一个或多个元素,可以不传入label或传入label="nolegend"。

fig,ax = plt.subplots()
# 在添加图表各组件时传入label参数
ax.plot(np.random.random(1000).cumsum(),color="black",label="one")
ax.plot(np.random.random(1000).cumsum(),color="black",linestyle="dashed",label="two")

# 调用ax.legend()来自动创建图例
ax.legend()

<img src="https://huihuiteresa.github.io/image/youdao/20250520004.png" width="400">

注释和绘制子图

除了标准的绘图类型,你可能还希望绘制一些绘图注释,注释可能包含文本、箭头或其他图形。注释和文字可以通过text、arrow和annotate函数进行添加。

# text可以将文本绘制在图表的指定坐标(x,y),还可以加上一些自定义格式
ax.text(x,y,"Hello world",family="monospace",fontsize=10)

根据2007年以来的标准普尔500指数的收盘价绘图(数据来自Yahoo!Finance),并标出2008—2009年金融危机期间的若干重要日期:

from datetime import datetime
fig,ax = plt.subplots()

data = pd.read_csv("examples/spx.csv",index_col=0,parse_dates=True)
spx = data["SPX"]

spx.plot(ax=ax,color="black")

crisis_data=[
    (datetime(2007,10,11),"Peak of bull market"),
    (datetime(2008,3,12),"Bear Stearns Fails"),
    (datetime(2008,9,15),"Lehman Bankruptcy")
]

for date,label in crisis_data:
    # 在指定的x和y坐标轴绘制标签
    ax.annotate(label,xy=(date,spx.asof(date)+75),
                xytext=(date,spx.asof(date)+255),
                arrowprops=dict(facecolor="black",headwidth=4,width=2,headlength=4),
                horizontalalignment="left",verticalalignment="top")
# 放大2007-2010 年(手动设定起始边界和结束边界,而不使用matplotlib的默认设置)
ax.set_xlim(["1/1/2007","1/1/2011"])
ax.set_ylim([600,1800])
# 添加图标标题
ax.set_title("Important dates in the 2008-2009 financial crisis")

图形的绘制要麻烦一些。matplotlib有一些表示常见形状的对象,称为补丁(patch)。其中一些(如Rectangle和Circle)可以在matplotlib.pyplot中找到,但完整集合位于matplotlib.patches。要在图表中添加一个图形,你需要创建一个补丁对象,然后将其传递给ax.add_patch,就能添加到子图ax中了:

fig,ax = plt.subplots()

rect = plt.Rectangle((0.2,0.75),0.4,0.15,color="black",alpha=0.3)
circ = plt.Circle((0.7,0.2),0.15,color="blue",alpha=0.3)
pgon = plt.Polygon([[0.15,0.15],[0.35,0.4],[0.2,0.6]],color="green",alpha=0.5)

ax.add_patch(rect)
ax.add_patch(circ)
ax.add_patch(pgon)

<img src="https://huihuiteresa.github.io/image/youdao/20250521001.png" width="400">

如果查看常见图表的具体实现代码,你就会发现它们其实就是由补丁组装而成的。

将图表保存到文件

# 利用图表对象的savefig实例方法,可以将当前图表保存到文件中
fig.savefig("figpath.svg")

# 文件类型是通过文件扩展名推断出来的。因此,如果你使用的是.pdf,就会得到一个PDF文件。
# 我在发布图片时最常用到的重要选项是dpi,用于控制“每英寸点数”的分辨率
fig.savefig("figpath.svg",dpi=400)

fig.savefig的一些选项:

  • fname:含有文件路径的字符串或Python 的文件型对象。图像格式由文件扩展名推断得出(例如,由·pdf 推断出 PDF,或由·png 推断出 PNG)
  • dpi:图像分辨率(每英寸点数);IPython中默认为100,Jupyter 中默认为72,可以修改
  • facecolo、edgecolor:位于子图外的图像背景色,默认为"w"(白色)
  • format:明确指定文件格式(比如"png"、"pdf"、"svg"、"ps"、"eps"等)

matplotlib配置

matplotlib自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。幸运的是,几乎所有默认配置都能通过一组全局参数进行自定义,它们可以管理图像大小、子图边距、配色方案、字体大小、网格类型等。使用rc方法是通过Python编程来修改配置的途径之一。

所有当前的配置设置存储于字典plt.rcParams,调用plt.rcdefaults()函数即可恢复默认值。

rc的第一个参数是想要自定义的组件,比如"figure"、"axes"、"xtick"、"ytick"、"grid"、"legend"等。

# 将全局的图像默认大小设置为10×10
plt.rc("figure",figsize=(10,10))
# 恢复默认值
plt.rcdefaults()
# 设置字体
plt.rc("font",family="monospace",weight="bold",size=8)

使用pandas和seaborn绘图

matplotlib实际上是比较底层的工具。要绘制一张图表,将matplotlib的基本组件组装起来就行:数据展示(即图表类型:线形图、柱状图、箱型图、散点图、等高线图等)、图例、标题、刻度标签以及其他注释信息。

在pandas中,我们可能有多列数据,还有行标签和列标签。pandas自身就有内置的方法,用于简化从DataFrame和Series绘制图形。另一个库是seaborn(https://seaborn.pydata.org)——一个基于matplotlib的高级统计绘图库。seaborn简化了许多常见图表类型的创建过程。

线形图

Series和DataFrame都有一个plot属性,用于绘制基本图表。

s = pd.Series(np.random.standard_normal(10).cumsum(),index=np.arange(0,100,10))
# 默认情况下,plot()生成的是线形图
s.plot()

<img src="https://huihuiteresa.github.io/image/youdao/20250521002.png" width="400">

Series.plot方法的参数:

  • label:用于图例的标签
  • ax:绘图所用的 matplotlib子图对象。如果没有设置,则使用当前的matplotlib 子图
  • style:传递给 matplotlib 的样式字符串(如"ko--")
  • alpha:图表的填充不透明度 (0到1之间)
  • kind:可以是"area""bar" "barh" "density""hist""kde" "line""pie"其中之一。默认为"line"
  • figsize:将要创建的图像的大小
  • logx:如果传入True,则x轴使用对数缩放;如果传入"sym",则使用对称对数,可以显示负值
  • logy:如果传入True,则y轴使用对数缩放;如果传入"sym",则使用对称对数,可以显示负值
  • title:图的标题
  • use_index:将对象的索引用作刻度标签
  • rot:旋转刻度标签 (0到360度)
  • xticks:用作x 轴刻度的值
  • yticks:用作y轴刻度的值
  • xlimx:轴的界限(例如[0,10])
  • ylim:y轴的界限
  • grid:显示轴网格线 (默认关闭)
df = pd.DataFrame(np.random.standard_normal((10,4)).cumsum(0),
                  columns=["A","B","C","D"],
                  index=np.arange(0,100,10))
# 将配色方案改为灰度
plt.style.use('grayscale')
# DataFrame的plot方法将各列绘制成同一子图中不同的线,并自动创建图例
df.plot()

<img src="https://huihuiteresa.github.io/image/youdao/20250521003.png" width="400">

专属于DataFrame的plot参数:

  • subplots:将 DataFrame 的列绘入不同的子图
  • layouts:用于子图布局的二元组 (行,列)
  • sharex:如果 subplots=True,则共享x轴,关联刻度和界限
  • sharey:如果 subplots=True,则共享y轴,关联刻度和界限
  • legend:添加子图图例 (默认为True)
  • sort_columns:以字母顺序绘制列;默认使用列已有的顺序

柱状图

plot.bar()和plot.barh()分别用于绘制水平柱状图和垂直柱状图。对于柱状图,Series或DataFrame的索引将用作x轴(bar)或y轴(barh)的刻度:

fig,axes = plt.subplots(2,1)
data = pd.Series(np.random.uniform(size=16),index=list("abcdefghijklmnop"))
data.plot.bar(ax=axes[0],color="black",alpha=0.7)
data.plot.barh(ax=axes[1],color="black",alpha=0.7)

<img src="https://huihuiteresa.github.io/image/youdao/20250521004.png" width="400">

对于DataFrame,柱状图会将每一行的值分为一组,并排显示:

df = pd.DataFrame(np.random.uniform(size=(6,4)),
                  index=["one","two","three","four","five","six"],
                  columns=pd.Index(["A","B","C","D"],name="Genus"))
df.plot.bar()

<img src="https://huihuiteresa.github.io/image/youdao/20250521005.png" width="400">

# 传入stacked=True,即可为DataFrame生成堆积柱状图,这样每行的值就会水平堆积在一起
df.plot.barh(stacked=True,alpha=0.5)

# 使用value_counts对Series的各值出现频率进行可视化非常有效,比如s.value_counts().plot.bar()。

<img src="https://huihuiteresa.github.io/image/youdao/20250521006.png" width="400">

再来看一个有关餐厅小费的数据集。假设我们想要做一张堆积柱状图,以展示每天各种聚会规模的数据点的百分比:

tips.csv文件地址:https://github.com/mwaskom/seaborn-data/blob/master/tips.csv

内容如下图:

[图片上传失败...(image-752ef7-1747958731525)]

tips = pd.read_csv("tips.csv")
party_counts = pd.crosstab(tips["day"],tips["size"])
# size  1   2   3   4  5  6
# day
# Fri   1  16   1   1  0  0
# Sat   2  53  18  13  1  0
# Sun   0  39  15  18  3  1
# Thur  1  48   4   5  1  3
party_counts = party_counts.reindex(index=["Thur","Fri","Sat","Sun"])
# 存在许多一人聚会和六人聚会
party_counts = party_counts.loc[:,2:5]
# size   2   3   4  5
# day
# Thur  48   4   5  1
# Fri   16   1   1  0
# Sat   53  18  13  1
# Sun   39  15  18  3

# 正态化各行的和等于1
party_pcts = party_counts.div(party_counts.sum(axis="columns"),axis="index")
# size         2         3         4         5
# day
# Thur  0.827586  0.068966  0.086207  0.017241
# Fri   0.888889  0.055556  0.055556  0.000000
# Sat   0.623529  0.211765  0.152941  0.011765
# Sun   0.520000  0.200000  0.240000  0.040000

party_pcts.plot.bar(stacked=True)

<img src="https://huihuiteresa.github.io/image/youdao/20250522001.png" width="400">

对于在绘制图形之前需要进行聚合和汇总的数据,使用seaborn可以减少工作量(使用conda install seaborn安装)。用seaborn可视化每天的小费比例:

seaborn的绘图函数需要使用data参数,可以用pandas的DataFrame。其他参数用于引用列名。因为day的每个值有多次观测值,柱状图的值是tip_pct的平均值。绘制在柱状图上的黑线代表95%置信区间(可以通过可选参数配置)。

import seaborn as sns
tips = pd.read_csv("tips.csv")
tips["tip_pct"] = tips["tip"]/(tips["total_bill"] * tips["tip"])
sns.barplot(x="tip_pct",y="day",data=tips,orient="h") # 消费的每日比例,带有误差条

<img src="https://huihuiteresa.github.io/image/youdao/20250522003.png" width="400">

可以用seaborn.set_style在不同的图形外观之间切换:

# 设置为灰度调色板
sns.set_palette("Greys_r")

直方图和密度图

直方图是一种可以对值频率进行离散化显示的柱状图。数据点被拆分到离散且间隔均匀的箱中,并绘制各箱中数据点的数量。

再以之前的个小费数据为例,通过在Series上使用plot.hist方法,我们可以生成“小费占消费总额百分比”的直方图:

tips = pd.read_csv("tips.csv")
tips["tip_pct"] = tips["tip"]/(tips["total_bill"] * tips["tip"])
tips["tip_pct"].plot.hist(bins=50)

<img src="https://huihuiteresa.github.io/image/youdao/20250522004.png" width="400">

与直方图相关的一种图表类型是密度图,它是通过计算“可能会产生观测数据的连续概率分布估计”得到的。一般的过程是将该分布近似为“核”的混合,即诸如正态分布之类的简单分布。因此,密度图也称作核密度估计(KDE)图。使用plot.density生成一幅常规混合正态分布估计的密度图:

# 密度图需要使用SciPy
pip install scipy

tips = pd.read_csv("tips.csv")
tips["tip_pct"] = tips["tip"]/(tips["total_bill"] * tips["tip"])
tips["tip_pct"].plot.density()

<img src="https://huihuiteresa.github.io/image/youdao/20250522005.png" width="400">

用seaborn的histplot方法绘制直方图和密度图更加简单,还可以同时画出直方图和连续密度估计图。作为例子,考虑一个由两个不同的标准正态分布组成的二项分布:

import seaborn as sns
comp1 = np.random.standard_normal(200)
comp2 = 10+2*np.random.standard_normal(200)
values = pd.Series(np.concatenate([comp1,comp2]))
sns.histplot(values,bins=100,color="black")

<img src="https://huihuiteresa.github.io/image/youdao/20250522006.png" width="400">

散点图或点图

点图或散点图是观察两个一维数据序列之间的关系的有效方式。在下面这个例子中,我加载了来自statsmodels项目的macrodata数据集,选择了几个变量,然后计算对数差:

macrodata.csv数据格式如下图:

[图片上传失败...(image-f12ee5-1747958731525)]

macro = pd.read_csv("macrodata.csv")
data=macro[["cpi","m1","tbilrate","unemp"]]
#      cpi     m1  tbilrate  unemp
# 0  28.98  139.7      2.82    5.8
# 1  29.15  141.7      3.08    5.1
# 2  29.35  140.5      3.82    5.3
# 3  29.37  140.0      4.33    5.6
# 4  29.54  139.6      3.50    5.2
trans_data = np.log(data).diff().dropna()
#         cpi        m1  tbilrate     unemp
# 1  0.005849  0.014215  0.088193 -0.128617
# 2  0.006838 -0.008505  0.215321  0.038466
# 3  0.000681 -0.003565  0.125317  0.055060
# 4  0.005772 -0.002861 -0.212805 -0.074108
# 5  0.000338  0.004289 -0.266946  0.000000

# 使用seaborn的regplot方法,它可以生成散点图,并添加一条线性回归拟合的直线
ax = sns.regplot(x="m1",y="unemp",data=trans_data)

<img src="https://huihuiteresa.github.io/image/youdao/20250522008.png" width="400">

# seaborn提供了一个便捷的pairplot函数,它支持在对角线上放置每个变量的直方图或密度估计
sns.pairplot(trans_data,diag_kind="kde",plot_kws={"alpha":0.2})

<img src="https://huihuiteresa.github.io/image/youdao/20250522009.png" width="600">

分面网格和分类数据

要是数据集有额外的分组维度,该怎么办呢?对于有多个分类变量的数据,一种可视化方法是使用分面网格。分面网格是一种二维绘图布局,它根据特定变量所具有的不同值,将数据在每个轴上的绘图中进行分割。seaborn有一个实用的内置函数catplot,可以简化制作多种分面图。

tips = pd.read_csv("tips.csv")
tips["tip_pct"] = tips["tip"]/(tips["total_bill"] * tips["tip"])
sns.catplot(x="day",y="tip_pct",hue="time",col="smoker",kind="bar",data=tips[tips.tip_pct<1])

<img src="https://huihuiteresa.github.io/image/youdao/20250523001.png" width="600">

# 给每个time值添加一行来扩展分面网格
sns.catplot(x="day",y="tip_pct",row="time",col="smoker",kind="bar",data=tips[tips.tip_pct<1]) # 按天的小费百分比,通过time/smoker分面

<img src="https://huihuiteresa.github.io/image/youdao/20250523002.png" width="400">

# 箱型图(它可以显示中位数、四分位数和异常值)
sns.catplot(x="tip_pct",y="day",kind="box",data=tips[tips.tip_pct<0.5])

<img src="https://huihuiteresa.github.io/image/youdao/20250523003.png" width="400">

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容