Up

甘味度と温度の関係

糖類間の比較のダミー変数による分析

2025.11CmdStanPy対応に改訂

 

 

ベイズ分析のソフトStanPythonインターフェースCmdStanPy対応に改訂した。CmdStaPyの簡単な説明を、このウェブサイトに用意した。

 

2種類の糖、フラクトースとグルコースの甘味度と温度の関係をそれぞれ回帰モデルで表し、2つの回帰モデルを、ダミー変数を用いて1つの回帰モデルにまとめた。

糖の甘味度と温度の関係が前橋(2011に報告されている。前橋(2011)の第2図からフラクトースとグルコースの温度と甘味度の関係を読み取ったものを表1に示す。

 

表1 前橋(2011)の第2図からの読み取り

フラクトース

グルコース

温度℃

甘味度

温度℃

甘味度

5

1.43

3

0.59

18

1.27

19

0.57

40

1

40

0.56

60

0.79

60

0.56

 

甘味度を従属変数、温度を独立変数とする回帰モデルをフラクトースおよびグルコースに対して以下のように設定することができる。

いま、ダミー変数をフラクトースに対して1、グルコースに対して0をとるものを用意する。

 

 

である。

このとき、回帰モデルを次のようにおく。

 

 

この回帰式は、フラクトースのときは

 

 

となり、グルコースのときは

 

 

となる。

 

このダミー変数を用いた回帰モデルのStanスクリプトをリスト1のように用意した。

スクリプトファイルおよびデータファイルは圧縮ファイルfilesSweetTemp.zipにまとめた。ダウンロード・展開すれば自由に利用できる。

Stan(旧版)の説明は、岡本(2019で行っている。

 

 

リスト1 ダミー変数を用いた回帰モデルの分析Stanスクリプト(ModelDummy.stan

 

 

data {

    int N;

    array[N] real Y;

    array[N] real X;

    array[N] real G;

}

parameters {

    real a1;

    real a2;

    real b1;

    real b2;

    real<lower = 0.0> sgm;

}

transformed parameters {

    array[N] real a;

    array[N] real b;

    for (i in 1:N) {

        a[i] = a1 + a2 * G[i];

        b[i] = b1 + b2 * G[i];

    }

}

model {

    a1 ~ normal(0, 10);

    a2 ~ normal(0, 10);

    b1 ~ normal(0, 10);

    b2 ~ normal(0, 10);

    sgm ~ exponential(0.1);

    for (i in 1:N) {

        Y[i] ~ normal(a[i] + b[i] * X[i], sgm);

    }

}

 

 

 

表1のデータを分析するために、2つのExcelファイルを図1のように用意した。

 

図1

 

図1のファイルを読み込んで、リスト1のStanスクリプトによりベイズ分析を行うPythonスクリプトをリスト2のように用意した。温度Xを中心化した値Zを独立変数としていることに注意。

 

 

リスト2 ダミー変数を用いた回帰モデルの分析Pythonスクリプト(SweetTempDummy.py

 

 

from cmdstanpy import CmdStanModel

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import seaborn as sb

import arviz as az

 

xlfile = pd.ExcelFile('fructose.xlsx')

print(xlfile)

df = pd.read_excel(xlfile)

print(df)

col_names = df.keys()

print(col_names)

Xf = df['Temperature'].values

Yf = df['Sweetness'].values

Gf = np.ones(len(Xf))         #    ダミー変数 G=1

print('Xf =\n', Xf)

print('Yf =\n', Yf)

print('Gf =\n', Gf)

 

xlfile = pd.ExcelFile('glucose.xlsx')

print(xlfile)

df = pd.read_excel(xlfile)

print(df)

col_names = df.keys()

print(col_names)

Xg = df['Temperature'].values

Yg = df['Sweetness'].values

Gg = np.zeros(len(Xf))        #   ダミー変数 G = 0

print('Xg =\n', Xg)

print('Yg =\n', Yg)

print('Gg =\n', Gg)

 

X = np.concatenate((Xf,Xg))

Y = np.concatenate((Yf, Yg))

G = np.concatenate((Gf, Gg))

print('X =\n', X)

print('Y =\n', Y)

print('G =\n', G)

 

model = CmdStanModel(stan_file='ModelDummy.stan')

 

Z = X - np.mean(X)          #   中心化

 

Data = {'N':len(Y), 'Y':Y, 'X':Z, 'G':G}

 

fit = model.sample(data=Data, iter_warmup=5000, adapt_delta=0.95)

 

print(fit.summary())

 

inf_data = az.from_cmdstanpy(fit)      #   Arviz DataFrame型に変換

az.plot_trace(inf_data, var_names=['a1','a2','b1','b2','sgm'],

              figsize=(12,8)) 

plt.tight_layout()

plt.show()

 

fit = fit.draws_pd()                   #   Pandas DataFrame型に変換

 

sgm_hat = np.median(fit['sgm'])

sb.kdeplot(fit['sgm'])

plt.title('Sigma: median = {0:.2}'.format(sgm_hat))

plt.savefig('FigSgm.png')

plt.show()

 

Y_hat = np.empty(len(Y))

a1_hat = np.median(fit['a1'])

a2_hat = np.median(fit['a2'])

b1_hat = np.median(fit['b1'])

b2_hat = np.median(fit['b2'])

 

sb.kdeplot(fit['a1'], label = 'a1')

sb.kdeplot(fit['a2'], label = 'a2')

plt.title('a1: median={0:.2f},   a2: median={1:.2f}'.format(

    a1_hat, a2_hat))

plt.legend()

plt.savefig('Figa1a2.png')

plt.show()

 

sb.kdeplot(fit['b1'], label = 'b1')

sb.kdeplot(fit['b2'], label = 'b2')

plt.title('b1: median={0:.4f},   b2: median={1:.4f}'.format(

    b1_hat, b2_hat) + '\n' + f'Prob(b1<0)={np.mean(fit["b1"]<0)}')

plt.xticks(rotation = 45)

plt.legend()

plt.tight_layout()

plt.savefig('Figb1b2.png')

plt.show()

 

plt.plot(Z, Y, 'o')

print(np.min(Z), np.max(Z))

plt.plot([np.min(Z), np.max(Z)], [a1_hat + b1_hat*np.min(Z), a1_hat + b1_hat*np.max(Z)],

        label = 'glucose')

plt.plot([np.min(Z), np.max(Z)],

         [a1_hat + a2_hat + (b1_hat + b2_hat)*np.min(Z), a1_hat + a2_hat + (b1_hat + b2_hat)*np.max(Z)],

         label = 'fructose')

x_coord = np.arange(0, 61, 10)

z_coord = x_coord - np.mean(X)

plt.xticks(z_coord, x_coord)

plt.xlabel('$^{\circ}C$', fontsize = 14)

plt.ylabel('Sweetness')

plt.plot([0, 0], [np.min(Y), np.max(Y)], label = 'Mean of X = {0:.1f}'.format(np.mean(X)))

plt.legend()

plt.tight_layout()

plt.savefig('FigRegressDummy.png')

plt.show()

 

print('Mean of X = {0:.5f}'.format(np.mean(X)))

 

ck_b1 = [1 if v < 0 else 0 for v in fit['b1']]

print('Prop(b1 < 0) = {0:.5f}'.format(np.sum(ck_b1) / len(ck_b1)))

 

 

 

リスト1のStanスクリプトファイル(ModelDummy.stan)、リスト2のPythonスクリプトファイル(SweetTempDummy.py)、および入力データファイル(fructose.xlsxglucose.xlsx)を同じフォルダに置く。そのフォルダにカレントディレクトリ(フォルダ)を移して、次のコマンドを、CmdStanPyがインストールされている環境で実行する。CmdStanPyの簡単な説明は、このウェブサイトに用意した。

 

(stan) *****/filesSweetTemp$ python SweetTempDummy.py

 

上のコマンドを実行すると、ファイルが読み込まれ、Stanスクリプトのコンパイルが始まる。このコンパイルには多少時間が掛かる。コンパイル後、MCMCサンプルのトレース図が表示される(図2)。

 

図2

 

左側に事後分布のグラフ、右側にサンプリングのトレース図が表示されている。

図2のWindowを閉じると、図3のグラフが表示される。

 

図3

 

sigmaの事後分布のグラフである。事後分布の代表値として中央値が表示されている。Gelmanら(2021は、代表値として、平均値より安定した中央値を勧めている。

図3のWindowを閉じると、図4のグラフが表示される。

 

図4

 

a1a2の事後分布のグラフである。a1はグルコースの回帰直線のy切片、a1+a2はフラクトースの回帰直線のy切片である。回帰直線は、中心化された独立変数Zに対して求められている。したがって、データの独立変数の平均値におけるグルコースの回帰直線の値が正の値であること、フラクトースの回帰直線の独立変数の平均値における値がグルコースより高いことが示されている。

 

図4のWindowを閉じると、図5のグラフが表示される。

 

図5

 

パラメータb1b2の事後分布のグラフである。パラメータb1の中央値はー0.0005であり、事後分布が負である確率は0.91であるので、グルコースの甘味度は、温度とともに減少し、減少の度合いは1℃あたり0.0005であると言える。

フラクトースの温度に対する甘味減少度はb1+b2であるが、b2の事後分布の中央値はー0.0112であり、事後分布(赤色のグラフ)の範囲は明瞭に負の領域にある。フラクトースの甘味減少度は、グルコースより速いと言える。

 

図5のWindowを閉じると、図6のグラフが表示される。

 

図6

 

フラクトースとグルコースの甘味度の温度に対する回帰直線である。中央の赤の垂直線は、温度データの平均値の位置を表している。この測定温度の平均値においてフラクトースの方が甘味度が高いことは、図4のグラフに対応している。温度による甘味度の減少がフラクトース方が急であることは、図5のグラフに対応している。

 

図6のWindowを閉じると、スクリプトの実行終了である。

 

 

 

引用文献

Gelman, A., Hill, J., & Vehtari, A. (2021). Regression and other stories. Cambridge University

前橋健二(2011).甘味の基礎知識.日本醸造協会誌、106818-825

 

 

Up