両親の血液型から子供の血液型を決定する

両親の血液型から子供の血液型を決定する

血液型をランダムに生成する

 ある調査によると、日本人の ABO 式血液型 は、おおよそ
 

O型:32%、A型:37%、B型:22%、AB型:9%

のような比率で分布していることが知られています (ただし地域差が大きいので、標本によって比率は僅かに異なるようです)。この比率にしたがって、血液型をランダムに決定する関数 を定義してみましょう。

 numpy.random.choice() は第 1 引数にオブジェクトのリスト、オプション引数 p に第 1 引数の各要素に対応する確率リストを渡すと、設定した確率で各オブジェクトを返します。

# PYTHON_BLOOD_TYPE_01-1

import numpy as np

# 血液型をランダムに決定する関数
def rand_blood(p=[0.32, 0.37, 0.22, 0.09]):

    # 血液型(blood type)リスト
    bt_list = ["O", "A", "B", "AB"]

    # O,A,B,AB型の比率
    prob = p

    # 血液型をランダムに決定
    x = np.random.choice(bt_list, p=prob)
    
    return x

 引数 p にはデフォルトの比率を与えていますが、ユーザーが任意の比率を与えることもできます。念のために 10000 個の血液型を生成して発生比率を確認しておきます。

# PYTHON_BLOOD_TYPE_01-2

# 血液型(blood type)リスト
bt_list = ["O", "A", "B", "AB"]

# 初期化されていない配列を用意
# データ型(dtype)は最大長2の文字列
arr = np.empty(10000, dtype="U2")

# 乱数シードを設定
np.random.seed(0)

# 血液型を10000個発生
for i in range(10000):
    arr[i] = rand_blood()

# 型ごとの個数をカウントして比率を計算
for j in bt_list:
    ct = arr[arr==j].size/10000
    print("{0}:{1}".format(j, ct))
O:0.3261
A:0.3657
B:0.218
AB:0.0902

 

両親の血液型から子供の血液型を決定する

 両親の血液型の組合わせにしたがって子供の血液型の比率を計算すると、以下のようになることが知られています。

 [Python] 両親の血液型と生まれてくる子供の血液型の確率

 たとえば、両親の血液型が O, A であった場合、生まれてくる子供の血液型は、O 型になる確率が 40%, A 型になる確率が 60% であり、それ以外の血液型になる確率は 0% です。

 任意の血液型の組み合わせを渡して、子供の血液型を正しい確率で決定する関数を作成してみましょう。両親の血液型を key, 子供の血液型を value とする辞書を作成することを考えるかもしれません。しかし、Python 本体のディクショナリは動作が重いので、やはり NumPy の配列を利用することにします。

 ここでも numpy.random.choice() を使うので、最初に両親の血液型の組合わせと、対応する子供の血液型の確率リストを配列に格納しておきます。ただし、たとえば (A, O) と (O, A) のように、両親の血液型の順序が異なっても対応する確率リストは同じなので、重複処理を避けるために両親の血液型は set型で格納しておきます。コードが見やすいように、AB 型は Z で置き換えます。

# PYTHON_BLOOD_TYPE_02-1

import numpy as np

# 両親の血液型の組合わせを配列に格納
# AB型はZに置き換えてある
type_parents = np.array([{"O", "O"}, {"O", "A"}, {"O", "B"},
                         {"O", "Z"}, {"A", "A"}, {"A", "B"},
                         {"A", "Z"}, {"B", "B"}, {"B", "Z"},
                         {"Z", "Z"}])

# 子供の血液型の確率
type_child = np.array([[1.00, 0.00, 0.00, 0.00],
                       [0.40, 0.60, 0.00, 0.00],
                       [0.43, 0.00, 0.57, 0.00],
                       [0.00, 0.50, 0.50, 0.00],
                       [0.16, 0.84, 0.00, 0.00],
                       [0.17, 0.26, 0.23, 0.34],
                       [0.00, 0.50, 0.20, 0.30],
                       [0.19, 0.00, 0.81, 0.00],
                       [0.00, 0.22, 0.50, 0.28],
                       [0.00, 0.25, 0.25, 0.50]])

 set型は要素の順が異なっても、同じオブジェクトとして扱われます:

{"A", "O"} == {"O", "A"}
True

 データを npzファイルに保存しておきます。

# PYTHON_BLOOD_TYPE_02-2

# npzファイルに配列を保存
np.savez('blood_type', x = type_parents, y = type_child)

 フォルダに blood_type.npz ファイルがあれば、今後はコード BT_02-1 は必要ありません (消しても大丈夫です)。次回以降に Jupyter を起動するときのために、blood_type.npz からデータを読み込むコードを載せておきます。

# PYTHON_BLOOD_TYPE_02-3

# npzファイルを読み込む
data = np.load("blood_type.npz")

# 変数に配列を格納
type_parents = data["x"]
type_child = data["y"]

 type_parents は type_child の特定の行を参照するためのインデックスを得るために使います。numpy.where() を使うと、たとえば {"Z", "O"} のインデックス配列を取得できます。

# PYTHON_BLOOD_TYPE_02-4

# {"A","B"}のインデックス配列を取得
idx_arr = np.where(type_parents == {"A", "B"})

print(idx_arr)
(array([5], dtype=int32),)

 戻り値が一次元配列とデータ型 (dtype) のタプルなので、idx_arr[0][0] でインデックスを取り出せます。

# PYTHON_BLOOD_TYPE_02-5

# idx_arrからインデックスを取り出す
idx = idx_arr[0][0]

print(idx)
5

 このインデックスを使って、type_child から A 型と B 型の両親から生まれる子供の血液型の確率リストを取得できます。

# PYTHON_BLOOD_TYPE_02-6

# {"A","B"}の確率を取得
prop = type_child[idx]

print(prop)
[0.17 0.26 0.23 0.34]

 以上を踏まえて、両親の血液型を受け取って子供の血液型をランダムに生成する inherit_blood() 関数を定義してみます。血液型の選択には上で定義した rand_blood() 関数を使います。

# PYTHON_BLOOD_TYPE_02-7

def inherit_blood(b1, b2):

    # "AB" が渡されたら "Z" に置き換える
    if b1 == "AB":
        b1 = "Z"
    
    if b2 == "AB":
        b2 = "Z"
    
    # インデックス配列を取得
    idx_arr = np.where(type_parents == {b1, b2})
    
    # idx_arrからインデックス番号を取り出す
    idx = idx_arr[0][0]
    
    # 子供の血液型の確率を取得
    prop = type_child[idx]
    
    return rand_blood(p=prop)

 A 型と B 型の両親から生まれる子供の血液型を生成してみます。

# PYTHON_BLOOD_TYPE_02-8

x = []

for i in range(10):
    b = inherit_blood("A", "B")
    x.append(b)
    
print(x)
['O', 'AB', 'O', 'AB', 'A', 'B', 'O', 'AB', 'A', 'B']