Python 視覺化(2) matplotlib 進階繪圖

紀錄做數據研究時,使用 matplotlib 繪圖的進階操作。直方圖疊加、如何合併 twinx 下不同的圖例(legend)、將圖例移出繪圖區、在軸上使用省略符號..。

多個直方圖疊加

  • histtype
    • bar:傳統的 bar 形式圖,屬於同個 bin 不同的資料會肩並肩的橫向排列。
    • barstackedbar 形式的,但是屬於同一個 bin 的不同資料會直接往上疊加。
    • step:只有階梯的框架線,沒有填滿顏色。
    • stepfilled:這是預設方法,除了階梯式之外在加上填滿色彩。
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
n_bins = 10
x = np.random.randn(1000, 3)

fig = plt.figure()
axes = fig.subplots(2, 3).reshape(-1)
colors = ['pink', 'blue', 'lime']

ax = axes[0]
ax.hist(x, n_bins, histtype='bar', color=colors, label=colors, alpha=0.5)
ax.set_title('histtype bar')

ax = axes[1]
ax.hist(x, n_bins, histtype='barstacked', color=colors, label=colors, alpha=0.5)
ax.set_title('histtype barstacked')

ax = axes[2]
ax.hist(x, n_bins, histtype='step', stacked=True, color=colors, label=colors, alpha=0.5)
ax.set_title('histtype step, stack steps')

ax = axes[3]
ax.hist(x, n_bins, histtype='step', color=colors, label=colors, alpha=0.5)
ax.set_title('histtype step')

ax = axes[4]
ax.hist(x, n_bins, histtype='stepfilled', color=colors, label=colors, alpha=0.5)
ax.set_title('histtype stepfilled')
plt.show()

matplotlib histtype

圖例集合

當兩分資料想要放在一起比較,尺度卻又相差過大時常常會使用副軸 ax.twinx(),但是副軸上的圖例會被分開。若想要合併圖例,需要手動指定 legend 函式內的內容。

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 8), dpi=80)
axes = fig.subplots(1, 2)
colors = ['pink', 'skyblue', 'lime']
ax = axes[0]
ax.plot(np.arange(0, 10, 0.1)/10+np.random.rand(100), color=colors[0], label="data 1")
ax_sub = ax.twinx()
ax_sub.plot(np.arange(100, 90, -0.1)+np.random.rand(100), color=colors[1], label="data 2")
ax.legend()
ax_sub.legend()
ax.set_title("No secondary axis legend")

ax = axes[1]
ax_sub = ax.twinx()
plist = []
plist += ax.plot(np.arange(0, 10, 0.1)/10+np.random.rand(100), color=colors[0], label="data 1")
plist += ax_sub.plot(np.arange(100, 90, -0.1)+np.random.rand(100), color=colors[1], label="data 2")
ax.legend(plist, [p.get_label() for p in plist], loc="best")
ax.set_title("Both axis legends")
fig.savefig("AxisLegends.jpg")

將圖例集合顯示

範例中使用 plist 匯集所有的繪圖物件,但需要注意繪圖物件回傳的形式為何。Scatter 回傳的是單純繪圖物件,plot 回傳的則是一個包含繪圖物件的 list ,要視情況將物件蒐集進 plist,選擇用 plist.append(xxx) 或是 plist.extend(xxx)

In [196]: ax.scatter(np.ones(10), np.random.rand(10))
Out[196]: <matplotlib.collections.PathCollection at 0x207bc23ad08>

In [197]: ax.plot(np.arange(0, 10, 0.1)/10+np.random.rand(100), color=colors[0], label="data 1")        
Out[197]: [<matplotlib.lines.Line2D at 0x207bca2c248>]

Break The axis

這裡先根據 matplotlib 的教學繪製了這種可以跳躍部份數值的圖:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(8, 8), dpi=80)
gs = gridspec.GridSpec(2, 2)

# 畫同一張
ax = plt.subplot(gs[:, 0]) 
data1 = np.random.rand(100, 2)*10+np.array([[0, 100]])
data2 = np.random.rand(25, 2)*5
ax.scatter(data1[:, 0], data1[:, 1], color="b")
ax.scatter(data2[:, 0], data2[:, 1], color="orange")
ax.set_title("Plot in 1 graph", fontsize="x-large")

# 切割不同張
ax_up = plt.subplot(gs[0, 1])
ax_bot = plt.subplot(gs[1, 1], sharex=ax_up)
ax_up.scatter(data1[:, 0], data1[:, 1], color="b")
ax_bot.scatter(data2[:, 0], data2[:, 1], color="orange")

ax_up.spines['bottom'].set_visible(False)
ax_bot.spines['top'].set_visible(False)
ax_up.xaxis.tick_top()
ax_up.tick_params(labeltop=False)  # don't put tick labels at the top
ax_bot.xaxis.tick_bottom()

d = .015  
kwargs = dict(transform=ax_up.transAxes, color='k', clip_on=False)
ax_up.plot((-d, +d), (-d, +d), **kwargs)        # top-left diagonal
ax_up.plot((1 - d, 1 + d), (-d, +d), **kwargs)  # top-right diagonal

kwargs.update(transform=ax_bot.transAxes)  # switch to the bottom axes
ax_bot.plot((-d, +d), (1 - d, 1 + d), **kwargs)  # bottom-left diagonal
ax_bot.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs)  # bottom-right diagonal
ax_up.set_title("Plot in broken axis", fontsize="x-large")
plt.show()

切割軸

其實原始的套件並不好用,網路上有一個 brokenaxes 的套件,能讓我們更輕鬆的達成這件事情。

from brokenaxes import brokenaxes 
fig = plt.figure(figsize=(8,8), dpi=80) 
baxes = brokenaxes(xlims=((-0.001, 5.001), (99, 111)), ylims=((-0.001, 5.001), (99, 111)), hspace=.1) 

data1 = np.random.rand(100, 2)*10+np.array([[100, 100]])
data2 = np.random.rand(25, 2)*5
baxes.scatter(data1[:, 0], data1[:, 1], color="b")
baxes.scatter(data2[:, 0], data2[:, 1], color="orange")
fig.savefig("brokenaxes_pkg.jpg")

切割軸在兩個維度上

圖例移出繪圖

ax.legend(bbox_to_anchor=(1.05, 1.0), loc='upper left')

但需要注意,將圖例移出繪圖之後,圖例很容易被切掉。我自己是在環境有寫 matplotlibrc 檔案,裡面有設置自動調整版面 :

figure.autolayout: True

如果沒有寫 matplotlibrc ,要記得對繪圖做 fig.tightlayout()

假色圖

data1 = np.repeat(np.sin(np.linspace(0, 1, 10)).reshape(1,-1), 10, axis=0)
pc = ax.pcolor(data1.T, cmap=plt.cm.Blues, vmin=0, vmax=1)
ax.set_title("Sin")
fig.colorbar(pc, ax=ax, orientation="horizontal")

在下圖一併展示

熱力圖

x = np.random.randn(1000)
y = np.random.randn(1000)
heatmap, xedges, yedges = np.histogram2d(x, y, bins=1000)
heatmap = gaussian_filter(heatmap, sigma=16)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
img = heatmap.T
pc = ax.imshow(img, extent=extent, origin='lower', cmap=cm.jet,)
fig.colorbar(pc, ax=ax, orientation="horizontal", )

Heatmap 的程式碼參考: Generate a heatmap in MatPlotLib using a scatter data set

Heatmap 的展示與圖移出圖例