Skip to content

Commit

Permalink
Merge pull request #548 from lmfit/html_repr
Browse files Browse the repository at this point in the history
alternative html_repr
  • Loading branch information
newville authored Apr 2, 2019
2 parents d1ed894 + 785fe08 commit e59a460
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 3 deletions.
6 changes: 6 additions & 0 deletions lmfit/minimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

from ._ampgo import ampgo
from .parameter import Parameter, Parameters
from .printfuncs import fitreport_html_table

# check for EMCEE
try:
Expand Down Expand Up @@ -349,6 +350,11 @@ def _calculate_statistics(self):
self.aic = _neg2_log_likel + 2 * self.nvarys
self.bic = _neg2_log_likel + np.log(self.ndata) * self.nvarys

def _repr_html_(self, show_correl=True, min_correl=0.1):
"""Returns a HTML representation of parameters data."""
return fitreport_html_table(self, show_correl=show_correl,
min_correl=min_correl)


class Minimizer(object):
"""A general minimizer for curve fitting and optimization."""
Expand Down
9 changes: 8 additions & 1 deletion lmfit/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .confidence import conf_interval
from .jsonutils import HAS_DILL, decode4js, encode4js
from .minimizer import validate_nan_policy, MinimizerResult
from .printfuncs import ci_report, fit_report
from .printfuncs import ci_report, fit_report, fitreport_html_table

# Use pandas.isnull for aligning missing data if pandas is available.
# otherwise use numpy.isnan
Expand Down Expand Up @@ -1565,6 +1565,13 @@ def fit_report(self, modelpars=None, show_correl=True,
modname = self.model._reprstring(long=True)
return '[[Model]]\n %s\n%s\n' % (modname, report)

def _repr_html_(self, show_correl=True, min_correl=0.1):
"""Returns a HTML representation of parameters data."""
report = fitreport_html_table(self, show_correl=show_correl,
min_correl=min_correl)
modname = self.model._reprstring(long=True)
return "<h2> Model</h2> %s %s" % (modname, report)

def dumps(self, **kws):
"""Represent ModelResult as a JSON string.
Expand Down
5 changes: 5 additions & 0 deletions lmfit/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import uncertainties

from .jsonutils import decode4js, encode4js
from .printfuncs import params_html_table

SCIPY_FUNCTIONS = {'gamfcn': scipy.special.gamma}
for name in ('erf', 'erfc', 'wofz'):
Expand Down Expand Up @@ -284,6 +285,10 @@ def pretty_print(self, oneline=False, colwidth=8, precision=4, fmt='g',
print(line.format(name_len=name_len, n=colwidth, p=precision,
f=fmt, **pvalues))

def _repr_html_(self):
"""Returns a HTML representation of parameters data."""
return params_html_table(self)

def add(self, name, value=None, vary=True, min=-inf, max=inf, expr=None,
brute_step=None):
"""Add a Parameter.
Expand Down
102 changes: 100 additions & 2 deletions lmfit/printfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
except ImportError:
HAS_NUMDIFFTOOLS = False

from .parameter import Parameters


def alphanumeric_sort(s, _nsre=re.compile('([0-9]+)')):
"""Sort alphanumeric string."""
Expand Down Expand Up @@ -111,6 +109,7 @@ def fit_report(inpars, modelpars=None, show_correl=True, min_correl=0.1,
Multi-line text of fit report.
"""
from .parameter import Parameters
if isinstance(inpars, Parameters):
result, params = None, inpars
if hasattr(inpars, 'params'):
Expand Down Expand Up @@ -210,6 +209,105 @@ def fit_report(inpars, modelpars=None, show_correl=True, min_correl=0.1,
return '\n'.join(buff)


def fitreport_html_table(result, show_correl=True, min_correl=0.1):
"""Report minimizer result as an html table"""
html = []
add = html.append

def stat_row(label, val, val2=''):
add('<tr><td>%s</td><td>%s</td><td>%s</td></tr>' % (label, val, val2))
add('<h2>Fit Statistics</h2>')
add('<table>')
stat_row('fitting method', result.method)
stat_row('# function evals', result.nfev)
stat_row('# data points', result.ndata)
stat_row('# variables', result.nvarys)
stat_row('chi-square', gformat(result.chisqr))
stat_row('reduced chi-square', gformat(result.redchi))
stat_row('Akaike info crit.', gformat(result.aic))
stat_row('Bayesian info crit.', gformat(result.bic))
add('</table>')
add('<h2>Variables</h2>')
add(result.params._repr_html_())
if show_correl:
correls = []
parnames = list(result.params.keys())
for i, name in enumerate(result.params):
par = result.params[name]
if not par.vary:
continue
if hasattr(par, 'correl') and par.correl is not None:
for name2 in parnames[i+1:]:
if (name != name2 and name2 in par.correl and
abs(par.correl[name2]) > min_correl):
correls.append((name, name2, par.correl[name2]))
if len(correls) > 0:
sort_correls = sorted(correls, key=lambda val: abs(val[2]))
sort_correls.reverse()
extra = '(unreported correlations are < %.3f)' % (min_correl)
add('<h2>Correlations %s</h2>' % extra)
add('<table>')
for name1, name2, val in sort_correls:
stat_row(name1, name2, "%.4f" % val)
add('</table>')
return ''.join(html)


def params_html_table(params):
"""Returns a HTML representation of parameters data."""
has_err = any([p.stderr is not None for p in params.values()])
has_expr = any([p.expr is not None for p in params.values()])
has_brute = any([p.brute_step is not None for p in params.values()])

html = []
add = html.append

def cell(x, cat='td'):
return add('<%s> %s </%s>' % (cat, x, cat))

add('<table><tr>')
headers = ['name', 'value']
if has_err:
headers.extend(['standard error', 'relative error'])
headers.extend(['initial value', 'min', 'max', 'vary'])
if has_expr:
headers.append('expression')
if has_brute:
headers.append('brute step')
for h in headers:
cell(h, cat='th')
add('</tr>')

for p in params.values():
rows = [p.name, gformat(p.value)]
if has_err:
serr, perr = '', ''
if p.stderr is not None:
serr = gformat(p.stderr)
perr = '%.2f%%' % (100 * p.stderr / p.value)
rows.extend([serr, perr])
rows.extend((p.init_value, gformat(p.min), gformat(p.max),
'%s' % p.vary))
if has_expr:
expr = ''
if p.expr is not None:
expr = p.expr
rows.append(expr)

if has_brute:
brute_step = 'None'
if p.brute_step is not None:
brute_step = gformat(p.brute_step)
rows.append(brute_step)

add('<tr>')
for r in rows:
cell(r)
add('</tr>')
add('</table>')
return ''.join(html)


def report_errors(params, **kws):
"""Print a report for fitted params: see error_report()."""
print(fit_report(params, **kws))
Expand Down
75 changes: 75 additions & 0 deletions tests/test_fitreports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import numpy as np

from lmfit import Minimizer, Parameters, conf_interval, ci_report, fit_report
from lmfit.models import GaussianModel
from lmfit.lineshapes import gaussian

np.random.seed(0)


def test_reports_created():
"""do a simple Model fit but with all the bells-and-whistles
and verify that the reports are created
"""
x = np.linspace(0, 12, 601)
data = gaussian(x, amplitude=36.4, center=6.70, sigma=0.88)
data = data + np.random.normal(size=len(x), scale=3.2)
model = GaussianModel()
params = model.make_params(amplitude=50, center=5, sigma=2)

params['amplitude'].min = 0
params['sigma'].min = 0
params['sigma'].brute_step = 0.001

result = model.fit(data, params, x=x)

report = result.fit_report()
assert(len(report) > 500)

html_params = result.params._repr_html_()
assert(len(html_params) > 500)

html_report = result._repr_html_()
assert(len(html_report) > 1000)


def test_ci_report():
"""test confidence interval report"""

def residual(pars, x, data=None):
argu = (x*pars['decay'])**2
shift = pars['shift']
if abs(shift) > np.pi/2:
shift = shift - np.sign(shift)*np.pi
model = pars['amp']*np.sin(shift + x/pars['period']) * np.exp(-argu)
if data is None:
return model
return model - data

p_true = Parameters()
p_true.add('amp', value=14.0)
p_true.add('period', value=5.33)
p_true.add('shift', value=0.123)
p_true.add('decay', value=0.010)

n = 2500
xmin = 0.
xmax = 250.0
x = np.linspace(xmin, xmax, n)
data = residual(p_true, x) + np.random.normal(scale=0.7215, size=n)

fit_params = Parameters()
fit_params.add('amp', value=13.0)
fit_params.add('period', value=2)
fit_params.add('shift', value=0.0)
fit_params.add('decay', value=0.02)

mini = Minimizer(residual, fit_params, fcn_args=(x,),
fcn_kws={'data': data})
out = mini.leastsq()
report = fit_report(out)
assert(len(report) > 500)

ci, tr = conf_interval(mini, out, trace=True)
report = ci_report(ci)
assert(len(report) > 250)

0 comments on commit e59a460

Please sign in to comment.