アクチュアリーのためのPython入門(第6回)
Decimal型に切り替えて生命保険計算を書く
はじめに
これまでの回では、
- 生命表
- 基数表(Dx, Cx, Nx, Mx)
- 給付現価
- 保険料・保険料積立金
を順にPythonで実装してきました。
第5回では、端数処理にDecimal型を使うことを説明しましたが、
実務的には次の疑問が残ります。
「最後の四捨五入だけ Decimal にすれば十分なのか?」
今回はこの疑問に答えるために、
計算の途中からDecimal型に切り替える実装例を示します。
どこから Decimal に切り替えるべきか
生命保険の計算を分解すると、
- 死亡率 → 生命表
- 生命表 → 基数表(Dx, Cx, Nx, Mx)
- 基数表 → 給付現価・収入現価
- 給付現価・収入現価 → 保険料・保険料積立金
という流れになります。
このうち、誤差が問題になりやすいのは 4. の部分です。
- 保険料
- 保険料積立金
はいずれも
「支払現価 − 収入現価」や「割り算」を含み、
ここでfloatの誤差が表に出やすくなります。
そのため本記事では、
基数表・給付現価まではfloat
現価を使って金額を確定させる段階からDecimalに切り替える
という構成を採用します。
Decimal変換の考え方
ポイントは単純で、
- floatの値を計算途中で無理に丸めない
- Decimalに切り替えるときはDecimal(str(value))を使う
という2点です。
Decimal(str(value))
とすることで、
floatが内部的に持っている誤差をそのままDecimalに持ち込むのを防ぎます。
途中からDecimal変換をしたコード
先のことから、保険料積立金の**「支払現価 − 収入現価」**の部分から、
Decimal変換をします。全体のコードは次のとおりです。
# 年齢
ages = list(range(40, 50))
# 死亡率(標準生命表2018)
qx = [0.00118,0.00129,0.00140,0.00151,0.00163,
0.00177,0.00194,0.00214,0.00236,0.00259]
# 初期生存者数
l0 = 100000
# lx, dx
lx = [l0]
dx = []
# 生命表の作成
for q in qx:
next_d = lx[-1] * q
next_l = lx[-1] - next_d
lx.append(next_l)
dx.append(next_d)
# 利率と現価率
i = 0.006
v = 1 / (1 + i)
# Dx, Cx
Dx = []
Cx = []
for age, l, d in zip(ages, lx, dx):
Dx.append(l * v ** age)
Cx.append(d * v ** (age + 0.5)) # 即時払(年央近似)
# Nx, Mx
Nx = []
Mx = []
Dx_sum = 0
Cx_sum = 0
for D, C in zip(reversed(Dx), reversed(Cx)):
Dx_sum += D
Cx_sum += C
Nx.append(Dx_sum)
Mx.append(Cx_sum)
Nx = list(reversed(Nx))
Mx = list(reversed(Mx))
# 死亡給付現価
def A1xn(start_age, n):
age_index = start_age - 40
return (Mx[age_index] - Mx[age_index + n]) / Dx[age_index]
# 年金現価(期始払)
def axn(start_age, n):
age_index = start_age - 40
return (Nx[age_index] - Nx[age_index + n]) / Dx[age_index]
# 純保険料
def netPremT(start_age, n):
return A1xn(start_age, n) / axn(start_age, n)
# decimal のインポート
from decimal import Decimal, ROUND_HALF_UP, getcontext
# 四捨五入
def roundhu(value, ndigits):
getcontext().prec = 28
d = Decimal(str(value))
quant = Decimal("1").scaleb(-ndigits)
return d.quantize(quant, rounding=ROUND_HALF_UP)
# 営業保険料
def PremT(start_age, n):
alpha = 0.001
delta = 0.06
gamma = 0.00115
beta = 0.03
value = (A1xn(start_age, n) + alpha + gamma * axn(start_age, n)) \
/ ((1 - beta - delta) * axn(start_age, n))
return roundhu(value, 6)
# 保険料積立金
def ResT(start_age, n, t):
# floatで現価を計算
benefit = A1xn(start_age + t, n - t)
income = netPremT(start_age, n) * axn(start_age + t, n - t)
# 確定時にDecimalへ
value = Decimal(str(benefit)) - Decimal(str(income))
return roundhu(value, 6)
まとめと今後について
ここまで、生命表から始めて、
- 基数表
- 給付現価
- 保険料
- 保険料積立金
- 端数処理
までを、Pythonで一通り実装してきました。
これらは、生命保険数理の基本部分です。
今後は、これらの計算を土台として、
死亡や解約が発生する前提のもとで
簡単な保険収支のモデルも考えてみたいです。
ただし、あくまで「勉強しながら進める」ことを目的としているため、
内容や進め方は、途中で見直す可能性があります。
次回以降は、
保険数理の計算を「収支の形」に組み立てていく
ところから、少しずつ進めていく予定です。