Pythonで検量線を作る|直線近似とR²を実験データで求める

Pythonで検量線を作る。共通の白いデスク背景と、散布点・回帰直線を表示したノートPC。 Python・データ解析

濃度が分かっている試料を測定し、濃度と信号の関係を調べる。その関係を表したものが検量線です。

今回は、説明用の6点のデータに直線を当てはめます。Pythonで検量線を描き、傾き・切片・R²を求めるところまで進めましょう。

使うのはNumPy、Matplotlib、SciPyです。Google Colabの新しいノートブックを開き、コードを上から順に実行してください。最後には、1つのセルで試せる完成コードも載せています。

Colabの開き方やコードの実行操作から確認したい場合は、Pythonで初めてのグラフ作成を参照してください。今回はCSVファイルを使わず、数値をコードに直接書きます。

今回使うデータ

横軸の濃度(Concentration)は「試料の濃さ」、縦軸の信号(Signal)は「測定器から得られる数値」です。ここでは、試料がどれくらい濃いと、測定器の値がどれくらいになるかをグラフにします。

以下は記事の説明用に用意したデータで、実際の実験で取得したものではありません。特定の物質・測定法・単位は想定していません。

濃度信号
00.12
11.05
22.08
32.91
44.02
55.10
import numpy as np

concentration = np.array([
    0, 1, 2, 3, 4, 5
])

signal = np.array([
    0.12, 1.05, 2.08, 2.91, 4.02, 5.10
])

np.array()で数値の並びをNumPyの配列にしています。後で、この並び全体に掛け算や足し算をするためです。同じ位置の濃度と信号が1組のデータになります。

まず散布図を描いてみる

計算する前に、まず元データをグラフで確認します。plt.scatter()は、濃度と信号の組を点で表示する命令です。

import matplotlib.pyplot as plt

plt.scatter(
    concentration,
    signal
)

plt.xlabel("Concentration")
plt.ylabel("Signal")
plt.show()
説明用の濃度と信号6点をGoogle Colabで描いた散布図

濃度が大きくなるにつれて、信号もほぼ直線的に増えています。この6点に、y = ax + bという形の直線を当てはめてみます。ここでxは濃度、yは直線から計算する信号です。

linregress()で直線を当てはめる

SciPyのlinregress()を使うと、データに合う直線の傾きと切片を求められます。

from scipy.stats import linregress

result = linregress(
    concentration,
    signal
)

結果はresultに入ります。今回は次の3つだけ使います。

  • result.slope:傾き
  • result.intercept:切片
  • result.rvalue:相関係数(後でR²を計算するために使います)

傾きと切片を確認する

print("slope =", result.slope)
print("intercept =", result.intercept)

Google Colabでの実行結果は次のとおりです。

slope = 0.9897142857142854
intercept = 0.07238095238095266

y = ax + bのaが傾き、bが切片です。aにはresult.slope、bにはresult.interceptを入れるので、今回の式はおよそ次のようになります。

y = 0.9897x + 0.0724

濃度xが1増えると、直線上の信号yは約0.9897増えます。また、x = 0のときの直線上のyは約0.0724です。直線は6点全体に当てはめたものなので、元データの点をすべて通るわけではありません。

R²を求める

R²(決定係数)は、今回求めた直線がデータの変動をどの程度説明しているかを見る指標の一つです。今回の単回帰では、相関係数rvalueを2乗して求めます。** 2は2乗という意味です。

r2 = result.rvalue ** 2
print("R^2 =", r2)
R^2 = 0.9984342522980586

今回はR² ≒ 0.9984です。1に近く、この意味ではデータがよく直線に沿っていると分かります。

ただし、R²が1に近いだけで、その検量線が実験的に妥当だと判断できるわけではありません。ここでは、直線近似の結果を確認するための指標として扱います。

求めた直線をグラフに描く

まず、直線上のyを計算する考え方を確認します。

y_fit = (
    result.slope * concentration
    + result.intercept
)

これはy = ax + bのxに、それぞれの濃度を代入する計算です。concentrationはNumPyの配列なので、6個の値それぞれに傾きを掛け、切片を足せます。

描画では、濃度の最小値から最大値まで100個の値を等間隔に作り、その位置で直線上のyを計算します。直線なので2点でも描けますが、ここでは描画用の位置をx_fitとして用意します。

x_fit = np.linspace(
    concentration.min(),
    concentration.max(),
    100
)

y_fit = (
    result.slope * x_fit
    + result.intercept
)

np.linspace()で作るのは、今回は0から5までの100個の値です。x_fitが直線を描く横軸の位置、y_fitがその位置の信号になります。

検量線を完成させる

元データは点、求めた直線は線として、同じグラフに重ねます。

plt.scatter(
    concentration,
    signal,
    label="Data"
)

plt.plot(
    x_fit,
    y_fit,
    label="Linear fit"
)

plt.xlabel("Concentration")
plt.ylabel("Signal")
plt.legend()
plt.show()
Google Colabで生成した6点の散布図と回帰直線。横軸は濃度、縦軸は信号

Dataが元データ、Linear fitが求めた直線です。6個の点と回帰直線が表示され、傾き・切片・R²を確認できたら、この記事のゴール達成です。

完成コード

ここまでの処理をまとめたコードです。Google Colabの1つのコードセルに貼り付けて実行できます。

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import linregress

concentration = np.array([
    0, 1, 2, 3, 4, 5
])

signal = np.array([
    0.12, 1.05, 2.08, 2.91, 4.02, 5.10
])

result = linregress(
    concentration,
    signal
)

r2 = result.rvalue ** 2

x_fit = np.linspace(
    concentration.min(),
    concentration.max(),
    100
)

y_fit = (
    result.slope * x_fit
    + result.intercept
)

plt.scatter(
    concentration,
    signal,
    label="Data"
)

plt.plot(
    x_fit,
    y_fit,
    label="Linear fit"
)

plt.xlabel("Concentration")
plt.ylabel("Signal")

plt.legend()
plt.show()

print("slope =", result.slope)
print("intercept =", result.intercept)
print("R^2 =", r2)

グラフの下に、次の数値が表示されます。

slope = 0.9897142857142854
intercept = 0.07238095238095266
R^2 = 0.9984342522980586

まとめ

今回は、説明用の濃度と信号のデータから散布図を描き、linregress()で直線を当てはめました。傾きと切片をy = ax + bに入れると直線の式になり、result.rvalue ** 2でR²を確認できます。

まず元データを見て、計算結果を確かめ、点と直線を重ねて見る。この流れで、Pythonによる検量線作成を試してみてください。

参考・動作確認環境

Google Colabで2026年9月26日に完成コードを実行し、グラフと数値を確認しました。Python 3.13.15/NumPy 2.1.3/Matplotlib 3.10.0/SciPy 1.16.3。追加インストールは不要でした。環境によって表示や数値の末尾が多少異なる場合があります。

参考:SciPy公式ドキュメント:linregress

コメント

タイトルとURLをコピーしました