【Python】ProPlot弥补Matplotlib这9大缺陷

Matplotlib是一个非常通用的绘图包,被科学家和工程师广泛使用,但是,Matplotlib也存在不足,例如:

  • 默认出图丑陋

  • 重复执行一行代码设置一个参数的繁琐行为

  • 复杂多子图个性化

  • 自定义字体困难等等......

de99d8f36990a2936ace344750ec1b4f.png本期的主角是ProPlot,ProPlot是Matplotlib面向对象绘图方法(object-oriented interface)的高级封装,整合了cartopy/basemap地图库、xarray和pandas,可弥补Matplotlib的部分缺陷,ProPlot让Matplotlib爱好者拥有更加smoother plotting experience。

在构造上,使用proplot.figure.Figure替代matplotlib.figure.Figure、proplot.axes.Axes替代matplotlib.axes.Axes、proplot.gridspec.GridSpec替代matplotlib.gridspec.GridSpec。

作者为气象学领域的PhD,难怪ProPlot重点整合cartopy、basemap地图库(见后文)98b7d15c6385a140774885ef3ea8fc71.png


直接来看看Proplot的9大亮点:

1、更简简洁的代码,更好看的图形

将Matplotlib一行代码设置一个参数的繁琐行为直接通过format方法一次搞定,比如下图,15cd527607bc1b0c525344438938fe6d.pngProplot中代码

import proplot as pplt

fig, axs = pplt.subplots(ncols=2)
axs.format(color='gray', linewidth=1) #format设置所有子图属性
axs[0].bar([10, 50, 80], [0.2, 0.5, 1])
axs[0].format(xlim=(0, 100), #format设置子图1属性
              xticks=10,
              xtickminor=True,
              xlabel='foo',
              ylabel='bar')

Matplotlib中代码,

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib as mpl
with mpl.rc_context(rc={'axes.linewidth': 1, 'axes.edgecolor': 'gray'}):
    fig, axs = plt.subplots(ncols=2, sharey=True)
    axs[0].set_ylabel('bar', color='gray')
    axs[0].bar([10, 50, 80], [0.2, 0.5, 1], width=14)
    for ax in axs:
        #每一行代码设置一个图形参数
        ax.set_xlim(0, 100) 
        ax.xaxis.set_major_locator(mticker.MultipleLocator(10))
        ax.tick_params(width=1, color='gray', labelcolor='gray')
        ax.tick_params(axis='x', which='minor', bottom=True)
        ax.set_xlabel('foo', color='gray')

可见,Proplot中代码量会少很多。 一个更完整的format使用案例,

import proplot as pplt
import numpy as np

fig, axs = pplt.subplots(ncols=2, nrows=2, refwidth=2, share=False)
state = np.random.RandomState(51423)
N = 60
x = np.linspace(1, 10, N)
y = (state.rand(N, 5) - 0.5).cumsum(axis=0)
axs[0].plot(x, y, linewidth=1.5)

# 图表诸多属性可在format中设置
axs.format(
    suptitle='Format command demo',
    abc='A.',
    abcloc='ul',
    title='Main',
    ltitle='Left',
    rtitle='Right',  # different titles
    ultitle='Title 1',
    urtitle='Title 2',
    lltitle='Title 3',
    lrtitle='Title 4',
    toplabels=('Column 1', 'Column 2'),
    leftlabels=('Row 1', 'Row 2'),
    xlabel='xaxis',
    ylabel='yaxis',
    xscale='log',
    xlim=(1, 10),
    xticks=1,
    ylim=(-3, 3),
    yticks=pplt.arange(-3, 3),
    yticklabels=('a', 'bb', 'c', 'dd', 'e', 'ff', 'g'),
    ytickloc='both',
    yticklabelloc='both',
    xtickdir='inout',
    xtickminor=False,
    ygridminor=True,
)
4d20d4bbf01809caf7455539667c4f23.png

2、更友好的类构造函数

将Matplotlib中类名书写不友好的类进行封装,可通过简洁的关键字参数调用。例如,mpl_toolkits.basemap.Basemap()、matplotlib.ticker.LogFormatterExponent()、ax.xaxis.set_major_locator(MultipleLocator(1.000))等等,封装后,040723cba2336160b68b2a4d10eac601.png


3、图形大小、子图间距自适应

proplot通过refwidthrefheightrefaspect、refheight、proplot.gridspec.GridSpec等控制图形大小和子图间距,替代Matplotlib自带的tightlayout,避免图形重叠、标签不完全等问题推荐阅读

一个案例,proplot如何更科学控制图形大小

import proplot as pplt
import numpy as np

state = np.random.RandomState(51423)
colors = np.tile(state.rand(8, 12, 1), (1, 1, 3))

fig, axs = pplt.subplots(ncols=3, nrows=2, refwidth=1.7) #refwidth的使用
fig.format(suptitle='Auto figure dimensions for grid of images')
for ax in axs:
    ax.imshow(colors)

# 结合上文第2部分看,使用proj='robin'关键字参数调用cartopy projections'
fig, axs = pplt.subplots(ncols=2, nrows=3, proj='robin') 
axs.format(land=True, landcolor='k')
fig.format(suptitle='Auto figure dimensions for grid of cartopy projections')

f6d28def199cc5062f980b169e370376.pngbde3da2b2fd9f4f2a0f76a972b71e642.png一个案例,proplot如何更科学控制子图间距

import proplot as pplt

fig, axs = pplt.subplots(
    ncols=4, nrows=3, refwidth=1.1, span=False,
    bottom='5em', right='5em',  
    wspace=(0, 0, None), hspace=(0, None),  
) # proplot新的子图间距控制算法
axs.format(
    grid=False,
    xlocator=1, ylocator=1, tickdir='inout',
    xlim=(-1.5, 1.5), ylim=(-1.5, 1.5),
    suptitle='Tight layout with user overrides',
    toplabels=('Column 1', 'Column 2', 'Column 3', 'Column 4'),
    leftlabels=('Row 1', 'Row 2', 'Row 3'),
)
axs[0, :].format(xtickloc='top')
axs[2, :].format(xtickloc='both')
axs[:, 1].format(ytickloc='neither')
axs[:, 2].format(ytickloc='right')
axs[:, 3].format(ytickloc='both')
axs[-1, :].format(xlabel='xlabel', title='Title\nTitle\nTitle')
axs[:, 0].format(ylabel='ylabel')
2da3bf7aaf3311f22e22648804f1818f.png

4、多子图个性化设置

推荐阅读?matplotlib-多子图绘制(为所欲为版)Matplotlib对于多子图轴标签、legend和colorbar等处理存在冗余问题,proplot使用Figure、colorbar和legend方法处理这种情况,使多子图绘图更简洁。

  • 子图灵活设置坐标轴标签

sharexshareyspanxspanyalignxaligny参数控制,效果见下图(相同颜色比较来看),a1361182013853a0362ef9f12e7d6754.png

  • 子图灵活添加编号

一行代码为各子图添加编号

import proplot as pplt
import numpy as np
N = 20
state = np.random.RandomState(51423)
data = N + (state.rand(N, N) - 0.55).cumsum(axis=0).cumsum(axis=1)

cycle = pplt.Cycle('greys', left=0.2, N=5)
fig, axs = pplt.subplots(ncols=2, nrows=2, figwidth=5, share=False)
axs[0].plot(data[:, :5], linewidth=2, linestyle='--', cycle=cycle)
axs[1].scatter(data[:, :5], marker='x', cycle=cycle)
axs[2].pcolormesh(data, cmap='greys')
m = axs[3].contourf(data, cmap='greys')
axs.format(
    abc='a.', titleloc='l', title='Title',
    xlabel='xlabel', ylabel='ylabel', suptitle='Quick plotting demo'
) #abc='a.'为各子图添加编号
fig.colorbar(m, loc='b', label='label')
bc48c49ad91c94d89c650c60d81c9e00.png
  • 子图灵活设置Panels

17b92eb39baf48d86a4fd9449f0ca942.png
  • 子图各自外观灵活自定义

主要使用proplot.gridspec.SubplotGrid.format,

import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)

# Selected subplots in a simple grid
fig, axs = pplt.subplots(ncols=4, nrows=4, refwidth=1.2, span=True)
axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Simple SubplotGrid')
axs.format(grid=False, xlim=(0, 50), ylim=(-4, 4))

# 使用axs[:, 0].format自定义某个子图外观
axs[:, 0].format(facecolor='blush', edgecolor='gray7', linewidth=1)  # eauivalent
axs[:, 0].format(fc='blush', ec='gray7', lw=1)
axs[0, :].format(fc='sky blue', ec='gray7', lw=1)
axs[0].format(ec='black', fc='gray5', lw=1.4)
axs[1:, 1:].format(fc='gray1')
for ax in axs[1:, 1:]:
    ax.plot((state.rand(50, 5) - 0.5).cumsum(axis=0), cycle='Grays', lw=2)

# 使用axs[1, 1:].format自定义某个子图外观
fig = pplt.figure(refwidth=1, refnum=5, span=False)
axs = fig.subplots([[1, 1, 2], [3, 4, 2], [3, 4, 5]], hratios=[2.2, 1, 1])
axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Complex SubplotGrid')
axs[0].format(ec='black', fc='gray1', lw=1.4)
axs[1, 1:].format(fc='blush')
axs[1, :1].format(fc='sky blue')
axs[-1, -1].format(fc='gray4', grid=False)
axs[0].plot((state.rand(50, 10) - 0.5).cumsum(axis=0), cycle='Grays_r', lw=2)

实现如下效果变得简单,赞啊~ fd79bfddf6723375586ddba781ddf1ec.png19e4c1527dc1cc8f4a45ca817a391067.png


5、图例、colorbar灵活设置

主要新增proplot.figure.Figure.colorbar、proplot.figure.Figure.legend方法,

  • 图例、colorbar位置指定

9f35fc2d30008bccb16516c011711e47.png
  • 图例、colorbar:On-the-fly,

79faa64d89db9899cf6fb1ab4af95d14.png
  • 图例、colorbar:Figure-wide

5ce503d83444fde23d3722c795c21313.png
  • 图例外观个性化

可轻松设置图例顺序、位置、颜色等等,7022fd86cc054a6cffc401e00a45f7a7.png

  • colorbar外观个性化

可轻松设置colorbar的刻度、标签、宽窄等,42d6e04c25314ac37cd28775cdd96700.png


6、更加优化的绘图指令

众所周知,matplotlib默认出图很丑陋,seaborn, xarray和pandas都做过改进,proplot将这些改进进一步优化。无论是1D或2D图,效果都非常不错,26e6835dce132c94a12f7842be562500.png45e57192e5ceecdba8301022d61a6289.png74644eda23ec01c29b97f9b552367e8b.png


7、整合地图库Cartopybasemap

Cartopybasemap是Python里非常强大的地图库,二者介绍?11个地理空间数据可视化工具

proplot将cartopy和basemap进行了整合,解决了basemap使用需要创建新的axes、cartopy使用时代码冗长等缺陷。

看案例,d55c20137a6468e5c8b9fe75bdcb5f46.png个性化设置,b52b7c58b326688df77c8b2a78ef6663.png 支持cartopy中的各种投影,'cyl', 'merc', 'mill', 'lcyl', 'tmerc', 'robin', 'hammer', 'moll', 'kav7', 'aitoff', 'wintri', 'sinu', 'geos', 'ortho', 'nsper', 'aea', 'eqdc', 'lcc', 'gnom', 'npstere', 'nplaea', 'npaeqd', 'npgnom', 'igh', 'eck1', 'eck2', 'eck3', 'eck4', 'eck5', 'eck6'32f53a02dab8aa1453ece0d17bf159ec.pnga3cbea988bb70e24c1cb96584f56fd6b.png当然,也支持basemap中的各种投影,'cyl', 'merc', 'mill', 'cea', 'gall', 'sinu', 'eck4', 'robin', 'moll', 'kav7', 'hammer', 'mbtfpq', 'geos', 'ortho', 'nsper', 'vandg', 'aea', 'eqdc', 'gnom', 'cass', 'lcc', 'npstere', 'npaeqd', 'nplaea'。7b307342ee7fb54c57137953fb7079c6.png43475e320a20a32fb20697108b7a27fc.png


8、更美观的colormaps, colors和fonts

proplot除了整合seaborn, cmocean, SciVisColor及Scientific Colour Maps projects中的colormaps之外,还增加了新的colormaps,同时增加PerceptualColormap方法来制作colormaps(貌似比Matplotlib的ListedColormap、LinearSegmentedColormap好用),ContinuousColormap和DiscreteColormap方法修改colormaps等等。

proplot中可非常便利的添加字体。

  • proplot新增colormaps

3d067c82e260b7f0c9e4dc1224785d5b.png
  • PerceptualColormap制作colormaps

效果还不错,1374cf5f0830baaa85c4cfe82f691f61.png

  • 将多个colormaps融合

417178c377aa74722e42aa1b7fe87ff8.png
  • ContinuousColormap和DiscreteColormap方法修改colormaps

ee7dc033aa8c90182ec317f2840bae48.png
  • proplot添加字体

自定义的.ttc、.ttf等格式字体保存~/.proplot/fonts文件中。


9、全局参数设置更灵活

新的rc方法更新全局参数

import proplot as pplt
import numpy as np

# 多种方法Update全局参数
pplt.rc.metacolor = 'gray6'
pplt.rc.update({'fontname': 'Source Sans Pro', 'fontsize': 11})
pplt.rc['figure.facecolor'] = 'gray3'
pplt.rc.axesfacecolor = 'gray4'

# 使用Update后的全局参数:with pplt.rc.context法
with pplt.rc.context({'suptitle.size': 13}, toplabelcolor='gray6', metawidth=1.5):
    fig = pplt.figure(figwidth=6, sharey='limits', span=False)
    axs = fig.subplots(ncols=2)
N, M = 100, 7
state = np.random.RandomState(51423)
values = np.arange(1, M + 1)
cycle = pplt.get_colors('grays', M - 1) + ['red']
for i, ax in enumerate(axs):
    data = np.cumsum(state.rand(N, M) - 0.5, axis=0)
    lines = ax.plot(data, linewidth=3, cycle=cycle)

# 使用Update后的全局参数:format()法
axs.format(
    grid=False, xlabel='xlabel', ylabel='ylabel',
    toplabels=('Column 1', 'Column 2'),
    suptitle='Rc settings demo',
    suptitlecolor='gray7',
    abc='[A]', abcloc='l',
    title='Title', titleloc='r', titlecolor='gray7'
)

# 恢复设置
pplt.rc.reset()

04273bfb823e94ec9e88f1c4353b80cc.png全局设置'ggplot', 'seaborn'的style

import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)
data = state.rand(10, 5)

# Set up figure
fig, axs = pplt.subplots(ncols=2, nrows=2, span=False, share=False)
axs.format(suptitle='Stylesheets demo')
styles = ('ggplot', 'seaborn', '538', 'bmh')

# 直接使用format()方法
for ax, style in zip(axs, styles):
    ax.format(style=style, xlabel='xlabel', ylabel='ylabel', title=style)
    ax.plot(data, linewidth=3)
243ae6a2844823c5427b8ddca2c4b6b5.png

在以上方面,proplot确实优势明显,这里只是介绍了proplot的皮毛,更多学习:https://github.com/lukelbd/proplot


-END-

往期精彩回顾




适合初学者入门人工智能的路线及资料下载(图文+视频)机器学习入门系列下载中国大学慕课《机器学习》(黄海广主讲)机器学习及深度学习笔记等资料打印《统计学习方法》的代码复现专辑机器学习交流qq群955171419,加入微信群请扫码

c5144625fc06e2c19663818fdec88350.png