同じデータを使って、CERN ROOT、Pythonのscipy、lmfit を使って正規分布でフィッティングするコードと結果を比較する。
まずは、データ生成部分
C++用
std::vector<double> vx{-3.65,-3.45,-3.35,-3.25,-3.15,-3.05,-2.95,-2.85,-2.75,-2.65,-2.55,-2.45,-2.35,-2.25,-2.15,-2.05,-1.95,-1.85,-1.75,-1.65,-1.55,-1.45,-1.35,-1.25,-1.15,-1.05,-0.95,-0.85,-0.75,-0.65,-0.55,-0.45,-0.35,-0.25,-0.15,-0.05,0.05,0.15,0.25,0.35,0.45,0.55,0.65,0.75,0.85,0.95,1.05,1.15,1.25,1.35,1.45,1.55,1.65,1.75,1.85,1.95,2.05,2.15,2.25,2.35,2.45,2.55,2.65,2.75,2.85,2.95,3.05,3.15,3.25,3.35,3.55,3.75}; std::vector<double> vy{1,1,2,3,4,6,1,7,15,8,18,22,20,40,36,41,68,75,92,117,130,118,151,185,192,205,297,280,302,330,314,362,388,366,403,388,423,367,416,389,358,338,329,296,294,283,236,208,171,153,129,116,91,65,81,52,41,33,31,18,23,23,14,8,9,6,2,2,1,3,1,2}; std::vector<double> vyErrors; for (int i=0; i<vx.size(); i++){ vyErrors.push_back(std::sqrt(vy[i])); }
Python用
vx = [-3.65,-3.45,-3.35,-3.25,-3.15,-3.05,-2.95,-2.85,-2.75,-2.65,-2.55,-2.45,-2.35,-2.25,-2.15,-2.05,-1.95,-1.85,-1.75,-1.65,-1.55,-1.45,-1.35,-1.25,-1.15,-1.05,-0.95,-0.85,-0.75,-0.65,-0.55,-0.45,-0.35,-0.25,-0.15,-0.05,0.05,0.15,0.25,0.35,0.45,0.55,0.65,0.75,0.85,0.95,1.05,1.15,1.25,1.35,1.45,1.55,1.65,1.75,1.85,1.95,2.05,2.15,2.25,2.35,2.45,2.55,2.65,2.75,2.85,2.95,3.05,3.15,3.25,3.35,3.55,3.75] vy = [1,1,2,3,4,6,1,7,15,8,18,22,20,40,36,41,68,75,92,117,130,118,151,185,192,205,297,280,302,330,314,362,388,366,403,388,423,367,416,389,358,338,329,296,294,283,236,208,171,153,129,116,91,65,81,52,41,33,31,18,23,23,14,8,9,6,2,2,1,3,1,2] vyError=[y**0.5 for y in vy]
次に、フィッティングの部分
ROOT CERN のコード。通常はヒストグラムに対してFitすることが多いが、今回はTGraphErrors
クラスを使う。
void FitGaussian() { //C++用のデータ生成部分をコピペ TGraph* graph = new TGraphErrors(vx.size(), vx.data(), vy.data(), nullptr, vyErrors.data()); TF1* func = new TF1("func", "gaus", -5, 5); func->SetParameter(0, 400); // Constantの初期値 func->SetParameter(1, 0); // Meanの初期値 func->SetParameter(2, 1); // Sigmaの初期値 graph->Fit("func", "Q"); cout << "Constant " << func->GetParameter(0); cout << " +/- " << func->GetParError(0) << endl; cout << "Mean " << func->GetParameter(1); cout << " +/- " << func->GetParError(1) << endl; cout << "Sigma " << func->GetParameter(2); cout << " +/- " << func->GetParError(2) << endl; cout << "Chi2 " << func->GetChisquare() << endl; cout << "ndf " << func->GetNDF() << endl; }
Python scipy.optimize.curve_fit
を使うコード。パラメータの誤差を正しく計算するためabsolute_sigma =True
とする。
import numpy as np import pandas as pd from scipy.optimize import curve_fit from scipy.stats import chisquare #Python用のデータ生成部分をコピペ def gaussian_func(x, constant, mean, sigma): return constant * np.exp(- (x - mean) ** 2 / (2 * sigma ** 2)) parameter_initial = np.array([400, 0, 1]) #初期値を与える popt, pcov = curve_fit(gaussian_func, vx, vy, sigma=vyError, absolute_sigma =True, p0=parameter_initial) #フィッティング vy_fit = gaussian_func(np.array(vx), popt[0], popt[1], popt[2]) #最確値を使ってvyを計算 chisq, p_value = chisquare(vy_fit, f_exp=vy/np.mean(vy)*np.mean(vy_fit), ddof = 2) #カイ二乗を計算 stderr = np.sqrt(np.diag(pcov)) #分散共分散行列の対角成分からパラメータの誤差を計算 mat = np.vstack((popt,stderr)).T #最確値と誤差の行列 print("Constant {:.3f} +/- {:.5f}".format(*mat[0])) print("Mean {:.8f} +/- {:.8f}".format(*mat[1])) print("Sigma {:.6f} +/- {:.8f}".format(*mat[2])) print("Chi2 {:.4f}".format(chisq))
Python lmfit
ライブラリを使うコード。パラメータの誤差を正しく計算するため、重み weights
には誤差の逆数を与えて scale_covar=False
とする。
import numpy as np import lmfit #Python用のデータ生成部分をコピペ model = lmfit.models.GaussianModel() #モデルの定義 params = model.guess(np.array(vy), x=np.array(vx)) #初期値を推定 result = model.fit(vy, params, x=vx, weights=1.0/np.array(vyError), scale_covar=False) #フィッティング params = result.result.params print("Constant {:.3f} +/- {:.5f}".format(params["height"].value,params["height"].stderr)) print("Mean {:.8f} +/- {:.8f}".format(params["center"].value,params["center"].stderr)) print("Sigma {:.6f} +/- {:.8f}".format(params["sigma"].value,params["sigma"].stderr)) print("Chi2 {:.4f}".format(result.chisqr)) print("ndf {}".format(result.nfree))
各コードで計算した結果を比較すると、ほぼ一致している。
ROOT の CERNで fitting の結果
Constant 402.531 +/- 4.95816 Mean -0.00397889 +/- 0.00989405 Sigma 0.983115 +/- 0.00704207 Chi2 83.6418 ndf 69
Python scipy.optimize.curve_fit
の結果
Constant 402.531 +/- 4.96311 Mean -0.00397894 +/- 0.00989406 Sigma 0.983115 +/- 0.00706389 Chi2 83.6418
Python lmfit
ライブラリを使った結果
Constant 402.531 +/- 4.96310 Mean -0.00397896 +/- 0.00989405 Sigma 0.983115 +/- 0.00706388 Chi2 83.6418 ndf 69
どのコードを使っても結果は変わらないので好きなコードを使うと良い。
lmfit
はビルトイン(定義済み)関数が多く、カイ二乗なども勝手に計算してくれるので便利である。複雑な非線形関数を使ってfittingしたいときは scipy
の curve_fit
の方が便利だろう。