data_grid.py 22.4 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
"""
Author: Christian Schneider <c.schneider@uibk.ac.at>
Date: 17.03.2018
5
6
7

data_grid structure for our DataModule.
Powered by xarray (xarray.pydata.org)
8
9
10
"""
from .base import data_module_base
import holoviews as hv
Oscar Gargiulo's avatar
Oscar Gargiulo committed
11
import holoviews.operation.datashader as hd
Christian Schneider's avatar
Christian Schneider committed
12
import scipy.signal as sp_sig
13
import scipy.interpolate as sp_intp
14
15
16
17
try:
    import matplotlib.pyplot as plt
except NotImplementedError:
    pass
18
19
import numpy as np
import xarray as xr
20
from .data_table import data_table
21
from IPython.display import display
Christian Schneider's avatar
Christian Schneider committed
22
from mpl_toolkits.axes_grid1 import make_axes_locatable
23
from .fit_functions import mode_fit, lorentzian_fit
Christian Schneider's avatar
Christian Schneider committed
24

Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
25
hv.extension('bokeh', logo=False)
Christian Schneider's avatar
Christian Schneider committed
26

27
28

class data_grid(data_module_base):
29
30
31
32
33
34
35
36
37
38
39
    """Class for grid like data in multiple dimensions. Powered by the
    excellent library xarray for python.

    Initialization should be [x1_coords, x2_coords, ...., xN_coords,
    N_dim_data_tensor]
    Which means that the matrix for data is the last element of the given array.

    Supports also custom names for each dimension which can be given as an
    array. The last element is the name for the values (not the dimesion).
    Example: ['Probe Frequency (GHz)', 'Readout Frequency (GHz)', 'Mag (dB)']

40
41
    """

42
    def __init__(self, data_array, data_names=None):
43
44
        super().__init__()
        # Create default names
Christian Schneider's avatar
Christian Schneider committed
45
46
47
        df_names = ['x', 'y']
        df_names += ['x{}'.format(i) for i in range(2, len(data_array))]
        df_names[-1] = 'Value'  # Last element is matrix/tensor
48
49
50
51
        # Replace default names by the names given
        if data_names:
            for idx in range(len(data_names)):
                if data_names[idx]:
Christian Schneider's avatar
Christian Schneider committed
52
                    df_names[idx] = data_names[idx]
53
54
        # Create xarray
        self.df = xr.DataArray(data_array[-1],
Christian Schneider's avatar
Christian Schneider committed
55
                               dims=tuple(d_name for d_name in df_names[:-1]),
56
                               coords={d_name: d_vals for (d_name, d_vals) in
Christian Schneider's avatar
Christian Schneider committed
57
58
                                       zip(df_names[:-1], data_array[:-1])},
                               name=df_names[-1])
59

Christian Schneider's avatar
Christian Schneider committed
60
61
62
63
64
65
66
67
68
        # Idx variables for limiting data
        self.x_min = 0
        self.x_max = None
        self.y_min = 0
        self.y_max = None
        # Easy access variables
        self.name_x = df_names[0]
        self.name_y = df_names[1]
        self.name_v = df_names[-1]
69
        self.dtype = 'data_grid'
Christian Schneider's avatar
Christian Schneider committed
70

71
    # Helpful data functions ###################################################
72
73
    def return_coord(self, coord_name):
        return np.array(self.df.coords[coord_name])
74
75
76

    @property
    def x(self):
77
        """Default for two dim grid: Return first dimension"""
78
        return self.return_coord(self.name_x)[self.x_min:self.x_max]
Christian Schneider's avatar
Christian Schneider committed
79
80
81

    @x.setter
    def x(self, value):
82
        self.df.coords[self.name_x] = value
83
84
85
86

    @property
    def y(self):
        """Default for two dim grid: Return second dimension"""
87
        return self.return_coord(self.name_y)[self.y_min:self.y_max]
Christian Schneider's avatar
Christian Schneider committed
88
89
90

    @y.setter
    def y(self, value):
91
        self.df.coords[self.name_y] = value
92
93
94
95

    @property
    def z(self):
        """Default for two dim grid: Return values"""
96
97
        return np.array(self.df.values)[self.x_min:self.x_max,
                        self.y_min:self.y_max]
Christian Schneider's avatar
Christian Schneider committed
98
99
100

    @z.setter
    def z(self, values):
101
        self.df.values[self.x_min:self.x_max, self.y_min:self.y_max] = values
Christian Schneider's avatar
Christian Schneider committed
102
103
104

    @property
    def values(self):
105
106
        return np.array(self.df.values)[self.x_min:self.x_max,
                        self.y_min:self.y_max]
Christian Schneider's avatar
Christian Schneider committed
107
108
109

    @values.setter
    def values(self, values):
110
        self.df.values[self.x_min:self.x_max, self.y_min:self.y_max] = values
Christian Schneider's avatar
Christian Schneider committed
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

    def rename_x(self, new_name):
        self.df = self.df.rename({self.name_x: new_name})
        self.name_x = new_name

    def rename_y(self, new_name):
        self.df = self.df.rename({self.name_y: new_name})
        self.name_y = new_name

    def rename_values(self, new_name):
        self.df = self.df.rename(new_name)
        self.name_v = new_name

    def rename_z(self, new_name):
        return self.rename_values(new_name)  # alias

    def select(self, xrng=None, yrng=None):
        """Limit data between specified ranges.

        This function will select the data in the specified range of the
        x-axis and y-axis. If nothing is specified all the data will be
        selected.

        Note
        -----
        To undo a selection just run `select()` without an argument.

        Parameters
        -----------
        xrng : list
            Start and stop x value [x_start, x_stop]
        yrng : list
            Start and stop y value [y_start, y_stop]
        """
        if xrng is None:
            x_idx = [0, None]
        else:
            x = self.df.coords[self.name_x]
            x_idx = np.where((x >= xrng[0]) & (x <= xrng[1]))[0]

        if yrng is None:
            y_idx = [0, None]
        else:
            y = self.df.coords[self.name_y]
            y_idx = np.where((y >= yrng[0]) & (y <= yrng[1]))[0]

        self.y_min = y_idx[0]
        self.y_max = y_idx[-1]
        self.x_min = x_idx[0]
        self.x_max = x_idx[-1]
161

162
163
164
165
166
167
168
169
    def extract_x(self, x0, plot=True):
        """Extract z values along  axis for specified x value x0.

        This function will return the data at the line corresponding at
        the specified value of the x-axis.
        If the value is not exact, it will take the closest one above the
        value.

170
171
        Parameters
        -----------
172
173
174
175
176
177
178
179
        x0 : float
            y value for which the data should be extracted
        plot : bool
            Plot the extracted datamodule directly

        Returns
        --------
        DataModule
180
            data_table module
181
        """
182
        x, y = self.df.dims[:2]
183
184
        kws = {x: x0}
        ex = self.df.sel(method='nearest', **kws)
185
        data = data_table([self.y, np.array(ex)[self.y_min:self.y_max]],
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
186
                          [y, self.name_v])
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
        data.par = self.par
        data.comments = self.comments
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop

        if plot:
            display(data.plot_hv(title='{} = {:.6e}'.format(x, x0)))

        return data

    def extract_y(self, y0, plot=True):
        """Extract z values along  axis for specified y value y0.

        This function will return the data at the line corresponding at
        the specified value of the x-axis.
        If the value is not exact, it will take the closest one above the
        value.

        Parameters
        -----------
        y0 : float
            y value for which the data should be extracted
        plot : bool
            Plot the extracted datamodule directly

        Returns
        --------
        DataModule
217
            data_table module
218
219
        """
        x, y = self.df.dims[:2]
220
221
        kws = {y: y0}
        ex = self.df.sel(method='nearest', **kws)
222
        data = data_table([self.x, np.array(ex)[self.x_min:self.x_max]],
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
223
                          [x, self.name_v])
224
225
226
227
228
229
230
231
        data.par = self.par
        data.comments = self.comments
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop

        if plot:
Christian Schneider's avatar
Christian Schneider committed
232
            display(data.plot_hv(title='{} = {:.6e}'.format(y, y0)))
233
234

        return data
235

Christian Schneider's avatar
Christian Schneider committed
236
    # Plotting #################################################################
237
    def plot(self, name_x=None, name_y=None, cmap='magma', height=400,
Christian Schneider's avatar
Christian Schneider committed
238
             width=800, z_min=None, z_max=None, mode='Image'):
239
240
241
242
        """Plot table with Holoviews

        Parameters
        -----------
243
244
245
246
247
248
        name_x : None, str
            Column name used for x axis
        name_y : None, str
            Column name used for y axis
        cmap : str
            Name of colormap
249
250
251
252
        height : int
            Height of plot in pixels
        width : int
            Width of plot in pixels
253
        z_min : None, float(Optional)
254
255
            Minimum value for z values. If you set this, the scale will not
            automatically updated to full contrast.
256
        z_max : None, float (Optional)
257
            Maximum value for z values
258
259
        mode : 'QuadMesh', 'Image'
            Choose mode for holoviews plotting
260
261
        """
        if name_x is None:
262
            x_vals = self.df.dims[0]
263
        else:
264
            x_vals = name_x
265
266

        if name_y is None:
267
            y_vals = self.df.dims[1]
268
        else:
269
            y_vals = name_y
270
271
272
        hv.opts.defaults(hv.opts.Image(
            width=width,
            height=height,
273
            cmap=cmap,
274
275
            colorbar=True,
            colorbar_position="bottom"
276
        ))
277
278
279
280
        # Rename z values (to prevent bug in holoviews)
        df = self.df[self.x_min:self.x_max, self.y_min:self.y_max].rename('z')
        # Create dataset
        ds = hv.Dataset(df)
Oscar Gargiulo's avatar
Oscar Gargiulo committed
281
        
282
        # Create HoloObject
283
284
285
286
        if mode == 'QuadMesh':
            holo_object = hd.regrid(ds.to(hv.QuadMesh, [x_vals, y_vals]))
        else:
            holo_object = hd.regrid(ds.to(hv.Image, [x_vals, y_vals]))
287
        # Rescale
288
        holo_object = holo_object.redim.range(z=(z_min, z_max))
289
        return holo_object
Oscar Gargiulo's avatar
Oscar Gargiulo committed
290
        
291

Christian Schneider's avatar
Christian Schneider committed
292
293
294
295
296
    def pcolormesh(self):
        """Simple color plot without options. Quick and light"""
        df = self.df[self.x_min:self.x_max, self.y_min:self.y_max]
        df.plot.pcolormesh(self.name_x, self.name_y)

297
    def imshow(self, colormap='magma', zlabel_pos="right",
Christian Schneider's avatar
Bugfix    
Christian Schneider committed
298
               labelpad=0, cbar_y=0.5, **kwargs):
Christian Schneider's avatar
Christian Schneider committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
        """Color plot using matplotlib

        Note
        -----
        Plots just the .select() data.

        Parameters
        -----------
        colormap : str
            Choose colormap from 'Magma' (Def), 'Inferno', 'Plasma', 'Viridis'
        levels : int
            Color levels. Default is 256
        data_type : str
            Choose if data is linear 'Lin', logarithmic 'Log' or 'Amplitude'
        xlabel : str
            Label for x axis
        ylabel : str
            Label for y axis
        zlabel : str
            Label for colorbar
        zlabel_pos : "top", "right", "right_invert"
            Position and orientation of zlabel next to colorbar. If location is
            wrong, play with labelpad and cbar_y
        """
        # Just get select range
        df = self.df[self.x_min:self.x_max, self.y_min:self.y_max]

        # Plot
        ax = plt.subplot(111)
Gerhard Kirchmair's avatar
Gerhard Kirchmair committed
328
        im = df.plot.imshow(self.name_x, self.name_y, cmap=colormap,
Christian Schneider's avatar
Bugfix    
Christian Schneider committed
329
                            add_colorbar=False, ax=ax, **kwargs)
Christian Schneider's avatar
Christian Schneider committed
330
331
332
333
334
335

        # Customizing colorbar
        divider = make_axes_locatable(ax)
        cax = divider.append_axes("right", size="5%", pad=0.05)
        cbar = plt.colorbar(im, cax=cax)
        if zlabel_pos == "top":
336
            cbar.set_label(self.name_v, labelpad=labelpad - 32, y=cbar_y + 0.58,
Christian Schneider's avatar
Christian Schneider committed
337
338
                           rotation=0)
        elif zlabel_pos == "right":
339
            cbar.set_label(self.name_v, labelpad=labelpad + 5, y=cbar_y,
Christian Schneider's avatar
Christian Schneider committed
340
341
                           rotation=90)
        else:
342
            cbar.set_label(self.name_v, labelpad=labelpad + 15, y=cbar_y,
Christian Schneider's avatar
Christian Schneider committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
                           rotation=-90)
        return ax

    def contourf(self):
        df = self.df[self.x_min:self.x_max, self.y_min:self.y_max]
        df.plot.contourf(self.name_x, self.name_y)

    # Data processing functions ################################################
    def smoothx(self, nnb=21, polyorder=2):
        """Smooth data along x axis using the Savitzky-Golay filter

        Currently just for xyz data.

        Parameters
        -----------
        nnb : int
            Window length of filter. Must be an odd integer
        polyorder : int
            Polynomial order for the fit

        Returns
        --------
        np.array
            Filtered z matrix.
        """
        zsel = self.values
        for idx, row in enumerate(zsel):
            zsel[idx, :] = sp_sig.savgol_filter(row, nnb, polyorder)
        return zsel

    def smoothy(self, nnb=11, polyorder=2):
        """"Smooth data along y axis using the Savitzky-Golay filter

        Parameters
        -----------
        nnb : int
            Window length of filter. Must be an odd integer
        polyorder : int
            Polynomial order for the fit

        Returns
        --------
        np.array
            Filtered z matrix.
        """
        zsel = self.values
        for idx, row in enumerate(zsel.T):
            zsel[:, idx] = sp_sig.savgol_filter(row, nnb, polyorder)
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
        return zsel

    def interp(self, xnew, ynew, kind='cubic'):
        """Interpolates data to new x, y values `xnew` and `ynew`

        Parameters
        -----------
        xnew : np.array
            New x array
        ynew : np.array
            New y array
        kind : str
            Chose interpolation kind out of ['linear', 'cubic', 'quintic']

        Returns
        --------
        DataModule
            A new data_surface DataModule
        """
        # interp2d has a weird structure, therefore we have to transpose
        f = sp_intp.interp2d(self.x, self.y, self.values.T, kind=kind)
        return data_grid([xnew, ynew, f(xnew, ynew).T], [self.name_x,
                                                         self.name_y,
                                                         self.name_v])

    def extract_min_x(self, argument=True, nnB=21, polyorder=2):
        """Extract minimum z-values sweeping through x.

        Smooth data and extract minimum z values sweeping through x.

        Note
        -----
            Return depends on argument.
            - If set to True, it will return a new datamodule with (y, x)
            - If set to False, it will return a new datamodule with (y, min(z))

        Parameters
        -----------
        argument : bool
            Return corresponding x value (True) or corresponding minimum value
        nnB : int
            Window length for smoothing. Set to 1 to disable
                        smoothing.
        polyorder : int
            Polynomial order for smoothing

        Returns
        --------
        DataModule
440
            A `data_table` DataModule. Values depend on argument keyword (see
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
            above)
        """
        if nnB == 1:
            zsel = self.values
        else:
            zsel = self.smoothx(nnB, polyorder)  # Z data is smoothened along y

        tmp = np.zeros_like(self.x, dtype=np.float)

        # Get minima
        if argument:
            # Return y values
            for i in range(len(tmp)):
                tmp[i] = self.y[np.argmin(zsel[i, :])]
            name_tmp = self.name_y

        else:
            # Return z values
            for i in range(len(tmp)):
                tmp[i] = np.min(zsel[i, :])
            name_tmp = self.name_v

        data = data_table([self.x, tmp], [self.name_x, name_tmp])
        data.par = self.par
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop
        return data

    def extract_min_y(self, argument=True, nnB=21, polyorder=2):
        """Extract minimum z-values sweeping through y.

        Smooth data and extract minimum z values sweeping through y.

        Note
        -----
            Return depends on argument.
            - If set to True, it will return a new datamodule with (y, x)
            - If set to False, it will return a new datamodule with (y, min(z))

        Parameters
        -----------
        argument : bool
            Return corresponding x value (True) or corresponding minimum value
        nnB : int
            Window length for smoothing. Set to 1 to disable
                        smoothing.
        polyorder : int
            Polynomial order for smoothing

        Returns
        --------
        DataModule
495
            A `data_table` DataModule. Values depend on argument keyword (see
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
            above)
        """
        if nnB == 1:
            zsel = self.values
        else:
            zsel = self.smoothx(nnB, polyorder)  # Z data is smoothened along y

        tmp = np.zeros_like(self.y, dtype=np.float)

        # Get minima
        if argument:
            # Return x values
            for i in range(len(tmp)):
                tmp[i] = self.x[np.argmin(zsel[:, i])]
            name_tmp = self.name_x

        else:
            # Return z values
            for i in range(len(tmp)):
                tmp[i] = np.min(zsel[:, i])
            name_tmp = self.name_v

        data = data_table([self.y, tmp], [self.name_y, name_tmp])
        data.par = self.par
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop
        return data

    def extract_min_lorentzianfit_y(self, p0=None, argument=True, plot=False,
                                    adapt_p0=False):
        """Use Lorentzian with initial parameters p0 to fit the maximum z-value
        while sweeping through y.

        Note
        -----
            Return depends on argument.
            - If set to True, it will return a new datamodule with (y, x)
            - If set to False, it will return a new datamodule with (y, max(z))

        Parameters
        -----------
        p0 : list
            Initial guess for lorentzianfit pars ['center', 'width', 'offset',
            'amplitude']
        argument : bool
            Return x or z values
        plot : bool
            Plot each iteration
        adapt_p0 : bool
            Use p0 of last iteration

        Returns
        --------
        DataModule
552
            A `data_table` DataModule. Values depend on argument keyword (see
553
554
555
556
557
558
559
560
561
562
563
            above)
        """
        tmp = np.zeros_like(self.y)
        err_bars = []

        # Guess initial parameters, if none is given
        if p0 is None:
            tmp_data = data_table([self.x, self.values[:, 0]])
            idx_min = np.argmin(tmp_data.y)
            offset = np.mean(tmp_data.y)
            center = tmp_data.x[idx_min]
564
            width = center / 1e6  # kHz as guess
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
            amplitude = tmp_data.y[idx_min] - offset
            p0 = [center, width, offset, amplitude]

        # Go through y values and find x coordinate for maximum z value
        if argument:
            # Return x values
            for i in range(len(tmp)):
                tmp_dm = data_table([self.x, self.values[:, i]])
                fpars, fpars_err, _ = tmp_dm.fit(lorentzian_fit, p0, plot=plot,
                                                 plot_init=False,
                                                 plot_params=False,
                                                 maxfev=10000)
                tmp[i] = fpars[0]
                err_bars.append(fpars_err[0])
                tmp_name = self.name_x
                if adapt_p0:
                    p0 = fpars
        else:
            # Return z values
            for i in range(len(tmp)):
                tmp_dm = data_table([self.x, self.values[:, i]])
                fpars, fpars_err, _ = tmp_dm.fit(lorentzian_fit, p0, plot=plot,
                                                 plot_init=False,
                                                 plot_params=False,
                                                 maxfev=10000)
                tmp[i] = lorentzian_fit(fpars[0], *fpars)
591
592
593
594
                tmp_err_bar = np.sqrt((2 * fpars[3] / (np.pi * fpars[1] ** 2) *
                                       fpars_err[1]) ** 2 + fpars_err[2] ** 2 +
                                      (2 / (np.pi * fpars[1]) * fpars_err[
                                          3]) ** 2)
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
                err_bars.append(tmp_err_bar)
                tmp_name = self.name_v
                if adapt_p0:
                    p0 = fpars

        data = data_table([self.y, tmp, err_bars],
                          [self.name_y, tmp_name, '{} Errors'.format(tmp_name)])
        data.par = self.par
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop
        return data

    def extract_min_modefit_y(self, p0=None, argument=True, plot=False):
        """Use mode fit with initial parameters p0 to fit the minimum z-value
        while sweeping through y.

        Note
        -----
            Return depends on argument.
            - If set to True, it will return a new datamodule with (y, x)
            - If set to False, it will return a new datamodule with (y, min(z))

        Parameters
        -----------
        p0 : list
            Initial guess for modefit pars ['offset', 'Qc', 'df', 'f0', 'Qi']
        argument : bool
            Return x or z values

        Returns
        --------
        DataModule
629
            A `data_table` DataModule. Values depend on argument keyword (see
630
631
632
633
634
635
636
637
638
639
640
641
642
643
            above)
        """
        tmp = np.zeros_like(self.y)
        err_bars = []

        # Guess initial parameters, if none is given
        if p0 is None:
            tmp_data = data_table([self.x, self.values[:, 0]])
            idx_min = np.argmin(tmp_data.y)

            offset = np.mean(tmp_data.y)
            Qc = 1e4  # Medium value
            Qi = 1e4  # Medium value
            f0 = tmp_data.x[idx_min]  # Frequency of minimum
644
            df = f0 / 1e6  # MHz?
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
            p0 = [offset, Qc, df, f0, Qi]

        # Go through y values and find x coordinate for minimum z value
        if argument:
            # Return x values
            for i in range(len(tmp)):
                tmp_dm = data_table([self.x, self.values[:, i]])
                fpars, fpars_err, _ = tmp_dm.fit(mode_fit, p0, plot=plot,
                                                 plot_init=False,
                                                 plot_params=False,
                                                 maxfev=10000)
                tmp[i] = fpars[3]
                err_bars.append(fpars_err[3])
                tmp_name = self.name_x
        else:
            # Return z values
            for i in range(len(tmp)):
                tmp_dm = data_table([self.x, self.values[:, 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)
                tmp_name = self.name_v

        data = data_table([self.y, tmp, err_bars],
                          [self.name_y, tmp_name, '{} Errors'.format(tmp_name)])
        data.par = self.par
        data.temp_start = self.temp_start
        data.temp_stop = self.temp_stop
        data.time_start = self.time_start
        data.time_stop = self.time_stop
        return data