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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from .bayes_factors import bayesfactor_afbf
|
from .bayes_factors import bayesfactor_afbf
|
||||||
|
from .config import config
|
||||||
from .distributions import beta_oddsratio, beta_ratio, hdi, transformed_dist
|
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 .io import pickle_read_compressed, pickle_read_encrypted, pickle_write_compressed, pickle_write_encrypted
|
||||||
from .regress import PenalisedLogit, regress, vif
|
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
|
import warnings
|
||||||
|
|
||||||
|
from .config import config
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Data cleaning and validation
|
# Data cleaning and validation
|
||||||
|
|
||||||
@ -63,25 +65,47 @@ def as_2groups(df, data, group):
|
|||||||
def do_fmt_p(p):
|
def do_fmt_p(p):
|
||||||
"""Return sign and formatted p value"""
|
"""Return sign and formatted p value"""
|
||||||
|
|
||||||
if p < 0.001:
|
if p < 10**-config.pvalue_max_dps:
|
||||||
return '<', '0.001*'
|
# Smaller than min value
|
||||||
elif p < 0.0095:
|
return '<', '{:.1g}'.format(10**-config.pvalue_max_dps)
|
||||||
return None, '{:.3f}*'.format(p)
|
|
||||||
elif p < 0.045:
|
if p > 1 - 10**-config.pvalue_min_dps:
|
||||||
return None, '{:.2f}*'.format(p)
|
# Larger than max value
|
||||||
elif p < 0.05:
|
return '>', '{0:.{dps}f}'.format(1 - 10**-config.pvalue_min_dps, dps=config.pvalue_min_dps)
|
||||||
return None, '{:.3f}*'.format(p) # 3dps to show significance
|
|
||||||
elif p < 0.055:
|
if round(p, config.pvalue_min_dps) == config.alpha:
|
||||||
return None, '{:.3f}'.format(p) # 3dps to show non-significance
|
# Rounding to pvalue_min_dps makes significance ambiguous
|
||||||
elif p < 0.095:
|
|
||||||
return None, '{:.2f}'.format(p)
|
if round(p, config.pvalue_max_dps) == config.alpha:
|
||||||
else:
|
# Still ambiguous to pvalue_max_dps
|
||||||
return None, '{:.1f}'.format(p)
|
|
||||||
|
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):
|
def fmt_p(p, *, html, nospace=False):
|
||||||
"""Format p value"""
|
"""Format p value"""
|
||||||
|
|
||||||
sign, fmt = do_fmt_p(p)
|
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 sign is not None:
|
||||||
if nospace:
|
if nospace:
|
||||||
pfmt = sign + fmt # e.g. "<0.001"
|
pfmt = sign + fmt # e.g. "<0.001"
|
||||||
|
Loading…
Reference in New Issue
Block a user