@@ -812,15 +812,15 @@ class NormalDist:
812
812
# https://en.wikipedia.org/wiki/Normal_distribution
813
813
# https://en.wikipedia.org/wiki/Variance#Properties
814
814
815
- __slots__ = {'mu ' : 'Arithmetic mean of a normal distribution' ,
816
- 'sigma ' : 'Standard deviation of a normal distribution' }
815
+ __slots__ = {'_mu ' : 'Arithmetic mean of a normal distribution' ,
816
+ '_sigma ' : 'Standard deviation of a normal distribution' }
817
817
818
818
def __init__ (self , mu = 0.0 , sigma = 1.0 ):
819
819
'NormalDist where mu is the mean and sigma is the standard deviation.'
820
820
if sigma < 0.0 :
821
821
raise StatisticsError ('sigma must be non-negative' )
822
- self .mu = mu
823
- self .sigma = sigma
822
+ self ._mu = mu
823
+ self ._sigma = sigma
824
824
825
825
@classmethod
826
826
def from_samples (cls , data ):
@@ -833,21 +833,21 @@ def from_samples(cls, data):
833
833
def samples (self , n , * , seed = None ):
834
834
'Generate *n* samples for a given mean and standard deviation.'
835
835
gauss = random .gauss if seed is None else random .Random (seed ).gauss
836
- mu , sigma = self .mu , self .sigma
836
+ mu , sigma = self ._mu , self ._sigma
837
837
return [gauss (mu , sigma ) for i in range (n )]
838
838
839
839
def pdf (self , x ):
840
840
'Probability density function. P(x <= X < x+dx) / dx'
841
- variance = self .sigma ** 2.0
841
+ variance = self ._sigma ** 2.0
842
842
if not variance :
843
843
raise StatisticsError ('pdf() not defined when sigma is zero' )
844
- return exp ((x - self .mu )** 2.0 / (- 2.0 * variance )) / sqrt (tau * variance )
844
+ return exp ((x - self ._mu )** 2.0 / (- 2.0 * variance )) / sqrt (tau * variance )
845
845
846
846
def cdf (self , x ):
847
847
'Cumulative distribution function. P(X <= x)'
848
- if not self .sigma :
848
+ if not self ._sigma :
849
849
raise StatisticsError ('cdf() not defined when sigma is zero' )
850
- return 0.5 * (1.0 + erf ((x - self .mu ) / (self .sigma * sqrt (2.0 ))))
850
+ return 0.5 * (1.0 + erf ((x - self ._mu ) / (self ._sigma * sqrt (2.0 ))))
851
851
852
852
def inv_cdf (self , p ):
853
853
'''Inverse cumulative distribution function. x : P(X <= x) = p
@@ -859,7 +859,7 @@ def inv_cdf(self, p):
859
859
'''
860
860
if (p <= 0.0 or p >= 1.0 ):
861
861
raise StatisticsError ('p must be in the range 0.0 < p < 1.0' )
862
- if self .sigma <= 0.0 :
862
+ if self ._sigma <= 0.0 :
863
863
raise StatisticsError ('cdf() not defined when sigma at or below zero' )
864
864
865
865
# There is no closed-form solution to the inverse CDF for the normal
@@ -888,7 +888,7 @@ def inv_cdf(self, p):
888
888
4.23133_30701_60091_1252e+1 ) * r +
889
889
1.0 )
890
890
x = num / den
891
- return self .mu + (x * self .sigma )
891
+ return self ._mu + (x * self ._sigma )
892
892
r = p if q <= 0.0 else 1.0 - p
893
893
r = sqrt (- log (r ))
894
894
if r <= 5.0 :
@@ -930,7 +930,7 @@ def inv_cdf(self, p):
930
930
x = num / den
931
931
if q < 0.0 :
932
932
x = - x
933
- return self .mu + (x * self .sigma )
933
+ return self ._mu + (x * self ._sigma )
934
934
935
935
def overlap (self , other ):
936
936
'''Compute the overlapping coefficient (OVL) between two normal distributions.
@@ -951,35 +951,35 @@ def overlap(self, other):
951
951
if not isinstance (other , NormalDist ):
952
952
raise TypeError ('Expected another NormalDist instance' )
953
953
X , Y = self , other
954
- if (Y .sigma , Y .mu ) < (X .sigma , X .mu ): # sort to assure commutativity
954
+ if (Y ._sigma , Y ._mu ) < (X ._sigma , X ._mu ): # sort to assure commutativity
955
955
X , Y = Y , X
956
956
X_var , Y_var = X .variance , Y .variance
957
957
if not X_var or not Y_var :
958
958
raise StatisticsError ('overlap() not defined when sigma is zero' )
959
959
dv = Y_var - X_var
960
- dm = fabs (Y .mu - X .mu )
960
+ dm = fabs (Y ._mu - X ._mu )
961
961
if not dv :
962
- return 1.0 - erf (dm / (2.0 * X .sigma * sqrt (2.0 )))
963
- a = X .mu * Y_var - Y .mu * X_var
964
- b = X .sigma * Y .sigma * sqrt (dm ** 2.0 + dv * log (Y_var / X_var ))
962
+ return 1.0 - erf (dm / (2.0 * X ._sigma * sqrt (2.0 )))
963
+ a = X ._mu * Y_var - Y ._mu * X_var
964
+ b = X ._sigma * Y ._sigma * sqrt (dm ** 2.0 + dv * log (Y_var / X_var ))
965
965
x1 = (a + b ) / dv
966
966
x2 = (a - b ) / dv
967
967
return 1.0 - (fabs (Y .cdf (x1 ) - X .cdf (x1 )) + fabs (Y .cdf (x2 ) - X .cdf (x2 )))
968
968
969
969
@property
970
970
def mean (self ):
971
971
'Arithmetic mean of the normal distribution.'
972
- return self .mu
972
+ return self ._mu
973
973
974
974
@property
975
975
def stdev (self ):
976
976
'Standard deviation of the normal distribution.'
977
- return self .sigma
977
+ return self ._sigma
978
978
979
979
@property
980
980
def variance (self ):
981
981
'Square of the standard deviation.'
982
- return self .sigma ** 2.0
982
+ return self ._sigma ** 2.0
983
983
984
984
def __add__ (x1 , x2 ):
985
985
'''Add a constant or another NormalDist instance.
@@ -992,8 +992,8 @@ def __add__(x1, x2):
992
992
independent or if they are jointly normally distributed.
993
993
'''
994
994
if isinstance (x2 , NormalDist ):
995
- return NormalDist (x1 .mu + x2 .mu , hypot (x1 .sigma , x2 .sigma ))
996
- return NormalDist (x1 .mu + x2 , x1 .sigma )
995
+ return NormalDist (x1 ._mu + x2 ._mu , hypot (x1 ._sigma , x2 ._sigma ))
996
+ return NormalDist (x1 ._mu + x2 , x1 ._sigma )
997
997
998
998
def __sub__ (x1 , x2 ):
999
999
'''Subtract a constant or another NormalDist instance.
@@ -1006,32 +1006,32 @@ def __sub__(x1, x2):
1006
1006
independent or if they are jointly normally distributed.
1007
1007
'''
1008
1008
if isinstance (x2 , NormalDist ):
1009
- return NormalDist (x1 .mu - x2 .mu , hypot (x1 .sigma , x2 .sigma ))
1010
- return NormalDist (x1 .mu - x2 , x1 .sigma )
1009
+ return NormalDist (x1 ._mu - x2 ._mu , hypot (x1 ._sigma , x2 ._sigma ))
1010
+ return NormalDist (x1 ._mu - x2 , x1 ._sigma )
1011
1011
1012
1012
def __mul__ (x1 , x2 ):
1013
1013
'''Multiply both mu and sigma by a constant.
1014
1014
1015
1015
Used for rescaling, perhaps to change measurement units.
1016
1016
Sigma is scaled with the absolute value of the constant.
1017
1017
'''
1018
- return NormalDist (x1 .mu * x2 , x1 .sigma * fabs (x2 ))
1018
+ return NormalDist (x1 ._mu * x2 , x1 ._sigma * fabs (x2 ))
1019
1019
1020
1020
def __truediv__ (x1 , x2 ):
1021
1021
'''Divide both mu and sigma by a constant.
1022
1022
1023
1023
Used for rescaling, perhaps to change measurement units.
1024
1024
Sigma is scaled with the absolute value of the constant.
1025
1025
'''
1026
- return NormalDist (x1 .mu / x2 , x1 .sigma / fabs (x2 ))
1026
+ return NormalDist (x1 ._mu / x2 , x1 ._sigma / fabs (x2 ))
1027
1027
1028
1028
def __pos__ (x1 ):
1029
1029
'Return a copy of the instance.'
1030
- return NormalDist (x1 .mu , x1 .sigma )
1030
+ return NormalDist (x1 ._mu , x1 ._sigma )
1031
1031
1032
1032
def __neg__ (x1 ):
1033
1033
'Negates mu while keeping sigma the same.'
1034
- return NormalDist (- x1 .mu , x1 .sigma )
1034
+ return NormalDist (- x1 ._mu , x1 ._sigma )
1035
1035
1036
1036
__radd__ = __add__
1037
1037
@@ -1045,10 +1045,14 @@ def __eq__(x1, x2):
1045
1045
'Two NormalDist objects are equal if their mu and sigma are both equal.'
1046
1046
if not isinstance (x2 , NormalDist ):
1047
1047
return NotImplemented
1048
- return (x1 .mu , x2 .sigma ) == (x2 .mu , x2 .sigma )
1048
+ return (x1 ._mu , x2 ._sigma ) == (x2 ._mu , x2 ._sigma )
1049
+
1050
+ def __hash__ (self ):
1051
+ 'NormalDist objects hash equal if their mu and sigma are both equal.'
1052
+ return hash ((self ._mu , self ._sigma ))
1049
1053
1050
1054
def __repr__ (self ):
1051
- return f'{ type (self ).__name__ } (mu={ self .mu !r} , sigma={ self .sigma !r} )'
1055
+ return f'{ type (self ).__name__ } (mu={ self ._mu !r} , sigma={ self ._sigma !r} )'
1052
1056
1053
1057
1054
1058
if __name__ == '__main__' :
@@ -1065,8 +1069,8 @@ def __repr__(self):
1065
1069
g2 = NormalDist (- 5 , 25 )
1066
1070
1067
1071
# Test scaling by a constant
1068
- assert (g1 * 5 / 5 ).mu == g1 .mu
1069
- assert (g1 * 5 / 5 ).sigma == g1 .sigma
1072
+ assert (g1 * 5 / 5 ).mean == g1 .mean
1073
+ assert (g1 * 5 / 5 ).stdev == g1 .stdev
1070
1074
1071
1075
n = 100_000
1072
1076
G1 = g1 .samples (n )
@@ -1090,8 +1094,8 @@ def __repr__(self):
1090
1094
print (NormalDist .from_samples (map (func , repeat (const ), G1 )))
1091
1095
1092
1096
def assert_close (G1 , G2 ):
1093
- assert isclose (G1 .mu , G1 .mu , rel_tol = 0.01 ), (G1 , G2 )
1094
- assert isclose (G1 .sigma , G2 .sigma , rel_tol = 0.01 ), (G1 , G2 )
1097
+ assert isclose (G1 .mean , G1 .mean , rel_tol = 0.01 ), (G1 , G2 )
1098
+ assert isclose (G1 .stdev , G2 .stdev , rel_tol = 0.01 ), (G1 , G2 )
1095
1099
1096
1100
X = NormalDist (- 105 , 73 )
1097
1101
Y = NormalDist (31 , 47 )
0 commit comments