Commit 81676722 authored by Christian Schneider's avatar Christian Schneider
Browse files

Merged data_xy to data_table

parent 43c7dd04
......@@ -2,12 +2,10 @@ from .version import __version__
from . import plot_style
from .base import data_module_base
from .data_c import data_c
from .data_xy import data_xy
from .data_surface import data_surface, data_3d
from .data_IQ import data_IQ
from .functions import *
from .fit_functions import *
from .data_xy import data_xy
from .data_grid import data_grid
from .data_complex import data_cplx
......
......@@ -6,7 +6,6 @@ import numpy as np
from CircleFit.plotting import plot_rawdata
import CircleFit.plotting as cp
from CircleFit.circuit import Notch, Reflection
from .data_xy import data_xy
from CircleFit.fit_toolbox import get_delay
......@@ -97,7 +96,7 @@ class data_c(data_table):
this data module converted in amplitude (dB). Data Module parameters
are copied as well.
"""
tmp = data_xy(self.x, 20*np.log10(np.abs(self.value)))
tmp = data_table([self.x, 20*np.log10(np.abs(self.value))])
# TODO Make a function for copying datamodules
tmp.par = self.par
tmp.time_start = self.time_start
......@@ -147,7 +146,7 @@ class data_c(data_table):
print('Wrong unit inserted')
raise ValueError
tmp = data_xy(self.x, tmp)
tmp = data_table([self.x, tmp])
tmp.par = self.par
tmp.time_start = self.time_start
tmp.time_stop = self.time_stop
......
......@@ -12,7 +12,7 @@ hv.extension('bokeh')
import holoviews.operation.datashader as hd
import numpy as np
import xarray as xr
from .data_xy import data_xy
from .data_table import data_table
from IPython.display import display
class data_grid(data_module_base):
......@@ -89,8 +89,8 @@ class data_grid(data_module_base):
"""
x, y = self.df.dims[:2]
ex = self.df.sel(x=x0, method='nearest')
data = data_xy(np.array(self.df.coords[y]), np.array(ex),
x, y)
data = data_table([np.array(self.df.coords[y]), np.array(ex)],
[x, y])
data.par = self.par
data.comments = self.comments
data.temp_start = self.temp_start
......@@ -125,8 +125,8 @@ class data_grid(data_module_base):
"""
x, y = self.df.dims[:2]
ex = self.df.sel(y=y0, method='nearest')
data = data_xy(np.array(self.df.coords[x]), np.array(ex),
x, y)
data = data_table([np.array(self.df.coords[x]), np.array(ex)],
[x, y])
data.par = self.par
data.comments = self.comments
data.temp_start = self.temp_start
......
......@@ -5,13 +5,13 @@
Author: Iman, Oscar Gargiulo, Christian Schneider
"""
from .base import data_module_base
from .data_xy import data_xy
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sp_sig
import scipy.interpolate as sp_intp
from .fit_functions import mode_fit, lorentzian_fit
from mpl_toolkits.axes_grid1 import make_axes_locatable
from .data_table import data_table
# Bokeh
import bokeh.plotting as bp
......@@ -313,7 +313,7 @@ class data_surface(data_module_base):
zsel = self.return_zsel()
idx = np.argmax(self.y >= y0)
data = data_xy()
data = data_table()
data.par = self.par
data.comments = self.comments
......@@ -355,7 +355,7 @@ class data_surface(data_module_base):
idx = np.argmax(self.x >= x0)
data = data_xy()
data = data_table()
data.par = self.par
data.comments = self.comments
data.temp_start = self.temp_start
......@@ -418,7 +418,7 @@ class data_surface(data_module_base):
for i in range(len(tmp)):
tmp[i] = np.min(zsel[i, :])
data = data_xy()
data = data_table()
data.par = self.par
data.temp_start = self.temp_start
data.temp_stop = self.temp_stop
......@@ -467,7 +467,7 @@ class data_surface(data_module_base):
if argument:
# Return x values
for i in range(len(tmp)):
tmp_dm = data_xy(xsel, zsel[i, :])
tmp_dm = data_table([xsel, zsel[i, :]])
fpars, fpars_err, _ = tmp_dm.fit(lorentzian_fit, p0, plot=plot,
plot_init=False,
plot_params=False,
......@@ -479,7 +479,7 @@ class data_surface(data_module_base):
else:
# Return z values
for i in range(len(tmp)):
tmp_dm = data_xy(xsel, zsel[i, :])
tmp_dm = data_table([xsel, zsel[i, :]])
fpars, fpars_err, _ = tmp_dm.fit(lorentzian_fit, p0, plot=plot,
plot_init=False,
plot_params=False,
......@@ -491,7 +491,7 @@ class data_surface(data_module_base):
if adapt_p0:
p0 = fpars
data = data_xy()
data = data_table()
data.par = self.par
data.temp_start = self.temp_start
data.temp_stop = self.temp_stop
......@@ -534,7 +534,7 @@ class data_surface(data_module_base):
if argument:
# Return x values
for i in range(len(tmp)):
tmp_dm = data_xy(xsel, zsel[i, :])
tmp_dm = data_table([xsel, zsel[i, :]])
fpars, fpars_err, _ = tmp_dm.fit(mode_fit, p0, plot=plot,
plot_init=False,
plot_params=False,
......@@ -544,13 +544,13 @@ class data_surface(data_module_base):
else:
# Return z values
for i in range(len(tmp)):
tmp_dm = data_xy(xsel, zsel[i, :])
tmp_dm = data_table([xsel, zsel[i, :]])
fpars, _, _ = tmp_dm.fit(mode_fit, p0, plot=plot,
plot_init=False, plot_params=False,
maxfev=10000)
tmp[i] = mode_fit(fpars[3], *fpars)
data = data_xy()
data = data_table()
data.par = self.par
data.temp_start = self.temp_start
data.temp_stop = self.temp_stop
......@@ -605,7 +605,7 @@ class data_surface(data_module_base):
for i in range(len(tmp)):
tmp[i] = np.min(zsel[:, i])
data = data_xy()
data = data_table()
data.par = self.par
data.temp_start = self.temp_start
data.temp_stop = self.temp_stop
......
#n -*- coding: utf-8 -*-
"""
Class for simple data y=f(x)
Author: Iman, Oscar Gargiulo, Christian Schneider
"""
from .data_table import data_table
from .plot_style import cc
import numpy as np
import matplotlib.pyplot as plt
import bokeh.plotting as bp
from bokeh.models import HoverTool
import scipy.optimize as sp_opt
import scipy.signal as sp_sig
import scipy.interpolate as sp_intp
import pandas as pd
from collections import OrderedDict
from .plot_style import plot_xy
class data_xy(data_table):
"""Class for real y=f(x) data."""
def __init__(self, x, y, name_x='x', name_y='y'):
super(data_xy, self).__init__([x, y], [name_x, name_y])
self._fit_executed = False
self._fit_labels = None
self.name_x = name_x
self.name_y = name_y
def _repr_html_(self):
return '<h3>data_xy</h3>' + self.df.head().to_html()
def return_x(self):
return np.array(self.df[self.name_x])[self.idx_min:self.idx_max]
def return_y(self):
if '{}_smoothed'.format(self.name_y) in self.df.keys():
# Return smoothed data
name = '{}_smoothed'.format(self.name_y)
return np.array(self.df[name])[self.idx_min:self.idx_max]
else:
return np.array(self.df[self.name_y])[self.idx_min:
self.idx_max]
@property
def x(self):
return self.return_x()
@x.setter
def x(self, value):
self.df[self.name_x] = value
@property
def y(self):
return self.return_y()
@y.setter
def y(self, value):
self.df[self.name_y] = value
def rename_x(self, new_name):
"""Rename x variable"""
self.df = self.df.rename(columns={self.name_x: new_name})
self.name_x = new_name
def rename_y(self, new_name):
"""Rename y variable"""
self.df = self.df.rename(columns={self.name_y: new_name})
self.name_y = new_name
def load_var(self, x, y, name_x='x', name_y='y'):
"""Import data from two tuples/lists/array.
Parameters
-----------
x : list
X-Array. Typically frequencies
y : list
Y-Array. Typically magnitude or phase values
"""
x = np.array(x)
y = np.array(y)
if x.size/len(x) != 1.:
print('Error in the x-axis, check it!')
raise Exception('NOTANARRAY')
if y.size/len(y) != 1.:
print('Error in the y-axis, check it!')
raise Exception('NOTANARRAY')
if np.isscalar(x[0]) is False:
print('Error: bad x-axis, maybe it is a list of list')
raise Exception('NOTANARRAY')
if np.isscalar(y[0]) is False:
print('Error: bad x-axis, maybe it is a list of list')
raise Exception('NOTANARRAY')
if len(x) != len(y):
print('WARNING: x and y length mismatch')
self.import_data([x, y], ['x', 'y'])
# Plotting ################################################################
def plot(self, style='b-o', color=None, xscale=1, yscale=1,
plot_fit=True, linewidth=1.5, markersize=3,
fit_linewidth=1, plot_error_bars=True, legend=None,
legend_pos=0, engine='bokeh', title='', show=True, fig=None,
fitcolor='r', fit_on_top=False, **kwargs):
"""Plot data and optionally the fit.
Choose between two plot-engines: 'bokeh' and 'pyplot'
Parameters
----------
style : str
Specify plot style in matplotlib language. E.g. 'b-o' for
blue dots connected with a line.
color : str
Color shortcut (eg. 'b') or specific color like '#123456'.
Type::
to see available colors.
xscale : float
X scaling
yscale : float
Y scaling
plot_fit : bool
Plot the fit if available
linewidth : int
Thickness of lines
markersize : int
Size of markers
fit_linewidth : int
Thickness of fit line
plot_error_bars : bool
Plot error bars
ToDo: Implement this for bokeh
legend : list
Custom legend for plot ['Label 1', 'Label 2']]
legend_pos : int, str
Location of legend
engine : str
Plot engine. Choose between 'bokeh' and 'pyplot'
title : str
Title of the plot
show : bool
Directly show plot. Useful if one wants to add labels etc.
and show plot afterwards.
fig : object
Figure to plot into (bokeh/matplotlib figure object)
fitcolor : str
Color shortcut (like 'b') or specific color (like #123456) for
fit
fit_on_top : bool
Data over fit or fit over data
Returns
--------
object
Returns a fit object for bokeh or matplotlib. This is useful, if
you want to add for example another points, lines, labels, etc
afterwards.
"""
x = self.x
y = self.y
# Don't show plot if figure is given (normally one does not need this)
if fig:
show = False
# Get fit function
if plot_fit and self._fit_executed:
exec(self._fit_function_code)
possibles = globals().copy()
possibles.update(locals())
fitfunc = possibles.get(self._fit_function)
if not fitfunc:
raise Exception('Method %s not implemented' %
self._fit_function)
# Bokeh
if engine in ['bokeh', 'b']:
tools = ['box_zoom', 'pan', 'wheel_zoom', 'reset',
'save', 'hover']
if not fig:
fig = bp.figure(plot_width=800, plot_height=400, tools=tools,
toolbar_location='above', title=title)
# Data
plot_xy(x*xscale, y*yscale, style, color, linewidth,
markersize, legend, legend_pos, engine, title, show, fig,
**kwargs)
# Fit
if plot_fit and self._fit_executed:
plot_xy(x*xscale, (fitfunc(x, *self._fit_parameters)) *
yscale, '-', fitcolor, fit_linewidth, markersize,
legend, legend_pos, engine, title, show, fig)
# Format nicer HoverTool
tooltips = [("x", "$x"), ("y", "$y")]
fig.select(dict(type=HoverTool)).tooltips = OrderedDict(tooltips)
# Add labels
fig.xaxis.axis_label = self.df.columns[0]
fig.yaxis.axis_label = self.df.columns[1]
if show:
bp.show(fig)
return fig
# Matplotlib
elif engine in ['pyplot', 'p']:
# Data
plt.title(title)
plot_xy(x*xscale, y*yscale, style, color, linewidth,
markersize, legend, legend_pos, engine, title, show=False,
fig=fig, **kwargs)
# Fit
if not fig:
try:
fig = plt.gcf()
except:
fig = plt.figure()
if plot_fit and self._fit_executed:
if not fit_on_top:
fit_zorder=0
else:
fit_zorder=99
plt.plot(x*xscale, fitfunc(x,
*self._fit_parameters)*yscale,
'-', color=fitcolor, linewidth=fit_linewidth,
zorder=fit_zorder)
elif engine in ['holoviews', 'h']:
return self.plot_hv(color=color, **kwargs)
# ToDO: Error Bars
# if plot_error_bars is True:
# plt.errorbar(xsel*xscale,ysel*yscale,self.return_yerr()*yscale,
# self.return_xerr()*xscale,fmt=plot_style)
def fit(self, fitfunc, fit_p_init, plot=True, plot_init=True,
plot_params=True, figuresize=(800, 400), labels=None, **kwargs):
"""Fit `fitfunc` to data with initial fit parameters `fit_p_init`
Note
-----
After the fit, the data module will contain the fit, however it
should be saved again. Don't forget this.
The fit will be performed on the stored data, not the plotted
one. If you use xscale or yscale different than the one you
plot, you should take it into account
Parameters
-----------
fitfunc : func
Function to fit to. There exist already some functions in the
DataModule library (DataModule/fit_functions)
fit_p_init : list
Initial (guessed) parameters
plot : bool
Plot fit after result
plot_init : bool
Plot initial guess.
figuresize : tuple
Figure size in pixels (width, height). If you forget your glasses,
you can increase the size.
labels : list
Label for fitparameters. E.g. ['offset', 'amplitude', 'fr']
**kwargs : keywords
Keywords for fitfunction like maxfev=10000, epsfcn=1e-10,
factor=0.1, xtol=1e-9, ftol=1e-9...
At the end of the fit the data module will contain the function
used to fit and some fit-related functions will be enabled.
The fit parameters are stored in
self._fit_parameters, self._fit_par_errors
and the average error (sigma) in self._fit_data_error.
Returns
list, list, float
"""
xsel, ysel = self.x, self.y
try:
fit_p_fit, err = sp_opt.curve_fit(fitfunc, xsel, ysel, fit_p_init,
**kwargs)
except RuntimeError:
print('At least one fit did not converge', end=' ', flush=True)
fit_p_fit = np.array([np.nan for i in fit_p_init])
err = np.array([[np.nan for i in fit_p_init] for j in fit_p_init])
raise Exception(RuntimeError)
if plot:
# Just use bokeh since we don't want to publish this plot
fig = bp.figure(title='Fit', plot_width=800, plot_height=400)
if plot_init:
# Plot initial parameter guess, fit and data
fig.line(xsel, fitfunc(xsel, *fit_p_init), color=cc['g'],
legend='Init guess')
fig.circle(xsel, ysel, color=cc['b'], legend='Data')
fig.line(xsel, fitfunc(xsel, *fit_p_fit), color=cc['r'],
legend='Fit')
bp.show(fig)
# Save fitfunction as string
import inspect
self._fit_executed = True
code = inspect.getsourcelines(fitfunc)[0]
self._fit_function = fitfunc.__name__
self._fit_function_code = ''.join(code)
self._fit_parameters = fit_p_fit
self._fit_par_errors = np.sqrt(np.diag(err))
# Chi squared
self._fit_data_error = (np.sum((fitfunc(xsel, *fit_p_fit)-ysel)**2) /
(len(xsel) - 2))
self._fit_labels = labels
if plot_params:
print(self.fit_pars())
return fit_p_fit, self._fit_par_errors, self._fit_data_error
def fit_func(self, x=None):
"""Calculates values of fit function for an x-array/values.
Parameters
-----------
x : None, float, list, np.array
X values. None means same x data as datamodule.
ToDo
-----
Would like to rename the function to calc_fitfunc(self, x=None)
"""
if not self._fit_executed:
print('No fit was executed on this data')
return
exec(self._fit_function_code)
possibles = globals().copy()
possibles.update(locals())
fitfunc = possibles.get(self._fit_function)
if not fitfunc:
raise Exception('Method %s not implemented' % self._fit_function)
if x is None:
return fitfunc(self.x, *self._fit_parameters)
else:
return fitfunc(x, *self._fit_parameters)
def localmin(self, min_threshold=None, npoints=1, mode='clip'):
"""Obtain all the local minimas
Parameters
-----------
min_threshold : float
Only consider minima below this value
npoints : int
How many points on each side to use for the comparison.
mode : str
'clip' (def) or 'wrap'. If wrap is used, the data will considered
periodic-like
Returns
--------
np.array
x and y values of local minima
"""
xsel = self.x
ysel = self.y
if min_threshold is not None:
msk = ysel <= min_threshold
xsel = xsel[msk]
ysel = ysel[msk]
min_idx = sp_sig.argrelextrema(ysel, np.less, order=npoints, mode=mode)
return np.vstack((xsel[min_idx], ysel[min_idx]))
def localmax(self, max_threshold=None, npoints=1, mode='clip'):
"""Obtain all the local maxima
Parameters
-----------
min_threshold : float
Only consider maxima above this value
npoints : int
How many points on each side to use for the comparison.
mode : str
'clip' (def) or 'wrap'. If wrap is used, the data will considered
periodic-like
Returns
--------
np.array
x and y values of local maxima
"""
xsel = self.x
ysel = self.y
if max_threshold is not None:
msk = ysel >= max_threshold
xsel = xsel[msk]
ysel = ysel[msk]
min_idx = sp_sig.argrelextrema(ysel, np.greater, order=npoints,
mode=mode)
return np.vstack((xsel[min_idx], ysel[min_idx]))
def smooth(self, nnb=21, polyorder=2):
"""Smooth data using the Savitzky-Golay filter.
Information
https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter
This has the advantage over moving_average, that the bias of smaller
local minima/maxima is removed.
Note
-----
Saves the smoothed data as dm.y values. To unsmooth the data run
`unsmooth()`
Parameters
-----------
nnb : int
Window length of filter
polyorder : int
Polynomial order for the fit
"""
y_filtered = sp_sig.savgol_filter(self.df[self.name_y], nnb, polyorder)
self.import_data([self.x, y_filtered],
['x', '{}_smoothed'.format(self.df.keys()[1])])
def unsmooth(self):
"""Recover unsmoothened y data."""
try:
del self.df['{}_smoothed'.format(self.name_y)]
except KeyError:
pass
def interp(self, xnew, kind='cubic'