orange 使用python python调用origin画图_orange 使用python


背景故事:

之前画热度图用的seaborn,里面好像有一个inversey的选项,可以把热度图直接做垂直翻转。结合numpy的histogram2d来看,这是十分实用的,因为histogram2d得到的2d heatmap直接用matplotlib.pyplot的Axes.imshow方法画出来的图,是上下颠倒的,如果用一次np.flip(heatmap, 0),会发现图放正了,但是纵坐标的刻度还是反的,需要反着写Axes.set_yticks与Axes.set_yticklabels来调正。很显然,plt作为使用如此广泛的库,没有理由这么搞人心态。


orange 使用python python调用origin画图_origin遇到不适当的参数_02


所以查了下np.histogram2d的文档,发现范例里有用到extent,再去看了看matplotlib的文档,发现了extent的具体用法,在这里记录一下。


官方原文点上面那个“extent的具体用法”,有超链接的,点进去就可以看了,下面的内容是我自己写的范例。

首先看看背景故事里说的麻烦的情况:


import numpy as np
import matplotlib.pyplot as plt

x = np.random.normal(0, 1, (100,))
y = np.random.normal(0, 1, (100,))
heatmap, xedge, yedge = np.histogram2d(x, y, bins=(100, 10), range=((-4, 4), (-3, 3)))

fig, ax = plt.subplots()
fig.set_size_inches(4, 12)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.imshow(heatmap)
fig.savefig('./bad1.png')


orange 使用python python调用origin画图_d3_03


这是不加任何修饰,直接画出来的图。该图体现了一个最显然的问题,就是我的x应该是100个bin,y应该是10个bin,应该是一个矮胖的形状而不是一个高瘦的。

这是因为np.histogram2d的返回值就是这样设定的,如果需要画图,要先转置一下:


heatmap = heatmap.T


那么再画一次:


fig, ax = plt.subplots()
fig.set_size_inches(12, 4)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.imshow(heatmap)
fig.savefig('./bad2.png')


orange 使用python python调用origin画图_origin遇到不适当的参数_04


现在看上去对劲了,但是纵坐标是反的,我们希望的图是横纵坐标都从0开始。那么怎么解决呢?


fig, ax = plt.subplots()
fig.set_size_inches(12, 4)
ax.set_xlabel('x')
ax.set_ylabel('y')
heatmap_inverse = np.flip(heatmap, 0)
ax.imshow(heatmap)
ax.set_yticks([5, 9])
ax.set_yticklabels(['5', '0'])
fig.savefig('./bad3.png')


orange 使用python python调用origin画图_d3_05


这么一看效果是达到了,实际上操作很麻烦而且思路很奇怪,如果把code给别人看别人可能会疑惑。所以就需要用到imshow的origin参数。

先在一个简单的2d heatmap上测试,产生一个长这样的方阵:


test_map = np.eye(10)
test_map *= np.arange(1, 11, 1)


就是对角元为1到10的对角矩阵,画出来是这样的:


test_fig, test_ax = plt.subplots()
test_ax.imshow(test_map)
test_fig.savefig('./test_default.png')


orange 使用python python调用origin画图_2d_06


修改一下origin参数试试:


test_fig, test_ax = plt.subplots()
test_ax.imshow(test_map, origin='upper')
test_ax.set_title('origin = upper')
test_fig.savefig('./test_upper.png')
test_ax.imshow(test_map, origin='lower')
test_ax.set_title('origin = lower')
test_fig.savefig('./test_lower.png')


orange 使用python python调用origin画图_2d_07


orange 使用python python调用origin画图_origin画图_08


可以看出,origin默认值的就是upper,它的意思是从上往下画;对应地,lower就是从下往上画。不难发现,不论怎样规定,横坐标都是从左往右的,这也确实比较符合人们的习惯。

那么之前的热度图就好画了,指定origin为lower即可。


fig, ax = plt.subplots()
fig.set_size_inches(12, 4)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.imshow(heatmap, origin='lower')
fig.savefig('./good.png')


orange 使用python python调用origin画图_2d_09


这就不再需要自己折腾了。以上就是origin的设置。然后可能有人就想说了,我不喜欢横坐标从左到右,我要复古一下,从右到左,怎么办呢?那就需要用到extent了。

首先给出四个不同的extent,保存在列表里:


extent = [[0, 9, 0, 9], [0, 9, 9, 0], [9, 0, 0, 9], [9, 0, 9, 0]]


extent参数接受1个array-like的参数,也就是有sequence概念的输入(tuple、list、1darray均可)。这个array-like的参数分别代表了左右下上,也就是[横轴最左边的值,横轴最右边的值,纵轴最下面的值,纵轴最上面的值]这样。

然后画图看一下效果:


for idx in range(4):
    test_fig, test_ax = plt.subplots()
    test_ax.imshow(test_map, extent=extent[idx])
    test_ax.set_title('extent%d'%(idx+1))
    test_fig.savefig('./test_extent%d.png'%(idx+1))


orange 使用python python调用origin画图_origin画图_10


orange 使用python python调用origin画图_d3_11


orange 使用python python调用origin画图_d3_12


orange 使用python python调用origin画图_2d_13


可以发现,成功地调换了坐标轴的顺序,但是轴变了,图没变,好像很离谱。这是因为,extent的作用是改变横纵坐标展示的范围,如果你去看官方的文档,会发现它可以搭配Axes.set_xlim与Axes.set_ylim使用,来让图片转起来,就像下面这样:


for idx in range(4):
    test_fig, test_ax = plt.subplots()
    test_ax.imshow(test_map, extent=extent[idx])
    test_ax.set_title('extent_with_xylim%d'%(idx+1))
    test_ax.set_xlim([0, 9])
    test_ax.set_ylim([0, 9])
    test_fig.savefig('./test_extent%d_with_xylim.png'%(idx+1))


orange 使用python python调用origin画图_2d_14


orange 使用python python调用origin画图_origin遇到不适当的参数_15


orange 使用python python调用origin画图_origin画图_16


orange 使用python python调用origin画图_origin画图_17


然而实际上你也会发现,图虽然转起来了,但是图上的点和横纵坐标并不能对应上,以这四张图为例,只有图2(extent_with_xylim2)是对的,而它等价于指定origin='lower'。那么我们想要的那种,图转起来,坐标也对应上,应该怎么操作呢?


xylim = [[[0, 9], [0, 9]], [[0, 9], [9, 0]], [[9, 0], [0, 9]], [[9, 0], [9, 0]]]
for idx in range(4):
    test_fig, test_ax = plt.subplots()
    test_ax.imshow(test_map, extent=[0, 9, 9, 0])
    test_ax.set_title('xylim%d'%(idx+1))
    test_ax.set_xlim(xylim[idx][0])
    test_ax.set_ylim(xylim[idx][1])
    test_fig.savefig('./test_xylim%d.png'%(idx+1))


orange 使用python python调用origin画图_orange 使用python_18


orange 使用python python调用origin画图_origin画图_19


orange 使用python python调用origin画图_orange 使用python_20


orange 使用python python调用origin画图_2d_21


其实只要设置好xlim和ylim就可以了,然后用extent来限定坐标范围(如果不设置的话,第一行、列和最后一行、列将很窄)。一个疑问也许是,为什么这里的extent=[0, 9, 9, 0]?因为默认的origin是upper,纵坐标是倒过来的;如果设置origin为lower,就可以把extent设置为更符合直觉的[0, 9, 0, 9]。

matplotlib的水真是深啊~


(完。)