宝くじは儲かるのか？～LOTO7と大数の法則～

# はじめに
「宝くじは儲かるのか？」という問いですが、結論から言います。

**儲かりません。**

宝くじは、主催者側の利益が極めて大きい事業です。当選金額の還元率は50%を下回ります[^1]。これは競馬、競輪、競艇、オートレースの75%前後と比べて圧倒的に低い値です。しかも、当選はランダムに決まる為、競馬の様な予想も不可能です。作家の橘玲氏はこれを「愚か者に課せられた税金」と表現しています。

しかしながら、勝算があるとすれば、当選金額のランダムな揺らぎが大きい小口で買う場合でしょう。確率論に「大数の法則」というものがあります。ある確率分布から標本を抽出して平均を出す時、その平均は確率分布の真の平均に収束していくという法則です。では、**標本数が小さい場合であれば、ランダムな揺らぎにより元が取れる可能性が高まるのではないか？**これを確かめてみようと思います。

# 環境
- Windows 10
- Python 3.8.3
- Jupyterlab Version 2.1.5

# 実験
宝くじにも色々ありますが、今回はLOTO7を選びました。理由は一等が極めて高額で話題性が高く、いかにも「宝くじらしい」からです。また、一般的な例について検討したかったので、キャリーオーバーは今回は考えませんでした。当選口数による当選金額の変動も考えていません。下に、Loto7の当選金額を計算するクラスを作成しました。

```python
import numpy as np

class Loto7:
    """
    Loto7の当選金額を計算するクラス
    """
    def __init__(self, main_nums, bonus_nums):
        self.main_nums = set(main_nums) # 本数字
        self.bonus_nums = set(bonus_nums) # ボーナス数字
        
    def prize_amount(self, nums=None):
        nums = self.rand() if nums is None else set(nums)
        diff_main = self.main_nums & nums
        len_main = len(diff_main) # 本数字との一致数
        diff_bonus = self.bonus_nums & nums
        len_bonus = len(diff_bonus) # ボーナス数字との一致数

        if len_main == 7: # 1st prize
            return 600_000_000
        if len_main == 6: # 2nd and 3rd
            return 7_300_000 if len_bonus == 1 else 730_000
        if len_main == 5: # 4th
            return 9_100
        if len_main == 4: # 5th
            return 1_400
        if len_main == 3 and len_bonus > 0: # 6th
            return 1_000
        else:
            return 0
        
    def rand(self): # ランダムな数字を選択
        r = np.random.choice(np.arange(1,38), 7, replace=False)
        return set(r)
```

上で定義したクラスを使って、ランダムに数字を選んで当選金額を計算します。計算の仕方は、以下です。

1. 宝くじをランダムな番号でn本買ったとして、当選金額の合計を出す
2. それをnで割って一本当たりの金額を出す
3. 上記を決まった数だけ繰り返して、一本当たりの当選金額の分布を出す
4. 上記について、nを色々な値で試す

また、当選番号である本数字とボーナス数字は固定にしました。実際の手続きとして、これらの番号はランダムに決まると考えられるので、購入者が決める番号との関係で考えれば、固定でも差し支えないと考えました。

```python
lt7 = Loto7([1,2,3,4,5,6,7], [8,9]) # 当選番号である本数字とボーナス数字は固定
n_lots = list(range(1,10)) + list(range(10,30,2)) + [30, 100, 300, 1000] # 買う宝くじの本数
reps = 1000 # 購入の繰り返しの回数
prize_amounts = [sum([lt7.prize_amount() for i in range(n)]) / n for n in n_lots for r in range(reps)]
prize_amounts = np.array(prize_amounts).reshape(len(n_lots), reps)
```

これで計算が済みました。では、LOTO7の一枚の購入価格300円以上の当選金額があるケースはどのくらいあるのでしょうか？以下に、購入枚数と「元が取れた割合」の関係を示したグラフを描きます。

```python
recovery_rate = np.apply_along_axis(lambda x: (x >= 300).sum() / len(x), 1, prize_amounts)

plt.plot(n_lots, recovery_rate);
plt.xscale('log')
plt.xlabel('number of LOTO7 tickets')
plt.ylabel('recovery rate')
plt.show()
```

![loto7graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89145/04bc28eb-f244-6fa3-a3eb-ddfb85fcacb6.png)

```python
print(n_lots)
print(recovery_rate)

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 100, 300, 1000]
# [0.042 0.083 0.104 0.062 0.018 0.024 0.022 0.025 0.019 0.014 0.018 0.017
#  0.022 0.012 0.009 0.024 0.024 0.024 0.024 0.028 0.002 0.008 0.011]
```

これから分かることは、以下の二つです。

- 最も「元が取れる」購入枚数は3枚
- その場合、10%程度の確率で元が取れる

結論。

**儲かりません。**

賢明な人は宝くじは止めましょう！

以上。

[^1]: https://www.soumu.go.jp/main_content/000084191.pdf