nscore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nscore-0.1.0/PKG-INFO +16 -0
- nscore-0.1.0/README.md +144 -0
- nscore-0.1.0/nscore/__init__.py +0 -0
- nscore-0.1.0/nscore/batch.py +149 -0
- nscore-0.1.0/nscore/nonparametric_nsm.py +577 -0
- nscore-0.1.0/nscore/nsm.py +703 -0
- nscore-0.1.0/nscore/savi.py +626 -0
- nscore-0.1.0/nscore/tools/__init__.py +0 -0
- nscore-0.1.0/nscore/tools/plotting.py +239 -0
- nscore-0.1.0/nscore/utils/__init__.py +2 -0
- nscore-0.1.0/nscore/utils/utils_general.py +323 -0
- nscore-0.1.0/nscore/utils/utils_wsr.py +151 -0
- nscore-0.1.0/nscore/wsr.py +159 -0
- nscore-0.1.0/nscore.egg-info/PKG-INFO +16 -0
- nscore-0.1.0/nscore.egg-info/SOURCES.txt +39 -0
- nscore-0.1.0/nscore.egg-info/dependency_links.txt +1 -0
- nscore-0.1.0/nscore.egg-info/requires.txt +6 -0
- nscore-0.1.0/nscore.egg-info/top_level.txt +2 -0
- nscore-0.1.0/scripts/__init__.py +0 -0
- nscore-0.1.0/scripts/general/__init__.py +0 -0
- nscore-0.1.0/scripts/general/generate_cld_plot.py +133 -0
- nscore-0.1.0/scripts/general/large_scale_bernoulli_test_gather_data.py +445 -0
- nscore-0.1.0/scripts/general/large_scale_bernoulli_test_process_data.py +106 -0
- nscore-0.1.0/scripts/general/large_scale_bernoulli_test_visualize_data.py +542 -0
- nscore-0.1.0/scripts/general/multivariate_savi_gather_data.py +113 -0
- nscore-0.1.0/scripts/general/multivariate_savi_process_data.py +30 -0
- nscore-0.1.0/scripts/general/nonparametric_density_evaluation_summary_statistics.py +48 -0
- nscore-0.1.0/scripts/general/nonparametric_density_evaluations.py +181 -0
- nscore-0.1.0/scripts/paper_results/__init__.py +0 -0
- nscore-0.1.0/scripts/paper_results/evaluate_full_RL_results.py +219 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_combine_part1.py +49 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_combine_part2.py +48 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_combine_part3.py +49 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_part1.py +281 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_part2.py +268 -0
- nscore-0.1.0/scripts/paper_results/lbm_data_eval_part3.py +267 -0
- nscore-0.1.0/scripts/paper_results/process_full_RL_results.py +58 -0
- nscore-0.1.0/scripts/paper_results/rl_mujoco_cartpole_data.py +27 -0
- nscore-0.1.0/setup.cfg +4 -0
- nscore-0.1.0/setup.py +18 -0
- nscore-0.1.0/tests/test_nscore_deprecation_shims.py +89 -0
nscore-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nscore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sequential statistical hypothesis testing for generalized performance measures.
|
|
5
|
+
Author: David Snyder, Haruki Nishimura
|
|
6
|
+
Author-email: dsnyder5@engineering.upenn.edu, haruki.nishimura@tri.global
|
|
7
|
+
Requires-Dist: matplotlib
|
|
8
|
+
Requires-Dist: numpy>=1.20
|
|
9
|
+
Requires-Dist: scipy
|
|
10
|
+
Requires-Dist: cvxpy
|
|
11
|
+
Requires-Dist: statistical-comparison-core<0.3,>=0.2.0
|
|
12
|
+
Requires-Dist: statistical-comparison-helpers
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: requires-dist
|
|
16
|
+
Dynamic: summary
|
nscore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# README
|
|
2
|
+
Instructions for installation and usage of NSCORE, a nonparametric sequential procedure for rigorous robot policy comparison. NSCORE has minimal and lightweight computational requirements, and is easy to wrap around existing simulation and hardware evaluation pipelines.
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
|
|
6
|
+
Install NSCORE from the repository root with pip:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install .
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
For local development, use an editable install:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
NSCORE depends on `statistical-comparison-core` and
|
|
19
|
+
`statistical-comparison-helpers`. Until those packages are available from your
|
|
20
|
+
configured package index, install them from their local repositories or ensure
|
|
21
|
+
they are otherwise available on your Python path before installing NSCORE.
|
|
22
|
+
|
|
23
|
+
### Optional conda environment
|
|
24
|
+
|
|
25
|
+
Conda is not required to install NSCORE, but it can be used to create an isolated
|
|
26
|
+
environment:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
conda create -n nscore --file requirements_conda.txt
|
|
30
|
+
conda activate nscore
|
|
31
|
+
pip install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Applicability (Non-Technical)
|
|
35
|
+
For additional motivation of N-SCORE and related methods, see Tutorials, below.
|
|
36
|
+
|
|
37
|
+
### Understanding the Arguments: Hypotheses
|
|
38
|
+
NSCORE performs _mean comparison_ between two different random variables of unknown (possibly nonparametric) distribution shape. We will term the variables $R_0$ and $R_1$, with respective (unknown) means $\mu_0$ and $\mu_1$.
|
|
39
|
+
|
|
40
|
+
The first thing that we will need is a _hypothesis_: what do we want the relationship to be between $\mu_0$ and $\mu_1$? For example, if $R_0$ is a measure of a baseline policy's performance and $R_1$ is a measure of the performance of our novel method, we want $\mu_0 < \mu_1$ (under the convention that $R_i$ is a reward, i.e., higher is better). This is specified precisely as `alternative=Hypothesis.P0LessThanP1` (the notation $p$ is derived from the original use case of comparing Bernoulli random variables, where the mean $\mu$ is equivalent to the conventional Bernoulli parameter $p$). Conversely, If the $R_i$ are cost measures, where lower is better, we may wish to specify the alternative $\mu_0 > \mu_1$. This is expressed directly as `alternative=Hypothesis.P0MoreThanP1`.
|
|
41
|
+
|
|
42
|
+
A second use also arises in the form of error checking. Returning to _reward measures_: imagine there might be a bug in our codebase. We might value a method that runs both tests simultaneously. The first test checks if we improve upon the baseline (assuming no bugs), while the second test quickly tells us if there might be a bug making us perform significantly worse than the baseline, allowing us to stop early and fix the problem! This corresponds to the case of a `Mirrored` (Two-Sided) Test, where we keep track of both directions. We will get into Mirrored tests a bit more, later.
|
|
43
|
+
|
|
44
|
+
### Understanding the Arguments: Alpha
|
|
45
|
+
When we compare ourselves to a baseline method, we want to establish that our performance is better. But evaluations have significant randomness, meaning that we cannot simply use empirical success to argue for policy improvement. Instead, we propose the following evidential justification: "the probability that our robot policy's performance is _not_ better than the baseline policy is less than $\alpha$."
|
|
46
|
+
|
|
47
|
+
This guarantee means that, if our policy was _no better than the baseline_, then the probability of observing as strong empirical evidence in favor of our method is less than $\alpha$. Conversely, we can claim $1-\alpha$ __confidence__ that our new policy is indeed better than the baseline (on the shared distribution of environments that both policies are evaluated on). This is a strong form of generalization that allows us to tune our confidence level to any desired $\alpha \in (0, 1)$.
|
|
48
|
+
|
|
49
|
+
### Understanding the Arguments: c
|
|
50
|
+
At present, NSCORE takes advantage of certain efficient properties of linear representations of the data-generating distributions. The vector $c \in [0, 1]^K$ corresponds to this representation, which is perhaps best understood as a discretization of the interval $[0, 1]$ into bins (where $c$ encodes the bin edge positions). We then construct an approximation of the distribution law from the empirical counts within each bin.
|
|
51
|
+
|
|
52
|
+
### Instantiating and Running an NSCORE Test on Full Datasets (Basic Usage)
|
|
53
|
+
To use NSCORE, it is necessary to specify the three aforementioned parameters.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
|
|
57
|
+
alternative: [Hypothesis]
|
|
58
|
+
alpha: [float]
|
|
59
|
+
c: [np.ndarray]
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
As an example: for Bernoulli data, to test if our policy $\pi_1$ has a higher success rate than a baseline $\pi_0$ at $95$\% confidence, we would specify the test:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
nscore_test = BernoulliNsmTest(alternative=Hypothesis.P0LessThanP1, alpha=0.05, c=np.arange(2)/1.)
|
|
67
|
+
```
|
|
68
|
+
and, given an evaluation sequence of success/failure outcomes for each policy, we would run the test as:
|
|
69
|
+
```python
|
|
70
|
+
nscore_result = nscore_test.run_on_sequence(outcomes_for_pi_0, outcomes_for_pi_1)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Instantiating and Running an NSCORE Test on Full Datasets (General Metrics)
|
|
74
|
+
If we have performance measures that are not bounded in $[0, 1]$, then the measures must be __normalized__ using _a priori_ knowledge of the test domain. For example, on the Mujoco InvertedPendulum-v4 task, the reward is bounded by the time horizon $T$; therefore, normalization can be undertaken simply by scaling down the results. First, we define a more detailed NSM test for the more complex performance measure:
|
|
75
|
+
```python
|
|
76
|
+
nscore_inverted_pendulum_test = ContinuousNsmTest(alternative=Hypothesis.P0LessThanP1, alpha=0.05, c=np.arange(101)/100.)
|
|
77
|
+
```
|
|
78
|
+
Next, we use the same `run_on_sequence()` functionality, but normalize the scores:
|
|
79
|
+
```python
|
|
80
|
+
nscore_inverted_pendulum_result = nscore_inverted_pendulum_test.run_on_sequence(outcomes_for_pi_0/T, outcomes_for_pi_1/T)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Instantiating and Running an NSCORE Test _Online_ (General Metrics)
|
|
84
|
+
NSCORE can be used to save time by allowing the evaluator to update the evaluation _online_ as they gather trials. The test stops precisely
|
|
85
|
+
when enough evidence has accumulated to be $1-\alpha$ confident that the intended decision is correct. Taking the InvertedPendulum-v4 task as the running example, we first initialize the test as before:
|
|
86
|
+
```python
|
|
87
|
+
nscore_online_evaluation_test = ContinuousNsmTest(alternative=Hypothesis.P0LessThanP1, alpha=0.05, c=np.arange(101)/100.)
|
|
88
|
+
```
|
|
89
|
+
and then implement a simple evaluation protocol
|
|
90
|
+
```python
|
|
91
|
+
time_of_decision = 0
|
|
92
|
+
decided = False
|
|
93
|
+
maximum_number_of_evals_per_policy = 100 # Optional
|
|
94
|
+
while (decided is False) and (time_of_decision < maximum_number_of_evals_per_policy):
|
|
95
|
+
new_datum_pi_0 = run_pi_0_on_new_iid_environment(ics, ...)
|
|
96
|
+
new_datum_pi_1 = run_pi_1_on_new_iid_environment(ics, ...)
|
|
97
|
+
|
|
98
|
+
result = nscore_online_evaluation_test.step(new_datum_pi_0, new_datum_pi_1)
|
|
99
|
+
|
|
100
|
+
if result.decision is Decision.AcceptAlternative:
|
|
101
|
+
decided = True
|
|
102
|
+
|
|
103
|
+
time_of_decision += 1
|
|
104
|
+
|
|
105
|
+
# Print the results
|
|
106
|
+
print(f"NSCORE test decided in {time_of_decision} trials per policy")
|
|
107
|
+
print(f"NSCORE decision: {result.decision}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Understanding an NSCORE Test Result
|
|
111
|
+
We utilize the same structure for test decisions as our previous work, [STEP](https://github.com/TRI-ML/sequentialized_barnard_tests). Formally, Neyman-Pearson statistical testing only allows for _accepting_ the alternative hypothesis (if sufficient evidence for it is accumulated, of course). This is encoded by a `Decision` object; for a realized test that rejects the null and accepts the alternative, the associated decision would be `test.decision = Decision.AcceptAlternative`.
|
|
112
|
+
|
|
113
|
+
Of course, the data might be insufficiently indicative of the alternative. Because the tests are sequential, there is the opportunity to gather more, so we define a placeholder, denoted by `Decision.FailToDecide`. For anytime-valid tests, data may in principle be collected in perpetuity; thus, `Decision.FailToDecide` implies simply that 'not enough information has yet accumulated to make up our mind one way or the other.'
|
|
114
|
+
|
|
115
|
+
Finally, as mentioned earlier, there are practical instances where we might wish to monitor both directions of a comparison. Informally, the standard direction seeks certify that our policy has improved over the baseline, while the other direction gives us reliable statistical evidence that we should stop early and 'give up;' that is, it tells us that our novel method is performing significantly worse, and that gathering more trials is unlikely to change that assessment. This can be used as a bug-catcher, as well as to reliable help with design iterations _when used responsibly_.
|
|
116
|
+
|
|
117
|
+
__We emphasize that a "kitchen sink" approach of just trying a bunch of different $\pi_1^{[i]}$ until a significant result is found against $\pi_0$ constitutes p-hacking, and invalidates statistical assurances__. More precisely, each $\pi_1^{[i]}$ must be accompanied by an additional union bound correction of $\alpha$ -- concretely: if you test five new policies against $\pi_0$, with each test at level $\alpha$, any significant difference found can only be reported at level $5\alpha$! This is the union bound, or "Bonferroni correction" in classical hypothesis testing.
|
|
118
|
+
|
|
119
|
+
However, when used responsibly, the two-sided test can add significant further empirical savings to the practitioner's evaluation burden. In our setup, a two-sided test is termed `Mirrored`, because it will consist of two _mirrored_ one-sided tests; the tests will individually check the hypotheses `Hypothesis.P0LessThanP1` and `Hypothesis.P0MoreThanP1`. In this context, the user will still specify a specific alternative, which then amounts to selecting which hypothesis is the semantic "Alternative" and which is the "Null." From this, we construct a third decision option, `Decision.AcceptNull`, which amounts to accepting the semantic "Null" as the alternative of a second one-sided test. For example:
|
|
120
|
+
```python
|
|
121
|
+
mirrored_nscore_test = MirroredContinuousNsmTest(alternative=Hypothesis.P0LessThanP1, alpha=0.05, c=np.arange(101)/100.)
|
|
122
|
+
```
|
|
123
|
+
will induce two one-sided tests:
|
|
124
|
+
```python
|
|
125
|
+
nscore_test_for_alternative: ContinuousNsmTest(alternative=Hypothesis.P0LessThanP1, alpha=0.05, c=np.arange(101)/100.)
|
|
126
|
+
nscore_test_for_null: ContinuousNsmTest(alternative=Hypothesis.P0MoreThanP1, alpha=0.05, c=np.arange(101)/100.)
|
|
127
|
+
```
|
|
128
|
+
Formally `result.decision = Decision.AcceptNull` here is equivalent to accepting the _alternative hypothesis_ of `nscore_test_for_null` (as required by Neyman-Pearson testing -- one is not generally allowed to accept a null hypothesis). Semantically, however, we have _informally_ accepted the null in the sense that we have concluded, with high confidence, that $\mu_0 > \mu_1$.
|
|
129
|
+
|
|
130
|
+
## Tutorials
|
|
131
|
+
Tutorials that illustrate standard use cases of NSCORE can be found as Jupyter Notebooks under `/notebooks`.
|
|
132
|
+
|
|
133
|
+
## Citation
|
|
134
|
+
If you find this code useful, please cite our work:
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
```bibtex
|
|
138
|
+
@inproceedings{snyder_beyond_2026,
|
|
139
|
+
title = {Beyond {Binary} {Success}: {Sample}-{Efficient} and {Statistically} {Rigorous} {Robot} {Policy} {Comparison}},
|
|
140
|
+
author = {Snyder, David and Badithela, Apurva and Matni, Nikolai and Pappas, George and Majumdar, Anirudha and Itkina, Masha and Nishimura, Haruki},
|
|
141
|
+
booktitle={arXiv preprint arXiv:2603.13616}
|
|
142
|
+
year = {2026},
|
|
143
|
+
}
|
|
144
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Batch tests.
|
|
2
|
+
|
|
3
|
+
This module defines batch methods for hypothesis testing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import ArrayLike
|
|
8
|
+
from scipy.stats import barnard_exact
|
|
9
|
+
|
|
10
|
+
from statistical_comparison_core import (
|
|
11
|
+
Decision,
|
|
12
|
+
Hypothesis,
|
|
13
|
+
MirroredTestMixin,
|
|
14
|
+
TestBase,
|
|
15
|
+
TestResult,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BarnardExactTest(TestBase):
|
|
20
|
+
"""Barnard's exact test.
|
|
21
|
+
|
|
22
|
+
This class is a wrapper around scipy's implementation of Barnard's exact test.
|
|
23
|
+
For more details, refer to scipy's documentation:
|
|
24
|
+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.barnard_exact.html
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
alternative: Specification of the alternative hypothesis.
|
|
28
|
+
alpha: Significance level of the test.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, alternative: Hypothesis, alpha: float) -> None:
|
|
32
|
+
"""Initializes the test object.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
alternative: Specification of the alternative hypothesis.
|
|
36
|
+
alpha: Significance level of the test.
|
|
37
|
+
"""
|
|
38
|
+
self.alternative = alternative
|
|
39
|
+
self.alpha = alpha
|
|
40
|
+
|
|
41
|
+
def run_on_sequence(
|
|
42
|
+
self, sequence_0: ArrayLike, sequence_1: ArrayLike
|
|
43
|
+
) -> TestResult:
|
|
44
|
+
"""Runs the test on a pair of two Bernoulli sequences.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
sequence_0: Sequence of Bernoulli data from the first source.
|
|
48
|
+
sequence_1: Sequence of Bernoulli data from the second source.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
TestResult: Result of the hypothesis test.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If the input sequences are not Bernoulli data.
|
|
55
|
+
"""
|
|
56
|
+
sequence_0_is_binary = np.all(
|
|
57
|
+
(np.array(sequence_0) == 0) + (np.array(sequence_0) == 1)
|
|
58
|
+
)
|
|
59
|
+
sequence_1_is_binary = np.all(
|
|
60
|
+
(np.array(sequence_1) == 0) + (np.array(sequence_1) == 1)
|
|
61
|
+
)
|
|
62
|
+
if not (sequence_0_is_binary and sequence_1_is_binary):
|
|
63
|
+
raise (ValueError("Input sequences must be all Bernoulli data."))
|
|
64
|
+
num_successes_0 = np.sum(sequence_0).item()
|
|
65
|
+
num_failures_0 = len(sequence_0) - num_successes_0
|
|
66
|
+
num_successes_1 = np.sum(sequence_1).item()
|
|
67
|
+
num_failures_1 = len(sequence_1) - num_successes_1
|
|
68
|
+
table = [[num_successes_0, num_successes_1], [num_failures_0, num_failures_1]]
|
|
69
|
+
|
|
70
|
+
barnard = barnard_exact(
|
|
71
|
+
table,
|
|
72
|
+
alternative=(
|
|
73
|
+
"less" if self.alternative == Hypothesis.P0LessThanP1 else "greater"
|
|
74
|
+
),
|
|
75
|
+
pooled=(len(sequence_0) == len(sequence_1)),
|
|
76
|
+
)
|
|
77
|
+
if barnard.pvalue <= self.alpha:
|
|
78
|
+
decision = Decision.AcceptAlternative
|
|
79
|
+
else:
|
|
80
|
+
decision = Decision.FailToDecide
|
|
81
|
+
result = TestResult(
|
|
82
|
+
decision,
|
|
83
|
+
{"p_value": barnard.pvalue.item(), "statistic": barnard.statistic.item()},
|
|
84
|
+
)
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MirroredBarnardExactTest(MirroredTestMixin, TestBase):
|
|
89
|
+
"""A pair of one-sided Barnard's exact tests with mirrored alternatives.
|
|
90
|
+
|
|
91
|
+
In our terminology, a mirrored test is one that runs two one-sided tests
|
|
92
|
+
simultaneously, with the null and the alternaive flipped from each other. This is so
|
|
93
|
+
that it can yield either Decision.AcceptNull or Decision.AcceptAlternative depending
|
|
94
|
+
on the input data, unlike standard one-sided tests that can never 'accept' the null.
|
|
95
|
+
(Those standard tests will at most fail to reject the null, as represented by
|
|
96
|
+
Decision.FailToDecide.)
|
|
97
|
+
|
|
98
|
+
For example, if the alternative is Hypothesis.P0MoreThanP1 and the decision is
|
|
99
|
+
Decision.AcceptNull, it should be interpreted as accepting Hypothesis.P0LessThanP1.
|
|
100
|
+
|
|
101
|
+
The significance level alpha controls the following two errors simultaneously: (1)
|
|
102
|
+
probability of wrongly accepting the alternative when the null is true, and (2)
|
|
103
|
+
probability of wrongly accepting the null when the alternative is true. Note that
|
|
104
|
+
Bonferroni correction is not needed since the null hypothesis for one test is the
|
|
105
|
+
alternative for the other.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
alternative: Specification of the alternative hypothesis.
|
|
109
|
+
alpha: Significance level of the test.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
_base_class = BarnardExactTest
|
|
113
|
+
|
|
114
|
+
def run_on_sequence(
|
|
115
|
+
self, sequence_0: ArrayLike, sequence_1: ArrayLike
|
|
116
|
+
) -> TestResult:
|
|
117
|
+
"""Runs the test on a pair of two Bernoulli sequences.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
sequence_0: Sequence of Bernoulli data from the first source.
|
|
121
|
+
sequence_1: Sequence of Bernoulli data from the second source.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
TestResult: Result of the hypothesis test.
|
|
125
|
+
"""
|
|
126
|
+
result_for_alternative = self._test_for_alternative.run_on_sequence(
|
|
127
|
+
sequence_0, sequence_1
|
|
128
|
+
)
|
|
129
|
+
result_for_null = self._test_for_null.run_on_sequence(sequence_0, sequence_1)
|
|
130
|
+
|
|
131
|
+
info = {
|
|
132
|
+
"result_for_alternative": result_for_alternative,
|
|
133
|
+
"result_for_null": result_for_null,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (not result_for_alternative.decision == Decision.FailToDecide) and (
|
|
137
|
+
result_for_null.decision == Decision.FailToDecide
|
|
138
|
+
):
|
|
139
|
+
decision = Decision.AcceptAlternative
|
|
140
|
+
elif (not result_for_null.decision == Decision.FailToDecide) and (
|
|
141
|
+
result_for_alternative.decision == Decision.FailToDecide
|
|
142
|
+
):
|
|
143
|
+
decision = Decision.AcceptNull
|
|
144
|
+
else:
|
|
145
|
+
decision = Decision.FailToDecide
|
|
146
|
+
|
|
147
|
+
result = TestResult(decision, info)
|
|
148
|
+
|
|
149
|
+
return result
|