# Matplotlib 繪圖技巧:讀取數據及線性擬合 > 作者:王一哲 日期:2018/11/23 <br></br> ## 前言 對於物理實驗而言,我們在處理數據時通常會繪製 **xy 散佈圖**,將應變變因的數值畫在 y 軸、操縱變因的數值畫在 x 軸,如果數據點分布在一條斜直線上,就可以利用**最小平方法**找出最接近直線的斜率和截距,再利用物理定律解釋斜率和截距的成因。但如果數據點不是分布在一條斜直線上,就要先想辦法處理數據,圖形化為直線。 以雙狹縫干涉的實驗為例,假設雷射光波長為$\lambda$、狹縫間距為$d$、狹縫與屏幕間的距離為$L$,則干涉的寬度 $$\Delta y = \frac{\lambda L}{d}$$ 如果我們固定$\lambda$和$d$,改變$L$並測量$\Delta y$,再畫出$\Delta y - L$關係圖,這些數據點應該會分布在一條斜直線上,而且斜率 = $\frac{\lambda}{d}$、截距 = 0。 如果我們固定$\lambda$和$L$,改變$d$並測量$\Delta y$,再畫出$\Delta y - d$關係圖,這些數據點會分布在一條曲線上。如果我們猜測$\Delta y$和$d$成反比,應該要計算$1/d$,再畫出$\Delta y - 1 / d$關係圖,如果數據點分布在一條斜直線上,才能證明$\Delta y$和$d$成反比。由理論式可知,直線的斜率 = $\lambda L$、截距 = 0。 <img height="100%" width="100%" src="https://i.imgur.com/cCh0T9Q.png" style="display: block; margin-left: auto; margin-right: auto;"/> <div style="text-align:center">左:雙狹縫干涉 Δy - d 關係圖;右:雙狹縫干涉 Δy - 1/d 關係圖</div> <br></br> ## 產生數據資料 我們需要有數據資料才能繪圖,我們利用以下的程式經由理論計算再加上一點隨機誤差,產生$\Delta y$和$d$的數據並儲存成csv檔。如果已經有實驗數據的同學可以直接跳到繪圖的部分。如果想要產生數據並直接繪圖的同學,可以將以下的兩個程式合併。 假設我們使用氦氖雷射進行實驗,則$\lambda = 633 ~\mathrm{nm}$,狹縫與屏幕間的距離設定為 $L = 1 ~\mathrm{m}$,狹縫間距為$d = 0.1, 0.2, ... , 0.5 ~\mathrm{mm}$。為了使數據大一點,以下的長度單位皆為 mm。 <br></br> ```python= """ 雙狹縫干涉: 產生數據用的程式 日期: 2018/11/22 作者: 王一哲 """ import numpy as np # y = \lambda L / d, 由於 lambda 是保留字, 改用 length # 產生 d = 0.1, 0.2, ... , 0.5 mm, 計算出 y 值 + 隨機誤差 length, L = 633E-6, 1E3 d = np.arange(0.1, 0.6, 0.1) error = (np.random.random(size = len(d)) - 0.5) * 0.1 y = length * L / d * (1 + error) data = np.vstack((d, y)).T np.savetxt("interference_data.csv", data, delimiter = ',', newline = '\n', fmt = '%.2f') ``` <br></br> 1. 由於 lambda 是 Python 的保留字,改用 length 作為波長的變數名稱。 2. 使用 **np.arange(0.1, 0.6, 0.1)** 產生陣列 [0.1, 0.2, ... , 0.5] 並指定給 d。語法為 ```python np.arange[起始值, 結束值, 間距] ``` 但是產生的數據不包含結束值。 3. 使用 **np.random.random(size = len(d)) - 0.5** 產生長度與 d 相等、數值介於 -0.5 到 +0.5 之間的浮點數。 4. 使用 **np.vstack((d, y)).T** 將陣列 d 和 y 合併後再轉置,由於這個部分比較不容易理解,詳細的說明在下一段。 5. 使用 **np.savetxt("interference_data.csv", data, delimiter = ',', newline = '\n', fmt = '%.2f')** 將 data 的內容儲存為 interference_data.csv,數據之間以',' 分隔,每一列的資料最末端加上換行符號 \n,數據的格式為 %.2f,取到小數點以下2位的浮點數。以這個實驗而言,能夠測量到 0.01 mm 已經很精準了,所以我們只取到小數點以下2位的數值。如何用電腦分析光的干涉、繞射照片請參考〈[ImageJ 教學:分析光的干涉、繞射照片](https://docs.google.com/document/d/e/2PACX-1vQhw-TqFuEHlZwgtudKii5LyYYDmV0l0o4_GYCFcZQ_mgEgtUE3NmRvDAwD-NYFJomYs8jBI99JdYDG/pub)〉。產生的資料檔如下 ``` 0.10,6.24 0.20,3.03 0.30,2.15 0.40,1.54 0.50,1.31 ``` <br></br> ### numpy.vstack 首先引入函式庫 **import numpy as np**,再用 np.arange 產生陣列 A、B,我們可以看到 np.vstack([A, B]) 的效果是將 A、B 以垂直方向疊起來,C.T 則是將陣列 C 當中的元素行、列對調。 ```python A = np.arange(1, 4, 1) B = np.arange(4, 7, 1) C = np.vstack([A, B]) D = C.T ``` 這些變數的內容如下 ```python A: array([1, 2, 3]) B: array([4, 5, 6]) C: array([[1, 2, 3], [4, 5, 6]]) D: array([[1, 4], [2, 5], [3, 6]]) ``` 在上方的程式當中,如果使用 np.vstack((d, y)) 產生的陣列會是 ```python [[d1, d2, d3, d4, d5], [y1, y2, y3, y4, y5]] ``` 但是我們需要的格式是 ```python [[d1, y1], [d2, y2], [d3, y3], [d4, y4], [d5, y5]] ``` 所以需要使用 np.vstack((d, y)).T 將陣列轉置。 <br></br> ## 讀取數據檔並繪圖 我們使用以下的程式讀取將剛才產生的數據檔,將數據處理過後作圖、計算最接近直線的斜率和截距。 ```python= """ 雙狹縫干涉: 讀取數據檔並繪圖的程式 日期: 2018/11/22 作者: 王一哲 """ import numpy as np import matplotlib.pyplot as plt # 從 interference_data.csv 讀取資料, 資料以 ',' 分隔, 將第0、1欄的資料分別存入 d, y,再計算 1 / d 並存入 dx d, y = np.loadtxt("interference_data.csv", delimiter = ',', usecols = (0, 1), unpack = True) dx = 1 / d # 產生計算最接近直線需要用到的陣列 A ,計算直線的斜率 m 和截距 c A = np.vstack([dx, np.ones(len(dx))]).T m, c = np.linalg.lstsq(A, y, rcond = -1)[0] print(m, c) # 建立繪圖物件 fig, 大小為 12 * 4.5, 內有 1 列 2 欄的小圖, 兩圖共用 x 軸和 y 軸 fig, (ax1, ax2) = plt.subplots(1, 2, sharex = False, sharey = True, figsize = (12, 4.5)) # 設定小圖 ax1 的坐標軸標籤, 格線顏色、種類、寬度, y軸繪圖範圍, 最後用 plot 繪圖 ax1.set_xlabel('d (mm)', fontsize = 14) ax1.set_ylabel(r'$\Delta y \mathrm{(mm)}$', fontsize = 14) ax1.grid(color = 'red', linestyle = '--', linewidth = 1) ax1.set_xlim(0, np.max(d) * 1.1) ax1.set_ylim(0, np.max(y) * 1.1) ax1.plot(d, y, color = 'blue', marker = 'o', markersize = 10, linestyle = '') # 設定小圖 ax2 的坐標軸標籤, 格線顏色、種類、寬度, 最後用 plot 繪圖 ax2.set_xlabel(r'$1 / d \mathrm{(mm)^{-1}}$', fontsize = 14) ax2.set_ylabel(r'$\Delta y \mathrm{(mm)}$', fontsize = 14) ax2.grid(color = 'red', linestyle = '--', linewidth = 1) ax2.set_xlim(0, np.max(dx) * 1.1) ax2.set_ylim(0, np.max(y) * 1.1) ax2.plot(dx, y, color = 'blue', marker = 'o', markersize = 10, linestyle = '', label = "Original Data") ax2.plot(dx, m * dx + c, color = 'orange', linewidth = 2, label = "Fitted Line") ax2.legend(loc = 'upper left') # 儲存、顯示圖片 fig.savefig('double_slit_y_d.svg') fig.savefig('double_slit_y_d.png') fig.show() ``` <br></br> 基本的繪圖技巧及設定請參考〈[Matplotlib 繪圖技巧:在同一張圖上繪製兩個函數、兩張圖並列](https://hackmd.io/s/Bkd0EMyCm)〉,以下只說明這個程式當中首次用到的功能。 1. 使用 **d, y = np.loadtxt("interference_data.csv", delimiter = ',', usecols = (0, 1), unpack = True)** 從 interference_data.csv 讀取資料,資料以 ',' 分隔,將第0、1欄的資料分別存入 d、y。**np.loadtxt** 的語法如下 ```python np.loadtxt('檔名', delimiter = '分隔符號', usecols = (欄位編號), unpack = [True 或 False]) ``` 其中欄位編號是從0開始,unpack = True 會將讀取的資料分開分別存入不同的變數中。 2. 假設最接近直線的方程式為 y = mx + c,若改用陣列型式可以寫成 y = Ap,其中 A = [x 1]、p = [m c]。所以我們需要先產生陣列 A ```python A = np.vstack([dx, np.ones(len(dx))]).T ``` A 的內容為 ``` [[ 10. 1. ] [ 5. 1. ] [ 3.33333333 1. ] [ 2.5 1. ] [ 2. 1. ]] ``` 再用 **m, c = np.linalg.lstsq(A, y, rcond = -1)[0]** 計算斜率 m、截距 c。 3. 使用 **plt.plot** 指令繪圖,如果只要畫數據點而不連線,點的大小為10,參數設定為 **marker = 'o', markersize = 10, linestyle = ''**;如果要畫最接近直線,縱軸的資料設定為 **m * dx + c**。 以下圖的數據為例,m = 0.618223336853、c = 0.030780095037,m 的理論值為 $\lambda L = 633 \times 10^{-6} \times 1000 = 0.633$,誤差約為 2.37%,這是因為我們在產生數據時有加上一點誤差值。 <img height="100%" width="100%" src="https://i.imgur.com/cCh0T9Q.png" style="display: block; margin-left: auto; margin-right: auto;"/> <div style="text-align:center">左:雙狹縫干涉 Δy - d 關係圖;右:雙狹縫干涉 Δy - 1/d 關係圖</div> <br></br> ## 結語 這是一些使用 numpy 及 Matplotlib 的筆記,我覺得這樣的功能對於處理高中物理實驗數據已經夠用了,如果想要更深入研究 numpy 及 Matplotlib 可以參考官方網站的說明文件。 <br></br> ## 參考資料 1. **numpy.arange**: https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.arange.html 2. **numpy.random.random**: https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.random.html 3. **numpy.vstack**: https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.vstack.html 4. **numpy.ndarray.T**: https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.ndarray.T.html 5. **numpy.savetxt**: https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.savetxt.html 6. **numpy.loadtxt**: https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.loadtxt.html 7. **使用numpy跟sympy實作Linear regression**: http://terrence.logdown.com/posts/314392-simple-linear-regressionnumpy 8. **numpy.linalg.lstsq**: https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.linalg.lstsq.html 9. **matplotlib.pyplot.plot**: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html --- ###### tags:`Physics`、`Python`