Используем цикл for для вычисления вероятностей

Перевод статьи «Using For Loops in Python: Calculating Probabilities».

Из этой статьи вы узнаете, почему циклы незаменимы в построении статистических моделей. 


Циклы — одно из фундаментальных понятий в программировании на Python. Особенно важны эти знания при работе с большим массивом данных. 

У многих занимающихся статистикой и дата сайенс, возникает мысль: «А зачем нам все это? Ведь привести наш код в порядок могут программисты!».

Но порой человек, пишущий код, должен знать и статистику, и способы обработки выходных данных. Эти процессы неразделимы! 

Вот один из примеров, когда умелое применение for облегчает и улучшает статистический анализ. 

Кумулятивные биномиальные вероятности — краткий экскурс 

Для вычисления вероятности нам необходимы две переменные: N (число наблюдений) и λ (лямбда — количество попаданий в цель/количество благоприятных исходов в серии экспериментов). Кумулятивная биномиальная вероятность означает следующее: с увеличением количества испытаний возрастает шанс возникновения какого-либо события. 

probability = 1-((1-λ)^N)

Пример. Возьмем «честный» шестигранный кубик и вычислим вероятность выпадения 6. Она равна 1/6. Но мы проведем этот эксперимент 10 раз подряд: 

1-((1-0.1667)^10) = 0.8385

Как видите, шанс выпадения 6 увеличивается до 83.85%

Закон больших чисел гласит: увеличиваем количество испытаний — увеличивается шанс возникновения какого-либо события. Это утверждение справедливо даже в том случае, если вероятность события в рамках одного испытания очень низкая. А теперь мы разберем, как увеличивается вероятность события с увеличением количества испытаний.

Модель без циклов

Ниже — скрипт, вычисляющий кумулятивную биномиальную вероятность без циклов:

import numpy as np
import pandas as pd

l = 0.02
m = 0.04
n = 0.06

p=np.arange(0, 100, 1)

h = 1 - l
j = 1 - m
k = 1 - n

q = 1-(h**p)
r = 1-(j**p)
s = 1-(k**p)
  • l, m и n — вероятности определенных событий. 
  • p — количество испытаний (до 100)
  • q, r и s — кумулятивные биномиальные вероятности. Их значение будет расти вместе с количеством испытаний. 

Вывод:

>>> q
array([0., 0.02, 0.0396, 0.058808, 0.07763184, 0.0960792, 0.11415762, 0.13187447, 0.14923698, 0.16625224, ..., 0.8532841, 0.85621842, 0.85909405, 0.86191217, 0.86467392])
>>> r
array([0., 0.04, 0.0784, 0.115264, 0.15065344, 0.1846273, 0.21724221, 0.24855252, 0.27861042, 0.307466, ..., 0.97930968, 0.9801373, 0.9809318, 0.98169453, 0.98242675])
>>> s
array([0., 0.06, 0.1164, 0.169416, 0.21925104, 0.26609598, 0.31013022, 0.35152241, 0.39043106, 0.4270052, 0.46138489, 0.49370179, 0.52407969, 0.5526349, 0.57947681, ..., 0.99720008, 0.99736807, 0.99752599, 0.99767443, 0.99781396])

Как видим, вероятности q, r и s растут прямо пропорционально количеству испытаний. 

Но у модели без использования циклов есть существенный недостаток — вероятности событий указываются конечным пользователем. А что, если мы захотим перебрать вероятности от 0.01 до 0.99?

Модель с циклами: генераторы списков и двумерные массивы

В этот раз мы рассмотрим другую модель: у нас будет лишь одно событие и его вероятность. Значения будут перебираться от 0.01 до 0.99. Количество испытаний все то же — 100.

import numpy as np
import pandas as pd

# генератор списка
probability = [x*0.01 for x in range(1,100)]
probability = np.array(probability)

h = 1 - probability

# создаем двумерный массив
result = 1-h[:, np.newaxis] ** np.arange(1,100)
print(result)

Вывод:

>>> probability
array([0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, ... 0.96, 0.97, 0.98, 0.99])

Обратите внимание, что использование генератора списков обязательно. Причина проста — функция range() работает только с int, а не с float

Возможно, вы заметили, что предпоследняя строка кода строит двумерный массив для вычисления кумулятивных биномиальных вероятностей. Если бы мы не использовали двумерный массив, результат был бы другой: мы бы вычислили значения, но они не были бы отсортированы. 

>>> for i in range(1,100,1):
>>>     print(1-(h**i))
[0.01 0.02] 
[0.0199 0.0396]
...
[0.62653572 0.86191217]
[0.63027036 0.86467392]

Вместо этого мы бы хотели видеть отсортированный массив: [0.01, 0.0199, …, 0.62653572, 0.63027036] и [0.02, 0.0396, …, 0.86191217, 0.86467392]

Что интересно — транспонирование не даст нужного результата, так как h — одномерный массив.

Можно поступить так: вычислить двумерный массив и вывести его в консоль напрямую:

>>> result = 1-h[:, np.newaxis] ** np.arange(1,100)
>>> result
array([[0.01, 0.0199, 0.029701, ..., 0.62653572, 0.63027036], 
[0.02, 0.0396, 0.058808, ..., 0.86191217, 0.86467392], 
[0.03, 0.0591, 0.087327, ..., 0.94946061, 0.9509768], ..., 
[0.97, 0.9991, 0.999973, ..., 1., 1., 1.], 
[0.98, 0.9996, 0.999992, ..., 1., 1., 1.], 
[0.99, 0.9999, 0.999999, ..., 1., 1.,1.]])

Как видите, мы только что вычислили кумулятивную биномиальную вероятность для интервала от 0.01 до 0.99.

Также использование цикла for облегчило нам задачу, ведь мы автоматически вычислили нужные нам вероятности — от 0.01 до 0.99! Делать все это вручную — задача сложная и неблагодарная. 

Вывод

Итак, мы научились:

  • вычислять кумулятивную биномиальную вероятность.
  • использовать циклы for для перебора огромного диапазона значений. 
  • работать с float при помощи генератора списков.
  • работать с двумерными массивами, если транспонирование одномерного не имеет смысла.