1'''More Goodness of fit tests
2
3contains
4
5GOF : 1 sample gof tests based on Stephens 1970, plus AD A^2
6bootstrap : vectorized bootstrap p-values for gof test with fitted parameters
7
8
9Created : 2011-05-21
10Author : Josef Perktold
11
12parts based on ks_2samp and kstest from scipy.stats
13(license: Scipy BSD, but were completely rewritten by Josef Perktold)
14
15
16References
17----------
18
19'''
20from statsmodels.compat.python import lmap
21import numpy as np
22
23from scipy.stats import distributions
24
25from statsmodels.tools.decorators import cache_readonly
26
27from scipy.special import kolmogorov as ksprob
28
29#from scipy.stats unchanged
30def ks_2samp(data1, data2):
31    """
32    Computes the Kolmogorov-Smirnof statistic on 2 samples.
33
34    This is a two-sided test for the null hypothesis that 2 independent samples
35    are drawn from the same continuous distribution.
36
37    Parameters
38    ----------
39    a, b : sequence of 1-D ndarrays
40        two arrays of sample observations assumed to be drawn from a continuous
41        distribution, sample sizes can be different
42
43
44    Returns
45    -------
46    D : float
47        KS statistic
48    p-value : float
49        two-tailed p-value
50
51
52    Notes
53    -----
54
55    This tests whether 2 samples are drawn from the same distribution. Note
56    that, like in the case of the one-sample K-S test, the distribution is
57    assumed to be continuous.
58
59    This is the two-sided test, one-sided tests are not implemented.
60    The test uses the two-sided asymptotic Kolmogorov-Smirnov distribution.
61
62    If the K-S statistic is small or the p-value is high, then we cannot
63    reject the hypothesis that the distributions of the two samples
64    are the same.
65
66    Examples
67    --------
68
69    >>> from scipy import stats
70    >>> import numpy as np
71    >>> from scipy.stats import ks_2samp
72
73    >>> #fix random seed to get the same result
74    >>> np.random.seed(12345678)
75
76    >>> n1 = 200  # size of first sample
77    >>> n2 = 300  # size of second sample
78
79    different distribution
80    we can reject the null hypothesis since the pvalue is below 1%
81
82    >>> rvs1 = stats.norm.rvs(size=n1,loc=0.,scale=1)
83    >>> rvs2 = stats.norm.rvs(size=n2,loc=0.5,scale=1.5)
84    >>> ks_2samp(rvs1,rvs2)
85    (0.20833333333333337, 4.6674975515806989e-005)
86
87    slightly different distribution
88    we cannot reject the null hypothesis at a 10% or lower alpha since
89    the pvalue at 0.144 is higher than 10%
90
91    >>> rvs3 = stats.norm.rvs(size=n2,loc=0.01,scale=1.0)
92    >>> ks_2samp(rvs1,rvs3)
93    (0.10333333333333333, 0.14498781825751686)
94
95    identical distribution
96    we cannot reject the null hypothesis since the pvalue is high, 41%
97
98    >>> rvs4 = stats.norm.rvs(size=n2,loc=0.0,scale=1.0)
99    >>> ks_2samp(rvs1,rvs4)
100    (0.07999999999999996, 0.41126949729859719)
101    """
102    data1, data2 = lmap(np.asarray, (data1, data2))
103    n1 = data1.shape[0]
104    n2 = data2.shape[0]
105    n1 = len(data1)
106    n2 = len(data2)
107    data1 = np.sort(data1)
108    data2 = np.sort(data2)
109    data_all = np.concatenate([data1,data2])
110    #reminder: searchsorted inserts 2nd into 1st array
111    cdf1 = np.searchsorted(data1,data_all,side='right')/(1.0*n1)
112    cdf2 = (np.searchsorted(data2,data_all,side='right'))/(1.0*n2)
113    d = np.max(np.absolute(cdf1-cdf2))
114    #Note: d absolute not signed distance
115    en = np.sqrt(n1*n2/float(n1+n2))
116    try:
117        prob = ksprob((en+0.12+0.11/en)*d)
118    except:
119        prob = 1.0
120    return d, prob
121
122
123
124#from scipy.stats unchanged
125def kstest(rvs, cdf, args=(), N=20, alternative = 'two_sided', mode='approx',**kwds):
126    """
127    Perform the Kolmogorov-Smirnov test for goodness of fit
128
129    This performs a test of the distribution G(x) of an observed
130    random variable against a given distribution F(x). Under the null
131    hypothesis the two distributions are identical, G(x)=F(x). The
132    alternative hypothesis can be either 'two_sided' (default), 'less'
133    or 'greater'. The KS test is only valid for continuous distributions.
134
135    Parameters
136    ----------
137    rvs : str or array or callable
138        string: name of a distribution in scipy.stats
139
140        array: 1-D observations of random variables
141
142        callable: function to generate random variables, requires keyword
143        argument `size`
144
145    cdf : str or callable
146        string: name of a distribution in scipy.stats, if rvs is a string then
147        cdf can evaluate to `False` or be the same as rvs
148        callable: function to evaluate cdf
149
150    args : tuple, sequence
151        distribution parameters, used if rvs or cdf are strings
152    N : int
153        sample size if rvs is string or callable
154    alternative : 'two_sided' (default), 'less' or 'greater'
155        defines the alternative hypothesis (see explanation)
156
157    mode : 'approx' (default) or 'asymp'
158        defines the distribution used for calculating p-value
159
160        'approx' : use approximation to exact distribution of test statistic
161
162        'asymp' : use asymptotic distribution of test statistic
163
164
165    Returns
166    -------
167    D : float
168        KS test statistic, either D, D+ or D-
169    p-value :  float
170        one-tailed or two-tailed p-value
171
172    Notes
173    -----
174
175    In the one-sided test, the alternative is that the empirical
176    cumulative distribution function of the random variable is "less"
177    or "greater" than the cumulative distribution function F(x) of the
178    hypothesis, G(x)<=F(x), resp. G(x)>=F(x).
179
180    Examples
181    --------
182
183    >>> from scipy import stats
184    >>> import numpy as np
185    >>> from scipy.stats import kstest
186
187    >>> x = np.linspace(-15,15,9)
188    >>> kstest(x,'norm')
189    (0.44435602715924361, 0.038850142705171065)
190
191    >>> np.random.seed(987654321) # set random seed to get the same result
192    >>> kstest('norm','',N=100)
193    (0.058352892479417884, 0.88531190944151261)
194
195    is equivalent to this
196
197    >>> np.random.seed(987654321)
198    >>> kstest(stats.norm.rvs(size=100),'norm')
199    (0.058352892479417884, 0.88531190944151261)
200
201    Test against one-sided alternative hypothesis:
202
203    >>> np.random.seed(987654321)
204
205    Shift distribution to larger values, so that cdf_dgp(x)< norm.cdf(x):
206
207    >>> x = stats.norm.rvs(loc=0.2, size=100)
208    >>> kstest(x,'norm', alternative = 'less')
209    (0.12464329735846891, 0.040989164077641749)
210
211    Reject equal distribution against alternative hypothesis: less
212
213    >>> kstest(x,'norm', alternative = 'greater')
214    (0.0072115233216311081, 0.98531158590396395)
215
216    Do not reject equal distribution against alternative hypothesis: greater
217
218    >>> kstest(x,'norm', mode='asymp')
219    (0.12464329735846891, 0.08944488871182088)
220
221
222    Testing t distributed random variables against normal distribution:
223
224    With 100 degrees of freedom the t distribution looks close to the normal
225    distribution, and the kstest does not reject the hypothesis that the sample
226    came from the normal distribution
227
228    >>> np.random.seed(987654321)
229    >>> stats.kstest(stats.t.rvs(100,size=100),'norm')
230    (0.072018929165471257, 0.67630062862479168)
231
232    With 3 degrees of freedom the t distribution looks sufficiently different
233    from the normal distribution, that we can reject the hypothesis that the
234    sample came from the normal distribution at a alpha=10% level
235
236    >>> np.random.seed(987654321)
237    >>> stats.kstest(stats.t.rvs(3,size=100),'norm')
238    (0.131016895759829, 0.058826222555312224)
239    """
240    if isinstance(rvs, str):
241        #cdf = getattr(stats, rvs).cdf
242        if (not cdf) or (cdf == rvs):
243            cdf = getattr(distributions, rvs).cdf
244            rvs = getattr(distributions, rvs).rvs
245        else:
246            raise AttributeError('if rvs is string, cdf has to be the same distribution')
247
248
249    if isinstance(cdf, str):
250        cdf = getattr(distributions, cdf).cdf
251    if callable(rvs):
252        kwds = {'size':N}
253        vals = np.sort(rvs(*args,**kwds))
254    else:
255        vals = np.sort(rvs)
256        N = len(vals)
257    cdfvals = cdf(vals, *args)
258
259    if alternative in ['two_sided', 'greater']:
260        Dplus = (np.arange(1.0, N+1)/N - cdfvals).max()
261        if alternative == 'greater':
262            return Dplus, distributions.ksone.sf(Dplus,N)
263
264    if alternative in ['two_sided', 'less']:
265        Dmin = (cdfvals - np.arange(0.0, N)/N).max()
266        if alternative == 'less':
267            return Dmin, distributions.ksone.sf(Dmin,N)
268
269    if alternative == 'two_sided':
270        D = np.max([Dplus,Dmin])
271        if mode == 'asymp':
272            return D, distributions.kstwobign.sf(D*np.sqrt(N))
273        if mode == 'approx':
274            pval_two = distributions.kstwobign.sf(D*np.sqrt(N))
275            if N > 2666 or pval_two > 0.80 - N*0.3/1000.0 :
276                return D, distributions.kstwobign.sf(D*np.sqrt(N))
277            else:
278                return D, distributions.ksone.sf(D,N)*2
279
280#TODO: split into modification and pvalue functions separately ?
281#      for separate testing and combining different pieces
282
283def dplus_st70_upp(stat, nobs):
284    mod_factor = np.sqrt(nobs) + 0.12 + 0.11 / np.sqrt(nobs)
285    stat_modified = stat * mod_factor
286    pval = np.exp(-2 * stat_modified**2)
287    digits = np.sum(stat > np.array([0.82, 0.82, 1.00]))
288    #repeat low to get {0,2,3}
289    return stat_modified, pval, digits
290
291dminus_st70_upp = dplus_st70_upp
292
293
294def d_st70_upp(stat, nobs):
295    mod_factor = np.sqrt(nobs) + 0.12 + 0.11 / np.sqrt(nobs)
296    stat_modified = stat * mod_factor
297    pval = 2 * np.exp(-2 * stat_modified**2)
298    digits = np.sum(stat > np.array([0.91, 0.91, 1.08]))
299    #repeat low to get {0,2,3}
300    return stat_modified, pval, digits
301
302def v_st70_upp(stat, nobs):
303    mod_factor = np.sqrt(nobs) + 0.155 + 0.24 / np.sqrt(nobs)
304    #repeat low to get {0,2,3}
305    stat_modified = stat * mod_factor
306    zsqu = stat_modified**2
307    pval = (8 * zsqu - 2) * np.exp(-2 * zsqu)
308    digits = np.sum(stat > np.array([1.06, 1.06, 1.26]))
309    return stat_modified, pval, digits
310
311def wsqu_st70_upp(stat, nobs):
312    nobsinv = 1. / nobs
313    stat_modified = (stat - 0.4 * nobsinv + 0.6 * nobsinv**2) * (1 + nobsinv)
314    pval = 0.05 * np.exp(2.79 - 6 * stat_modified)
315    digits = np.nan  # some explanation in txt
316    #repeat low to get {0,2,3}
317    return stat_modified, pval, digits
318
319def usqu_st70_upp(stat, nobs):
320    nobsinv = 1. / nobs
321    stat_modified = (stat - 0.1 * nobsinv + 0.1 * nobsinv**2)
322    stat_modified *= (1 + 0.8 * nobsinv)
323    pval = 2 * np.exp(- 2 * stat_modified * np.pi**2)
324    digits = np.sum(stat > np.array([0.29, 0.29, 0.34]))
325    #repeat low to get {0,2,3}
326    return stat_modified, pval, digits
327
328def a_st70_upp(stat, nobs):
329    nobsinv = 1. / nobs
330    stat_modified = (stat - 0.7 * nobsinv + 0.9 * nobsinv**2)
331    stat_modified *= (1 + 1.23 * nobsinv)
332    pval = 1.273 * np.exp(- 2 * stat_modified / 2. * np.pi**2)
333    digits = np.sum(stat > np.array([0.11, 0.11, 0.452]))
334    #repeat low to get {0,2,3}
335    return stat_modified, pval, digits
336
337
338
339gof_pvals = {}
340
341gof_pvals['stephens70upp'] = {
342    'd_plus' : dplus_st70_upp,
343    'd_minus' : dplus_st70_upp,
344    'd' : d_st70_upp,
345    'v' : v_st70_upp,
346    'wsqu' : wsqu_st70_upp,
347    'usqu' : usqu_st70_upp,
348    'a' : a_st70_upp }
349
350def pval_kstest_approx(D, N):
351    pval_two = distributions.kstwobign.sf(D*np.sqrt(N))
352    if N > 2666 or pval_two > 0.80 - N*0.3/1000.0 :
353        return D, distributions.kstwobign.sf(D*np.sqrt(N)), np.nan
354    else:
355        return D, distributions.ksone.sf(D,N)*2, np.nan
356
357gof_pvals['scipy'] = {
358    'd_plus' : lambda Dplus, N: (Dplus, distributions.ksone.sf(Dplus, N), np.nan),
359    'd_minus' : lambda Dmin, N: (Dmin, distributions.ksone.sf(Dmin,N), np.nan),
360    'd' : lambda D, N: (D, distributions.kstwobign.sf(D*np.sqrt(N)), np.nan)
361    }
362
363gof_pvals['scipy_approx'] = {
364    'd' : pval_kstest_approx }
365
366class GOF(object):
367    '''One Sample Goodness of Fit tests
368
369    includes Kolmogorov-Smirnov D, D+, D-, Kuiper V, Cramer-von Mises W^2, U^2 and
370    Anderson-Darling A, A^2. The p-values for all tests except for A^2 are based on
371    the approximatiom given in Stephens 1970. A^2 has currently no p-values. For
372    the Kolmogorov-Smirnov test the tests as given in scipy.stats are also available
373    as options.
374
375
376
377
378    design: I might want to retest with different distributions, to calculate
379    data summary statistics only once, or add separate class that holds
380    summary statistics and data (sounds good).
381
382
383
384
385    '''
386
387
388
389
390    def __init__(self, rvs, cdf, args=(), N=20):
391        if isinstance(rvs, str):
392            #cdf = getattr(stats, rvs).cdf
393            if (not cdf) or (cdf == rvs):
394                cdf = getattr(distributions, rvs).cdf
395                rvs = getattr(distributions, rvs).rvs
396            else:
397                raise AttributeError('if rvs is string, cdf has to be the same distribution')
398
399
400        if isinstance(cdf, str):
401            cdf = getattr(distributions, cdf).cdf
402        if callable(rvs):
403            kwds = {'size':N}
404            vals = np.sort(rvs(*args,**kwds))
405        else:
406            vals = np.sort(rvs)
407            N = len(vals)
408        cdfvals = cdf(vals, *args)
409
410        self.nobs = N
411        self.vals_sorted = vals
412        self.cdfvals = cdfvals
413
414
415
416    @cache_readonly
417    def d_plus(self):
418        nobs = self.nobs
419        cdfvals = self.cdfvals
420        return (np.arange(1.0, nobs+1)/nobs - cdfvals).max()
421
422    @cache_readonly
423    def d_minus(self):
424        nobs = self.nobs
425        cdfvals = self.cdfvals
426        return (cdfvals - np.arange(0.0, nobs)/nobs).max()
427
428    @cache_readonly
429    def d(self):
430        return np.max([self.d_plus, self.d_minus])
431
432    @cache_readonly
433    def v(self):
434        '''Kuiper'''
435        return self.d_plus + self.d_minus
436
437    @cache_readonly
438    def wsqu(self):
439        '''Cramer von Mises'''
440        nobs = self.nobs
441        cdfvals = self.cdfvals
442        #use literal formula, TODO: simplify with arange(,,2)
443        wsqu = ((cdfvals - (2. * np.arange(1., nobs+1) - 1)/nobs/2.)**2).sum() \
444               + 1./nobs/12.
445        return wsqu
446
447    @cache_readonly
448    def usqu(self):
449        nobs = self.nobs
450        cdfvals = self.cdfvals
451        #use literal formula, TODO: simplify with arange(,,2)
452        usqu = self.wsqu - nobs * (cdfvals.mean() - 0.5)**2
453        return usqu
454
455    @cache_readonly
456    def a(self):
457        nobs = self.nobs
458        cdfvals = self.cdfvals
459
460        #one loop instead of large array
461        msum = 0
462        for j in range(1,nobs):
463            mj = cdfvals[j] - cdfvals[:j]
464            mask = (mj > 0.5)
465            mj[mask] = 1 - mj[mask]
466            msum += mj.sum()
467
468        a = nobs / 4. - 2. / nobs * msum
469        return a
470
471    @cache_readonly
472    def asqu(self):
473        '''Stephens 1974, does not have p-value formula for A^2'''
474        nobs = self.nobs
475        cdfvals = self.cdfvals
476
477        asqu = -((2. * np.arange(1., nobs+1) - 1) *
478                (np.log(cdfvals) + np.log(1-cdfvals[::-1]) )).sum()/nobs - nobs
479
480        return asqu
481
482
483    def get_test(self, testid='d', pvals='stephens70upp'):
484        '''
485
486        '''
487        #print gof_pvals[pvals][testid]
488        stat = getattr(self, testid)
489        if pvals == 'stephens70upp':
490            return gof_pvals[pvals][testid](stat, self.nobs), stat
491        else:
492            return gof_pvals[pvals][testid](stat, self.nobs)
493
494
495
496
497
498
499
500
501def gof_mc(randfn, distr, nobs=100):
502    #print '\nIs it correctly sized?'
503    from collections import defaultdict
504
505    results = defaultdict(list)
506    for i in range(1000):
507        rvs = randfn(nobs)
508        goft = GOF(rvs, distr)
509        for ti in all_gofs:
510            results[ti].append(goft.get_test(ti, 'stephens70upp')[0][1])
511
512    resarr = np.array([results[ti] for ti in all_gofs])
513    print('         ', '      '.join(all_gofs))
514    print('at 0.01:', (resarr < 0.01).mean(1))
515    print('at 0.05:', (resarr < 0.05).mean(1))
516    print('at 0.10:', (resarr < 0.1).mean(1))
517
518def asquare(cdfvals, axis=0):
519    '''vectorized Anderson Darling A^2, Stephens 1974'''
520    ndim = len(cdfvals.shape)
521    nobs = cdfvals.shape[axis]
522    slice_reverse = [slice(None)] * ndim  #might make copy if not specific axis???
523    islice = [None] * ndim
524    islice[axis] = slice(None)
525    slice_reverse[axis] = slice(None, None, -1)
526    asqu = -((2. * np.arange(1., nobs+1)[tuple(islice)] - 1) *
527            (np.log(cdfvals) + np.log(1-cdfvals[tuple(slice_reverse)]))/nobs).sum(axis) \
528            - nobs
529
530    return asqu
531
532
533#class OneSGOFFittedVec(object):
534#    '''for vectorized fitting'''
535    # currently I use the bootstrap as function instead of full class
536
537    #note: kwds loc and scale are a pain
538    # I would need to overwrite rvs, fit and cdf depending on fixed parameters
539
540    #def bootstrap(self, distr, args=(), kwds={}, nobs=200, nrep=1000,
541def bootstrap(distr, args=(), nobs=200, nrep=100, value=None, batch_size=None):
542    '''Monte Carlo (or parametric bootstrap) p-values for gof
543
544    currently hardcoded for A^2 only
545
546    assumes vectorized fit_vec method,
547    builds and analyses (nobs, nrep) sample in one step
548
549    rename function to less generic
550
551    this works also with nrep=1
552
553    '''
554    #signature similar to kstest ?
555    #delegate to fn ?
556
557    #rvs_kwds = {'size':(nobs, nrep)}
558    #rvs_kwds.update(kwds)
559
560
561    #it will be better to build a separate batch function that calls bootstrap
562    #keep batch if value is true, but batch iterate from outside if stat is returned
563    if batch_size is not None:
564        if value is None:
565            raise ValueError('using batching requires a value')
566        n_batch = int(np.ceil(nrep/float(batch_size)))
567        count = 0
568        for irep in range(n_batch):
569            rvs = distr.rvs(args, **{'size':(batch_size, nobs)})
570            params = distr.fit_vec(rvs, axis=1)
571            params = lmap(lambda x: np.expand_dims(x, 1), params)
572            cdfvals = np.sort(distr.cdf(rvs, params), axis=1)
573            stat = asquare(cdfvals, axis=1)
574            count += (stat >= value).sum()
575        return count / float(n_batch * batch_size)
576    else:
577        #rvs = distr.rvs(args, **kwds)  #extension to distribution kwds ?
578        rvs = distr.rvs(args, **{'size':(nrep, nobs)})
579        params = distr.fit_vec(rvs, axis=1)
580        params = lmap(lambda x: np.expand_dims(x, 1), params)
581        cdfvals = np.sort(distr.cdf(rvs, params), axis=1)
582        stat = asquare(cdfvals, axis=1)
583        if value is None:           #return all bootstrap results
584            stat_sorted = np.sort(stat)
585            return stat_sorted
586        else:                       #calculate and return specific p-value
587            return (stat >= value).mean()
588
589
590
591def bootstrap2(value, distr, args=(), nobs=200, nrep=100):
592    '''Monte Carlo (or parametric bootstrap) p-values for gof
593
594    currently hardcoded for A^2 only
595
596    non vectorized, loops over all parametric bootstrap replications and calculates
597    and returns specific p-value,
598
599    rename function to less generic
600
601    '''
602    #signature similar to kstest ?
603    #delegate to fn ?
604
605    #rvs_kwds = {'size':(nobs, nrep)}
606    #rvs_kwds.update(kwds)
607
608
609    count = 0
610    for irep in range(nrep):
611        #rvs = distr.rvs(args, **kwds)  #extension to distribution kwds ?
612        rvs = distr.rvs(args, **{'size':nobs})
613        params = distr.fit_vec(rvs)
614        cdfvals = np.sort(distr.cdf(rvs, params))
615        stat = asquare(cdfvals, axis=0)
616        count += (stat >= value)
617    return count * 1. / nrep
618
619
620class NewNorm(object):
621    '''just a holder for modified distributions
622    '''
623
624    def fit_vec(self, x, axis=0):
625        return x.mean(axis), x.std(axis)
626
627    def cdf(self, x, args):
628        return distributions.norm.cdf(x, loc=args[0], scale=args[1])
629
630    def rvs(self, args, size):
631        loc=args[0]
632        scale=args[1]
633        return loc + scale * distributions.norm.rvs(size=size)
634
635
636
637
638
639if __name__ == '__main__':
640    from scipy import stats
641    #rvs = np.random.randn(1000)
642    rvs = stats.t.rvs(3, size=200)
643    print('scipy kstest')
644    print(kstest(rvs, 'norm'))
645    goft = GOF(rvs, 'norm')
646    print(goft.get_test())
647
648    all_gofs = ['d', 'd_plus', 'd_minus', 'v', 'wsqu', 'usqu', 'a']
649    for ti in all_gofs:
650        print(ti, goft.get_test(ti, 'stephens70upp'))
651
652    print('\nIs it correctly sized?')
653    from collections import defaultdict
654
655    results = defaultdict(list)
656    nobs = 200
657    for i in range(100):
658        rvs = np.random.randn(nobs)
659        goft = GOF(rvs, 'norm')
660        for ti in all_gofs:
661            results[ti].append(goft.get_test(ti, 'stephens70upp')[0][1])
662
663    resarr = np.array([results[ti] for ti in all_gofs])
664    print('         ', '      '.join(all_gofs))
665    print('at 0.01:', (resarr < 0.01).mean(1))
666    print('at 0.05:', (resarr < 0.05).mean(1))
667    print('at 0.10:', (resarr < 0.1).mean(1))
668
669    gof_mc(lambda nobs: stats.t.rvs(3, size=nobs), 'norm', nobs=200)
670
671    nobs = 200
672    nrep = 100
673    bt = bootstrap(NewNorm(), args=(0,1), nobs=nobs, nrep=nrep, value=None)
674    quantindex = np.floor(nrep * np.array([0.99, 0.95, 0.9])).astype(int)
675    print(bt[quantindex])
676
677    #the bootstrap results match Stephens pretty well for nobs=100, but not so well for
678    #large (1000) or small (20) nobs
679    '''
680    >>> np.array([15.0, 10.0, 5.0, 2.5, 1.0])/100.  #Stephens
681    array([ 0.15 ,  0.1  ,  0.05 ,  0.025,  0.01 ])
682    >>> nobs = 100
683    >>> [bootstrap(NewNorm(), args=(0,1), nobs=nobs, nrep=10000, value=c/ (1 + 4./nobs - 25./nobs**2)) for c in [0.576, 0.656, 0.787, 0.918, 1.092]]
684    [0.1545, 0.10009999999999999, 0.049000000000000002, 0.023, 0.0104]
685    >>>
686    '''
687