Skip to content

Commit a146631

Browse files
authored
Merge pull request statsmodels#5143 from josef-pkt/bootstrap_clone_rebased
ENH/BUG Bootstrap clone if no exog, rebased
2 parents 79d925a + f68ca0b commit a146631

File tree

3 files changed

+94
-11
lines changed

3 files changed

+94
-11
lines changed

statsmodels/base/model.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,13 +2213,17 @@ def bootstrap(self, nrep=100, method='nm', disp=0, store=1):
22132213
"""
22142214
results = []
22152215
print(self.model.__class__)
2216-
hascloneattr = True if hasattr(self, 'cloneattr') else False
2216+
hascloneattr = True if hasattr(self.model, 'cloneattr') else False
22172217
for i in range(nrep):
22182218
rvsind = np.random.randint(self.nobs, size=self.nobs)
22192219
# this needs to set startparam and get other defining attributes
22202220
# need a clone method on model
2221+
if self.exog is not None:
2222+
exog_resamp = self.exog[rvsind, :]
2223+
else:
2224+
exog_resamp = None
22212225
fitmod = self.model.__class__(self.endog[rvsind],
2222-
self.exog[rvsind, :])
2226+
exog=exog_resamp)
22232227
if hascloneattr:
22242228
for attr in self.model.cloneattr:
22252229
setattr(fitmod, attr, getattr(self.model, attr))

statsmodels/iolib/summary.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ def summary_top(results, title=None, gleft=None, gright=None, yname=None, xname=
298298
#create dictionary with default
299299
#use lambdas because some values raise exception if they are not available
300300
#alternate spellings are commented out to force unique labels
301+
def num_to_str(x, width=6):
302+
if np.isnan(x):
303+
return (width - 3) * ' ' + 'NaN'
304+
return "%#6d" % x
305+
301306
default_items = dict([
302307
('Dependent Variable:', lambda: [yname]),
303308
('Dep. Variable:', lambda: [yname]),
@@ -307,16 +312,16 @@ def summary_top(results, title=None, gleft=None, gright=None, yname=None, xname=
307312
('Time:', lambda: time_of_day),
308313
('Number of Obs:', lambda: [results.nobs]),
309314
#('No. of Observations:', lambda: ["%#6d" % results.nobs]),
310-
('No. Observations:', lambda: ["%#6d" % results.nobs]),
315+
('No. Observations:', lambda: [num_to_str(results.nobs)]),
311316
#('Df model:', lambda: [results.df_model]),
312-
('Df Model:', lambda: ["%#6d" % results.df_model]),
317+
('Df Model:', lambda: [num_to_str(results.df_model)]),
313318
#TODO: check when we have non-integer df
314-
('Df Residuals:', lambda: ["%#6d" % results.df_resid]),
315-
#('Df resid:', lambda: [results.df_resid]),
316-
#('df resid:', lambda: [results.df_resid]), #check capitalization
317-
('Log-Likelihood:', lambda: ["%#8.5g" % results.llf]) #doesn't exist for RLM - exception
318-
#('Method:', lambda: [???]), #no default for this
319-
])
319+
('Df Residuals:', lambda: [num_to_str(results.df_resid)]),
320+
# ('Df resid:', lambda: [results.df_resid]),
321+
# ('df resid:', lambda: [results.df_resid]), #check capitalization
322+
('Log-Likelihood:', lambda: ["%#8.5g" % results.llf]) # doesn't exist for RLM - exception
323+
# ('Method:', lambda: [???]), # no default for this
324+
])
320325

321326
if title is None:
322327
title = results.model.__class__.__name__ + 'Regression Results'

statsmodels/miscmodels/tests/test_generic_mle.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from scipy import stats
1212
from statsmodels.base.model import GenericLikelihoodModel
1313

14-
from numpy.testing import assert_array_less, assert_almost_equal, assert_allclose
14+
from numpy.testing import (assert_array_less, assert_almost_equal,
15+
assert_allclose, assert_)
1516

1617
class MyPareto(GenericLikelihoodModel):
1718
'''Maximum Likelihood Estimation pareto distribution
@@ -149,3 +150,76 @@ def setup_class(cls):
149150

150151
# Note: loc is fixed, no problems with parameters close to min data
151152
cls.skip_bsejac = False
153+
154+
155+
class TwoPeakLLHNoExog(GenericLikelihoodModel):
156+
"""Fit height of signal peak over background."""
157+
start_params = [10, 1000]
158+
cloneattr = ['start_params', 'signal', 'background']
159+
exog_names = ['n_signal', 'n_background']
160+
endog_names = ['alpha']
161+
162+
def __init__(self, endog, exog=None, signal=None, background=None,
163+
*args, **kwargs):
164+
# assume we know the shape + location of the two components,
165+
# so we re-use their PDFs here
166+
self.signal = signal
167+
self.background = background
168+
super(TwoPeakLLHNoExog, self).__init__(endog=endog, exog=exog,
169+
*args, **kwargs)
170+
171+
def loglike(self, params): # pylint: disable=E0202
172+
return -self.nloglike(params)
173+
174+
def nloglike(self, params):
175+
endog = self.endog
176+
return self.nlnlike(params, endog)
177+
178+
def nlnlike(self, params, endog):
179+
n_sig = params[0]
180+
n_bkg = params[1]
181+
if (n_sig < 0) or n_bkg < 0:
182+
return np.inf
183+
n_tot = n_bkg + n_sig
184+
alpha = endog
185+
sig = self.signal.pdf(alpha)
186+
bkg = self.background.pdf(alpha)
187+
sumlogl = np.sum(np.log((n_sig * sig) + (n_bkg * bkg)))
188+
sumlogl -= n_tot
189+
return -sumlogl
190+
191+
192+
class TestTwoPeakLLHNoExog(object):
193+
194+
@classmethod
195+
def setup_class(cls):
196+
np.random.seed(42)
197+
pdf_a = stats.halfcauchy(loc=0, scale=1)
198+
pdf_b = stats.uniform(loc=0, scale=100)
199+
200+
n_a = 50
201+
n_b = 200
202+
params = [n_a, n_b]
203+
204+
X = np.concatenate([pdf_a.rvs(size=n_a),
205+
pdf_b.rvs(size=n_b),
206+
])[:, np.newaxis]
207+
cls.X = X
208+
cls.params = params
209+
cls.pdf_a = pdf_a
210+
cls.pdf_b = pdf_b
211+
212+
def test_fit(self):
213+
np.random.seed(42)
214+
llh_noexog = TwoPeakLLHNoExog(self.X,
215+
signal=self.pdf_a,
216+
background=self.pdf_b)
217+
218+
res = llh_noexog.fit()
219+
assert_allclose(res.params, self.params, rtol=1e-1)
220+
# TODO: nan if exog is None,
221+
assert_(np.isnan(res.df_resid))
222+
res_bs = res.bootstrap(nrep=50)
223+
assert_allclose(res_bs[2].mean(0), self.params, rtol=1e-1)
224+
# SMOKE test,
225+
res.summary()

0 commit comments

Comments
 (0)