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

Add "summary" method to Results classes #105

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 10 additions & 3 deletions examples/02_meta-analysis/plot_run_meta-analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
# -----------------------------------------------------------------------------
from pprint import pprint

from pymare import core, datasets, estimators

###############################################################################
# Load the data
# -----------------------------------------------------------------------------
Expand All @@ -24,8 +22,14 @@
#
# We only want to do a mean analysis, so we won't have any covariates except for
# an intercept.
import numpy as np

from pymare import core, datasets, estimators

data, meta = datasets.michael2013()
dset = core.Dataset(data=data, y="yi", v="vi", X=None, add_intercept=True)
data["age"] = np.random.randint(1, 100, size=data.shape[0])
data["n_houses"] = np.random.randint(1, 10, size=data.shape[0])
dset = core.Dataset(data=data, y="yi", v="vi", X=["age", "n_houses"], add_intercept=True)
dset.to_df()

###############################################################################
Expand Down Expand Up @@ -62,6 +66,9 @@
perm_results = results.permutation_test(n_perm=1000)
perm_results.to_df()

###############################################################################
print(results.summary())

###############################################################################
# References
# -----------------------------------------------------------------------------
Expand Down
97 changes: 97 additions & 0 deletions pymare/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
az = None

from pymare.stats import q_gen, q_profile
from pymare.utils import _get_sig_code


class MetaRegressionResults:
Expand Down Expand Up @@ -327,6 +328,10 @@ def permutation_test(self, n_perm=1000):

exact = n_exact < n_perm
if exact:
LGR.warning(
"The number of requested permutations is larger than the number of possible ones. "
f"An exact test with {n_exact} permutations will be performed."
)
n_perm = n_exact

# Loop over parallel datasets
Expand Down Expand Up @@ -379,6 +384,55 @@ def permutation_test(self, n_perm=1000):

return PermutationTestResults(self, params, n_perm, exact)

def summary(self):
"""Print a summary of the results.

Returns
-------
:obj:`str`
A summary of the results, modeled after ``metafor``'s results.
"""
n_obs, n_datasets = self.dataset.y.shape
df = n_obs - self.dataset.X.shape[1]

if n_datasets > 1:
raise ValueError(
"More than one set of results found! "
"A summary table cannot be displayed for multidimensional results at the moment."
)

re_stats = self.get_re_stats()
heterogeneity_stats = self.get_heterogeneity_stats()

# Extract info
tau2 = re_stats["tau^2"]
if isinstance(tau2, (list, tuple, np.ndarray)):
tau2 = tau2[0]

tau2_se = "n/a"
tau2_estimator = self.estimator.__class__.__name__

model_results = self.to_df()
model_results[""] = model_results["p-value"].apply(_get_sig_code)

pattern = f"""Random-Effects Model (k = {n_obs}; tau^2 estimator: {tau2_estimator})

tau^2 (estimated amount of total heterogeneity): {tau2:.04f} (SE = {tau2_se})
tau (square root of estimated tau^2 value): {np.sqrt(tau2):.04f}
I^2 (total heterogeneity / total variability): {heterogeneity_stats['I^2'][0]:.2f}%
H^2 (total variability / sampling variability): {heterogeneity_stats['H'][0] ** 2:.2f}

Test for Heterogeneity:
Q(df = {df}) = {heterogeneity_stats['Q'][0]:.04f}, p-val = {heterogeneity_stats['p(Q)'][0]:.04f}

Model Results:

{model_results.to_string(index=False, float_format="%.4f", na_rep="n/a")}

---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1"""
return pattern


class CombinationTestResults:
"""Container for results generated by p-value combination methods.
Expand Down Expand Up @@ -460,6 +514,11 @@ def permutation_test(self, n_perm=1000):
# Calculate # of permutations and determine whether to use exact test
n_exact = 2**n_obs
if n_exact < n_perm:
LGR.warning(
"The number of requested permutations is larger than the number of possible ones. "
f"An exact test with {n_exact} permutations will be performed."
)

perms = np.array(list(itertools.product([-1, 1], repeat=n_obs))).T
exact = True
n_perm = n_exact
Expand Down Expand Up @@ -497,6 +556,44 @@ def permutation_test(self, n_perm=1000):

return PermutationTestResults(self, {"fe_p": p_p}, n_perm, exact)

def summary(self):
"""Print a summary of the results.

Returns
-------
:obj:`str`
A summary of the results, modeled after ``metafor``'s results.
"""
n_obs, n_datasets = self.dataset.y.shape
df = n_obs - self.dataset.X.shape[1]

if n_datasets > 1:
raise ValueError(
"More than one set of results found! "
"A summary table cannot be displayed for multidimensional results at the moment."
)

model_results = self.to_df()
model_results[""] = model_results["p-value"].apply(_get_sig_code)

pattern = f"""Random-Effects Model (k = {n_obs}; tau^2 estimator: {tau2_estimator})

tau^2 (estimated amount of total heterogeneity): {tau2:.04f} (SE = {tau2_se})
tau (square root of estimated tau^2 value): {np.sqrt(tau2):.04f}
I^2 (total heterogeneity / total variability): {heterogeneity_stats['I^2'][0]:.2f}%
H^2 (total variability / sampling variability): {heterogeneity_stats['H'][0] ** 2:.2f}

Test for Heterogeneity:
Q(df = {df}) = {heterogeneity_stats['Q'][0]:.04f}, p-val = {heterogeneity_stats['p(Q)'][0]:.04f}

Model Results:

{model_results.to_string(index=False, float_format="%.4f", na_rep="n/a")}

---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1"""
return pattern


class PermutationTestResults:
"""Lightweight container to hold and display permutation test results."""
Expand Down
3 changes: 3 additions & 0 deletions pymare/tests/test_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def test_meta_regression_results_init_1d(fitted_estimator):
assert results.fe_params.shape == (2, 1)
assert results.fe_cov.shape == (2, 2, 1)
assert results.tau2.shape == (1,)
assert isinstance(results.summary(), str)


def test_meta_regression_results_init_2d(results_2d):
Expand All @@ -116,6 +117,8 @@ def test_meta_regression_results_init_2d(results_2d):
assert results_2d.fe_params.shape == (2, 3)
assert results_2d.fe_cov.shape == (2, 2, 3)
assert results_2d.tau2.shape == (1, 3)
with pytest.raises(ValueError):
results_2d.summary()


def test_mrr_fe_se(results, results_2d):
Expand Down
13 changes: 13 additions & 0 deletions pymare/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ def _listify(obj):
This provides a simple way to accept flexible arguments.
"""
return obj if isinstance(obj, (list, tuple, type(None), np.ndarray)) else [obj]


def _get_sig_code(p_value):
if p_value < 0.001:
return "***"
elif p_value < 0.01:
return "**"
elif p_value < 0.05:
return "*"
elif p_value < 0.1:
return "."
else:
return " "