Skip to content

Commit 035eca7

Browse files
committed
Merge pull request #44 from bashtage/test-single-mcs
TST: Add tests and protections for edge cases
2 parents 3947628 + 657279d commit 035eca7

File tree

3 files changed

+36
-3
lines changed

3 files changed

+36
-3
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ ci = bs.conf_int(sharpe_ratio, 1000, method='percentile')
128128
* Stepwise (StepM)
129129
* Model Confidence Set (MCS)
130130

131-
See the [multiple comparison example notebook](http://nbviewer.ipython.org/github/bashtage/arch/blob/master/examples/bootstrap_multiple_comparison.ipynb)
131+
See the [multiple comparison example notebook](http://nbviewer.ipython.org/github/bashtage/arch/blob/master/examples/multiple-comparison_examples.ipynb)
132132
for examples of the multiple comparison procedures.
133133

134134
## Requirements

arch/bootstrap/multiple_comparrison.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ class MCS(MultipleComparison):
112112
def __init__(self, losses, size, reps=1000, block_size=None, method='R',
113113
bootstrap='stationary'):
114114
self.losses = ensure2d(losses, 'losses')
115-
self._losses_arr = np.array(losses)
115+
self._losses_arr = np.asarray(self.losses)
116+
if self._losses_arr.shape[1] < 2:
117+
raise ValueError('losses must have at least two columns')
116118
self.size = size
117119
self.reps = reps
118120
if block_size is None:
@@ -379,7 +381,7 @@ def compute(self):
379381
better_models = list(self.spa.better_models(self.size))
380382
all_better_models = better_models
381383
# 3. Stop if nothing superior
382-
while better_models:
384+
while better_models and (len(better_models) < self.k):
383385
# A. Use Selector to remove better models
384386
selector = np.ones(self.k, dtype=np.bool)
385387
if isinstance(self.models, pd.DataFrame): # Columns
@@ -403,6 +405,9 @@ def superior_models(self):
403405
"""
404406
Returns a list of the indices or column names of the superior models.
405407
"""
408+
if self._superior_models is None:
409+
msg = 'compute must be callded before accessing superior_models'
410+
raise RuntimeError(msg)
406411
return self._superior_models
407412

408413

arch/bootstrap/tests/test_multiple_comparrison.py

+28
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ def test_bootstrap_selection(self):
147147
spa = SPA(self.benchmark, self.models, bootstrap='moving block')
148148
assert_true(isinstance(spa.bootstrap, MovingBlockBootstrap))
149149

150+
def test_single_model(self):
151+
spa = SPA(self.benchmark, self.models[:,0])
152+
spa.compute()
153+
154+
spa = SPA(self.benchmark_series, self.models_df.iloc[:,0])
155+
spa.compute()
156+
157+
150158

151159
class TestStepM(TestCase):
152160
@classmethod
@@ -213,6 +221,24 @@ def test_str_repr(self):
213221
expected = 'StepM(FWER (size): 0.05, studentization: none, bootstrap: ' + str(stepm.spa.bootstrap) + ')'
214222
assert_equal(expected, str(stepm))
215223

224+
def test_single_model(self):
225+
stepm = StepM(self.benchmark, self.models[:,0], size=0.10)
226+
stepm.compute()
227+
228+
stepm = StepM(self.benchmark_series, self.models_df.iloc[:,0])
229+
stepm.compute()
230+
231+
def test_all_superior(self):
232+
adj_models = self.models - 100.0
233+
stepm = StepM(self.benchmark, adj_models, size=0.10)
234+
stepm.compute()
235+
assert_equal(len(stepm.superior_models), self.models.shape[1])
236+
237+
def test_errors(self):
238+
stepm = StepM(self.benchmark, self.models, size=0.10)
239+
with assert_raises(RuntimeError):
240+
stepm.superior_models
241+
216242

217243
class TestMCS(TestCase):
218244
@classmethod
@@ -328,12 +354,14 @@ def test_smoke(self):
328354
assert_true(isinstance(mcs.pvalues, pd.DataFrame))
329355

330356
def test_errors(self):
357+
assert_raises(ValueError, MCS, self.losses[:,1], 0.05)
331358
mcs = MCS(self.losses, 0.05, reps=100, block_size=10, method='max', bootstrap='circular')
332359
mcs.compute()
333360
mcs = MCS(self.losses, 0.05, reps=100, block_size=10, method='max', bootstrap='moving block')
334361
mcs.compute()
335362
assert_raises(ValueError, MCS, self.losses, 0.05, bootstrap='unknown')
336363

364+
337365
def test_str_repr(self):
338366
mcs = MCS(self.losses, 0.05)
339367
expected = 'MCS(size: 0.05, bootstrap: ' + str(mcs.bootstrap) + ')'

0 commit comments

Comments
 (0)