Python 要素数の和が一定値となる制約の元に、整数の乱数を作成する「numpy」

'22/03/12更新:自然な分布になる雛形コードも追記しました。
 本記事では、例えば複数のパラメータA, B, Cの3つがあって、それらの和が一定値Sの制約条件の元に、整数の乱数を作成する雛形コードを載せました。
下図は、S=150と指定して2000水準作成した例です。生成したデータはcsvファイルに出力する仕様です。

f:id:HK29:20210511000057p:plain

 

▼1. 各要素の生成する乱数の範囲を確率で決定する場合
下図のように、A, B, Cの分布はおよそ同じ分布で生成されます。和が一定の場合はこれが自然な分布と想像できます。

f:id:HK29:20220312001052p:plain

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import numpy as np
import random
import math
import pandas as pd

# パラメータ
parameter_name_list = ['A', 'B', 'C']
S = 150 # 制約条件 S=A+B+C
N = 2000 # 水準数

data_list = [] # データを格納するリスト

i = 0
while i < N:
    # Aの値を確率によって決める
    flag_A = np.random.choice(['low', 'center', 'high'], p=[1/3, 1/3, 1/3])
    if flag_A == 'low': # この例では、Aは0~50の範囲で乱数により決定
        A = np.random.randint(0, S / 3)
    elif flag_A == 'center': # この例では、Aは51~100の範囲で乱数により決定
        A = random.randint(S / 3, S / 3 * 2)
    else: # この例では、Aは101~150の範囲で乱数により決定
        A = random.randint(S / 3 * 2, S)
    #pint(flag_A, A)
    
    if S == A or A == 0: # 0割は飛ばして、次のループへ
        continue
    else:
        # Bの値を確率によって決める
        flag_B = np.random.choice(['low', 'center', 'high'], p=[1/3, 1/3, 1/3])
        if flag_B == 'low': 
            B = np.random.randint(0, math.ceil((S) / 3))
        elif flag_B == 'center':
            B = np.random.randint(math.ceil((S) / 3), math.ceil((S) / 3) * 2)
        else:
            B = random.randint(math.ceil((S) / 3) * 2, (S))
        #print(flag_B, B)

        if A + B > S or B == 0:
            continue
        else:
            # Cの値を決める。これは総和150から引くだけ
            C = S - (A + B)
            i = i + 1
    data_list.append([A, B, C])

# パンダスデータフレーム形式へ変換
df = pd.DataFrame(data_list, columns=parameter_name_list)
# 各列の和を計算して、列名「sum」に格納する
df['sum'] = df['A'] + df['B'] + df['C']
# インデックス番号を1からに振り直す
df.index = np.arange(1, len(df) + 1)
df.to_csv('data1.csv', index=True)
df


# In[2]:


import matplotlib.pyplot as plt

# ヒストグラムで分布を確認する
fig = plt.figure(figsize=(6,6))
ax = fig.gca()
df.hist(ax = ax)
plt.tight_layout()

▼2. 各要素の生成する乱数の範囲をある程度指定する場合
下図のように、Aは正規分布に近いといった分布を恣意的に作成できます。当然ですが、和が一定のためにBとCはそれにならった分布になります。

f:id:HK29:20220312000405p:plainA

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

parameter_name_list = ['A', 'B', 'C']
S = 150 # 制約条件 S=A+B+Cが固定値
N = 2000 # 水準数

# 乱数のインスタンス生成
rng = np.random.default_rng(1) # ()内はランダムシード
rng


# In[2]:


# パラメータAの生成
parameter_A = rng.integers(15, 85, size=N) # 15以上85未満の整数の乱数N個
parameter_A


# In[3]:


# パラメータBの生成
parameter_B = rng.integers(20, 55, size=N) # 20以上55未満の整数の乱数N個
parameter_B


# In[4]:


# パラメータCの生成
parameter_C = rng.integers(15, 35, size=N) # 15以上35未満の整数の乱数N個
parameter_C


# In[5]:


# 上記で生成した各パラメータをひとつのパラメータセットに合成する
np_list = [parameter_A, parameter_B, parameter_C]
param_arr = np.vstack(np_list).T
param_arr


# In[6]:


# パラメータA,B,Cの合計がSになるように変換する。ここで、整数ではなくなってしまう
arr = param_arr * S / param_arr.sum(axis=1)[:, np.newaxis]
arr


# In[7]:


# Sが指定した値になっているかを確認する
arr.sum(axis=1)


# In[8]:


# 四捨五入して整数にする
arr2 = np.floor(arr)
arr2


# In[9]:


# pandasデータフレーム型変換する
df = pd.DataFrame(arr2, columns=parameter_name_list)
df


# In[10]:


df['sum'] = df.sum(axis=1)
df


# In[11]:

# 和がSになるように最後のパラメータ(ここではC)の帳尻を合わせる df2 = df.copy() df2[parameter_name_list[-1]] = df[parameter_name_list[-1]] + (S - df['sum']) df3 = df2.drop(['sum'], axis=1) df3['sum'] = df3.sum(axis=1) df3 # In[12]: # csvファイルに出力する df3.to_csv('data2.csv', index=False) # In[13]:
# ヒストグラムで分布を確認する fig = plt.figure(figsize=(6,8)) ax = fig.gca() df3.hist(ax = ax) plt.tight_layout()

以上

<広告>