译文出品:Python数据之道
原文作者:Rizky Maulana Nurhidayat
翻译:Lemon
Matplotlib 实操干货
本文是 Matplotlib 可视化的第二篇。在第一篇文章中,我们学习了如何生成和自定义散点图、折线图、直方图和条形图。本文将继续使用 Matplotlib 在 Python 绘图中进行研究,涉及生成和自定义箱形图、小提琴图、饼图、极坐标图、地理投影、3D图和等高线图。
「Python数据之道」注,文末提供包括第一篇和第二篇的完整pdf文件获取方式。
与第一篇文章中一样,我已经在 Matplotlib 中自定义了默认参数。这是我的绘画风格:
-
import numpy as np
-
import matplotlib.pyplot as plt
-
plt.rcParams[
'text.usetex'] = True
-
plt.rcParams[
'font.size'] =
15
-
plt.rcParams[
'font.family'] =
"serif"
-
tdir =
'in'
-
major =
5.0
-
minor =
3.0
-
plt.rcParams[
'xtick.direction'] = tdir
-
plt.rcParams[
'ytick.direction'] = tdir
-
plt.rcParams[
'xtick.major.size'] = major
-
plt.rcParams[
'xtick.minor.size'] = minor
-
plt.rcParams[
'ytick.major.size'] = major
-
plt.rcParams[
'ytick.minor.size'] = minor
01. 箱形图(Box plot)
你知道箱形图吗?维基百科将箱形图定义为一种通过四分位数以图形方式描绘数字数据组的方法。它用于描述性统计。您可以在 图1 中看到箱形图的示例。
通常,箱形图表示分布图。它是由一个框体、晶须和离群值构成的。在 图1 中,没有异常值。在框体元素中,可以显示分布的中位数或均值。我们可以在 图1 中看到中位数。该框体受 Q1(第一个四分位数)和 Q3(第三个四分位数)值的限制。Q1 和 Q3 的差值称为四分位数(IQR)。默认情况下,晶须显示分布的边界,最小值和最大值。
在 图2 中,可以看到一个异常值。箱形图如何检测离群值?
当其值小于 Q1-1.5 x IQR 或大于 Q3 + 1.5 x IQR 时,将在箱形图中检测到异常值。
在使用 Matplotlib 创建箱形图之前,将使用下面的代码生成模拟数据:
-
N =
50
-
np.random.seed(
100)
-
box1 = np.random.randn(N) *
2 +
1
要以箱形图的形式显示变量 box1 ,可以使用以下代码:
-
plt.figure()
-
plt.boxplot(box1)
运行上面的代码时,可以在 图3 中看到结果:
水平箱形图 (Horizontal box plot)
如果要水平更改箱形图的方向,则需要在 plt.boxplot()
代码中应用以下参数。
vert = False
水平箱形图如 图4 所示:
在下一个示例中,将通过更改随机数据来创建一个具有异常值的分布,如以下代码所示:
-
N =
50
-
np.random.seed(
140)
-
box1 = np.random.randn(N) *
2 +
1
-
plt.boxplot(box1, vert=False)
运行代码后,如 图5 所示:
下面将展示 Matplotlib 如何通过计算 Q1–1.5 x IQR,Q3 + 1.5 x IQR 的值并使用以下代码对分布进行排序来检测离群值:
-
q1 = np.quantile(box1,
.25)
-
q3 = np.quantile(box1,
.75)
-
iqr = q3-q1
-
lower_boundary = q1 -
1.5 * iqr
-
upper_boundary = q3 +
1.5 * iqr
-
sort = np.sort(box1)
为了可视化需要的值,可以运行以下代码:
-
N =
50
-
np.random.seed(
140)
-
box1 = np.random.randn(N) *
2 +
1
-
q1 = np.quantile(box1,
.25)
-
q3 = np.quantile(box1,
.75)
-
iqr = q3-q1
-
lower_boundary = q1 -
1.5 * iqr
-
upper_boundary = q3 +
1.5 * iqr
-
sort = np.sort(box1)
-
colors = [
'#6b2983',
'#5f82cb',
'#00d6ff',
'#e77ca3',
'#93003a']
-
plt.figure(figsize=(
9,
3.5))
-
plt.boxplot(box1, vert=False)
-
plt.scatter(box1, np.linspace(
1.3,
1.3, box1.shape[
0]), color =
'r', alpha =
.4, label =
'random numbers')
-
plt.vlines(lower_boundary, ymin =
0.2, ymax=
.65, alpha =
.7, ls =
'-.', color =
'k', label =
'Q1 - 1.5 x IQR')
-
plt.vlines(sort[
0], ymin =
0.2, ymax =
.65, ls =
'-', lw =
1.25, color = colors[
2], label =
'minimum')
-
plt.vlines(sort[
-2], ymin =
0.2, ymax =
.65, ls =
'-', lw =
1.25, color = colors[
3], label =
'maximum')
-
plt.vlines(sort[
-1], ymin =
0.2, ymax =
.65, ls =
'-', lw =
1.25, color = colors[
4], label =
'outliers')
-
plt.vlines(upper_boundary, ymin =
0.2, ymax=
.65, alpha =
.7, ls =
'--', color =
'k', label =
'Q3 + 1.5 x IQR')
-
plt.annotate(np.round(lower_boundary,
2), xy = (lower_boundary,
0.7), ha =
'center', color =
'k')
-
plt.annotate(np.round(sort[
0],
2), xy = (sort[
0],
0.7), ha =
'center', color = colors[
2])
-
plt.annotate(np.round(sort[
-2],
2), xy = (sort[
-2],
0.7), ha =
'center', color = colors[
3])
-
plt.annotate(np.round(sort[
-1],
2), xy = (sort[
-1],
0.7), ha =
'center', color = colors[
4])
-
plt.annotate(np.round(upper_boundary,
2), xy = (upper_boundary,
0.8), ha =
'center', color =
'k')
-
plt.legend(bbox_to_anchor = (
1.0,
1.05))
-
plt.savefig(
'box4.png', dpi =
300, bbox_inches =
'tight', facecolor=
'w')
上述代码运行的结果如 图6 所示:
在 图6 中,可以看到在红点中生成的随机数。下边界(Q1-1.5 x IQR)等于 -3.69,分布的最小值是 -2.41 。因此,图6 中的左晶须极限为最小值 -2.41。上限(Q3 + 1.5 x IQR)在 5.98 上,因此值 7.0 的数据被定义为离群值。晶须右边的极限值将在 5.47 处结束,处在上限之前的最大值。
在下一个示例中,将显示四个分布的平均值。需要使用以下代码为四个分布创建模拟数据:
-
np.random.seed(
1214)
-
data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
显示均值
数据将生成四个正态分布,其中 mu 的值为0,并且每个分布的 sigma 值不同(10、11、12和13)。要在箱形图中显示均值,需要使用以下代码:
plt.boxplot(data, showmeans=True)
如果运行上面的代码,将生成一个箱形图,如 图7-1 所示。绿色三角形代表每个箱形图的均值:
同样的,可以使用下面代码在水平方箱形图中显示均值:
plt.boxplot(data, showmeans=True, vert = False)
运行代码,如 图7-2 所示:
可以通过添加以下参数来更改均值显示的符号:
meanline=True
或使用以下参数隐藏框体形状:
showbox=False
箱形图中的一些自定义设置如 图8 所示:
要更改框体的颜色可以使用以下设置进行操作。首先,需要设置下面的参数,代码所示如下:
patch_artist=True
然后,准备颜色,并使用以下代码将其应用到箱形图中:
-
colors = [
'royalblue',
'lightblue',
'lightgreen',
'pink']
-
for patch, color in zip(box[
'boxes'], colors):
-
patch.set_facecolor(color)
以下是完整的代码:
-
np.random.seed(
123)
-
all_data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
-
box = plt.boxplot(all_data, notch=False, patch_artist=True)
-
colors = [
'royalblue',
'lightblue',
'lightgreen',
'pink']
-
for patch, color in zip(box[
'boxes'], colors):
-
patch.set_facecolor(color)
-
plt.ylim(
-50,
50)
结果如 图9 所示:
激活缺口(Activate notch)
可以使用以下参数在箱形图中显示缺口,如 图10 所示:
notch=True
生成 图10 的完整代码如下:
-
np.random.seed(
123)
-
all_data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
-
box = plt.boxplot(all_data, notch=True, patch_artist=True)
-
colors = [
'royalblue',
'lightblue',
'lightgreen',
'pink']
-
for patch, color in zip(box[
'boxes'], colors):
-
patch.set_facecolor(color)
-
plt.ylim(
-50,
50)
02. 小提琴图(Violin plot)
小提琴图与箱形图的可视化几乎相似,但未定义离群值。要创建小提琴图,可以使用以下代码:
-
N =
50
-
np.random.seed(
140)
-
viol = np.random.randn(N) *
2 +
1
-
plt.figure()
-
plt.violinplot(viol)
以下代码将显示小提琴图,如 图11 所示:
水平的小提琴图,设置如下:
vert=False
默认情况下,小提琴图未显示中位数和均值。如果要显示它,则需要插入这些参数:
showmeans=True, showmedians=True
如果我组合上面的参数并运行它,将显示如 图12 所示的小提琴图:
在 图12 中,估计不能区别中位数和平均值,因为它们具有相似的颜色。要更改小提琴图中每个元素的颜色,例如条形的颜色,中位数,平均值,最小值和最大值,需要进行以下设置:
-
violin_parts = plt.violinplot(data, showmedians=True,
-
showmeans=True)
下面生成四个不同的正态分布,并将其定义为数据变量。
要更改中位数、均值、条形、最小值和最大值的颜色,可以使用以下代码:
-
vmedian = violin_parts[
'cmedians']
-
vmedian.set_edgecolor(
'r')
-
vmean = violin_parts[
'cmeans']
-
vmean.set_edgecolor(
'k')
-
vbars = violin_parts[
'cbars']
-
vbars.set_edgecolor(
'k')
-
vmax = violin_parts[
'cmaxes']
-
vmax.set_edgecolor(
'darkorange')
-
vmin = violin_parts[
'cmins']
-
vmin.set_edgecolor(
'darkorange')
自定义后的小提琴图如 图13 所示:
生成 图13 的完整代码如下:
-
np.random.seed(
1214)
-
data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
-
plt.figure(figsize = (
10,
6))
-
violin_parts = plt.violinplot(data, showmedians=True,
-
showmeans=True)
-
vbars = violin_parts[
'cbars']
-
vbars.set_edgecolor(
'k')
-
vmedian = violin_parts[
'cmedians']
-
vmedian.set_edgecolor(
'r')
-
vmean = violin_parts[
'cmeans']
-
vmean.set_edgecolor(
'k')
-
vmax = violin_parts[
'cmaxes']
-
vmax.set_edgecolor(
'darkorange')
-
vmin = violin_parts[
'cmins']
-
vmin.set_edgecolor(
'darkorange')
要更改轮廓颜色,需要使用以下代码:
-
for vp in violin_parts[
'bodies']:
-
vp.set_facecolor(
'orange')
-
vp.set_edgecolor(
'k')
-
vp.set_linewidth(
3)
上面的代码会将主体颜色更改为橙色,边缘为黑色,并将线宽调整为 3 ,如 图14 所示:
在 图14 中,仅显示每个分布的中值,可以使用以下代码生成 图14 :
-
np.random.seed(
1214)
-
data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
-
plt.figure(figsize = (
10,
6))
-
violin_parts = plt.violinplot(data, widths=
0.9, showmedians=True)
-
for vp in violin_parts[
'bodies']:
-
vp.set_facecolor(
'orange')
-
vp.set_edgecolor(
'k')
-
vp.set_linewidth(
3)
高度自定义的小提琴图如 图15 所示:
生成 图15 的代码如下:
-
np.random.seed(
1214)
-
data = [np.random.normal(
0, std,
100)
for std in
range(
10,
14)]
-
# Create violin plot objects:
-
plt.figure(figsize=(
10,
6))
-
violin_parts=plt.violinplot(data, showmedians=True, showmeans=True)
-
# Make the violin body blue with a red border:
-
for vp in violin_parts[
'bodies']:
-
vp.set_facecolor(
'royalblue')
-
vp.set_edgecolor(
'k')
-
vp.set_linewidth(
2)
-
vp.set_alpha(
.8)
-
colors = [
'k',
'red',
'red',
'w',
'k']
-
part = [
'cbars',
'cmins',
'cmaxes',
'cmeans',
'cmedians']
-
for i in
range(
len(part)):
-
vp = violin_parts[part[i]]
-
vp.set_edgecolor(colors[i])
-
vp.set_linewidth(
2)
03. 饼图(Pie chart)
在生成饼图之前,将使用以下代码创建一些数据:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
numbers = [
15,
30,
45,
10]
要在饼图中可视化,可以使用以下代码:
plt.pie(numbers)
结果如 图16 所示:
图16 显示了一个简单的饼图, 可以使用以下参数为每个数据提供标签:
labels = labels
如果要显示每个动物的数量,则需要插入以下参数:
autopct='%1.1f%%'
在 图17 中添加标签和数字后,可以看到饼图的结果:
以下是生成 图17 的完整代码:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
sizes = [
15,
30,
45,
10]
-
plt.figure()
-
plt.pie(sizes, labels = labels, autopct=
'%1.1f%%')
可以使用以下参数修改饼图的起始角度:
startangle=90
默认起始角度为 0 ,应用于第一个数据(青蛙)。下面以两种不同的角度自定义起始角度,如 图18 所示:
以下是生成 图18 的代码:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
sizes = [
15,
30,
45,
10]
-
explode1 = (
0,
0.1,
0,
0)
-
explode2 = (
0,
0,
0.1,
0)
-
plt.figure(figsize=(
10,
10))
-
plt.subplot(
121)
-
plt.pie(sizes, labels=labels, autopct=
'%1.1f%%', startangle=
90)
-
plt.title(
'Startangle = 90$^\circ$')plt.subplot(
122)
-
plt.pie(sizes, labels=labels, autopct=
'%1.1f%%', startangle=
180)
-
plt.title(
'Startangle = 180$^\circ$')
还可以为特定的饼图设置边距,如 图19 所示:
以下是生成 图19 的代码:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
sizes = [
15,
30,
45,
10]
-
explode1 = (
0,
0.1,
0,
0)
-
explode2 = (
0,
0,
0.1,
0)
-
plt.figure(figsize=(
10,
10))plt.subplot(
121)
-
plt.pie(sizes, explode=explode1, labels=labels,
-
autopct=
'%1.1f%%', startangle=
90)plt.subplot(
122)
-
plt.pie(sizes, explode=explode2, labels=labels,
-
autopct=
'%1.1f%%', startangle=
180)
您可以通过调整变量 explode
中的值来更改边距。
在饼图中应用不同的样式
您是否要以不同的样式在饼图中可视化数据?是的,您可以看到 图20 :
与之前的饼图的不同之处在于,这里将标签放置在图例中。为此,需要使用以下参数:
textprops={'color':"w"}
以及:
plt.legend(labels, bbox_to_anchor = (1., .95), title="Labels Name")
生成 图20 的代码如下:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
sizes = [
15,
30,
45,
10]
-
explode = (
0,
0.05,
0,
0)
-
plt.figure(figsize=(
7,
7))
-
plt.pie(sizes, autopct=
'%1.1f%%', textprops={
'color':
"w"},
-
explode = explode, startangle =
90)
饼图中设置阴影
可以使用以下参数在饼图中添加阴影:
shadow=True
结果如 图21 所示:
我想我知道你在想什么。阴影很难看,对不对?为此我尝试了许多替代方法,如下:
如果您使用的是 Jupyter,则可以通过右键单击该图来手动保存饼图。然后,单击“将图像另存为”,如 图22 所示:
保存后的图片如 图23 所示:
另一种选择是将白色描边添加为前景。要应用它,您需要使用以下代码从 Matplotlib 导入
path_effects
。
import matplotlib.patheffects as path_effects
然后,为饼图定义变量名,如以下代码所示:
-
patches, texts, autotexts = plt.pie(sizes, autopct=
'%1.1f%%',
-
textprops={
'color':
"w"},
-
explode = explode,
-
startangle =
90, shadow=True)
接下来,自定义设置 path_effects
,如下:
-
for patch in patches:
-
patch.set_path_effects([path_effects.Stroke(linewidth=
2.5,
-
foreground =
'w')])
以下是完整的代码:
-
labels =
'Frogs',
'Hogs',
'Dogs',
'Logs'
-
sizes = [
15,
30,
45,
10]
-
explode = (
0,
0.05,
0,
0)
-
plt.figure(figsize=(
7,
7), facecolor =
'w')
-
patches, texts, autotexts = plt.pie(sizes, autopct=
'%1.1f%%',
-
textprops={
'color':
"w"},
-
explode = explode,
-
startangle =
90, shadow=True)
-
for patch in patches:
-
patch.set_path_effects([path_effects.Stroke(linewidth=
2.5,
-
foreground =
'w')])
-
plt.legend(labels, bbox_to_anchor = (
1.,
.95), title=
"Labels Name")
运行代码后,结果如 图24 所示:
效果并不完美,还可以组合第一和第二种选择,并获得如 图25 所示的饼图:
环形图(Doughnut chart)
在 Matplotlib 中,也可以绘制环形图,如 图26 所示:
生成 图26 的数据如下:
-
size =
0.3
-
vals = np.array([[
60.], [
37.], [
29.]])cmap = plt.get_cmap(
"tab20c")
-
outer_colors = cmap(np.arange(
3)*
4)
-
inner_colors = cmap(np.array([
1,
2,
5,
6,
9,
10]))
环形图与饼图相似,但没有中心的完整半径。在 图26 中,我将半径设置为 0.3,这是生成 图26 的代码。
-
plt.figure(figsize=(
10,
10))
-
plt.pie(vals.sum(axis=
1), radius=
1, colors=outer_colors,
-
wedgeprops=dict(width=size, edgecolor=
'w'))
我还创建了两个不同的半径大小,如 图27 所示:
生成 图27 的代码如下:
-
vals = np.array([[
60.], [
37.], [
29.]])
-
plt.figure(figsize=(
10,
10))plt.subplot(
121)
-
plt.pie(vals.sum(axis=
1), radius=
1, colors=outer_colors,
-
wedgeprops=dict(width=
.3, edgecolor=
'w'))
-
plt.title(
'Size = 0.3')plt.subplot(
122)
-
plt.pie(vals.sum(axis=
1), radius=
1, colors=outer_colors,
-
wedgeprops=dict(width=
.5, edgecolor=
'w'))
-
plt.title(
'Size = 0.5')
也可以生成双层的环形图,如 图28 所示:
以下是生成 图28 的代码:
-
plt.figure(figsize=(
10,
10))
-
size =
0.3
-
vals = np.array([[
60.,
32.], [
37.,
40.], [
29.,
10.]])
-
cmap = plt.get_cmap(
"tab20c")
-
outer_colors = cmap(np.arange(
3)*
4)
-
inner_colors = cmap(np.array([
1,
2,
5,
6,
9,
10]))
-
plt.pie(vals.sum(axis=
1), radius=
1, colors=outer_colors,
-
wedgeprops=dict(width=size, edgecolor=
'w'))
-
plt.pie(vals.flatten(), radius=
1-size, colors=inner_colors,
-
wedgeprops=dict(width=size, edgecolor=
'w'))
如果您在 Matplotlib 官方文档上阅读了环形图的内容,您将看到一个不错的环形图,如 图29 所示:
生成 图29 的代码如下:
-
fig, ax = plt.subplots(figsize=(
10,
4.5), subplot_kw=dict(aspect=
"equal"))
-
recipe = [
"225 g flour",
-
"90 g sugar",
-
"1 egg",
-
"60 g butter",
-
"100 ml milk",
-
"1/2 package of yeast"]
-
data = [
225,
90,
50,
60,
100,
5]
-
wedges, texts = ax.pie(data, wedgeprops=dict(width=
0.5), startangle=
-40)
-
bbox_props = dict(boxstyle=
"square,pad=0.3", fc=
"w", ec=
"k", lw=
0.72)
-
kw = dict(arrowprops=dict(arrowstyle=
"-"),
-
bbox=bbox_props, zorder=
0, va=
"center")
-
for i, p in enumerate(wedges):
-
ang = (p.theta2 - p.theta1)/
2. + p.theta1
-
y = np.sin(np.deg2rad(ang))
-
x = np.cos(np.deg2rad(ang))
-
horizontalalignment = {
-1:
"right",
1:
"left"}[
int(np.sign(x))]
-
connectionstyle =
"angle,angleA=0,angleB={}".format(ang)
-
kw[
"arrowprops"].update({
"connectionstyle": connectionstyle})
-
ax.annotate(recipe[i], xy=(x, y), xytext=(
1.35*np.sign(x),
1.4*y),
-
horizontalalignment=horizontalalignment, **kw)
-
ax.set_title(
"Matplotlib bakery: A donut")
04. 极坐标图(Polar chart)
在平面投影中,将具有x轴和y轴。在极坐标投影中,需要以半径和角度的形式定义它,如 图30 所示:
在极坐标投影中,半径轴以圆半径的大小显示,并且以每个角度为 0 度的圆的角度作为起点投影角度。要生成极坐标投影,需要将投影类型定义为极坐标,如以下参数所示:
projection='polar'
以下是生成 图30 的代码:
-
r = np.linspace(
0,
2,
100)
-
theta =
2 * np.pi * r
-
fig = plt.figure(figsize=(
13,
4))
-
ax1 = plt.subplot(
121, projection=
'polar')
-
ax1.scatter(theta, r, label =
'Polar Projection', s =
10)
-
ax1.legend(bbox_to_anchor = (
.85,
1.35))
-
ax2 = plt.subplot(
122)
-
ax2.scatter(theta, r, label =
'Planar Projection', s =
10)
-
ax2.legend(bbox_to_anchor = (
0.85,
1.35))
-
ax2.set_xlabel(
'R')
-
ax2.set_ylabel(r
'$\theta$')
还可以在极坐标投影中以绘图线样式显示数据,如图31所示:
以下是生成 图31 的代码:
-
r = np.linspace(
0,
2,
100)
-
theta =
2 * np.pi * r
-
fig = plt.figure(figsize=(
13,
4))
-
ax1 = plt.subplot(
121, projection=
'polar')
-
ax1.plot(theta, r, label =
'Polar Projection', ls =
'--', color =
'r')
-
ax1.legend(bbox_to_anchor = (
.85,
1.35))
-
ax2 = plt.subplot(
122)
-
ax2.plot(theta, r, label =
'Planar Projection')
-
ax2.legend(bbox_to_anchor = (
.85,
1.35))
接下来是在极坐标图中创建条形图,如 图32 所示:
以下是生成 图32 的数据:
-
np.random.seed(
10130)
-
N =
20
-
theta = np.linspace(
0.0,
2 * np.pi, N, endpoint=False)
-
radii =
10 * np.random.rand(N)
-
width = np.random.rand(N) *
.8 -
.1
-
colors = plt.cm.Spectral(radii /
10)
用以下代码来进行可视化:
-
plt.figure(figsize=(
7,
7))
-
ax = plt.subplot(
111, projection=
'polar')
-
ax.bar(theta, radii, width=width, bottom=
0.0,
-
color=colors, alpha=
0.5)
如果您对使用极坐标图生成 Matplotlib 徽标感兴趣,则可以阅读以下文档。
https://matplotlib.org/3.1.0/gallery/misc/logos2.html
05. 地理投影(Geographic projection)
要可视化地理数据,可以使用 Matplotlib 提供的地理投影。有四种类型的投影: Aitoff
, Hammer
, Mollweide
和 Lambert
投影。为了理解它,我将使用以下代码创建一个模拟数据:
-
N =
100
-
np.random.seed(
157)
-
long = np.random.random(N) *
360 -
180
-
lat = np.random.random(N) *
180 -
90
To visualize it in the Aitoff projection, you can use this code
Aitoff
投影的代码如下:
-
plt.figure(figsize=(
12,
7))
-
plt.subplot(
111, projection=
"aitoff")
-
plt.scatter(long, lat, marker =
'*', color =
'red', s =
40)
-
plt.title(
"Aitoff")
-
plt.grid(True)
运行代码,结果如 图33 所示:
在 Aitoff
投影中,需要确保数据以度为单位。
Hammer
投影的代码如下:
-
plt.figure(figsize=(
12,
7))
-
plt.subplot(
111, projection=
"hammer")
-
plt.scatter(long, lat, marker =
'*', color =
'red', s =
40)
-
plt.title(
"Hammer")
-
plt.grid(True)
Hammer
投影中所示的 long 和 lat 变量的单位是度。上面的代码将生成一个图形,如 图34 所示:
我不确定 Aitoff
和 Hammer
投影之间有什么区别。如果需要一些说明,可以阅读这些链接:
Aitoff
投影
https://en.wikipedia.org/wiki/Aitoff_projection
Hammer
投影
https://en.wikipedia.org/wiki/Hammer_projection
在 Mollweide
投影中,数据单位必须以弧度转换, 这是生成弧度数据的代码。
-
N =
100
-
np.random.seed(
157)
-
long = np.random.random(N) *
2 * np.pi - np.pi
-
lat = np.random.random(N) * np.pi - (np.pi /
2)
Hammer
投影的代码如下:
-
plt.figure(figsize=(
12,
7))
-
plt.subplot(
111, projection=
"mollweide")
-
plt.scatter(long, lat, marker =
'*', color =
'red', s =
40)
-
plt.title(
"Mollweide")
-
plt.grid(True)
代码运行后,如 图35 所示:
Lambert
投影的图示如 图36 所示:
生成 图36 的代码如下:
-
N =
100
-
np.random.seed(
157)
-
long = np.random.random(N) *
2 * np.pi - np.pi
-
lat = np.random.random(N) * np.pi - (np.pi /
2)
-
plt.figure(figsize=(
12,
12))
-
plt.subplot(
111, projection=
"lambert")
-
plt.scatter(long, lat, marker =
'*', color =
'red', s =
40)
-
plt.title(
"Lambert")
-
plt.grid(True)
06. 3D图(3D plot)
要创建 3D 图,需要将投影类型定义为 3d ,如以下参数所示:
projection = '3d'
3D 投影将为您提供如 图37 所示的结果:
上图的数据如下:
-
N =
250
-
np.random.seed(
124)
-
x =
15 * np.random.random(N)
-
y = np.sin(x) +
0.25 * np.random.random(N)
-
z = np.cos(x) +
0.25 * np.random.random(N)
生成 3D 图,可以用如下代码:
-
plt.figure(figsize=(
9,
6))
-
ax = plt.axes(projection =
'3d')
-
ax.scatter3D(x, y, z, color =
'r')
-
ax.set_xlabel(
'x', fontsize =
20, labelpad =
20)
-
ax.set_ylabel(
'y', fontsize =
20, labelpad =
20)
-
ax.set_zlabel(
'z', fontsize =
20, labelpad =
20)
3D 图的结果如 图38 所示:
在最新的 Matplotlib 版本中,每个轴的长宽比始终相等。要更改它,可以使用以下代码:
ax.set_box_aspect((2., 1.5, 1.2))
将x轴,y轴和z轴的长宽比更改为 2:1.5:1.2 。应用上面的代码后,将得到一个图,如 图39 所示:
3D线形图(3D plotline)
3D线形图如 图40 所示:
生成 图40 的代码如下:
-
N =
100
-
np.random.seed(
124)
-
xline = np.linspace(
0,
15, N)
-
yline = np.sin(xline)
-
zline = np.cos(xline)
-
fig = plt.figure(figsize=(
9,
6))
-
ax = plt.axes(projection =
'3d')
-
ax.plot3D(xline, yline, zline)
-
ax.set_xlabel(
'x')
-
ax.set_ylabel(
'y')
-
ax.set_zlabel(
'z')
-
ax.set_box_aspect((
2,
1.5,
1.2))
可以使用以下参数来修改角度:
ax.view_init(10, 180)
view_init
中的第一个参数是仰角,第二个参数是方位角,可以看到不同角度的不同表示形式,如 图41 所示:
三角3D曲面(Triangular 3D surfaces)
要在 Matplotlib 中生成三角形3D曲面,可以使用以下代码:
ax.plot_trisurf()
我使用以下代码生成要在三角形3D曲面中可视化的数据:
-
N =
2000
-
np.random.seed(
124)
-
r =
2 * np.pi * np.random.random(N)
-
theta =
20 * np.pi * np.random.random(N)
-
xdata = np.ravel(r * np.sin(theta))
-
ydata = np.ravel(r * np.cos(theta))
-
zdata = np.sin(xdata) + np.cos(ydata)
可以从上面的数据中看到三角形3D曲面图,见 图42:
生成 图42 的代码如下:
-
N =
2000
-
np.random.seed(
124)
-
r =
2 * np.pi * np.random.random(N)
-
theta =
20 * np.pi * np.random.random(N)
-
xdata = np.ravel(r * np.sin(theta))
-
ydata = np.ravel(r * np.cos(theta))
-
zdata = np.sin(xdata) + np.cos(ydata)
-
fig = plt.figure(figsize=(
15,
6))
-
plt.subplots_adjust(wspace=
0)
-
ax1 = plt.subplot(
121, projection =
'3d')
-
ax1.plot_trisurf(xdata, ydata, zdata, cmap =
'inferno')
-
ax1.set_xlabel(
'x')
-
ax1.set_ylabel(
'y')
-
ax1.set_zlabel(
'z')
-
ax1.view_init(
40,
100)
-
ax1.set_box_aspect((
4.5,
4.5,
1.5))
-
ax1.set_title(
'Elevation = 40$^\circ$, Azimuth = 100$^\circ$')
-
ax2 = plt.subplot(
122, projection =
'3d')
-
ax2.plot_trisurf(xdata, ydata, zdata, cmap =
'inferno')
-
ax2.set_xlabel(
'x')
-
ax2.set_ylabel(
'y')
-
ax2.set_zlabel(
'z')
-
ax2.view_init(
20,
100)
-
ax2.set_box_aspect((
4.5,
4.5,
1.5))
-
ax2.set_title(
'Elevation = 20$^\circ$, Azimuth = 100$^\circ$')
3D等高线图(3D contour plot)
Matplotlib 提供 3D等高线图,可以使用下面的代码创建:
ax.contour3D()
使用的数据如下:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
运行以下代码来进行可视化:
-
fig = plt.figure(figsize=(
9,
6))
-
ax = plt.axes(projection =
'3d')
-
ax.contour3D(X, Y, Z, cmap =
'Spectral')
进行自定义后,3D等高线图如 图43 所示:
生成 图43 的代码如下:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
9,
6))
-
ax = plt.axes(projection =
'3d')
-
ax.contour3D(X, Y, Z, cmap =
'Spectral')
-
ax.set_xlabel(
'x')
-
ax.set_ylabel(
'y')
-
ax.set_zlabel(
'z')
-
ax.set_box_aspect((
2,
2,
1))
-
ax.view_init(
60,
100)
-
ax.set_title(
'Contour counts = Default, elevation = 60, azimuth = 100')
默认情况下,Matplotlib 以 7 个计数的轮廓生成 3D轮廓图。可以通过在 ax.contour3D()
的第四个参数中写下所需的计数来更改它,如下面的代码所示:
ax.contour3D(X, Y, Z, 256, cmap = 'Spectral')
图44 显示的是不一样的 3D等高线图:
图44的完整代码如下:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
20,
6))
-
ax = plt.axes(projection =
'3d')
-
p = ax.contour3D(X, Y, Z,
256, cmap =
'Spectral')
-
ax.set_xlabel(
'x')
-
ax.set_ylabel(
'y')
-
ax.set_zlabel(
'z')
-
ax.set_box_aspect((
3,
3,
1))
-
ax.view_init(
60,
100)
-
plt.colorbar(p)
-
ax.set_title(
'Contour counts = 256, elevation = 60, azimuth = 100')
如果要显示 colorbar
,可以进行下面的设置:
plt.colorbar()
所需的参数是您的3D等高线图。因此,最好将绘图定义为变量。在我的代码中,它被定义为变量 p 。
可以使用前面图中使用的相同代码来更改视角,如 图45 所示:
3D等高线图的一些 bugs
我认为 Matplotlib 3D等高线图中存在一个 bug ,如图46所示。
要创建 图46,请在上一个代码(创建图45的代码)中更改仰角和方位角的值。如果仔细分析图46,将获得一个奇怪的功能,可以在 图47 中看到它。
蓝线覆盖的区域不在正确的位置。我们不应该看到它,因为它放在后面。我不确定这是否是bug,但我认为需要 Matplotlib 对其进行修复。
为了更详细地了解它,我在 Matplotlib 中引入了两种不同的轮廓表示形式, ax.contour()
和 ax.contourf()
,如 图48 所示:
ax.contourf
中的 contourf 表示填充轮廓。因此,可以在 图48 中看到 ax.contour()
和 ax.contourf()
之间的区别。 ax.contour
具有与 ax.contour3D()
类似的默认轮廓计数,为 7 个计数。但是, ax.contourf()
的计数不同,为8。您可以使用此代码重现 图48 :
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
plt.figure(figsize=(
14,
6))
-
ax1 = plt.subplot(
121, projection =
'3d')
-
ax1.contour(X, Y, Z, cmap =
'Spectral')
-
ax1.set_xlabel(
'x')
-
ax1.set_ylabel(
'y')
-
ax1.set_zlabel(
'z')
-
ax1.set_box_aspect((
3,
3,
1))
-
ax1.view_init(
10,
100)
-
ax1.set_title(
'Contour Default, elevation = 10, azimuth = 100')ax2 = plt.subplot(
122, projection =
'3d')
-
ax2.contourf(X, Y, Z, cmap =
'Spectral')
-
ax2.set_xlabel(
'x')
-
ax2.set_ylabel(
'y')
-
ax2.set_zlabel(
'z')
-
ax2.set_box_aspect((
3,
3,
1))
-
ax2.view_init(
10,
100)
-
ax2.set_title(
'Contourf Default, elevation = 10, azimuth = 100')
可以在右侧面板中仔细观察 图48(轮廓图)。在3D等高线图中提到的bug未检测到。可以在 图49 中看到更多详细信息。
您可以看到 ax.contour()
是否会给您同样的错误,但 ax.contourf()
不会。因此,如果要在3D投影中可视化等高线图,建议使用 ax.contourf()
。
线框图 (Wireframe plot)
使用以下设置生成线框图:
ax.plot_wireframe()
以下是生成 线框图 (Wireframe plot)的数据:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
默认的线框图 (Wireframe plot)如 图50 所示:
生成 图50 的代码如下:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
10,
10))
-
ax = plt.axes(projection =
'3d')
-
ax.plot_wireframe(X, Y, Z, color =
'k', alpha =
.2)
线框图 (Wireframe plot)的自定义设置,如 图51 所示:
我将线框图层的数量更改为 5 ,将仰角设置为 60 度,将方位角设置为 100 度。您可以使用以下代码生成 图51 :
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
9,
6))
-
ax = plt.axes(projection =
'3d')
-
#
3d contour plot
-
ax.plot_wireframe(X, Y, Z,
5, color =
'k', alpha =
.2)
-
ax.set_xlabel(
'x')
-
ax.set_ylabel(
'y')
-
ax.set_zlabel(
'z')
-
ax.set_box_aspect((
2,
2,
1))
-
ax.view_init(
60,
100)
-
ax.set_title(
'Wireframe counts = 5, elevation = 60, azimuth = 100')
如果要检查在 ax.contour()
和 ax.contour3D()
中是否遇到类似的错误,可以将仰角更改为 10 度,将方位角更改为 100 度,如图 52 所示:
在线框图中,没有遇到 bugs 。
3D表面图 (3D surface plot)
3D表面图 (3D surface plot)的代码设置如下:
ax.plot_surface()
3D表面图 (3D surface plot)的自定义设置如 图53:
生成 图53 的代码如下:
-
N =
100
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
8,
8))ax = plt.axes(projection =
'3d')
-
#
3d contour plot
-
ax.plot_surface(X, Y, Z, )
-
ax.set_xlabel(
'x')
-
ax.set_ylabel(
'y')
-
ax.set_zlabel(
'z')
-
ax.set_box_aspect((
2,
2,
1))
-
ax.view_init(
10,
100)
-
ax.set_title(
'Plot surface Default, elevation = 10, azimuth = 100')
可以使用以下参数指定 cstride 和 rstride 的值:
rstride = 1, cstride = 1
可以在图54中看到默认的 cstride 和 rstride 值与自定义值之间的差异。
生成 图54 的代码如下:
-
N =
200
-
np.random.seed(
3124)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
fig = plt.figure(figsize=(
14,
6))
-
ax1 = plt.subplot(
121, projection =
'3d')#
3d contour plot
-
ax1.plot_surface(X, Y, Z, cmap =
'Spectral')
-
ax1.set_xlabel(
'x')
-
ax1.set_ylabel(
'y')
-
ax1.set_zlabel(
'z')
-
ax1.set_box_aspect((
2,
2,
1))
-
ax1.view_init(
60,
100)
-
ax1.set_title(
'Plot surface rstride = cstride = default, \n elevation = 60, azimuth = 100')
-
ax2 = plt.subplot(
122, projection =
'3d')#
3d contour plot
-
ax2.plot_surface(X, Y, Z, cmap =
'Spectral', rstride =
1, cstride =
1)
-
ax2.set_xlabel(
'x')
-
ax2.set_ylabel(
'y')
-
ax2.set_zlabel(
'z')
-
ax2.set_box_aspect((
2,
2,
1))
-
ax2.view_init(
60,
100)
-
ax2.set_title(
'Plot surface rstride = cstride = 1, \n elevation = 60, azimuth = 100')
您不会遇到等高线图中遇到的错误,如 图55 所示:
在Matplotlib中创建球体
在Matplotlib中创建球体,如 图56 所示:
图 56 的代码如下:
-
u = np.linspace(
0,
2 * np.pi,
100)
-
v = np.linspace(
0, np.pi,
100)
-
x =
10 * np.outer(np.cos(u), np.sin(v))
-
y =
10 * np.outer(np.sin(u), np.sin(v))
-
z =
10 * np.outer(np.ones(np.size(u)), np.cos(v))
-
plt.figure(figsize=(
10,
10))
-
ax = plt.subplot(projection =
'3d')
-
ax.plot_surface(x, y, z, cmap =
'inferno')
可以自定义视角,如 图57 所示:
生成 图57 的代码如下:
-
u = np.linspace(
0,
2 * np.pi,
100)
-
v = np.linspace(
0, np.pi,
100)
-
x =
10 * np.outer(np.cos(u), np.sin(v))
-
y =
10 * np.outer(np.sin(u), np.sin(v))
-
z =
10 * np.outer(np.ones(np.size(u)), np.cos(v))
-
rows =
2
-
columns =
2
-
grid = plt.GridSpec(rows, columns, wspace =
.2, hspace =
.2)
-
elev = np.arange(
0,
40,
10)
-
azim = np.arange(
0,
200,
50)
-
plt.figure(figsize=(
12,
12))
-
for i in
range(rows * columns):
-
ax = plt.subplot(grid[i], projection =
'3d')
-
ax.plot_surface(x, y, z, cmap =
'inferno')
-
ax.view_init(elev[i], azim[i])
-
ax.set_title(
'Elevation = ' + str(elev[i]) +
', Azimuth = ' + str(azim[i]))
07. 2D等高线图(2D Contour plot)
绘制 2D等高线图,使用以下设置:
plt.contour()
虽然名称是2D等高线图,但它显示了3D数据。使用以下代码创建模拟数据:
-
N =
100
-
np.random.seed(
100)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
将模拟数据呈现为等高线图,如 图58 所示:
生成 图58 的代码如下:
-
N =
100
-
np.random.seed(
100)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
plt.figure(figsize=(
7,
5))
-
plt.contour(X, Y, Z)
-
plt.title(
'Contour 2D Default', pad =
10)
我生成了两个自定义的等高线图,如 图59 所示:
生成 图59 的代码如下:
-
N =
100
-
np.random.seed(
100)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)
-
plt.figure(figsize=(
15,
5))
-
plt.subplot(
121)
-
plt.contour(X, Y, Z,
256)
-
plt.title(
'Contour 2D counts = 256, cmap = viridis', pad =
10)
-
plt.colorbar()
-
plt.subplot(
122)
-
plt.contour(X, Y, Z,
256, cmap =
'Spectral')
-
plt.colorbar()
-
plt.title(
'Contour 2D counts = 256, cmap = Spectral', pad =
10)
另外,可以生成填充到2D投影中的等高线图,如 图60 所示:
生成 图60 的代码如下:
-
N =
100
-
np.random.seed(
100)x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)**
3
-
plt.figure(figsize=(
7,
5))
-
plt.contourf(X, Y, Z, cmap =
'Spectral')
-
plt.colorbar()
-
plt.title(
'Contourf 2D Default', pad =
10)
在这里,生成了两个不同的 contourf 图,如 图61 所示:
生成 图61 的代码如下:
-
N =
100
-
np.random.seed(
100)
-
x = np.linspace(
-2,
2, N) + np.random.random(N)
-
y = np.linspace(
-2,
2, N) + np.random.random(N)
-
X, Y = np.meshgrid(x, y)
-
Z = np.sin(X) + np.cos(Y)**
3
-
plt.figure(figsize=(
15,
5))
-
plt.subplot(
121)
-
plt.contourf(X, Y, Z,
50, cmap =
'inferno')
-
plt.colorbar()
-
plt.title(
'Contourf 2D counts = 50', pad =
10)plt.subplot(
122)
-
plt.contourf(X, Y, Z,
200, cmap =
'inferno')
-
plt.colorbar()
-
plt.title(
'Contourf 2D counts = 200', pad =
10)
总结
与第一篇文章一起,这两篇文章展示了用 Matplotlib 进行可视化的 100 余个图形示例,这些示例分布在 11 种不同的样式图中:散点图、折线图、一维直方图、二维直方图、边际图、条形图、箱形图、小提琴图,饼图,极坐标图,地理投影,3D图和等高线图。大家可以通过这些案例,快速的掌握 Matplotlib 的绘图技巧,对于熟练使用 Matplotlib 有诸多益处。
原文链接:
https://towardsdatascience.com/python-data-visualization-with-matplotlib-part-2-66f1307d42fb
PDF文件获取方式
最后, Lemon 将第一篇文章和第二篇文章整合在一个 pdf 文件中,方便大家查看,文件获取方式如下:
1. 扫描下方二维码
2. 回复关键词:mat
(建议复制关键词)
-
????长按上方二维码
2 秒
-
回复「mat」即可获取资料
转载:https://blog.csdn.net/lemonbit/article/details/109505517