Mise en œuvre D'un test Smirnov de Kolmogorov en python scipy

j'ai un ensemble de données sur N nombres que je veux tester pour la normalité. Je connais scipy.stats a une fonction kstest mais il n'y a pas d'exemples sur la façon de l'utiliser et comment interpréter les résultats. Quelqu'un ici le connaît-il et peut-il me donner des conseils?

selon la documentation, en utilisant kstest renvoie deux nombres, la statistique de test KS D et la valeur p. Si la valeur de p est supérieure au niveau de signification (disons 5%), alors nous ne peut pas rejeter l'hypothèse que les données proviennent de la distribution donnée.

quand je fais un essai en traçant 10000 échantillons d'une distribution normale et en testant la gaussianité:

import numpy as np
from scipy.stats import kstest

mu,sigma = 0.07, 0.89
kstest(np.random.normal(mu,sigma,10000),'norm')

j'obtiens la sortie suivante:

(0.04957880905196102, 8.9249710700788814 e-22)

La p-valeur est inférieure à 5%, ce qui signifie que nous pouvons rejeter l'hypothèse que les données sont normalement distribués. Mais les échantillons ont été tirés d'une distribution normale!

est-ce que quelqu'un peut me comprendre et m'expliquer la divergence ici?

(test de normalité assumer mu = 0 et sigma = 1? Si oui, comment puis-je tester que mes données sont distribuées de manière gaussienne mais avec un mu et un sigma différents?)

22
demandé sur Hooloovoo 2011-10-26 18:16:15

4 réponses

vos données ont été générées avec mu=0,07 et sigma=0,89. Vous testez ces données par rapport à une distribution normale avec une moyenne 0 et un écart-type de 1.

l'hypothèse nulle ( H0 ) est que la distribution dont vos données constituent un échantillon est égale à la distribution normale standard avec moyenne 0, Écart type 1.

la petite valeur de p indique qu'une statistique de test aussi grande que D serait attendue avec Probabilité la p-valeur.

en d'autres termes, (valeur p ~8,9 e-22) Il est très peu probable que H0 soit vrai.

c'est raisonnable, puisque les écarts moyens et std ne correspondent pas.

Comparez votre résultat avec:

In [22]: import numpy as np
In [23]: import scipy.stats as stats
In [24]: stats.kstest(np.random.normal(0,1,10000),'norm')
Out[24]: (0.007038739782416259, 0.70477679457831155)

pour tester vos données est gaussien, vous pouvez décaler et rééchelonner il est donc normal avec moyenne 0 et écart std 1:

data=np.random.normal(mu,sigma,10000)
normed_data=(data-mu)/sigma
print(stats.kstest(normed_data,'norm'))
# (0.0085805670733036798, 0.45316245879609179)

Avertissement: ( un grand merci à user333700 (aka scipy développeur Josef Perktold )) Si vous ne savez pas mu et sigma , l'estimation des paramètres rend la p-valeur non valide:

import numpy as np
import scipy.stats as stats

mu = 0.3
sigma = 5

num_tests = 10**5
num_rejects = 0
alpha = 0.05
for i in xrange(num_tests):
    data = np.random.normal(mu, sigma, 10000)
    # normed_data = (data - mu) / sigma    # this is okay
    # 4915/100000 = 0.05 rejects at rejection level 0.05 (as expected)
    normed_data = (data - data.mean()) / data.std()    # this is NOT okay
    # 20/100000 = 0.00 rejects at rejection level 0.05 (not expected)
    D, pval = stats.kstest(normed_data, 'norm')
    if pval < alpha:
        num_rejects += 1
ratio = float(num_rejects) / num_tests
print('{}/{} = {:.2f} rejects at rejection level {}'.format(
    num_rejects, num_tests, ratio, alpha))     

imprime

20/100000 = 0.00 rejects at rejection level 0.05 (not expected)

qui montre que stats.kstest ne peut pas rejeter le nombre d'hypothèses nulles si l'échantillon est normalisée à l'aide de la moyenne et écart type de l'échantillon

normed_data = (data - data.mean()) / data.std()    # this is NOT okay
21
répondu unutbu 2017-09-10 18:41:33

mise à jour sur la réponse d'unutbu:

pour les distributions qui dépendent seulement de l'emplacement et de l'échelle, mais qui n'ont pas de paramètre de forme, les distributions de plusieurs statistiques de test de la qualité de l'ajustement sont indépendantes des valeurs de l'emplacement et de l'échelle. La distribution n'est pas standard, mais elle peut être mise en tableaux et utilisée avec n'importe quel emplacement et échelle de la distribution sous-jacente.

le test de Kolmogorov-Smirnov pour la distribution normale avec l'emplacement et l'échelle estimés sont également appelés lilliefors test .

il est maintenant disponible en modèles statistiques, avec des valeurs approximatives de p pour la gamme de décision pertinente.

>>> import numpy as np
>>> mu,sigma = 0.07, 0.89
>>> x = np.random.normal(mu, sigma, 10000)
>>> import statsmodels.api as sm
>>> sm.stats.lilliefors(x)
(0.0055267411213540951, 0.66190841161592895)

la plupart des études de Monte Carlo montrent que le test Anderson-Darling est plus puissant que le test Kolmogorov-Smirnov. Il est disponible en scipy.statistiques avec valeurs critiques, et dans les modèles de statistiques avec des valeurs approximatives de p:

>>> sm.stats.normal_ad(x)
(0.23016468240712129, 0.80657628536145665)

Ni de le test rejette l'hypothèse Nulle que l'échantillon est normal distribués. Alors que le kstest dans la question rejette L'hypothèse nulle que l'échantillon est normal standard distribué.

9
répondu Josef 2017-09-10 19:26:03

vous pouvez également envisager d'utiliser le test de Shapiro-Wilk, qui "vérifie l'hypothèse nulle que les données ont été tirées d'une distribution normale."Il est également mis en place de scipy :

http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.shapiro.html

Vous aurez besoin de passer vos données directement dans la fonction.

import scipy

W, p = scipy.stats.shapiro(dataset)
print("Shapiro-Wilk test statistic, W:", W, "\n", "p-value:", p)

qui renvoie quelque chose comme:

 Shapiro-Wilk test statistic, W: 0.7761164903640747 
 p-value: 6.317247641091492e-37

avec p < < 0,01 (ou 0,05, Si vous préférez - cela n'a pas d'importance,) nous avons de bonnes raisons de rejeter l'hypothèse nulle que ces données ont été tirées d'une distribution normale.

3
répondu D. Betchkal 2016-03-29 16:51:06

en complément de la réponse de @unutbu , vous pouvez également fournir les paramètres de distribution pour la distribution test dans kstest. Supposons que nous ayons des échantillons d'une variable (et que nous les appelions datax), et que nous voulions vérifier si ces échantillons ne pouvaient pas provenir d'un lognormal, d'un uniforme ou d'une normale. Notez que pour les statistiques scipy, la façon dont les paramètres d'entrée sont pris pour chaque distribution varie un peu. Maintenant, grâce à "args" (tuple ou séquence) dans kstest, est possible fournir les arguments en faveur de la scipy.vous voulez tester la distribution de stats.

:) j'ai également ajouté l'option d'utiliser un test à deux échantillons, au cas où vous vouliez le faire de toute façon:

import numpy as np
from math import sqrt
from scipy.stats import kstest, ks_2samp, lognorm
import scipy.stats

def KSSeveralDists(data,dists_and_args,samplesFromDists=100,twosampleKS=True):
    returnable={}
    for dist in dists_and_args:
        try:
            if twosampleKS:
                try:
                    loc=dists_and_args[dist][0]
                    scale=dists_and_args[dist][1]
                    expression='scipy.stats.'+dist+'.rvs(loc=loc,scale=scale,size=samplesFromDists)'
                    sampledDist=eval(expression)
                except:
                    sc=dists_and_args[dist][0]
                    loc=dists_and_args[dist][1]
                    scale=dists_and_args[dist][2]
                    expression='scipy.stats.'+dist+'.rvs(sc,loc=loc,scale=scale,size=samplesFromDists)'
                    sampledDist=eval(expression)
                D,p=ks_2samp(data,sampledDist)
            else:
                D,p=kstest(data,dist,N=samplesFromDists,args=dists_and_args[dist])
        except:
            continue
        returnable[dist]={'KS':D,'p-value':p}
    return returnable

a=lambda m,std: m-std*sqrt(12.)/2.
b=lambda m,std: m+std*sqrt(12.)/2.
sz=2000

sc=0.5 #shape 
datax=lognorm.rvs(sc,loc=0.,scale=1.,size=sz)
normalargs=(datax.mean(),datax.std())

#suppose these are the parameters you wanted to pass for each distribution
dists_and_args={'norm':normalargs,
               'uniform':(a(*normalargs),b(*normalargs)),
               'lognorm':[0.5,0.,1.]
              }
print "two sample KS:"
print KSSeveralDists(datax,dists_and_args,samplesFromDists=sz,twosampleKS=True)
print "one sample KS:"
print KSSeveralDists(datax,dists_and_args,samplesFromDists=sz,twosampleKS=False)

qui donne comme sortie quelque chose comme:

deux échantillons KS: {'lognorm': {'KS': 0.02349999999965, 'p-value': 0.63384188886455217},' norm': {'KS': 0.1060000000004, 'p-value': 2.9187666723155 e-10}, 'uniform': {'KS':: 0.15300000000000002, 'p-value': 6.443660021191129 e-21}}

un échantillon KS: {'lognorm': {'KS': 0.01763415915126032, "p-value": 0.56275820961065193}, 'norme': {'KS': 0.10792612430093562, "p-value": 0.0}, "uniforme": {'KS': 0.14910036159697559, "p-value": 0.0}}

Note: pour le scipy.distribution uniforme des statistiques, a et b sont pris comme a=loc et b=Loc + scale (voir documentation ).

1
répondu lia-statsletters 2016-10-26 09:05:02