Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Expose fitted parameter values of implicit fits in test statistic calls #1554

Merged
merged 40 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
efce526
add failing tests for return_fitted_pars kwarg in teststat signatures
lhenkelm Aug 16, 2021
c901238
fix the tests: no TypeError when not returning a tuple
lhenkelm Aug 16, 2021
6ba3aa9
fix the test: unpack correctly so the shared assert can pass
lhenkelm Aug 16, 2021
04b0c3b
feat: implement return_fitted_pars kwarg on test statistics
lhenkelm Aug 16, 2021
51c3d15
failing tests for return_fitted_pars in generate_asimov_data
lhenkelm Aug 17, 2021
a2f1c86
feat: generate_asimov_data can return the best-fit pars
lhenkelm Aug 17, 2021
7e0810e
more robust asserts
lhenkelm Aug 17, 2021
239fe61
failing tests about AsymptoticCalculator having fitted_pars
lhenkelm Aug 18, 2021
e804185
implement AsymptoticCalculators.fitted_pars
lhenkelm Aug 18, 2021
75457ec
failing tests about returning the best-fit parameters from hypotest
lhenkelm Aug 19, 2021
74020af
test: hypotest raises error when using ToyCalculator and asked to return
lhenkelm Aug 20, 2021
84a8af8
implement return_fitted_pars for hypotest
lhenkelm Aug 20, 2021
7b17058
fix docs issues
lhenkelm Aug 20, 2021
4b3995f
fix docstrings for sphinx
lhenkelm Aug 23, 2021
75e586c
no "="-syntax in f-strings
lhenkelm Aug 23, 2021
c8a77e0
fix codefactor issues
lhenkelm Aug 23, 2021
4650bb8
Merge branch 'master' into fitted-pars-from-infer
kratsg Aug 25, 2021
a6be28d
fix broken link in docs
lhenkelm Aug 25, 2021
0d1fe2a
remove duplicate "Returns:" section in docstring
lhenkelm Aug 25, 2021
8f7bc66
clarify
lhenkelm Aug 25, 2021
08b57f3
add to contributors.rst
lhenkelm Aug 25, 2021
0a65d75
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Aug 26, 2021
464a55d
failing tests for return_calculator to hypotest
lhenkelm Aug 26, 2021
13eb9ed
implement return_calculator and remove return_fitted_pars
lhenkelm Aug 26, 2021
e9cc0d6
fix inline-without-endstring warning
lhenkelm Aug 27, 2021
d2dc953
Merge branch 'scikit-hep:master' into fitted-pars-from-infer
lhenkelm Aug 31, 2021
9006f5a
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Sep 10, 2021
aaee60a
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Sep 15, 2021
c2c1d11
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Sep 23, 2021
29f968c
rename: BestFitParameters -> HypoTestFitResults
lhenkelm Sep 23, 2021
2551a86
Merge branch 'master' into fitted-pars-from-infer
kratsg Oct 4, 2021
b999973
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2021
5037bf9
give assurance through example that .fitted_pars is a HypoTestFitResult
lhenkelm Oct 5, 2021
658943a
simplify 'hypotest returns the calculator' tests
lhenkelm Oct 5, 2021
fd3c740
AsymptoticCalculator.fitted_pars is attribute instead of property
lhenkelm Oct 5, 2021
6cd7a65
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Oct 5, 2021
950615e
Merge branch 'master' into fitted-pars-from-infer
lhenkelm Oct 8, 2021
e860309
Merge branch 'master' into fitted-pars-from-infer
kratsg Oct 12, 2021
051271b
Merge branch 'master' into fitted-pars-from-infer
kratsg Oct 12, 2021
7f69d53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Calculators
:nosignatures:

calculators.generate_asimov_data
calculators.BestFitParameters
calculators.AsymptoticTestStatDistribution
calculators.EmpiricalDistribution
calculators.AsymptoticCalculator
Expand Down
1 change: 1 addition & 0 deletions docs/contributors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ Contributors include:
- Eric Schanet
- Henry Schreiner
- Saransh Chopra
- Lars Henkelmann
14 changes: 13 additions & 1 deletion src/pyhf/infer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def hypotest(
return_tail_probs=False,
return_expected=False,
return_expected_set=False,
return_calculator=False,
**kwargs,
):
r"""
Expand Down Expand Up @@ -66,9 +67,12 @@ def hypotest(
return_tail_probs (:obj:`bool`): Bool for returning :math:`\mathrm{CL}_{s+b}` and :math:`\mathrm{CL}_{b}`
return_expected (:obj:`bool`): Bool for returning :math:`\mathrm{CL}_{\mathrm{exp}}`
return_expected_set (:obj:`bool`): Bool for returning the :math:`(-2,-1,0,1,2)\sigma` :math:`\mathrm{CL}_{\mathrm{exp}}` --- the "Brazil band"
return_calculator (:obj:`bool`): Bool for returning calculator.

Returns:
Tuple of Floats and lists of Floats:
Tuple of Floats and lists of Floats and
a :py:class:`~pyhf.infer.calculators.AsymptoticCalculator`
or :py:class:`~pyhf.infer.calculators.ToyCalculator` instance:

- :math:`\mathrm{CL}_{s}`: The modified :math:`p`-value compared to
the given threshold :math:`\alpha`, typically taken to be :math:`0.05`,
Expand Down Expand Up @@ -139,6 +143,12 @@ def hypotest(
referred to as the "Brazil band".
Only returned when ``return_expected_set`` is ``True``.

- a calculator: The calculator instance used in the computation of the :math:`p`-values.
Either an instance of :py:class:`~pyhf.infer.calculators.AsymptoticCalculator`
or :py:class:`~pyhf.infer.calculators.ToyCalculator`,
depending on the value of ``calctype``.
Only returned when ``return_calculator`` is ``True``.

"""
init_pars = init_pars or pdf.config.suggested_init()
par_bounds = par_bounds or pdf.config.suggested_bounds()
Expand Down Expand Up @@ -188,6 +198,8 @@ def hypotest(
_returns.append(pvalues_exp_band)
elif return_expected:
_returns.append(tb.astensor(pvalues_exp_band[2]))
if return_calculator:
_returns.append(calc)
# Enforce a consistent return type of the observed CLs
return tuple(_returns) if len(_returns) > 1 else _returns[0]

Expand Down
84 changes: 77 additions & 7 deletions src/pyhf/infer/calculators.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pyhf.infer import utils
import tqdm

from dataclasses import dataclass
import logging

log = logging.getLogger(__name__)
Expand All @@ -29,7 +30,9 @@ def __dir__():
return __all__


def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds, fixed_params):
def generate_asimov_data(
asimov_mu, data, pdf, init_pars, par_bounds, fixed_params, return_fitted_pars=False
):
"""
Compute Asimov Dataset (expected yields at best-fit values) for a given POI value.

Expand All @@ -46,6 +49,14 @@ def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds, fixed_para
>>> pyhf.infer.calculators.generate_asimov_data(mu_test, data, model, None, None, None)
array([ 60.61229858, 56.52802479, 270.06832542, 48.31545488])

It is possible to access the Asimov parameters as well:

>>> pyhf.infer.calculators.generate_asimov_data(
... mu_test, data, model, None, None, None,
... return_fitted_pars = True
... )
(array([ 60.61229858, 56.52802479, 270.06832542, 48.31545488]), array([1. , 0.97224597, 0.87553894]))

Comment on lines +52 to +59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples should be contiguous else they break the copy button usefulness.

Args:
asimov_mu (:obj:`float`): The value for the parameter of interest to be used.
data (:obj:`tensor`): The observed data.
Expand All @@ -56,15 +67,23 @@ def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds, fixed_para
The shape should be ``(n, 2)`` for ``n`` model parameters.
fixed_params (:obj:`tensor` of :obj:`bool`): The flag to set a parameter constant to its starting
value during minimization.
return_fitted_pars (:obj:`bool`): Return the best-fit parameter values for the given ``asimov_mu``.


Returns:
Tensor: The Asimov dataset.
A Tensor or a Tuple of two Tensors:

- The Asimov dataset.

- The Asimov parameters. Only returned if ``return_fitted_pars`` is ``True``.
"""
bestfit_nuisance_asimov = fixed_poi_fit(
asimov_mu, data, pdf, init_pars, par_bounds, fixed_params
)
return pdf.expected_data(bestfit_nuisance_asimov)
asimov_data = pdf.expected_data(bestfit_nuisance_asimov)
if return_fitted_pars:
return asimov_data, bestfit_nuisance_asimov
return asimov_data


class AsymptoticTestStatDistribution:
Expand Down Expand Up @@ -188,6 +207,21 @@ def expected_value(self, nsigma):
)


@dataclass(frozen=True)
class BestFitParameters:
kratsg marked this conversation as resolved.
Show resolved Hide resolved
"""
Fitted model parameters of the fits in
:py:meth:`AsymptoticCalculator.teststatistic <pyhf.infer.calculators.AsymptoticCalculator.teststatistic>`
"""

# ignore "F821 undefined name 'Tensor'" so as to avoid typing.Any
asimov_pars: 'Tensor' # noqa: F821
free_fit_to_data: 'Tensor' # noqa: F821
free_fit_to_asimov: 'Tensor' # noqa: F821
fixed_poi_fit_to_data: 'Tensor' # noqa: F821
fixed_poi_fit_to_asimov: 'Tensor' # noqa: F821


class AsymptoticCalculator:
"""The Asymptotic Calculator."""

Expand Down Expand Up @@ -252,6 +286,22 @@ def __init__(
self.calc_base_dist = calc_base_dist
self.sqrtqmuA_v = None

@property
def fitted_pars(self):
"""
Best-fit parameters of the fits that have been run in
:py:meth:`self.teststatistic <pyhf.infer.calculators.AsymptoticCalculator.teststatistic>`.

Returns:
~pyhf.infer.calculators.BestFitParameters: The collection of fitted parameter tensors.
"""
try:
return self._fitted_pars
except AttributeError:
if self.sqrtqmuA_v is None:
raise RuntimeError("need to call .teststatistic(poi_test) first")
raise

def distributions(self, poi_test):
r"""
Probability distributions of the test statistic, as defined in
Expand Down Expand Up @@ -297,9 +347,13 @@ def distributions(self, poi_test):
return sb_dist, b_dist

def teststatistic(self, poi_test):
"""
r"""
Compute the test statistic for the observed data under the studied model.

The fitted parameters of the five fits that are implicitly ran at every call
of this method are afterwards accessible through ``self.fitted_pars``,
which is a :py:class:`~pyhf.infer.calculators.BestFitParameters` instance.

Example:

>>> import pyhf
Expand All @@ -314,6 +368,12 @@ def teststatistic(self, poi_test):
>>> asymptotic_calculator.teststatistic(mu_test)
array(0.14043184)

Access the best-fit parameters afterwards:
(here: :math:`\hat{\mu}` and :math:`\hat{\theta}` fitted to the asimov dataset):

>>> asymptotic_calculator.fitted_pars.free_fit_to_asimov
kratsg marked this conversation as resolved.
Show resolved Hide resolved
array([0. , 1.00304893, 0.96263365])

Args:
poi_test (:obj:`float` or :obj:`tensor`): The value for the parameter of interest.

Expand All @@ -325,35 +385,45 @@ def teststatistic(self, poi_test):

teststat_func = utils.get_test_stat(self.test_stat)

qmu_v = teststat_func(
qmu_v, (mubhathat, muhatbhat) = teststat_func(
poi_test,
self.data,
self.pdf,
self.init_pars,
self.par_bounds,
self.fixed_params,
return_fitted_pars=True,
)
sqrtqmu_v = tensorlib.sqrt(qmu_v)

asimov_mu = 1.0 if self.test_stat == 'q0' else 0.0

asimov_data = generate_asimov_data(
asimov_data, asimov_mubhathat = generate_asimov_data(
asimov_mu,
self.data,
self.pdf,
self.init_pars,
self.par_bounds,
self.fixed_params,
return_fitted_pars=True,
)
qmuA_v = teststat_func(
qmuA_v, (mubhathat_A, muhatbhat_A) = teststat_func(
poi_test,
asimov_data,
self.pdf,
self.init_pars,
self.par_bounds,
self.fixed_params,
return_fitted_pars=True,
)
self.sqrtqmuA_v = tensorlib.sqrt(qmuA_v)
self._fitted_pars = BestFitParameters(
asimov_pars=asimov_mubhathat,
free_fit_to_data=muhatbhat,
free_fit_to_asimov=muhatbhat_A,
fixed_poi_fit_to_data=mubhathat,
fixed_poi_fit_to_asimov=mubhathat_A,
)

if self.test_stat in ["q", "q0"]: # qmu or q0
teststat = sqrtqmu_v - self.sqrtqmuA_v
Expand Down
Loading