Matplotlib 散佈圖


在〈NumPy 的 Universal 函式〉中,在文字模式中顯示了謝爾賓斯基三角形,有沒有辦法在 Matplotlib 中也畫個謝爾賓斯基三角形呢?可以的!

Matplotlib 散佈圖

依〈NumPy 的 Universal 函式〉的演算法,在文字模式中必須有黑與白對應的字元,不過使用 Matplotlib 來畫的話,因為預設背景為白色,只要在黑的部份佈點就可以了,這可以使用散佈圖來達到,在 Matplotlib 中,可透過 pyplotscatter 函式,指定 x 軸與 y 軸的座標清單就可以了。

暫且不使用 NumPy 的話,想在黑的部份佈點,就是收集黑的部份座標,用以呼叫 scatter 函式:

import matplotlib.pyplot as plt

n = 32
xs = []
ys = []
for y in range(n):
    for x in range(n):
        if x & y == 0:
            xs.append(x)
            ys.append(y)

plt.title('Sierpinski triangle') 
plt.xlabel('x')           
plt.ylabel('y')   
plt.gca().set_aspect(1)
plt.scatter(xs, ys)
plt.show()  

不過,這只會畫出以下的圖案:

Matplotlib 散佈圖

Matplotlib 預設的標示(marker)是圓形,想調整標示圖案,可以透過 scattermarker 參數,預設值是 'o',可指定的其他值可在 matplotlib.markers 查詢,我想要使用方塊,就要指定 's'

另外,我想要作為點的方塊彼此銜接,這必須設定標示的大小,可以透過 scatters 參數來設定,這個參數有點特別,scatter 的文件談到,s 要指定 points**2,point 是啥?

Matplotlib 在指定一些尺寸參數時,並不是使用像素之類的單位,而是跟印刷方面有關的單位,Matplotlib 中的 point,是印刷上使用的長度單位,一個點是 1/72 英吋(inch),而 s 是指定標示尺寸的平方(可以想成大致上是標示佔有的方形區域的面積)。

在標示指定為方塊時,為了讓他們彼此能銜接,必須知道軸的座標範圍與長度,範圍可以透過 pyplotxlimylim 得知,不過預設情況下,軸的範圍是 Matplotlib 自動運算得到的,無論運算後的範圍為何,這時呼叫 xlimylim,只會傳回 (0.0, 1.0)

既然如此,那就自行設定軸的範圍,這可以透過 xlimylim 來設定,那麼軸的長度呢?可以透過 pyplotgcf 取得目前的圖,然後呼叫 set_size_inches 來設定圖表大小,不過這設定的是整個圖表大小,包含了那些標示文字,如果設定比例為 1:1 的話,那麼座標軸佔的長度,大致上會是圖長度的 0.775。

因此,為了能計算標示的尺寸,使用 set_size_inches 設定圖表為 (plotwidth, plotwidth),x、y 軸範圍都是 (-0.5, n - 0.5),那麼標示大小就可以如下計算:

PTS_PER_INCH = 72                        # 一英吋有 72 個點
plotwidth_pts = PTS_PER_INCH * plotwidth # 圖表的長度有幾個點?
marksize = 0.775 * plotwidth_pts / n     # 座標軸切為 n 分,每一份的長度有幾個點?

整理一下程式碼,如下就可以畫出方才第一張圖的結果:

import matplotlib.pyplot as plt

def scatter_plot(title, plotsize, axislim, markersize, xs, ys):
    plt.title(title) 
    plt.gcf().set_size_inches(plotsize)
    plt.xlim(axislim)
    plt.ylim(axislim)
    plt.xlabel('x')           
    plt.ylabel('y')   
    plt.scatter(xs, ys, marker = 's', s = markersize ** 2)
    plt.show()

n = 128

xs = []
ys = []
for y in range(n):
    for x in range(n):
        if x & y == 0:
            xs.append(x)
            ys.append(y)

plotwidth = 6
axislim = (-0.5, n - 0.5)

plotsize = (plotwidth, plotwidth)

PTS_PER_INCH = 72
plotwidth_pts = PTS_PER_INCH * plotwidth
marksize = 0.775 * plotwidth_pts / n

scatter_plot(
    'Sierpinski triangle',
    plotsize,
    axislim,
    marksize,
    xs, 
    ys
)