利用Python計算與繪製3D擬合曲面(3D Surface Regression)

plot_surface

工作上的事情還真是無奇不有,什麼工具都得會一點才行。在這個例子中,共有100組數據存放在data.csv裡,每一組數據(每一列)包含x座標、y座標與對應的數值z,依序放在data.csv檔裡的前三欄,data.csv的內容如下

x,y,z
-5366.14,-4776.75,-2053.64
-5148.93,-3732.71,-5843.89
-5286.6,-2664.6,-7058.72
-4877.6,-2495.51,-8136.17
-5157.85,-1314.45,-14301.11
...
4983.15,3716.11,-5360.5
5228.11,4954.56,1150.98

本次的需求是要找出一個曲面去擬合這100組數據,所以先利用pandas套件iloc的功能把csv檔的內容讀進來並利用numpy套件將其合併成一個二維numpy陣列,方便後續的運算和處理。

import pandas as pd
import numpy as np

df = pd.read_csv('data.csv')
x = df.iloc[:, 0]
y = df.iloc[:, 1]
z = df.iloc[:, 2]
data = np.array([x, y, z]).T

df.iloc[:, n] 從df中選擇所有的行(冒號表示選擇所有行),並選擇第n列的數據。

np.array([x, y, z]).T 將x、y和z三個一維數列合併成二維陣列並轉置,此時data的內容為

[[ -5366.14  -4776.75  -2053.64]
 [ -5148.93  -3732.71  -5843.89]
 [ -5286.6   -2664.6   -7058.72]
 [ -4877.6   -2495.51  -8136.17]
 [ -5157.85  -1314.45 -14301.11]
...
 [  4983.15   3716.11  -5360.5 ]
 [  5228.11   4954.56   1150.98]]

再來定義一個擬合曲面fit_equation並利用scipy的curve_fit找出擬合曲面中每一項的係數

from scipy.optimize import curve_fit 

def fit_equation(xy, a, b, c, d, e, f): 
    x, y = xy 
    return a*x + b*y + c*x*x + d*y*y + e*x*y + f

popt, pcov = curve_fit(fit_equation, (x, y), z) 
print(popt)

popt包含了每一項的最佳擬合係數,它讓我們定義的擬合曲面fit_equation與實際數據擬合出最好的結果,而pcov是協方差矩陣,它提供了popt各係數的不確定性估計。

print(popt)可以把每一項的係數給列出來,結果如下

[ 2.31992768e-02 -1.45919396e-01 -4.64725528e-04 1.73354088e-04
2.46451189e-04 -5.35930627e+01]

如果不要以科學記號表示,且只取小數點下八位的話,加上

np.set_printoptions(suppress=True, precision=8)

這樣可以得到

[  0.02319928  -0.1459194   -0.00046473   0.00017335   0.00024645
 -53.59306267]

如果需要將數據點與擬合曲面做繪圖的話,利用pyplot套件

import matplotlib.pyplot as plt 

fig = plt.figure() 
ax = fig.add_subplot(111, projection='3d') 
ax.scatter(x, y, z, color='blue') 
x_r = np.linspace(-6000, 6000, 100)
y_r = np.linspace(-6000, 6000, 100)
X, Y = np.meshgrid(x_r, y_r) 
Z = fit_equation((X, Y), *popt) 
ax.plot_surface(X, Y, Z, color='red', alpha=0.2) 
ax.set_xlabel('X') 
ax.set_ylabel('Y') 
ax.set_zlabel('Z') 
plt.show()

fig = plt.figure() 建立一個新的Matplotlib容器。

ax = fig.add_subplot(111, projection=’3d’) 在fig上增加一個3D subplot。111 的意思是將圖分為1行1列,並在第1個位置新增subplot,而projection=’3d’則表示我們要使用3D的投影方式。

ax.scatter(x, y, z, color=’blue’) 在建立的3D subplot上繪製分佈圖,color=’blue’指定數據點以藍色顯示。

x_r = np.linspace(-6000, 6000, 100) 繪製曲面的參數,其限制座標軸x範圍為-6000至6000,並在-6000至6000之間均勻置入 100 個點。若置入的點數愈多,繪製出來的曲面愈平滑,下圖分別為點數2、3、5和100所繪製出的曲面。

np.linspace

y_r = np.linspace(-6000, 6000, 100) 同上。

X, Y = np.meshgrid(x_r, y_r) 使用np.meshgrid函數來產生網格,用於繪製曲面。

Z = fit_equation((X, Y), *popt) 根據擬合出來的曲面fit_equation來計算網格座標(X, Y)對應的 Z 值。

ax.plot_surface(X, Y, Z, color=’red’, alpha=0.2) 在3D subplot上繪製擬合曲面,color=’red’指定擬合曲面以紅色顯示,alpha=0.2則表示此曲面的透明度為0.2。

ax.set_xlabel(‘X’)、ax.set_ylabel(‘Y’)和ax.set_zlabel(‘Z’) 指定3D subplot的座標軸標籤。

plt.show() 輸出繪製的圖形。

import pandas as pd
import numpy as np
from scipy.optimize import curve_fit 
import matplotlib.pyplot as plt 

df = pd.read_csv('data.csv')
x = df.iloc[:, 0]
y = df.iloc[:, 1]
z = df.iloc[:, 2]
data = np.array([x, y, z]).T

def fit_equation(xy, a, b, c, d, e, f): 
    x, y = xy 
    return a*x + b*y + c*x*x + d*y*y + e*x*y + f

popt, pcov = curve_fit(fit_equation, (x, y), z) 
print(popt)

#有繪圖需求才需要下列的程式碼
fig = plt.figure() 
ax = fig.add_subplot(111, projection='3d') 
ax.scatter(x, y, z, color='blue') 
x_r = np.linspace(-6000, 6000, 100)
y_r = np.linspace(-6000, 6000, 100)
X, Y = np.meshgrid(x_r, y_r) 
Z = fit_equation((X, Y), *popt) 
ax.plot_surface(X, Y, Z, color='red', alpha=0.2) 
ax.set_xlabel('X') 
ax.set_ylabel('Y') 
ax.set_zlabel('Z') 
plt.show()

如果要把擬合曲面畫成類似漁網的效果,將倒數第五行改成

ax.plot_wireframe(X, Y, Z, color='red', alpha=0.2, rstride=10, cstride=10)

可以得到

plot_wireframe

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

返回頂端