Allow customising p value formatting
This commit is contained in:
parent
49b9230484
commit
bf5cc434b5
88
tests/test_fmt_pvalues.py
Normal file
88
tests/test_fmt_pvalues.py
Normal file
@ -0,0 +1,88 @@
|
||||
# scipy-yli: Helpful SciPy utilities and recipes
|
||||
# Copyright © 2022 Lee Yingtong Li (RunasSudo)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import yli
|
||||
from yli.config import config
|
||||
from yli.utils import fmt_p
|
||||
|
||||
def test_fmt_pvalues_ord():
|
||||
"""Test formatting of p values requiring no special handling"""
|
||||
|
||||
# Default config
|
||||
config.pvalue_min_dps = 2
|
||||
config.pvalue_max_dps = 3
|
||||
config.pvalue_leading_zero = True
|
||||
config.alpha = 0.05
|
||||
|
||||
assert fmt_p(0.0096, html=False) == '= 0.01*'
|
||||
assert fmt_p(0.01, html=False) == '= 0.01*'
|
||||
assert fmt_p(0.04, html=False) == '= 0.04*'
|
||||
assert fmt_p(0.11, html=False) == '= 0.11'
|
||||
assert fmt_p(0.55, html=False) == '= 0.55'
|
||||
|
||||
def test_fmt_pvalues_ord_noleadingzero():
|
||||
"""Test formatting of p values requiring no special handling, no leading zero"""
|
||||
|
||||
# Default config
|
||||
config.pvalue_min_dps = 2
|
||||
config.pvalue_max_dps = 3
|
||||
config.pvalue_leading_zero = False
|
||||
config.alpha = 0.05
|
||||
|
||||
assert fmt_p(0.0096, html=False) == '= .01*'
|
||||
assert fmt_p(0.01, html=False) == '= .01*'
|
||||
assert fmt_p(0.04, html=False) == '= .04*'
|
||||
assert fmt_p(0.11, html=False) == '= .11'
|
||||
assert fmt_p(0.55, html=False) == '= .55'
|
||||
|
||||
def test_fmt_pvalues_small():
|
||||
"""Test formatting of small p values requiring extra decimal points to represent"""
|
||||
|
||||
# Default config
|
||||
config.pvalue_min_dps = 2
|
||||
config.pvalue_max_dps = 3
|
||||
config.pvalue_leading_zero = True
|
||||
config.alpha = 0.05
|
||||
|
||||
assert fmt_p(0.009, html=False) == '= 0.009*'
|
||||
|
||||
def test_fmt_pvalues_ambiguous():
|
||||
"""Test formatting of p values requiring extra decimal points to avoid ambiguity"""
|
||||
|
||||
# Default config
|
||||
config.pvalue_min_dps = 2
|
||||
config.pvalue_max_dps = 3
|
||||
config.pvalue_leading_zero = True
|
||||
config.alpha = 0.05
|
||||
|
||||
assert fmt_p(0.048, html=False) == '= 0.048*'
|
||||
assert fmt_p(0.052, html=False) == '= 0.052'
|
||||
|
||||
# Special rounding rules
|
||||
assert fmt_p(0.04999, html=False) == '= 0.049*'
|
||||
assert fmt_p(0.05001, html=False) == '= 0.051'
|
||||
|
||||
def test_fmt_pvalues_extreme():
|
||||
"""Test formatting of p values too small or large to be represented"""
|
||||
|
||||
# Default config
|
||||
config.pvalue_min_dps = 2
|
||||
config.pvalue_max_dps = 3
|
||||
config.pvalue_leading_zero = True
|
||||
config.alpha = 0.05
|
||||
|
||||
assert fmt_p(0.0009, html=False) == '< 0.001*'
|
||||
assert fmt_p(0.999, html=False) == '> 0.99'
|
@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from .bayes_factors import bayesfactor_afbf
|
||||
from .config import config
|
||||
from .distributions import beta_oddsratio, beta_ratio, hdi, transformed_dist
|
||||
from .io import pickle_read_compressed, pickle_read_encrypted, pickle_write_compressed, pickle_write_encrypted
|
||||
from .regress import PenalisedLogit, regress, vif
|
||||
|
32
yli/config.py
Normal file
32
yli/config.py
Normal file
@ -0,0 +1,32 @@
|
||||
# scipy-yli: Helpful SciPy utilities and recipes
|
||||
# Copyright © 2022 Lee Yingtong Li (RunasSudo)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
class Config:
|
||||
"""Stores global configuration for the library"""
|
||||
|
||||
def __init__(self):
|
||||
# Display at least this many decimal places for p values
|
||||
self.pvalue_min_dps = 2
|
||||
# Display at most this many decimal places for p values
|
||||
self.pvalue_max_dps = 3
|
||||
# Display a leading zero for p values
|
||||
self.pvalue_leading_zero = True
|
||||
|
||||
# Alpha level for significance tests, confidence intervals
|
||||
self.alpha = 0.05
|
||||
|
||||
"""Global config singleton"""
|
||||
config = Config()
|
52
yli/utils.py
52
yli/utils.py
@ -20,6 +20,8 @@ import patsy
|
||||
|
||||
import warnings
|
||||
|
||||
from .config import config
|
||||
|
||||
# ----------------------------
|
||||
# Data cleaning and validation
|
||||
|
||||
@ -63,25 +65,47 @@ def as_2groups(df, data, group):
|
||||
def do_fmt_p(p):
|
||||
"""Return sign and formatted p value"""
|
||||
|
||||
if p < 0.001:
|
||||
return '<', '0.001*'
|
||||
elif p < 0.0095:
|
||||
return None, '{:.3f}*'.format(p)
|
||||
elif p < 0.045:
|
||||
return None, '{:.2f}*'.format(p)
|
||||
elif p < 0.05:
|
||||
return None, '{:.3f}*'.format(p) # 3dps to show significance
|
||||
elif p < 0.055:
|
||||
return None, '{:.3f}'.format(p) # 3dps to show non-significance
|
||||
elif p < 0.095:
|
||||
return None, '{:.2f}'.format(p)
|
||||
else:
|
||||
return None, '{:.1f}'.format(p)
|
||||
if p < 10**-config.pvalue_max_dps:
|
||||
# Smaller than min value
|
||||
return '<', '{:.1g}'.format(10**-config.pvalue_max_dps)
|
||||
|
||||
if p > 1 - 10**-config.pvalue_min_dps:
|
||||
# Larger than max value
|
||||
return '>', '{0:.{dps}f}'.format(1 - 10**-config.pvalue_min_dps, dps=config.pvalue_min_dps)
|
||||
|
||||
if round(p, config.pvalue_min_dps) == config.alpha:
|
||||
# Rounding to pvalue_min_dps makes significance ambiguous
|
||||
|
||||
if round(p, config.pvalue_max_dps) == config.alpha:
|
||||
# Still ambiguous to pvalue_max_dps
|
||||
|
||||
if p < config.alpha:
|
||||
# Significant: round down
|
||||
p = config.alpha - 10**-config.pvalue_max_dps
|
||||
else:
|
||||
# Nonsignificant: round up
|
||||
p = config.alpha + 10**-config.pvalue_max_dps
|
||||
|
||||
return None, '{0:.{dps}f}'.format(p, dps=config.pvalue_max_dps)
|
||||
|
||||
if p < 10**-config.pvalue_min_dps:
|
||||
# Insufficient resolution at pvalue_min_dps
|
||||
# We know from earlier comparison that 1 s.f. fits within pvalue_max_dps
|
||||
return None, '{:.1g}'.format(p)
|
||||
|
||||
# OK to round to pvalue_min_dps
|
||||
return None, '{0:.{dps}f}'.format(p, dps=config.pvalue_min_dps)
|
||||
|
||||
def fmt_p(p, *, html, nospace=False):
|
||||
"""Format p value"""
|
||||
|
||||
sign, fmt = do_fmt_p(p)
|
||||
|
||||
if not config.pvalue_leading_zero:
|
||||
fmt = fmt.lstrip('0')
|
||||
if p < config.alpha:
|
||||
fmt += '*'
|
||||
|
||||
if sign is not None:
|
||||
if nospace:
|
||||
pfmt = sign + fmt # e.g. "<0.001"
|
||||
|
Loading…
Reference in New Issue
Block a user