## Libraries

from typing import Tuple
import math as m

import pandas as pd
import numpy as np


## Probability below a threshold

$p = \frac {1 + \text{erf} \ z ( \frac {x - \mu} {\sqrt{2} \sigma} )} {2}$

where

$\text{erf} \ z = \frac {2} {\sqrt{\pi}} \ \int_0^z e^{-t^2} dt$

is the error function.

def calc_normal_cdf(x: float, mu: float = 0, sigma: float = 1) -> float:
return (1 + m.erf((x - mu) / m.sqrt(2) / sigma)) / 2

normal_probability_below = calc_normal_cdf

for i in [n / 10 for n in range(-10, 10 + 1, 1)]:
print("\t".join([str(i), f"{normal_probability_below(i):.4f}"]))

-1.0	0.1587
-0.9	0.1841
-0.8	0.2119
-0.7	0.2420
-0.6	0.2743
-0.5	0.3085
-0.4	0.3446
-0.3	0.3821
-0.2	0.4207
-0.1	0.4602
0.0	0.5000
0.1	0.5398
0.2	0.5793
0.3	0.6179
0.4	0.6554
0.5	0.6915
0.6	0.7257
0.7	0.7580
0.8	0.7881
0.9	0.8159
1.0	0.8413


## Probability above a threshold

def normal_probability_above(lo: float, mu: float = 0, sigma: float = 1) -> float:
return 1 - normal_probability_below(lo, mu, sigma)

for i in [n / 10 for n in range(-10, 10 + 1, 1)]:
print("\t".join([str(i), f"{normal_probability_above(i):.4f}"]))

-1.0	0.8413
-0.9	0.8159
-0.8	0.7881
-0.7	0.7580
-0.6	0.7257
-0.5	0.6915
-0.4	0.6554
-0.3	0.6179
-0.2	0.5793
-0.1	0.5398
0.0	0.5000
0.1	0.4602
0.2	0.4207
0.3	0.3821
0.4	0.3446
0.5	0.3085
0.6	0.2743
0.7	0.2420
0.8	0.2119
0.9	0.1841
1.0	0.1587


## Probability between thresholds

domain = np.arange(-10, 10 + 1, 2) / 10
domain

array([-1. , -0.8, -0.6, -0.4, -0.2,  0. ,  0.2,  0.4,  0.6,  0.8,  1. ])
def normal_probability_between(lo: float, hi: float, mu: float = 0, sigma: float = 1) -> float:
return normal_probability_below(hi, mu, sigma) - normal_probability_below(lo, mu, sigma)

probabilities_between = pd.DataFrame()

for i in domain:
for j in domain:
probabilities_between.loc[i, j] = normal_probability_between(i, j)

probabilities_between = pd.DataFrame(np.triu(probabilities_between), index=domain,
columns=domain).replace(0, np.NaN)

for i in domain:
probabilities_between.loc[i, i] = 0

probabilities_between

-1.0 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 1.0
-1.0 0.0 0.0532 0.115598 0.185923 0.262085 0.341345 0.420604 0.496766 0.567092 0.629489 0.682689
-0.8 NaN 0.0000 0.062398 0.132723 0.208885 0.288145 0.367404 0.443566 0.513891 0.576289 0.629489
-0.6 NaN NaN 0.000000 0.070325 0.146487 0.225747 0.305007 0.381169 0.451494 0.513891 0.567092
-0.4 NaN NaN NaN 0.000000 0.076162 0.155422 0.234681 0.310843 0.381169 0.443566 0.496766
-0.2 NaN NaN NaN NaN 0.000000 0.079260 0.158519 0.234681 0.305007 0.367404 0.420604
0.0 NaN NaN NaN NaN NaN 0.000000 0.079260 0.155422 0.225747 0.288145 0.341345
0.2 NaN NaN NaN NaN NaN NaN 0.000000 0.076162 0.146487 0.208885 0.262085
0.4 NaN NaN NaN NaN NaN NaN NaN 0.000000 0.070325 0.132723 0.185923
0.6 NaN NaN NaN NaN NaN NaN NaN NaN 0.000000 0.062398 0.115598
0.8 NaN NaN NaN NaN NaN NaN NaN NaN NaN 0.000000 0.053200
1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 0.000000

## Probabilities outside a threshold range

def normal_probability_outside(lo: float, hi: float, mu: float = 0, sigma: float = 1) -> float:
return 1 - normal_probability_between(lo, hi, mu, sigma)

probabilities_outside = pd.DataFrame()

for i in domain:
for j in domain:
probabilities_outside.loc[i, j] = normal_probability_outside(i, j)

pd.DataFrame(np.triu(probabilities_outside), index=domain, columns=domain).replace(0, np.NaN)

-1.0 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 1.0
-1.0 1.0 0.9468 0.884402 0.814077 0.737915 0.658655 0.579396 0.503234 0.432908 0.370511 0.317311
-0.8 NaN 1.0000 0.937602 0.867277 0.791115 0.711855 0.632596 0.556434 0.486109 0.423711 0.370511
-0.6 NaN NaN 1.000000 0.929675 0.853513 0.774253 0.694993 0.618831 0.548506 0.486109 0.432908
-0.4 NaN NaN NaN 1.000000 0.923838 0.844578 0.765319 0.689157 0.618831 0.556434 0.503234
-0.2 NaN NaN NaN NaN 1.000000 0.920740 0.841481 0.765319 0.694993 0.632596 0.579396
0.0 NaN NaN NaN NaN NaN 1.000000 0.920740 0.844578 0.774253 0.711855 0.658655
0.2 NaN NaN NaN NaN NaN NaN 1.000000 0.923838 0.853513 0.791115 0.737915
0.4 NaN NaN NaN NaN NaN NaN NaN 1.000000 0.929675 0.867277 0.814077
0.6 NaN NaN NaN NaN NaN NaN NaN NaN 1.000000 0.937602 0.884402
0.8 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.000000 0.946800
1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.000000