Skip to content

Commit

Permalink
Add code to make curve fitting optional return dicts of results. Add …
Browse files Browse the repository at this point in the history
…an example

of using it with DataFolder
  • Loading branch information
gb119 committed Aug 24, 2019
1 parent 1b2cb29 commit 4de91d1
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 21 deletions.
17 changes: 11 additions & 6 deletions Stoner/Core.py
Expand Up @@ -1699,12 +1699,17 @@ def add_column(self, column_data, header=None, index=None, func_args=None, repla
if not replace:
colums = copy.copy(self.column_headers)
old_setas = self.setas.clone
self.data = DataArray(
_np_.append(
self.data[:, :index], _np_.append(_np_.zeros_like(_np__data), self.data[:, index:], axis=1), axis=1
),
setas=self.setas.clone,
)
if index == self.data.shape[1]: # appending column
self.data = DataArray(_np_.append(self.data, _np__data, axis=1), setas=self.setas.clone)
else:
self.data = DataArray(
_np_.append(
self.data[:, :index],
_np_.append(_np_.zeros_like(_np__data), self.data[:, index:], axis=1),
axis=1,
),
setas=self.setas.clone,
)
for ix in range(0, index):
self.column_headers[ix] = colums[ix]
self.setas[ix] = old_setas[ix]
Expand Down
53 changes: 50 additions & 3 deletions Stoner/analysis/fitting/mixins.py
Expand Up @@ -222,6 +222,18 @@ def name(self):
"""Name of the model fitted."""
return self.func.__name__

@property
def dict(self):
"""Optimal parameters and errors as a Dictionary."""
ret = {}
for p, v, e, p0 in zip(self.params, self.popt, self.perr):
ret[p] = v
ret["d_{}", format(p)] = e
ret["chi-square"] = self.chisqr
ret["red. chi-sqr"] = self.redchi
ret["nfev"] = self.nfev
return ret

@property
def full(self):
"""The same as :py:attr:`_curve_fit_result.row`"""
Expand Down Expand Up @@ -717,7 +729,22 @@ def __lmfit_one(self, model, data, params, prefix, columns, scale_covar, **kargs
ycol=columns.ycol,
)

retval = {"fit": (row[::2], fit.covar), "report": fit, "row": row, "full": (fit, row), "data": self}
res_dict = {}
for ix, p in enumerate(fit.params):
res_dict[p] = fit.params[p].value
res_dict["d_{}".format(p)] = fit.params[p].stderr
res_dict["chi-square"] = fit.chisqr
res_dict["red. chi-sqr"] = fit.redchi
res_dict["nfeev"] = fit.nfev

retval = {
"fit": (row[::2], fit.covar),
"report": fit,
"row": row,
"full": (fit, row),
"data": self,
"dict": res_dict,
}
if output not in retval:
raise RuntimeError("Failed to recognise output format:{}".format(output))
else:
Expand Down Expand Up @@ -896,15 +923,21 @@ def _odr_one(self, data, model, prefix, _, **kargs):
# Store our current mask, calculate new column's mask and turn off mask

param_names = getattr(model, "param_names", None)
for i in range(len(param_names)):
ret_dict = {}
for i, p in enumerate(param_names):
row.extend([fit_result.beta[i], fit_result.sd_beta[i]])
ret_dict[p], ret_dict["d_{}".format(p)] = fit_result.beta[i], fit_result.sd_beta[i]
row.append(fit_result.redchi)
ret_dict["chi-square"] = fit_result.chisqr
ret_dict["red. chi-sqr"] = fit_result.redchi

retval = {
"fit": (row[::2], fit_result.cov_beta),
"report": fit_result,
"row": row,
"full": (fit_result, row),
"data": self,
"dict": ret_dict,
}
if output not in retval:
raise RuntimeError("Failed to recognise output format:{}".format(output))
Expand Down Expand Up @@ -1221,8 +1254,22 @@ def differential_evolution(self, model, xcol=None, ycol=None, p0=None, sigma=Non
row = self._record_curve_fit_result(
model, fit, _.xcol, header, result, replace, residuals=residuals, prefix=prefix, ycol=_.ycol
)
ret_dict = {}
for i, p in enumerate(model.param_names):
ret_dict[p], ret_dict["d_{}".format(p)] = row[2 * i], row[2 * i + 1]
ret_dict["chi-square"] = polish.chisqr
ret_dict["red. chi-sqr"] = polish.redchi
ret_dict["nfev"] = fit.nfev
fit.dict = ret_dict

retval = {"fit": (row[::2], fit.covar), "report": fit, "row": row, "full": (fit, row), "data": self}
retval = {
"fit": (row[::2], fit.covar),
"report": fit,
"row": row,
"full": (fit, row),
"data": self,
"dict": fit.dict,
}
if output not in retval:
raise RuntimeError("Failed to recognise output format:{}".format(output))
else:
Expand Down
28 changes: 16 additions & 12 deletions Stoner/core/utils.py
Expand Up @@ -76,20 +76,24 @@ def add_core(other, newdata):
ret = newdata
elif isinstance(other, Mapping):
# First check keys all in newdata
order = dict()
for k in other:
try:
order[k] = newdata.find_col(k)
except KeyError:
newdata.add_column(np.ones(len(newdata)) * np.NaN, header=k)
order[k] = newdata.shape[1] - 1
row = np.ones(newdata.shape[1]) * np.NaN
for k in order:
row[order[k]] = other[k]
newdata.data = np.append(newdata.data, np.atleast_2d(row), axis=0)
if len(newdata) == 0:
newdata.data = np.atleast_2d(list(other.values()))
newdata.column_headers = list(other.keys())
else:
order = dict()
for k in other:
try:
order[k] = newdata.find_col(k)
except (KeyError, re.error):
newdata.add_column(np.ones(len(newdata)) * np.NaN, header=k)
order[k] = newdata.shape[1] - 1
row = np.ones(newdata.shape[1]) * np.NaN
for k in order:
row[order[k]] = other[k]
newdata.data = np.append(newdata.data, np.atleast_2d(row), axis=0)
ret = newdata
else:
ret = NotImplemented
return NotImplemented
ret._data._setas.shape = ret.shape
for attr in newdata.__dict__:
if attr not in ("setas", "metadata", "data", "column_headers", "mask") and not attr.startswith("_"):
Expand Down
46 changes: 46 additions & 0 deletions doc/samples/folder_fit.py
@@ -0,0 +1,46 @@
"""Demo of Fitting a directory of files
"""
from os.path import join

from Stoner import __home__, DataFolder, Data
from Stoner.plot.formats import TexEngFormatter
from Stoner.Fit import Quadratic

# Set up the directory with our data
datafiles = join(__home__, "..", "sample-data", "NLIV")

# DataFolder of our data files
fldr = DataFolder(datafiles, pattern="*.txt", setas="yx.")

# Another Data object to keep the results in
result = Data()

# Loop over the files in the DataFolder
for d in fldr:
# Fit, returning results as a dictionary
row = d.lmfit(Quadratic, output="dict")
# Add Extra information from metadata
row["Magnetic Field $\\mu_0 H (T)$"] = d["y"]
# Add the new row of data to the result.
result += row

# Set columns and set the plot axis formatting to use SI precfixes
result.setas(x="Field", y="b", e="d_b")
result.template.xformatter = TexEngFormatter
result.template.yformatter = TexEngFormatter

# Plot
result.plot(fmt="k.")

# An alternative way to run the Analysis - this time with
# an orthogonal didstance regression algorithm

# Run the fitt for each file in the fldr. Set the outpout to "data" to
# Have the amended results replace the existing data files
fldr.each.odr(Quadratic, output="data")
# Now take a slice through the metadata to get the files we want.
result_2 = fldr.metadata.slice(["y", "Quadratic:b", "Quadratic:b err"], output="Data")

# Set the columns assignments and plot
result_2.setas = "xye"
result_2.plot(fmt="r.", figure=result.fig)
10 changes: 10 additions & 0 deletions tests/Stoner/test_Core.py
Expand Up @@ -277,6 +277,16 @@ def test_operators(self):
self.assertEqual(f1,self.d2.shape[0]+1,"Failed to add a row by providing a dictionary")
self.assertEqual(f2,self.d2.shape[1]+1,"Failed to add an extra columns by adding a dictionary with a new key")
self.assertTrue(np.isnan(f[-1,2]) and np.isnan(f[0,-1]),"Unset values when adding a dictionary not NaN")
d=Data()
d+=np.ones(5)
d+=np.zeros(5)
self.assertTrue(np.all(d//1==np.array([1,0])),"Adding 1D arrays should add new rows")
try:
d+=True
except TypeError:
pass
else:
self.assertTrue(False,"Failed to raise TypeError on adding unsupported type")

def test_properties(self):
self.little=Data()
Expand Down

0 comments on commit 4de91d1

Please sign in to comment.