チョコレートの選好比率
3種類のチョコレートの嗜好を、ノーズクリップの脱着によりレトロネーザルアロマの影響をコントロールして調べたデータが星野ら(2020)に報告されている。3種のチョコレート、ガーナ、ベネズエラ、エクアドルからの選択を、ノーズクリップなしの条件とノーズクリップあり(レトロネーザルアロマの関与の除外)の2条件で行われた結果が星野ら(2020)のTable 4に掲載されているが、Table 4から各条件ごとのチョコレートの選択頻度を抜き出して表1にまとめた。各条件におけるチョコレートの選択頻度は、多項分布に従うと見なすことができる。
表1 チョコレートの2条件における選択(星野ら(2020)より) |
|||
|
ガーナ |
ベネズエラ |
エクアドル |
ノーズクリップなし |
55 |
24 |
16 |
ノーズクリップあり |
38 |
27 |
30 |
多項分布は、2項分布の拡張と見做すことができる。3種のチョコレートから1つを選ぶという実験において、それぞれのチョコレートが選ばれた頻度を、、とおき、それぞれのチョコレートが選ばれる確率を、、とおけば、多項分布は次式で表される。
ここで、
である。
式(2)の条件を満たすパラメータは事前分布としてDirichlet分布を用いると得ることができる。Dirichlet分布は3変量の場合、次式で表される。
ここで、
である。
パラメータの値を
と置けば、確率分布(3)は一様分布を表す。
表1のデータを、「ノーズクリップなし」条件と「ノーズクリップあり」条件の2条件における多項分布として、各条件にモデル(1)を適用するベイズ分析モデルのStanスクリプトをリスト1のように用意した。多項分布のパラメータの事前分布はパラメータ値がであるDirichlet分布を設定している。
Stanについては、岡本(2019)で説明している。
リスト1 多項分布による分析のStanスクリプト(choco_choice.stan)
data {
int freq_1[3];
int freq_2[3];
}
transformed data {
vector[3]
alpha;
alpha[1]
= 1; alpha[2] = 1; alpha[3] = 1;
}
parameters {
simplex[3] p1;
simplex[3] p2;
}
model {
p1 ~ dirichlet(alpha);
p2 ~ dirichlet(alpha);
freq_1 ~ multinomial(p1);
freq_2 ~ multinomial(p2);
}
リスト1のStanスクリプトを用いて表1のデータを分析するPythonスクリプトをリスト2のように用意した。スクリプトファイルは圧縮ファイルfiles_freq.zipにまとめたので、ダウンロード・展開すれば自由に利用できる。
リスト2 表1のデータの分析スクリプト(AnalFreq.py)
import numpy
as np
import matplotlib.pyplot as plt
import seaborn as sb
import pystan
import platform
sm = pystan.StanModel(file =
'choco_choice.stan')
freq_noclip = [55, 24, 16]
freq_clip = [38, 27, 30]
Data = {'freq_1': freq_noclip,
'freq_2':freq_clip}
fit = sm.sampling(data = Data,
n_jobs = 1 if platform.system() == 'Windows' else -1)
print(fit)
plt.rcParams['font.family'] = ['Yu
Gothic', 'Noto Sans CJK JP', 'Hiragino Maru Gothic Pro']
p1_smpls = fit['p1']
p2_smpls = fit['p2']
def calc_entropy(p):
return
-p.T[0]*np.log2(p.T[0]) -p.T[1]*np.log2(p.T[1]) \
-p.T[2]*np.log2(p.T[2])
entropies1 = calc_entropy(p1_smpls)
entropies2 = calc_entropy(p2_smpls)
ent1_hat = np.median(entropies1)
ent2_hat = np.median(entropies2)
sb.kdeplot(entropies1, label =
'No-clip')
sb.kdeplot(entropies2, label = 'Clip')
plt.title('Entropy: No-clip={0:.2f},
Clip={1:.2f}'.format(ent1_hat, ent2_hat))
plt.legend()
plt.savefig('FigEntropies.png')
plt.show()
p1_1_hat = np.median(fit['p1'].T[0])
p1_2_hat = np.median(fit['p1'].T[1])
p1_3_hat = np.median(fit['p1'].T[2])
sb.kdeplot(fit['p1'].T[0], label =
'p1[1]:ガーナ')
sb.kdeplot(fit['p1'].T[1], label =
'p1[2]:ベネズエラ')
sb.kdeplot(fit['p1'].T[2], label =
'p1[3]:エクアドル')
plt.legend()
plt.title('No-clip\nガーナ={0:.2f}, ベネズエラ={1:.2f}, エクアドル={2:.2f}'.format(
p1_1_hat, p1_2_hat, p1_3_hat))
plt.savefig('Figp1.png')
plt.show()
p2_1_hat = np.median(fit['p2'].T[0])
p2_2_hat = np.median(fit['p2'].T[1])
p2_3_hat = np.median(fit['p2'].T[2])
sb.kdeplot(fit['p2'].T[0], label =
'p2[1]:ガーナ')
sb.kdeplot(fit['p2'].T[1], label =
'p2[2]:ベネズエラ')
sb.kdeplot(fit['p2'].T[2], label =
'p2[3]:エクアドル')
plt.legend()
plt.title('Clip\nガーナ={0:.2f}, ベネズエラ={1:.2f}, エクアドル={2:.2f}'.format(
p2_1_hat, p2_2_hat, p2_3_hat))
plt.savefig('Figp2.png')
plt.show()
e1_smpls = np.sort(entropies1)
e2_smpls = np.sort(entropies2)
def SmallerEqProp(a, v):
i = 0
while v > a[i]:
i +=
1
if i
>= len(a):
break
prop = i / len(a)
return prop
sum_prop = 0.0
for v in e1_smpls:
sum_prop += (1 -
SmallerEqProp(e2_smpls, v))
prop_Larger = sum_prop / len(e1_smpls)
print('Prop_Larger =', prop_Larger)
リスト2のスクリプトを実行すると、以下の結果を得る。
まず、3つの確率、、に対するエントロピー
の事後分布のグラフが表示される(図1)。
図1
ノーズクリップあり条件のエントロピーの方が大きいので、ノーズクリップあり条件の3つのチョコレートの選好確率の違いは、ノーズクリップなし条件の場合より顕著ではないと言える。
図1のフォームを閉じると、図2のフォームが表示される。「ノーズクリップなし」条件での3つのチョコレートの選好確率の事後分布が表示されている。
図2
ガーナが他の2つより好まれていることが明瞭に示されている。
図2のフォームを閉じると、図3のフォームが表示される。
図3
「ノーズクリップあり」条件での3つのチョコレートの選好確率の事後分布が示されている。図2の分布と比べると、3つのチョコレートの選好確率の違いは不明瞭になっている。このことは、図1のエントロピーの事後分布の違いに対応するものである。
上のスクリプトでは、エントロピーの計算をPythonスクリプト(リスト2)で行っているが、これをStanスクリプトで行うようにしたものが、リスト3のStanスクリプトである。
リスト3 エントロピーの計算も行うStanスクリプト(choco_choice.stan)
functions {
real neg_p_log(real p) {
if
(p > 0.0) {
return -p * log2(p);
}
else
{
return 0.0;
}
}
}
data {
int freq_1[3];
int freq_2[3];
}
transformed data {
vector[3]
alpha;
alpha[1]
= 1; alpha[2] = 1; alpha[3] = 1;
}
parameters {
simplex[3] p1;
simplex[3] p2;
}
model {
p1 ~ dirichlet(alpha);
p2 ~ dirichlet(alpha);
freq_1 ~ multinomial(p1);
freq_2 ~ multinomial(p2);
}
generated quantities {
real entropy1;
real entropy2;
entropy1 = neg_p_log(p1[1])
+ neg_p_log(p1[2]) + neg_p_log(p1[3]);
entropy2 = neg_p_log(p2[1])
+ neg_p_log(p2[2]) + neg_p_log(p2[3]);
}
リスト3のStanスクリプトを用いるPythonスクリプトはリスト4のように用意した。
これらのファイルは圧縮ファイルfiles_freq_ubuntu.zipにまとめた。ダウンロード・展開すれば自由に利用できる。
リスト3およびリスト4のスクリプトは、pystan2.19.1.1/Python 3.9.13/Anaconda 3/ubuntu 20/WSL/Windows 10で実行した。
pystan 2.17.1.0/Python 3.7.13/Anaconda 3/Windows 10で実行するとエラーになる。WSLについては、このウェブページで説明している。
リスト4 リスト3のStanスクリプトを用いるPythonスクリプト(Anal_freqs.py)
import numpy as np
import matplotlib.pyplot as plt
import pystan
import matplotlib.pyplot as plt
import seaborn as sb
sm = pystan.StanModel(file =
'choco_choice.stan')
freq_noclip = [55, 24, 16]
freq_clip = [38, 27, 30]
Data = {'freq_1': freq_noclip,
'freq_2':freq_clip}
fit = sm.sampling(data = Data)
print(fit)
ent1_hat = np.median(fit['entropy1'])
ent2_hat = np.median(fit['entropy2'])
sb.kdeplot(fit['entropy1'], label =
'No-clip')
sb.kdeplot(fit['entropy2'], label =
'Clip')
plt.title('Entropy: No-clip={0:.2f},
Clip={1:.2f}'.format(ent1_hat, ent2_hat))
plt.legend()
plt.savefig('FigEntropies.png')
plt.show()
plt.close()
p1_1_hat = np.median(fit['p1'].T[0])
p1_2_hat = np.median(fit['p1'].T[1])
p1_3_hat = np.median(fit['p1'].T[2])
sb.kdeplot(fit['p1'].T[0], label =
'p1[1]:Ghana')
sb.kdeplot(fit['p1'].T[1], label =
'p1[2]:Venezuela')
sb.kdeplot(fit['p1'].T[2], label =
'p1[3]:Equador')
plt.legend()
plt.title('No-clip\nGhana={0:.2f}, Venezuela={1:.2f}, Equador={2:.2f}'.format(
p1_1_hat, p1_2_hat, p1_3_hat))
plt.savefig('Figp1.png')
plt.show()
plt.close()
p2_1_hat = np.median(fit['p2'].T[0])
p2_2_hat = np.median(fit['p2'].T[1])
p2_3_hat = np.median(fit['p2'].T[2])
sb.kdeplot(fit['p2'].T[0], label =
'p2[1]:Ghana')
sb.kdeplot(fit['p2'].T[1], label =
'p2[2]:Venezuela')
sb.kdeplot(fit['p2'].T[2], label =
'p2[3]:Equador')
plt.legend()
plt.title('Clip\nGhana={0:.2f}, Venezuela={1:.2f}, Equador={2:.2f}'.format(
p2_1_hat, p2_2_hat, p2_3_hat))
plt.savefig('Figp2.png')
plt.show()
plt.close()
ck_count = 0
e1_smpls = fit['entropy1']
e2_smpls = fit['entropy2']
e1_smpls = np.sort(e1_smpls)
e2_smpls = np.sort(e2_smpls)
def SmallerEqProp(a, v):
i = 0
while v > a[i]:
i +=
1
if i
>= len(a):
break
prop = i / len(a)
return prop
sum_prop = 0.0
for v in e1_smpls:
sum_prop += (1 -
SmallerEqProp(e2_smpls, v))
prop_Larger = sum_prop / len(e1_smpls)
print('Prop_Larger =', prop_Larger)
pystan2.19.1.1/Python 3.9.13/Anaconda 3/ubuntu 20/WSL/Windows 10で実行するときは、matplotlibによるグラフ表示はないが、savefigメソッドでグラフを保存することができる。また、複数のグラフを描くときは、各グラフ描画の最後でclose()メソッドを実行する必要がある。メソッドclose()を実行せずに次のグラフ描画に進むと、複数のグラフが重ね書きされる。
星野亜由美、石川華衣、岡田綾、飯田文子(2020).カカオ豆の香がチョコレート嗜好に及ぼす影響.日本官能評価学会誌、24、29-42.