
在進行資料的視覺化時,我們很常會使用散佈圖來呈現,但是當我們想要知道圖片上特定的點其背後所代表的資料、圖像時,我們除了從座標去回推之外,可以透過hover annotation的方式來更直觀的讓使用者了解到該資料中所蘊含之資訊。
簡單來說,該功能就是使用matplotlib.pyplot畫圖,當滑鼠游標經過該資料點時,於其周圍顯示出該資料點之資訊,就像是下列影片中所展示的功能:
在這個討論串中有大大提供了一個簡單的範例程式,有興趣的讀者可以直接根據本程式碼來進行修改使用。
底下為分部詳解,有能力的讀者可以跳過:
import matplotlib.pyplot as plt | |
from matplotlib.offsetbox import OffsetImage, AnnotationBbox | |
import numpy as np; np.random.seed(42) | |
# Generate data x, y for scatter and an array of images. | |
x = np.arange(20) | |
y = np.random.rand(len(x)) | |
arr = np.empty((len(x),10,10)) | |
for i in range(len(x)): | |
f = np.random.rand(5,5) | |
arr[i, 0:5,0:5] = f | |
arr[i, 5:,0:5] =np.flipud(f) | |
arr[i, 5:,5:] =np.fliplr(np.flipud(f)) | |
arr[i, 0:5:,5:] = np.fliplr(f) | |
# create figure and plot scatter | |
fig = plt.figure() | |
ax = fig.add_subplot(111) | |
line, = ax.plot(x,y, ls="", marker="o") | |
# create the annotations box | |
im = OffsetImage(arr[0,:,:], zoom=5) | |
xybox=(50, 50) | |
ab = AnnotationBbox(im, (x[0],y[0]), xybox=xybox, xycoords='data', | |
boxcoords="offset points", pad=0.3, arrowprops=dict(arrowstyle="->")) | |
# add it to the axes and make it invisible | |
ax.add_artist(ab) | |
ab.set_visible(False) | |
def hover(event): | |
# if the mouse is over the scatter points | |
if line.contains(event)[0]: | |
# find out the index within the array from the event | |
ind, = line.contains(event)[1]["ind"] | |
# get the figure size | |
w,h = fig.get_size_inches()*fig.dpi | |
ws = (event.x > w/2.)*–1 + (event.x <= w/2.) | |
hs = (event.y > h/2.)*–1 + (event.y <= h/2.) | |
# if event occurs in the top or right quadrant of the figure, | |
# change the annotation box position relative to mouse. | |
ab.xybox = (xybox[0]*ws, xybox[1]*hs) | |
# make annotation box visible | |
ab.set_visible(True) | |
# place it at the position of the hovered scatter point | |
ab.xy =(x[ind], y[ind]) | |
# set the image corresponding to that point | |
im.set_data(arr[ind,:,:]) | |
else: | |
#if the mouse is not over a scatter point | |
ab.set_visible(False) | |
fig.canvas.draw_idle() | |
# add callback for mouse moves | |
fig.canvas.mpl_connect('button_press_event', hover) |
直接執行的話,會得到本文最一開始的圖片,將滑鼠移到點上時,就會在旁邊顯示出該點所連結到的圖片檔。
當然,matplotlib本身也有提供範例。
接下來進行本範例程式碼的說明:
import matplotlib.pyplot as plt | |
from matplotlib.offsetbox import OffsetImage, AnnotationBbox | |
import numpy as np; np.random.seed(42) | |
# Generate data x, y for scatter and an array of images. | |
x = np.arange(20) | |
y = np.random.rand(len(x)) | |
arr = np.empty((len(x),10,10)) | |
for i in range(len(x)): | |
f = np.random.rand(5,5) | |
arr[i, 0:5,0:5] = f | |
arr[i, 5:,0:5] =np.flipud(f) | |
arr[i, 5:,5:] =np.fliplr(np.flipud(f)) | |
arr[i, 0:5:,5:] = np.fliplr(f) | |
首先我們一樣是將Library給匯入,除了常見的plt跟np之外呢,是這次功能的主要腳色:OffsetImage、AnnotationBbox。
我在讀這功能時,一直想到我怎樣都學不會的Bbox,不好意思扯遠了。
接下來,我們要畫散佈圖,那就必須要有資料,於是我們先將x令為一從0-19,共含20個數字的array,而y則是一含有20個隨機數字的array,len(x)只是為了方便,如果想要看更多點的話只要改變x = np.arange(20)中的數字即可。
接下來將20張10×10的隨機影像儲存在arr這個變數裡。製作方式,是先產生一個5×5的隨機矩陣,之後上下、左右翻轉填入先前製作好的空矩陣裡。
# create figure and plot scatter | |
fig = plt.figure() | |
ax = fig.add_subplot(111) | |
line, = ax.plot(x,y, ls="", marker="o") |
這一段就很直白了,先建立一個畫框叫做fig,在fig裡面加上一個繪圖區叫做ax,最後在ax裡面以x,y資料畫圖。
這邊值得一提的是,到底111代表什麼? 其實就是 1×1 張圖中的第1張。你也可以改成234這樣就是2×3張圖中的第四張。
在plot函數裡ls代表linestyle,這邊填上「""」表示不用畫線,因為plot這個函數是用來畫折線圖的,我們偷吃步假裝它是散佈圖。而marker就是代表點的圖示,有以下選擇:

上述三行執行完之後會得到這個靜態圖片:

# create the annotations box | |
im = OffsetImage(arr[0,:,:], zoom=5) | |
xybox=(50., 50.) | |
ab = AnnotationBbox(im, (50,50), xybox=xybox, xycoords='data', | |
boxcoords="offset points", pad=0.3, arrowprops=dict(arrowstyle="->")) | |
# add it to the axes and make it visible | |
ax.add_artist(ab) | |
ab.set_visible(True) |
這一段,則是製作要顯示在點旁邊的小圖示。首先我們將先前製作的隨機圖片中的第一個 (也就是arr[ 0 , : , : ])轉換成OffsetImage這個Class底下的一個物件,並稱之為im。
ab,是AnnotationBbox這個Class底下的一個物件,裡面包含了上面製作的im。 (50,50)表示該AnnotationBbox的位置,xybox則是距離該標示點的距離,不過第一個參數在底下的程式裡都會不斷的改變,所以在這邊只是先給一個初始值而已,並不太重要。而xycoords在官方文件中的說明如下:
簡單來說呢,用預設值data就好了。boxcoords後面的參數代表了使用與畫圖時相同的座標軸。pad則是邊示圖片的白色邊框大小。arrowprops則是指向該標註點的箭頭相關設定,想要修改的朋友可以往這邊走: 點我
最後兩行程式碼,則是在ax中加入我們剛剛製作好的小箱子,並使之顯示。
到這邊為止的範例程式碼如下:
import matplotlib.pyplot as plt | |
from matplotlib.offsetbox import OffsetImage, AnnotationBbox | |
import numpy as np; np.random.seed(42) | |
# Generate data x, y for scatter and an array of images. | |
x = np.arange(20) | |
y = np.random.rand(len(x)) | |
arr = np.empty((len(x),10,10)) | |
for i in range(len(x)): | |
f = np.random.rand(5,5) | |
arr[i, 0:5,0:5] = f | |
arr[i, 5:,0:5] =np.flipud(f) | |
arr[i, 5:,5:] =np.fliplr(np.flipud(f)) | |
arr[i, 0:5:,5:] = np.fliplr(f) | |
# create figure and plot scatter | |
fig = plt.figure() | |
ax = fig.add_subplot(111) | |
line, = ax.plot(x,y, ls="", marker="o") | |
# create the annotations box | |
im = OffsetImage(arr[0,:,:], zoom=5) | |
xybox=(50, 50) | |
ab = AnnotationBbox(im, (x[0],y[0]), xybox=xybox, xycoords='data', | |
boxcoords="offset points", pad=0.3, arrowprops=dict(arrowstyle="->")) | |
# add it to the axes and make it invisible | |
ax.add_artist(ab) | |
ab.set_visible(True) |
執行完畢之後,會得到下圖:

接下來就是我們的重頭戲了,該如何將互動性的功能給加上去。
def hover(event): | |
# if the mouse is over the scatter points | |
if line.contains(event)[0]: | |
# find out the index within the array from the event | |
ind, = line.contains(event)[1]["ind"] | |
# get the figure size | |
w,h = fig.get_size_inches()*fig.dpi | |
ws = (event.x > w/2.)*–1 + (event.x <= w/2.) | |
hs = (event.y > h/2.)*–1 + (event.y <= h/2.) | |
# if event occurs in the top or right quadrant of the figure, | |
# change the annotation box position relative to mouse. | |
ab.xybox = (xybox[0]*ws, xybox[1]*hs) | |
# make annotation box visible | |
ab.set_visible(True) | |
# place it at the position of the hovered scatter point | |
ab.xy =(x[ind], y[ind]) | |
# set the image corresponding to that point | |
im.set_data(arr[ind,:,:]) | |
else: | |
#if the mouse is not over a scatter point | |
ab.set_visible(False) | |
fig.canvas.draw_idle() |
我們自定義一個function 名叫hover,這個名字隨便我們取,你喜歡讓程式碼裡充滿屎尿的話也可以叫做shit。
其實每一行都有完整的註解了,那我這邊就不再多贅述。
最精華的,在這一行:
fig.canvas.mpl_connect('motion_notify_event', hover) |
將我們在上頭所製作的fig給連結到我們方才定義的function,那當什麼時候要執行該hover功能呢?就是 "motion_notify_event",它代表了當滑鼠有動作時,就呼叫後面的hover功能,當然也可以改,以下是可以選擇的列表:
如果今天我們替換成'button_press_event',那就變成點擊之後才會顯示圖片,可以試試看。
好,以上就是如何製作互動式圖片的方式,那實際應用在Deep Learning中的話,就是在畫完t-SNE之後,可以透過滑鼠移動,來觀察特定群聚之間的圖片特性。
有任何問題,歡迎寫信給我。