diff --git a/Stoner/Core.py b/Stoner/Core.py index 0a1801210..1d5189c38 100644 --- a/Stoner/Core.py +++ b/Stoner/Core.py @@ -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] diff --git a/Stoner/analysis/fitting/mixins.py b/Stoner/analysis/fitting/mixins.py index 86d23b986..569ea9246 100644 --- a/Stoner/analysis/fitting/mixins.py +++ b/Stoner/analysis/fitting/mixins.py @@ -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`""" @@ -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: @@ -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)) @@ -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: diff --git a/Stoner/core/utils.py b/Stoner/core/utils.py index 2ba189d27..72b3dddf4 100644 --- a/Stoner/core/utils.py +++ b/Stoner/core/utils.py @@ -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("_"): diff --git a/doc/samples/folder_fit.py b/doc/samples/folder_fit.py new file mode 100755 index 000000000..b57aecbc4 --- /dev/null +++ b/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) diff --git a/tests/Stoner/test_Core.py b/tests/Stoner/test_Core.py index 64f88625e..fe830e485 100644 --- a/tests/Stoner/test_Core.py +++ b/tests/Stoner/test_Core.py @@ -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()