サンプリングによって先祖(親)の血液型を推定する
このページでは、家系図から親や先祖の血液型を推定する方法について考える。
母親の遺伝子型を推定する
親子の血液型の遺伝を考えてみよう。 遺伝子型を小文字のa,b,oで表すことにした場合、 父親の血液の遺伝子型がaoで、その子の遺伝子型もaoであったとする。一方、母親の遺伝子型は不明であったとしよう。 既知の情報から、母親の血液の遺伝子型を推定するには、どのように考えるのが良いだろうか。
ここで、遺伝子型を$x,y$として、父親の遺伝子型を$\textrm{F:}xy$、母親を$\textrm{M:}xy$、子供を$\textrm{C:}xy$のように表すとする。 ベイズの公式によれば、母親の遺伝子型を$xy$とすると、 $$ P( \textrm{M:}xy, \textrm{F:ao} | \textrm{C:ao} ) = \frac{P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:ao}) P(\textrm{M:}xy) P(\textrm{F:ao})} {\sum_{x,y,w,z} P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:}wz) P(\textrm{M:}xy) P(\textrm{F:}wz)} $$ となるが、いま、父親と子供の遺伝子型は完全に分かっているのだから、 $$ P(\textrm{F:ao}) = P(\textrm{C:ao}) = 1 $$ で、それ以外の遺伝子型についての確率は0であるから($P(\textrm{F:aa})=P(\textrm{F:ab})=\cdots=0$)、 $$ P( \textrm{M:}xy, \textrm{F:ao} | \textrm{C:ao} ) = \frac{P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:ao}) P(\textrm{M:}xy)} {\sum_{x,y} P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:ao}) P(\textrm{M:}xy)} $$ となる。 すると、子供がao型、父親もao型で、かつ、母親の遺伝子型が$xy$である確率は $$ \begin{eqnarray} P( \textrm{M:}xy, \textrm{F:ao}, \textrm{C:ao} ) = P( \textrm{M:}xy, \textrm{F:ao} | \textrm{C:ao} ) P(\textrm{C:ao}) \\ = \frac{P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:ao}) P(\textrm{M:}xy)} {\sum_{x,y} P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:ao}) P(\textrm{M:}xy)} \end{eqnarray} $$ である。
実際に値を求めるには、確率分布 $P(xy)$ を知らねばならないが、ここで、日本人の 遺伝子型の比率(a: 0.28, b:0.17, o:0.55)を想定し、 ハーディー・ワインベルクの法則を仮定すると、 以下のコードで母親の遺伝子型毎の確率が計算できる:
# coding: utf-8
ptab = {'a': 0.28, 'b':0.17, 'o':0.55}
gt_ALL = ['aa','ao','ab','bb','bo','oo']
def pt(gt):
x = gt[0]
y = gt[1]
if x==y:
return ptab[x]*ptab[y]
else:
return 2*ptab[x]*ptab[y]
# P(C|M,F)
def pc(gt_c, gt_m, gt_f):
gt=[ ]
for c1 in gt_m:
for c2 in gt_f:
gt.append(''.join(sorted(c1+c2)))
gt_c = ''.join(sorted(gt_c))
n_match=0
for c2 in gt:
if c2 == gt_c:
n_match += 1
return n_match/len(gt)
# メイン部
for t_m in gt_ALL:
psum=0
for x in gt_ALL:
psum += pc('ao',x,'ao')*pt(x)
p = pc('ao', t_m, 'ao')*pt(t_m) / psum
print(t_m,p)
血液型しかわからない場合
通常は、遺伝子型ではなくて血液型(A,B,AB,O)しかわからない。 そのような部分情報から、親(ここでは母)の血液型を推定する方法についても考えてみよう。
上図の例では、父親は aa, ao の可能性があり、他方、子供も aa, ao の可能性がある。 そうすると、母親は aa, ab, ao, bo, oo の可能性がある。
仮に、父親が aa で 母親が ao であれば、子供の遺伝子型が$xy$である確率は $$ P( \textrm{C:}xy | \textrm{M:ao}, \textrm{F:aa}) $$ 具体的には、子供が aa の確率は 1/2、aoである確率は 1/2である。
もし、父親が aa で子供が ao である場合は、母親が遺伝子型 $xy$ である確率は、前節のとおり $$ P( \textrm{M:}xy, \textrm{F:aa} | \textrm{C:ao} ) = \frac{P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:aa}) P(\textrm{M:}xy)} {\sum_{x,y} P( \textrm{C:ao} | \textrm{M:}xy, \textrm{F:aa}) P(\textrm{M:}xy)} $$ となる。 ここで、$P(\textrm{M:}xy)$ はハーディー・ワインベルクの法則によって与えるのが自然であろう。
今度は、もし母親が ao で、子供も ao であったとすると、父親の遺伝子型が$xy$である確率は $$ P( \textrm{M:ao}, \textrm{F:}xy | \textrm{C:ao} ) = \frac{P( \textrm{C:ao} | \textrm{M:ao}, \textrm{F:}xy) P(\textrm{F:}xy)} {\sum_{x,y} P( \textrm{C:ao} | \textrm{M:ao}, \textrm{F:}xy) P(\textrm{F:}xy)} $$ で与えられる。ここでも $P(\textrm{F:}xy)$はハーディー・ワインベルクの法則で決めることができるが、 実際に$xy$として許されるのは aa と ao のみである(父親の血液型がAであるから)。
このように、ある1人に着目すると、その「周り」の遺伝子型が既知であったとすれば、その人の遺伝子型の確率分布を推定するのは(式を計算する手間を除けば)容易である。
そこで、とりあえずの状態を設定しておいて、ある人に注目し、とりあえずの状態を元にして求めた確率に応じて、注目している人の状態を更新する作業を繰り返すようにする。 着目する人は順番に変えても、ランダムに決めてもよい(ただし、一様な重みでサンプルする必要がある)。すると、例えば、その状態は aa → aa → ao → aa → ... といった具合に目まぐるしく変化するはずである。
うまいことに、こうした更新作業を多数回繰り返すと、aaやaoの出現頻度は一定値に収束し、ちょうどそれぞれの遺伝子型の期待値(推定値)となることがわかっている。 このことは、父親と母親についても同様である。対象のうちのひとつ(ひとり)を選びながら、その状態を確率的に更新することで、知りたい量の期待値を得るこの方法は、 一般に、ギブスサンプリングと呼ばれている。
上記の手続きを100回繰り返すPythonコードの例を以下に示した:
右のコード中、関数 choose() で、NumPyの関数 np.random.choice() を使用している。
np.random.choice(選択肢のリスト, p=選択確率のリスト) とすると、リストで与えられた確率に応じて、選択肢のリストから要素が1つ返される。
# coding: utf-8
import random
import numpy as np
ptab = {'a': 0.28, 'b':0.17, 'o':0.55}
bt_A = ['aa','ao']
bt_AB = ['ab']
bt_B = ['bb','bo']
bt_O = ['oo']
gt_ALL = ['aa','ao','ab','bb','bo','oo']
def pt(gt):
x = gt[0]
y = gt[1]
if x==y:
return ptab[x]*ptab[y]
else:
return 2*ptab[x]*ptab[y]
# P(C|M,F)
def pc(gt_c, gt_m, gt_f):
gt=[ ]
for c1 in gt_m:
for c2 in gt_f:
gt.append(''.join(sorted(c1+c2)))
gt_c = ''.join(sorted(gt_c))
n_match=0
for c2 in gt:
if c2 == gt_c:
n_match += 1
return n_match/len(gt)
# P(M,F|C)
def pp(gt_m, gt_f, gt_c):
psum=0
for x in gt_ALL:
for w in gt_ALL:
psum += pc(gt_c,x,w)*pt(x)*pt(w)
return pc(gt_c, gt_m, gt_f)*pt(gt_m)*pt(gt_f) / psum
def choose(choices):
vtab=[]
ktab=[]
for k,v in choices.items():
vtab.append(v)
ktab.append(k)
plist = np.array(vtab)/sum(vtab)
return np.random.choice(ktab,p=plist)
# メイン部
state = {'M':'aa', 'F':'aa', 'C':'aa'}
for _ in range(100):
# 母親を更新
t_f = state['F']
t_c = state['C']
choices={ }
for t_m in gt_ALL:
choices[t_m] = pp(t_m, t_f, t_c)
state['M'] = choose(choices)
# 父親を更新
t_m = state['M']
t_c = state['C']
choices={ }
for t_f in bt_A:
choices[t_f] = pp(t_m, t_f, t_c)
state['F'] = choose(choices)
# 子供を更新
t_f = state['F']
t_m = state['M']
choices={ }
for t_c in bt_A:
choices[t_c] = pc(t_c, t_m, t_f)
state['C'] = choose(choices)
print(state)
練習:ギブスサンプリングによる推定
上記のコードの例を出発点に、母親の血液型を推定し、確率として表示するコードを書いてみなさい。
ヒント
確からしい値を得るには、サンプリングは十分な回数行う(長い系列を得る)必要がある。 また、サンプリングを始めてから暫くは状態が「安定」しないので、初期の状態は期待値の計算には用いないないようにすると良い。