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