Exemplo n.º 1
0
def prepare(self, measurand, inputs, options=dict()):
    """
    Prepares a test for a regression model
    Parameters
    ----------
        measurand: dict
            measurand = {'8019043': ['NO2']}
        inputs: dict
            inputs per device and reading
                inputs = {'devicename': ['reading-1', 'reading-2']}
        options: dict
            Options including data processing. Defaults in config._model_def_opt
    Returns
    -------
        df = pandas Dataframe
        measurand_name = string
    """

    options = dict_fmerge(options, config._model_def_opt)

    # Measurand
    measurand_device = list(measurand.keys())[0]
    measurand_metric = measurand[measurand_device][0]
    measurand_name = measurand[measurand_device][0] + '_' + measurand_device

    df = DataFrame()
    df[measurand_name] = self.devices[measurand_device].readings[
        measurand_metric]

    for input_device in inputs.keys():
        combined_df = self.combine(devices=[input_device],
                                   readings=inputs[input_device])
        df = df.combine_first(combined_df)

    if options['common_avg']:

        common_channels = inputs[list(inputs.keys())[0]]
        for input_device in inputs.keys():
            common_channels = list(
                set(common_channels).intersection(set(inputs[input_device])))
        std_out(f'Performing avg in common columns {common_channels}')
        for channel in common_channels:
            columns_list = [
                channel + '_' + device for device in list(inputs.keys())
            ]
            df[channel + '_AVG'] = df[columns_list].mean(axis=1)

        df = df.loc[:,
                    df.columns.str.contains("_AVG")
                    | df.columns.str.contains(measurand_name)]

    if options['clean_na'] is not None:
        df = clean(df, options['clean_na'], how='any')

    return df, measurand_name
Exemplo n.º 2
0
def apply_regressor(dataframe, **kwargs):
    '''
	Applies a regressor model based on a pretrained model
	Parameters
    ----------
    	model: sklearn predictor
    		Model with .predict method
		options: dict
			Options for data preprocessing. Defaults in config.model_def_opt
		variables: dict
			variables dictionary with:
				{
				'measurand': {
								'measurand-device-name': ['measurand']
								},
					'inputs': {'input-device-names': ['input-1', 'input_2', 'input-3']
					 			}
					}
    Returns
    ----------
    pandas series containing the prediction
	'''

    inputs = list()
    for device in kwargs['variables']['inputs']:
        inputs = list(
            set(inputs).union(set(kwargs['variables']['inputs'][device])))

    try:
        inputdf = dataframe[inputs].copy()
        inputdf = inputdf.reindex(sorted(inputdf.columns), axis=1)
    except KeyError:
        std_out('Inputs not in dataframe', 'ERROR')
        pass
        return None

    if 'model' not in kwargs:
        std_out('Model not in inputs', 'ERROR')
    else:
        model = kwargs['model']

    if 'options' not in kwargs:
        options = config.model_def_opt
    else:
        options = dict_fmerge(config.model_def_opt, kwargs['options'])

    # Remove na
    inputdf = clean(inputdf, options['clean_na'], how='any')

    features = array(inputdf)
    result = DataFrame(model.predict(features)).set_index(inputdf.index)

    return result
Exemplo n.º 3
0
    def set_descriptor_attrs(self):

        # Descriptor attributes
        for ditem in self.description.keys():
            if ditem not in vars(self):
                std_out(f'Ignoring {ditem} from input')
                continue
            if type(self.__getattribute__(ditem)) == dict:
                self.__setattr__(
                    ditem,
                    dict_fmerge(self.__getattribute__(ditem),
                                self.description[ditem]))
            else:
                self.__setattr__(ditem, self.description[ditem])
Exemplo n.º 4
0
    def merge_sensor_metrics(self, ignore_empty=True):
        std_out('Merging sensor and metrics channels')
        all_channels = dict_fmerge(self.sensors, self.metrics)

        if ignore_empty:
            to_ignore = []
            for channel in all_channels.keys():
                if channel not in self.readings: to_ignore.append(channel)
                elif self.readings[channel].dropna().empty:
                    std_out(f'{channel} is empty')
                    to_ignore.append(channel)

            [all_channels.pop(x) for x in to_ignore]

        return all_channels
Exemplo n.º 5
0
    def __init__(self, blueprint, descriptor):
        '''
        Creates an instance of device. Devices are objects that contain sensors readings, metrics 
        (calculations based on sensors readings), and metadata such as units, dates, frequency and source
        
        Parameters:
        -----------
            blueprint: String
            
            Defines the type of device. For instance: sck_21, sck_20, csic_station, muv_station
            parrot_soil, sc_20_station, sc_21_station. A list of all the blueprints is found in 
            blueprints.yaml and accessible via the scdata.utils.load_blueprints function.
            
            descriptor: dict()
            
            A dictionary containing information about the device itself. Depending on the blueprint, this descriptor
            needs to have different data. If not all the data is present, the corresponding blueprint's default will 
            be used
        Returns
        ----------
            Device object
        '''

        self.blueprint = blueprint

        # Set attributes
        for bpitem in config.blueprints[blueprint]:
            self.__setattr__(bpitem, config.blueprints[blueprint][bpitem])
        for ditem in descriptor.keys():
            if type(self.__getattribute__(ditem)) == dict:
                self.__setattr__(
                    ditem,
                    dict_fmerge(self.__getattribute__(ditem),
                                descriptor[ditem]))
            else:
                self.__setattr__(ditem, descriptor[ditem])

        # Add API handler if needed
        if self.source == 'api':
            hmod = __import__('scdata.io.read_api', fromlist=['io.read_api'])
            Hclass = getattr(hmod, self.sources[self.source]['handler'])
            # Create object
            self.api_device = Hclass(did=self.id)

        self.readings = DataFrame()
        self.loaded = False
        self.options = dict()
def ts_scatter(self, **kwargs):
    """
    Plots timeseries and scatter comparison in matplotlib
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            traces = {
                        "1": {"devices": "10751",
                              "channel": "EXT_PM_A_1"},
                        "2": {"devices": "10751",
                              "channel": "EXT_PM_A_10"
                              }    
                    }    
        options: dict 
            Options including data processing prior to plot. Defaults in config.plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe
    Returns
    -------
        Matplotlib figure containing timeseries and scatter plot with correlation 
        coefficients on it
    """
    if config.framework == 'jupyterlab': plt.ioff()
    plt.clf()

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config.plot_def_opt
    else:
        options = dict_fmerge(config.plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config.ts_scatter_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config.ts_scatter_def_fmt['mpl'],
                                 kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config.plot_style)

    # Palette
    if formatting['palette'] is not None: set_palette(formatting['palette'])

    # Font size
    if formatting['fontsize'] is not None:
        rcParams.update({'font.size': formatting['fontsize']})

    # Make it standard
    for trace in traces:
        if 'subplot' not in trace: traces[trace]['subplot'] = 1

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    fig = plt.figure(figsize=(formatting['width'], formatting['height']))

    gs = gridspec.GridSpec(1, 3, figure=fig)

    ax1 = fig.add_subplot(gs[0, :-1])
    ax2 = fig.add_subplot(gs[0, -1])

    feature_trace = subplots[0][0]
    ref_trace = subplots[0][1]

    # Calculate basic metrics
    pearsonCorr = list(df.corr('pearson')[list(df.columns)[0]])[-1]
    rmse = sqrt(
        mean_squared_error(df[feature_trace].fillna(0),
                           df[ref_trace].fillna(0)))

    std_out(f'Pearson correlation coefficient: {pearsonCorr}')
    std_out(f'Coefficient of determination R²: {pearsonCorr*pearsonCorr}')
    std_out(f'RMSE: {rmse}')

    # Time Series plot
    ax1.plot(df.index,
             df[feature_trace],
             color='g',
             label=feature_trace,
             linewidth=1,
             alpha=0.9)
    ax1.plot(df.index,
             df[ref_trace],
             color='k',
             label=ref_trace,
             linewidth=1,
             alpha=0.7)
    ax1.axis('tight')

    # Correlation plot
    ax2.plot(df[ref_trace],
             df[feature_trace],
             'go',
             label=feature_trace,
             linewidth=1,
             alpha=0.3)
    ax2.plot(df[ref_trace],
             df[ref_trace],
             'k',
             label='1:1 Line',
             linewidth=0.2,
             alpha=0.6)
    ax2.axis('tight')

    if formatting['title'] is not None:
        ax1.set_title('Time Series Plot for {}'.format(formatting['title']),
                      fontsize=formatting['title_fontsize'])
        ax2.set_title('Scatter Plot for {}'.format(formatting['title']),
                      fontsize=formatting['title_fontsize'])

    if formatting['grid'] is not None:
        ax1.grid(formatting['grid'])
        ax2.grid(formatting['grid'])

    if formatting['legend']:
        ax1.legend(loc="best")
        ax2.legend(loc="best")

    if formatting['ylabel'] is not None:
        ax1.set_ylabel(formatting['ylabel'])
        ax2.set_xlabel(formatting['ylabel'])
        ax2.set_ylabel(formatting['ylabel'])

    if formatting['xlabel'] is not None:
        ax1.set_xlabel(formatting['xlabel'])

    if formatting['yrange'] is not None:
        ax1.set_ylim(formatting['yrange'])
        ax2.set_xlim(formatting['yrange'])
        ax2.set_ylim(formatting['yrange'])

    if formatting['xrange'] is not None:
        ax1.set_xlim(to_datetime(formatting['xrange']))

    if options['show']: plt.show()

    return fig
Exemplo n.º 7
0
def ts_dendrogram(self, **kwargs):
    """
    Plots dendrogram of devices and channels in matplotlib plot. Takes all the channels 
    in channels that are in the test `devices`
    Parameters
    ----------
        devices: list or string
            'all'
            If 'all', uses all devices in the test
        channels: list
            'all'
            If 'all', uses all channels in the devices        
        metric: string
            'correlation' for normal R2 or custom metric by callable
        'method': string
            'single'
            Method for dendrogram
        'options': dict
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config._ts_plot_def_fmt
    Returns
    -------
        Dendrogram matrix, shows plot
    """
    if 'metric' not in kwargs: metric = 'correlation'
    else: metric = kwargs['metric']

    if 'method' not in kwargs: method = 'single'
    else: method = kwargs['method']

    if 'devices' not in kwargs: devices = list(self.devices.keys())
    else: devices = kwargs['devices']

    if 'channels' not in kwargs: channels = 'all'
    else: channels = kwargs['channels']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config._ts_plot_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config._ts_plot_def_fmt['mpl'],
                                 kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config._plot_style)

    # Palette
    if formatting['palette'] is not None: set_palette(formatting['palette'])

    # Size sanity check
    if formatting['width'] > 50:

        std_out('Reducing width to 12')
        formatting['width'] = 12

    if formatting['height'] > 50:

        std_out('Reducing height to 10')
        formatting['height'] = 10

    # Font size
    if formatting['fontsize'] is not None:
        rcParams.update({'font.size': formatting['fontsize']})

    df = DataFrame()

    for device in devices:
        dfd = self.devices[device].readings.copy()
        dfd = dfd.resample(options['frequency']).mean()

        if channels != 'all':
            for channel in channels:
                if channel in dfd.columns:
                    df = df.append(dfd[channel].rename(device + '_' + channel))
        else:
            df = df.append(dfd)

    df = clean(df, options['clean_na'], how='any')

    # if options['clean_na'] is not None:
    #     if options['clean_na'] == 'drop': df.dropna(axis = 1, inplace=True)
    #     if options['clean_na'] == 'fill': df = df.fillna(method='ffill')

    # Do the clustering
    Z = hac.linkage(df, method=method, metric=metric)

    # Plot dendogram
    plt.figure(figsize=(formatting['width'], formatting['height']))
    plt.title(formatting['title'], fontsize=formatting['titlefontsize'])
    plt.subplots_adjust(top=formatting['suptitle_factor'])
    plt.xlabel(formatting['xlabel'])
    plt.ylabel(formatting['ylabel'])
    hac.dendrogram(
        Z,
        orientation=formatting['orientation'],
        leaf_font_size=formatting[
            'fontsize'],  # font size for the x axis labels
        labels=df.index)

    plt.show()

    return Z
Exemplo n.º 8
0
def heatmap_plot(self, **kwargs):
    """
    Plots heatmap in seaborn plot, based on period binning
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": '8019043',
                             "channel" : "PM_10"}
                        }     
        options: dict 
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config._heatmap_def_fmt
    Returns
    -------
        Matplotlib figure
    """

    if config._framework == 'jupyterlab': plt.ioff();
    plt.clf();

    if 'traces' not in kwargs: 
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config._heatmap_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config._heatmap_def_fmt['mpl'], kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config._plot_style)
    
    # Font size
    if formatting['fontsize'] is not None:
            rcParams.update({'font.size': formatting['fontsize']});

    # Make it standard
    for trace in traces:
        if 'subplot' not in trace: traces[trace]['subplot'] = 1            

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    gskwags = {'frequency_hours': formatting['frequency_hours']}

    dfgb, labels, yaxis, _ = groupby_session(df, **gskwags)

    # Sample figsize in inches
    _, ax = plt.subplots(figsize=(formatting['width'], formatting['height']));         
    
    
    xticks = [i.strftime("%Y-%m-%d") for i in dfgb.resample(formatting['session']).mean().index]
    
    # Pivot with 'session'
    g = heatmap(dfgb.pivot(columns='session').resample(formatting['session']).mean().T, ax = ax,
                cmap = formatting['cmap'], robust = formatting['robust'],
                vmin = formatting['vmin'], vmax = formatting['vmax'],
                xticklabels = xticks, yticklabels = labels);

    # ax.set_xticks(xticks*ax.get_xlim()[1]/(2))
    _ = g.set_xlabel(formatting['xlabel']);
    _ = g.set_ylabel(yaxis);

    # Set title
    _ = g.figure.suptitle(formatting['title'], fontsize=formatting['title_fontsize']);
    plt.subplots_adjust(top=formatting['suptitle_factor'])
    
    # Show
    if options['show']: plt.show()

    return g.figure
def scatter_plot(self, **kwargs):
    """
    Plots correlation in matplotlib plot
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            traces = {1: {'devices': ['10751', '10751'],
                          'channels': ['TEMP', 'GB_2A'],
                          'subplot': 1},
                      2: {'devices': ['10752', '10752'],
                          'channels': ['TEMP', 'GB_2A'],
                          'subplot': 1}
                      3: {'devices': ['10751', '10751'],
                          'channels': ['TEMP', 'GB_2W'],
                          'subplot': 2}
                      4: {'devices': ['10752', '10752'],
                          'channels': ['TEMP', 'GB_2W'],
                          'subplot': 2} 
                    }
        options: dict 
            Options including data processing prior to plot. Defaults in config.plot_def_opt
        formatting: dict
            Formatting dict. Defaults in config.scatter_plot_def_fmt
    Returns
    -------
        Matplotlib figure and axes
    """

    if config.framework == 'jupyterlab': plt.ioff()
    plt.clf()

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config.plot_def_opt
    else:
        options = dict_fmerge(config.plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config.scatter_plot_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config.scatter_plot_def_fmt['mpl'],
                                 kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config.plot_style)

    # Palette
    if formatting['palette'] is not None: set_palette(formatting['palette'])
    print(formatting['palette'])

    # Font size
    if formatting['fontsize'] is not None:
        rcParams.update({'font.size': formatting['fontsize']})

    # Make it standard
    ptraces = dict()

    for trace in traces:
        if 'subplot' not in traces[trace]: traces[trace]['subplot'] = 1
        if 'channels' not in traces[trace]:
            ptraces = traces
            continue

        ptrace_1 = trace * 10 + 1
        ptrace_2 = trace * 10 + 2

        ptraces[ptrace_1] = {
            'devices': traces[trace]['devices'][0],
            'channel': traces[trace]['channels'][0],
            'subplot': traces[trace]['subplot']
        }

        ptraces[ptrace_2] = {
            'devices': traces[trace]['devices'][1],
            'channel': traces[trace]['channels'][1],
            'subplot': traces[trace]['subplot']
        }

    # Get dataframe
    df, subplots = prepare_data(self, ptraces, options)
    n_subplots = len(subplots)

    # Plot
    nrows = min(n_subplots, formatting['nrows'])
    ncols = ceil(n_subplots / nrows)

    figure, axes = plt.subplots(nrows,
                                ncols,
                                figsize=(formatting['width'],
                                         formatting['height']))

    if n_subplots == 1:
        axes = array(axes)
        axes.shape = (1)

    cind = 0
    y_axes = list()
    x_axes = list()

    for i in subplots:
        for j in range(int(len(i) / 2)):
            cind += 1
            if cind > len(colors) - 1: cind = 0

            if nrows > 1 and ncols > 1:
                row = floor(subplots.index(i) / ncols)
                col = subplots.index(i) - row * ncols
                ax = axes[row][col]
            else:
                ax = axes[subplots.index(i)]

            kwargs = {
                'data': df,
                'ax': ax,
                'label': f'{i[2*j+1]} vs. {i[2*j]}'
            }

            if formatting['palette'] is None: kwargs['color'] = colors[cind]

            regplot(df[i[2 * j]], df[i[2 * j + 1]], **kwargs)

            if formatting['legend']:
                ax.legend(loc='best')

            if formatting['ylabel'] is not None:
                try:
                    ax.set_ylabel(formatting['ylabel'][subplots.index(i) + 1])
                except:
                    std_out(f'y_label for subplot {subplots.index(i)} not set',
                            'WARNING')
                    ax.set_ylabel('')
                    pass
            else:
                ax.set_ylabel('')

            if formatting['xlabel'] is not None:
                try:
                    ax.set_xlabel(formatting['xlabel'][subplots.index(i) + 1])
                except:
                    std_out(f'x_label for subplot {subplots.index(i)} not set',
                            'WARNING')
                    ax.set_xlabel('')
                    pass
            else:
                ax.set_xlabel('')

            y_axes.append(ax.get_ylim())
            x_axes.append(ax.get_xlim())

    # Unify axes or set what was ordered
    for i in subplots:
        for j in range(int(len(i) / 2)):

            if nrows > 1 and ncols > 1:
                row = floor(subplots.index(i) / ncols)
                col = subplots.index(i) - row * ncols
                ax = axes[row][col]
            else:
                ax = axes[subplots.index(i)]

            # Set y axis limit
            if formatting['yrange'] is not None and not formatting['sharey']:
                try:
                    ax.set_ylim(formatting['yrange'][subplots.index(i) + 1])
                except:
                    std_out(f'yrange for subplot {subplots.index(i)} not set',
                            'WARNING')
                    pass
            elif formatting['sharey']:
                ax.set_ylim(min([yl[0] for yl in y_axes]),
                            max([yl[1] for yl in y_axes]))

            # Set x axis limit
            if formatting['xrange'] is not None and not formatting['sharex']:
                try:
                    ax.set_xlim(formatting['xrange'][subplots.index(i) + 1])
                except:
                    std_out(f'xrange for subplot {subplots.index(i)} not set',
                            'WARNING')
                    pass
            elif formatting['sharex']:
                ax.set_xlim(min([xl[0] for xl in x_axes]),
                            max([xl[1] for xl in x_axes]))

    # Set title
    figure.suptitle(formatting['title'], fontsize=formatting['title_fontsize'])
    plt.subplots_adjust(top=formatting['suptitle_factor'])

    if options['show']: plt.show()

    return figure
Exemplo n.º 10
0
def box_plot(self, **kwargs):
    """
    Plots heatmap in seaborn plot, based on period binning
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": ['8019043', '8019044', '8019004'],
                             "channel" : "PM_10",
                             "subplot": 1,
                             "extras": ['max', 'min', 'avg']},
                        "2": {"devices": "all",
                             "channel" : "TEMP",
                             "subplot": 2}
                        }     
        options: dict 
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config._boxplot_def_fmt
    Returns
    -------
        Matplotlib figure
    """

    if config._framework == 'jupyterlab': plt.ioff()
    plt.clf()

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config._boxplot_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config._boxplot_def_fmt['mpl'],
                                 kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config._plot_style)

    # Make it standard
    for trace in traces:
        if 'subplot' not in trace: traces[trace]['subplot'] = 1

    # Palette
    if formatting['palette'] is not None: set_palette(formatting['palette'])

    # Font size
    if formatting['fontsize'] is not None:
        rcParams.update({'font.size': formatting['fontsize']})

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    dfgb, labels, xaxis, channel = groupby_session(
        df,
        frequency_hours=formatting['frequency_hours'],
        periods=formatting['periods'])

    # Sample figsize in inches
    _, ax = plt.subplots(figsize=(formatting['width'], formatting['height']))

    # Pivot with 'session'
    if formatting['periods'] is not None:
        g = boxplot(x=dfgb['session'], y=dfgb[channel], hue = dfgb['period'], \
            ax=ax, palette = formatting['cmap'])
    else:
        g = boxplot(x=dfgb['session'],
                    y=dfgb[channel],
                    ax=ax,
                    palette=formatting['cmap'])

    # TODO make this to compare to not None, so that we can send location
    if formatting['ylabel'] is not None:
        _ = g.set_ylabel(formatting['ylabel'])

    if formatting['grid'] is not None:
        _ = g.grid(formatting['grid'])

    if formatting['yrange'] is not None:
        ax.set_ylim(formatting['yrange'])

    _ = g.set_xlabel(xaxis)

    # Set title
    if formatting['title'] is not None:
        _ = g.figure.suptitle(formatting['title'],
                              fontsize=formatting['title_fontsize'])

    # Suptitle factor
    if formatting['suptitle_factor'] is not None:
        plt.subplots_adjust(top=formatting['suptitle_factor'])

    # Show
    if options['show']: plt.show()

    return g.figure
Exemplo n.º 11
0
def ts_iplot(self, **kwargs):
    """
    Plots timeseries in plotly interactive plot
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": ['8019043', '8019044', '8019004'],
                             "channel" : "PM_10",
                             "subplot": 1,
                             "extras": ['max', 'min', 'avg']},
                        "2": {"devices": "all",
                             "channel" : "TEMP",
                             "subplot": 2}
                        }     
        options: dict 
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config._ts_plot_def_fmt
    Returns
    -------
        Plotly figure
    """

    if config._framework == 'jupyterlab': renderers.default = config._framework

    if 'traces' not in kwargs: 
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options', 'WARNING')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting', 'WARNING')
        formatting = config._ts_plot_def_fmt['plotly']
    else:
        formatting = dict_fmerge(config._ts_plot_def_fmt['plotly'], kwargs['formatting'])

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    # Size sanity check
    if formatting['width'] < 100: 
        std_out('Setting width to 800')
        formatting['width'] = 800
    if formatting['height'] < 100: 
        std_out('Reducing height to 600')
        formatting['height'] = 600             
    
    figure = make_subplots(rows = n_subplots, cols=1, 
                           shared_xaxes = formatting['sharex'])
    # Add traces
    for isbplt in range(n_subplots):
        
        for trace in subplots[isbplt]:
            
            figure.append_trace({'x': df.index, 
                                 'y': df[trace], 
                                 'type': 'scatter',
                                 'mode': 'lines+markers',
                                 'name': trace}, 
                                isbplt + 1, 1)
        # Name the axis
        if formatting['ylabel'] is not None:
            figure['layout']['yaxis' + str(isbplt+1)]['title']['text'] = formatting['ylabel'][isbplt+1]
        
        if formatting['yrange'] is not None:
            figure['layout']['yaxis' + str(isbplt+1)]['range'] = formatting['yrange'][isbplt+1]

    # Add axis labels
    if formatting['xlabel'] is not None:
        figure['layout']['xaxis' + str(n_subplots)]['title']['text'] = formatting['xlabel']
    
    # Add layout
    figure['layout'].update(height = formatting['height'],
                            legend = dict(x=0.2, y=-0.3, 
                                        traceorder='normal',
                                        font = dict(family='sans-serif',
                                                    size=10,
                                                    color='#000'),
                                        xanchor = 'center',
                                        orientation = 'h',
                                        itemsizing = 'trace',
                                        yanchor = 'bottom',
                                        bgcolor ='rgba(0,0,0,0)',
                                        bordercolor = 'rgba(0,0,0,0)',
                                        borderwidth = 0),
                            title=dict(text=formatting['title'])
                           )
    
    if options['show']: figure.show()

    return figure
Exemplo n.º 12
0
def ts_uplot(self, **kwargs):
    """
    Plots timeseries in uplot interactive plot - Fast, fast fast
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": ['8019043', '8019044', '8019004'],
                             "channel" : "PM_10",
                             "subplot": 1,
                             "extras": ['max', 'min', 'avg']},
                        "2": {"devices": "all",
                             "channel" : "TEMP",
                             "subplot": 2}
                        }     
        options: dict 
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config._ts_plot_def_fmt
    Returns
    -------
        uPlot figure
    """

    head_template = '''
        <link rel="stylesheet" href="https://leeoniya.github.io/uPlot/dist/uPlot.min.css">
        <script src="https://leeoniya.github.io/uPlot/dist/uPlot.iife.js"></script>
        
        <div style="text-align:center">
            <h2 style="font-family: Roboto"> {{title}} </h2>
        </div>

        '''

    uplot_template = '''
        <div id="plot{{subplot}}"></div>
        <script>
            data = {{data}};
            options = {{options}};

            if (typeof options.scatter == 'undefined') {
                options.scatter = false
            }

            if (options.scatter) {
                for (i=1; i<data.length; i++) {
                    options['series'][i]["paths"] = u => null;
                }
            }

            u = new uPlot(options, data, document.getElementById("plot{{subplot}}"))
        </script>
        '''

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options', 'WARNING')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting', 'WARNING')
        formatting = config._ts_plot_def_fmt['uplot']
    else:
        formatting = dict_fmerge(config._ts_plot_def_fmt['uplot'],
                                 kwargs['formatting'])

    # Size sanity check
    if formatting['width'] < 100:
        std_out('Setting width to 800')
        formatting['width'] = 800
    if formatting['height'] < 100:
        std_out('Reducing height to 600')
        formatting['height'] = 600

    if 'html' not in options:
        options['html'] = False

    h = Template(head_template).render(title=formatting['title'])

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    df = df.fillna('null')
    n_subplots = len(subplots)

    # Get data in uplot expected format
    udf = df.copy()
    udf.index = udf.index.astype(int) / 10**9

    for isbplt in range(n_subplots):

        sdf = udf.loc[:, subplots[isbplt]]
        sdf = sdf.reset_index()
        data = sdf.values.T.tolist()

        labels = sdf.columns
        useries = [{'label': labels[0]}]

        if formatting['ylabel'] is None:
            ylabel = None
        else:
            ylabel = formatting['ylabel'][isbplt + 1]

        uaxes = [{
            'label': formatting['xlabel'],
            'labelSize': formatting['fontsize'],
        }, {
            'label': ylabel,
            'labelSize': formatting['fontsize']
        }]

        color_idx = 0

        for label in labels:
            if label == labels[0]: continue
            if color_idx + 1 > len(colors): color_idx = 0

            nser = {
                'label': label,
                'stroke': colors[color_idx],
                'points': {
                    'space': 0,
                    'size': formatting['size']
                }
            }

            useries.append(nser)
            color_idx += 1

        u_options = {
            'width': formatting['width'],
            'height': formatting['height'],
            'cursor': {
                'lock': True,
                'focus': {
                    'prox': 16,
                },
                'sync': {
                    'key': 'moo',
                    'setSeries': True,
                },
                'drag': {
                    'x': True,
                    'y': True,
                    'uni': 50,
                    'dist': 10,
                }
            },
            'scales': {
                'x': {
                    'time': True
                },
                'y': {
                    'auto': True
                },
            },
            'series': useries,
            'axes': uaxes
        }

        h2 = Template(uplot_template).render(data=json.dumps(data),
                                             options=json.dumps(u_options),
                                             subplot=isbplt)

        h += h2

    h = h.replace('"', "'")
    h = h.replace("'null'", "null")

    if options['html']:
        return h
    else:
        iframe = f'''<iframe srcdoc="{h}" src="" 
            frameborder="0" width={formatting['width'] + formatting['padding-right']}
            height={formatting['height'] + formatting['padding-bottom']}
            sandbox="allow-scripts">
            </iframe>'''

        return HTML(iframe)
Exemplo n.º 13
0
def device_metric_map(self, channel, start_date, end_date, options=dict()):
    '''
    Creates a folium map showing the evolution of a metric dynamically with colors
    Parameters
    -------
    channel: String
        The channel to make the map from
    start_date, end_date: String
        Date convertible string
    options: dict()
        Possible keys are (default otherwise)
            location: list
                [41.400818, 2.1825157]
                Center map location
            tiles: (String)
                'Stamen Toner'
                Tiles for the folium.Map
            zoom: (float)
                2.5
                Zoom to start with in folium.Map
            period: 'String'
                '1W'
                Period for 'dynamic' map
            radius: float
                10
                Circle radius for icon
            fillOpacity: float
                1
                (<1) Fill opacity for the icon
            stroke: 'String'
                'false'
                'true' or 'false'. For icon's stroke
            icon: 'String'
                'circle'
                A valid folium.Map icon style
    Returns
    -------
        Folium.Map object
    '''

    # Set defaults
    options = dict_fmerge(config._map_def_opt, options)

    # Make date range
    date_r = date_range(start=start_date,
                        end=end_date,
                        normalize=True,
                        freq=options['period']).strftime('%Y-%m-%d')
    date_l = list()
    for item in date_r.values:
        date_l.append(str(item))

    # Get bins
    for bname in config._channel_bins.keys():
        if bname in channel:
            bins = config._channel_bins[bname]
            break

    # Make features
    features = []
    for device in self.devices:

        # Get lat, long
        try:
            self.devices[str(device)].api_device.get_device_lat_long()
            _lat = self.devices[str(device)].api_device.lat
            _long = self.devices[str(device)].api_device.long
        except AttributeError:
            std_out(f'Cannot retrieve [lat, long] from device {device}',
                    'WARNING')
            pass
            continue

        if _lat is None or _long is None: continue

        # Resample
        try:
            dfc = self.devices[str(device)].readings.resample(
                options['period']).mean()
        except:
            pass
            continue

        if channel not in dfc.columns: continue
        # Make color column
        dfc['color'] = cut(dfc[channel],
                           bins,
                           labels=config._map_colors_palette)

        # Add point for each date
        for date in date_l:
            if date not in dfc.index: continue
            if date_l.index(date) > len(date_l) - 2: continue
            features.append({
                'type': 'Feature',
                'geometry': {
                    'type': 'LineString',
                    'coordinates': [[str(_long), str(_lat)]] * 2,
                    'popup': str(device),
                },
                'properties': {
                    'times': [date, date_l[date_l.index(date) + 1]],
                    'icon': options['icon'],
                    'iconstyle': {
                        'fillColor': str(dfc.loc[date, 'color']),
                        'fillOpacity': options['fillOpacity'],
                        'stroke': options['stroke'],
                        'radius': options['radius']
                    },
                    'style': {
                        'weight': '0'
                    },
                    'id': 'man'
                }
            })

    # Make map
    m = Map(
        location=options['location'],
        tiles=options['tiles'],
        zoom_start=options['zoom'],
    )

    TimestampedGeoJson(
        {
            'type': 'FeatureCollection',
            'features': features
        },
        period='P' + convert_rollup(options['period']),
        add_last_point=True,
        auto_play=False,
        loop=False,
        max_speed=5,
        loop_button=True,
        # date_options='YYYY/MM/DD',
        time_slider_drag_update=True,
        duration='P' + options['period']).add_to(m)

    return m
Exemplo n.º 14
0
def ts_plot(self, **kwargs):
    """
    Plots timeseries in matplotlib plot
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": ['8019043', '8019044', '8019004'],
                             "channel" : "PM_10",
                             "subplot": 1,
                             "extras": ['max', 'min', 'avg']},
                        "2": {"devices": "all",
                             "channel" : "TEMP",
                             "subplot": 2}
                        }     
        options: dict 
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Formatting dict. Defaults in config._ts_plot_def_fmt
    Returns
    -------
        Matplotlib figure
    """

    if config.framework == 'jupyterlab': plt.ioff();
    plt.clf();

    if 'traces' not in kwargs: 
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config._ts_plot_def_fmt['mpl']
    else:
        formatting = dict_fmerge(config._ts_plot_def_fmt['mpl'], kwargs['formatting'])

    # Style
    if formatting['style'] is not None: style.use(formatting['style'])
    else: style.use(config._plot_style)
    
    # Palette
    if formatting['palette'] is not None: set_palette(formatting['palette'])
    
    # Font size
    if formatting['fontsize'] is not None:
            rcParams.update({'font.size': formatting['fontsize']});

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    # Size sanity check
    if formatting['width'] > 50: 

        std_out('Reducing width to 12')
        formatting['width'] = 12
    
    if formatting['height'] > 50: 

        std_out('Reducing height to 10')
        formatting['height'] = 10
    
    # Plot
    figure, axes = plt.subplots(n_subplots, 1,
                                sharex = formatting['sharex'],
                                figsize = (formatting['width'],
                                           formatting['height'])
                                );

    if n_subplots == 1: 
        axes = array(axes)
        axes.shape = (1)

    for ax in axes:

        isbplt = where(axes == ax)[0][0];

        # Check if we are plotting any highlight for the trace
        if any(['-MEAN' in trace for trace in subplots[isbplt]]): has_hl = True
        elif any(['-MAX' in trace for trace in subplots[isbplt]]): has_hl = True
        elif any(['-MIN' in trace for trace in subplots[isbplt]]): has_hl = True
        else: has_hl = False

        for trace in subplots[isbplt]:

            if has_hl:
                
                if '-MEAN' in trace: alpha = formatting['alpha_highlight']
                elif '-MAX' in trace: alpha = formatting['alpha_highlight']
                elif '-MIN' in trace: alpha = formatting['alpha_highlight']
                else: alpha = formatting['alpha_other']
            
            else: alpha = 1

            ax.plot(df.index, df[trace], label = trace, alpha = alpha);

        # TODO make this to compare to not None, so that we can send location
        if formatting['legend']: 
            ax.legend(loc='center left', bbox_to_anchor=(1, 0.5));
        
        if formatting['ylabel'] is not None: 
            ax.set_ylabel(formatting['ylabel'][isbplt+1]);
        
        if formatting['xlabel'] is not None: 
            ax.set_xlabel(formatting['xlabel']);
        
        if formatting['yrange'] is not None: 
            ax.set_ylim(formatting['yrange'][isbplt+1]);
        
        if formatting['xrange'] is not None:
            if formatting['sharex']: ax.set_xlim(to_datetime(formatting['xrange'][1]));
            else: ax.set_xlim(to_datetime(formatting['xrange'][isbplt+1]));

        if formatting['grid'] is not None:
            ax.grid(formatting['grid']);


        if formatting["decorators"] is not None:

            if 'axvline' in formatting['decorators']:
                for vline in formatting['decorators']['axvline']:
                    ax.axvline(to_datetime(vline), linestyle = 'dotted', color = 'gray');

            if 'axhline' in formatting['decorators']:
                for vline in formatting['decorators']['axhline']:
                    ax.axhline(vline, linestyle = 'dotted', color = 'gray');

            if 'xtext' in formatting['decorators']:
                for xtext in formatting['decorators']['xtext'].keys():
                    text = formatting['decorators']['xtext'][xtext]
                    position = formatting['yrange'][isbplt+1][1]-(formatting['yrange'][isbplt+1][1]-formatting['yrange'][isbplt+1][0])/10
                    ax.text(to_datetime(xtext), position, text, size=15, color = 'gray');

            # TODO Fix
            if 'ytext' in formatting['decorators']:
                for ytext in formatting['decorators']['ytext'].keys():
                    text = formatting['decorators']['ytext'][ytext]
                    position = formatting['xrange'][isbplt+1][1]-(formatting['xrange'][isbplt+1][1]-formatting['yrange'][isbplt+1][0])/10
                    ax.text(ytext, position, text, size=15, color = 'gray');

    figure.suptitle(formatting['title'], fontsize=formatting['title_fontsize']);
    plt.subplots_adjust(top = formatting['suptitle_factor']);

    if options['show']: plt.show();

    return figure
def heatmap_iplot(self, **kwargs):
    """
    Plots heatmap in plotly interactive plot
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            "traces":  {"1": {"devices": '8019043',
                             "channel" : "PM_10"}
                        }      
        options: dict 
            Options including data processing prior to plot. Defaults in config.plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config.heatmap_def_fmt
    Returns
    -------
        Plotly figure
    """
    if config.framework == 'jupyterlab': renderers.default = config.framework

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config.plot_def_opt
    else:
        options = dict_fmerge(config.plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config.heatmap_def_fmt['plotly']
    else:
        formatting = dict_fmerge(config.heatmap_def_fmt['plotly'],
                                 kwargs['formatting'])

    # Make it standard
    for trace in traces:
        if 'subplot' not in trace: traces[trace]['subplot'] = 1

    # Get dataframe
    df, subplots = prepare_data(self, traces, options)
    n_subplots = len(subplots)

    gskwags = {'frequency_hours': formatting['frequency_hours']}

    dfgb, labels, yaxis, channel = groupby_session(df, **gskwags)
    xticks = [
        i.strftime("%Y-%m-%d")
        for i in dfgb.resample(formatting['session']).mean().index
    ]

    # Data
    data = [
        Heatmap(
            z=dfgb[channel],
            x=dfgb.index.date,
            y=dfgb['session'],
            # colorscale=colorscale
        )
    ]

    layout = Layout(title=formatting['title'],
                    xaxis=dict(ticks=''),
                    yaxis=dict(ticks='',
                               categoryarray=labels,
                               autorange='reversed'))

    figure = Figure(data=data, layout=layout)

    if options['show']: iplot(figure)

    return figure
Exemplo n.º 16
0
def path_plot(self,
              channel=None,
              map_type='dynamic',
              devices='all',
              start_date=None,
              end_date=None,
              options=dict()):
    '''
    Creates a folium map showing a path
    Parameters
    -------
    channel: String
        None
        If None, shows path, otherwise, colored path with channel mapping
    map_type: String
        'dynamic'
        'dynamic' or 'static'. Whether is a dinamic map or not
    devices: list or 'all'
        List of devices to include, or 'all' from self.devices
    channel: String
        The channel to make the map from
    start_date, end_date: String
        Date convertible string
    options: dict()
        Possible keys are (default otherwise)
            location: list
                [41.400818, 2.1825157]
                Center map location
            tiles: (String)
                'Stamen Toner'
                Tiles for the folium.Map
            zoom: (float)
                2.5
                Zoom to start with in folium.Map
            period: 'String'
                '1W'
                Period for 'dynamic' map
            radius: float
                10
                Circle radius for icon
            fillOpacity: float
                1
                (<1) Fill opacity for the icon
            stroke: 'String'
                'false'
                'true' or 'false'. For icon's stroke
            icon: 'String'
                'circle'
                A valid folium.Map icon style
    Returns
    -------
        Folium.Map object
    '''

    # Set defaults
    options = dict_fmerge(config._map_def_opt, options)

    # Make features
    features = []
    if devices == 'all':
        mdev = self.devices
    else:
        mdev = list()
        for device in devices:
            if device in self.devices: mdev.append(device)
            else: std_out(f'Device {device} not found, ignoring', 'WARNING')

    if len(mdev) == 0:
        std_out('Requested devices not in test', 'ERROR')
        return None

    for device in mdev:
        chs = ['GPS_LAT', 'GPS_LONG']
        if channel is not None:
            if channel not in self.devices[str(device)].readings.columns:
                std_out(
                    f'Channel {channel} not in columns: {self.devices[str(device)].readings.columns}',
                    'ERROR')
                return None

            # Get bins
            minmax = False
            if not options['minmax']:
                if all([key not in channel for key in config._channel_bins]):
                    std_out(
                        f'Requested channel {channel} not in config mapped bins {config._channel_bins.keys()}.Using min/max mapping',
                        'WARNING')
                    minmax = True
            else:
                minmax = True

            if minmax:
                bins = linspace(
                    self.devices[str(device)].readings[channel].min(),
                    self.devices[str(device)].readings[channel].max(),
                    config._channel_bin_n)
            else:
                for bname in config._channel_bins.keys():
                    if bname in channel:
                        bins = config._channel_bins[bname]
                        break
            chs.append(channel)

        # Create copy
        dfc = self.devices[str(device)].readings[chs].copy()
        # Resample and cleanup
        # TODO THIS CAN INPUT SOME MADE UP READINGS
        dfc = clean(dfc.resample(options['period']).mean(), 'fill')

        # Make color column
        legend_labels = None
        if channel is not None:
            dfc['COLOR'] = cut(dfc[channel], bins, labels =\
                config._map_colors_palette)

            # Make legend labels
            legend_labels = {}
            for ibin in range(len(bins) - 1):
                legend_labels[f'{round(bins[ibin],2)} : {round(bins[ibin+1],2)}'] =\
                    config._map_colors_palette[ibin]
        else:
            dfc['COLOR'] = config._map_colors_palette[0]

        if start_date is not None:
            dfc = dfc[dfc.index > start_date]
        if end_date is not None:
            dfc = dfc[dfc.index < end_date]

        # Add point for each date
        for date in dfc.index:
            if date == dfc.index[-1]: break
            times = []

            color = str(dfc.loc[date, 'COLOR'])
            if color == 'nan' or isnan(dfc.loc[date, 'GPS_LONG'])\
            or isnan(dfc.loc[date, 'GPS_LAT']):
                std_out(f'Skipping point {date}', 'WARNING')
                continue

            geometry = {
                'type':
                'LineString',
                'coordinates':
                [[dfc.loc[date, 'GPS_LONG'], dfc.loc[date, 'GPS_LAT']],
                 [
                     dfc.loc[date + dfc.index.freq, 'GPS_LONG'],
                     dfc.loc[date + dfc.index.freq, 'GPS_LAT']
                 ]],
            }

            properties = {
                'icon':
                options['icon'],
                'iconstyle': {
                    'fillColor': color,
                    'fillOpacity': options['fillOpacity'],
                    'stroke': options['stroke'],
                    'radius': options['radius']
                },
                'device':
                device,
                'timestamp':
                date.strftime('%Y-%m-%dT%H:%M:%S'),
                "coordinates": [
                    dfc.loc[date + dfc.index.freq, 'GPS_LAT'],
                    dfc.loc[date + dfc.index.freq, 'GPS_LONG']
                ],
                'style': {
                    'color': color,
                    'stroke-width': options['stroke-width'],
                    'fillOpacity': options['fillOpacity']
                }
            }

            # Add reading to tooltip
            if channel is not None:
                properties['channel'] = channel
                properties['value'] = dfc.loc[date, channel]

            if map_type == 'dynamic':
                properties['times'] = [
                    date.strftime('%Y-%m-%dT%H:%M:%S'),
                    (date + dfc.index.freq).strftime('%Y-%m-%dT%H:%M:%S')
                ]

            features.append({
                'type': 'Feature',
                'geometry': geometry,
                'properties': properties
            })

    featurecol = {'type': 'FeatureCollection', 'features': features}

    # Make map
    if options['location'] == 'average':
        avg_long = dfc['GPS_LONG'].mean()
        avg_lat = dfc['GPS_LAT'].mean()
        loc = [avg_lat, avg_long]
    else:
        loc = options['location']

    m = Map(
        location=loc,
        tiles=options['tiles'],
        zoom_start=options['zoom'],
    )

    if map_type == 'static':
        # TODO WORKAROUND UNTIL GEOJSON ACCEPTS MARKERS
        if options['markers']:
            for feature in features:
                Circle(location=[
                    feature['geometry']['coordinates'][0][1],
                    feature['geometry']['coordinates'][0][0]
                ],
                       fill='true',
                       radius=feature['properties']['iconstyle']['radius'],
                       color=feature['properties']['iconstyle']['fillColor'],
                       fill_opacity=feature['properties']['iconstyle']
                       ['fillOpacity']).add_to(m)

        if channel is not None:
            fields = ["device", "channel", "timestamp", "coordinates", "value"]
            aliases = [
                "Device:", "Sensor:", "Timestamp:", "Coordinates:", "Reading:"
            ]
        else:
            fields = ["device", "timestamp", "coordinates"]
            aliases = ["Device:", "Timestamp:", "Coordinates:"]

        popup = GeoJsonPopup(
            fields=fields,
            aliases=aliases,
            localize=True,
            labels=True,
            max_width=800,
        )

        tooltip = GeoJsonTooltip(
            fields=fields,
            aliases=aliases,
            localize=True,
            sticky=True,
            labels=True,
            style="""
                background-color: #F0EFEF;
                border: 1px solid gray;
                border-radius: 1px;
                box-shadow: 2px;
            """,
            max_width=800,
        )

        GeoJson(
            featurecol,
            tooltip=tooltip,
            popup=popup,
            style_function=lambda x: {
                'color': x['properties']['style']['color'],
                'weight': x['properties']['style']['stroke-width'],
                'fillOpacity': x['properties']['style']['fillOpacity']
            },
        ).add_to(m)

    elif map_type == 'dynamic':
        TimestampedGeoJson(featurecol,
                           period='PT' + convert_rollup(options['period']),
                           add_last_point=True,
                           auto_play=False,
                           loop=False,
                           max_speed=options['max_speed'],
                           loop_button=True,
                           time_slider_drag_update=True).add_to(m)

    else:
        std_out(f'Not supported map type {map_type}', 'ERROR')
        return None

    if options['minimap']:
        minimap = MiniMap(toggle_display=True, tile_layer=options['tiles'])
        minimap.add_to(m)

    if options['legend'] and not legend_labels is None:

        templateLoader = FileSystemLoader(searchpath=join(dirname(__file__),\
            'templates'))
        templateEnv = Environment(loader=templateLoader)
        template = templateEnv.get_template("map_legend.html")

        filled_map_legend = template.render(legend_labels=legend_labels)

        map_legend_html = '{% macro html(this, kwargs) %}'+\
            filled_map_legend+\
            '{% endmacro %}'

        legend = element.MacroElement()
        legend._template = element.Template(map_legend_html)

        m.get_root().add_child(legend)

    return m
def scatter_iplot(self, **kwargs):
    """
    Plots Correlation in plotly plot. Calls corr_plot and then converts it
    Parameters
    ----------
        traces: dict
            Data for the plot, with the format:
            traces = {
                        "1": {"devices": "10751",
                              "channel": "EXT_PM_A_1"},
                        "2": {"devices": "10751",
                              "channel": "EXT_PM_A_10"
                              }    
                    }     
        options: dict 
            Options including data processing prior to plot. Defaults in config.plot_def_opt
        formatting: dict
            Name of auxiliary electrode found in dataframe. Defaults in config.corr_plot_def_fmt
    Returns
    -------
        Plotly figure
    """
    std_out('Not yet working', 'ERROR')
    return None
    if config.framework == 'jupyterlab': renderers.default = config.framework

    if 'traces' not in kwargs:
        std_out('No traces defined', 'ERROR')
        return None
    else:
        traces = kwargs['traces']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config.plot_def_opt
    else:
        options = dict_fmerge(config.plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config.scatter_plot_def_fmt['plotly']
    else:
        formatting = dict_fmerge(config.scatter_plot_def_fmt['plotly'],
                                 kwargs['formatting'])

    # Set options to not show in scatter_plot
    toshow = options['show']
    options['show'] = False

    # Make sns plot
    mfig = scatter_plot(self,
                        traces=traces,
                        options=options,
                        formatting=formatting)
    options['show'] = toshow

    pfig = tls.mpl_to_plotly(mfig)

    if options['show']: pfig.show()

    return pfig
Exemplo n.º 18
0
    def __init__(self, blueprint='sck_21', descriptor={}):
        '''
        Creates an instance of device. Devices are objects that contain sensors readings, metrics 
        (calculations based on sensors readings), and metadata such as units, dates, frequency and source

        Parameters:
        -----------
        blueprint: String
            Default: 'sck_21'
            Defines the type of device. For instance: sck_21, sck_20, csic_station, muv_station
            parrot_soil, sc_20_station, sc_21_station. A list of all the blueprints is found in 
            blueprints.yaml and accessible via the scdata.utils.load_blueprints function.
            The blueprint can also be defined from the postprocessing info in SCAPI. The manual
            parameter passed here is prioritary to that of the API

        descriptor: dict()
            Default: empty dict
            A dictionary containing information about the device itself. Depending on the blueprint, this descriptor
            needs to have different data. If not all the data is present, the corresponding blueprint's default will 
            be used

        Examples:
        ----------
        Device('sck_21', descriptor = {'source': 'api', 'id': '1919'})
            device with sck_21 blueprint with 1919 ID
        Device(descriptor = {'source': 'api', 'id': '1919'})
            device with sck_21 blueprint with 1919 ID

        Returns
        ----------
            Device object
        '''

        if blueprint is not None:
            self.blueprint = blueprint

        # Set attributes
        for bpitem in config.blueprints[blueprint]:
            self.__setattr__(bpitem, config.blueprints[blueprint][bpitem])
        for ditem in descriptor.keys():
            if type(self.__getattribute__(ditem)) == dict:
                self.__setattr__(
                    ditem,
                    dict_fmerge(self.__getattribute__(ditem),
                                descriptor[ditem]))
            else:
                self.__setattr__(ditem, descriptor[ditem])

        # Add API handler if needed
        if self.source == 'api':
            hmod = __import__('scdata.io.device_api',
                              fromlist=['io.device_api'])
            Hclass = getattr(hmod, self.sources[self.source]['handler'])
            # Create object
            self.api_device = Hclass(did=self.id)

        std_out(f'Checking postprocessing info from API device')
        self.load_postprocessing_info()

        if self.blueprint is None:
            std_out('Need a blueprint to proceed', 'ERROR')
            return None
        else:
            std_out(f'Device {self.id} is using {blueprint}')

        self.readings = DataFrame()
        self.loaded = False
        self.options = dict()

        self.hw_id = None
        self.latest_postprocessing = None
Exemplo n.º 19
0
def device_history_map(map_type='dynamic', dataframe=None, options=dict()):
    '''
    Creates a folium map with either location of devices or their "existence period"
    -------
    Parameters:
    map_type: String
        'dynamic'
        'dynamic' or 'static'. Whether is a dinamic map or not
    dataframe: Pandas Dataframe
        None
        Contains information about when the devices started posting data, ids, location.
        It follows the format of world_map in api device
    options: dict
        dict()

    Returns:
        Folium.Map object
    '''
    def coordinates(x):
        return [x['latitude'], x['longitude']]

    def color(x):
        iSCAPE_IDs = [19, 20, 21, 28]
        making_sense_IDs = [11, 14]
        SCK_21_IDs = [26]

        color = '#0019ff'

        try:
            if x['kit_id'] in iSCAPE_IDs: color = '#7dbd4c'
            elif x['kit_id'] in making_sense_IDs: color = '#f88027'
            elif x['kit_id'] in SCK_21_IDs: color = '#ffb500'
        except:
            print_exc()
            pass

        return color

    def validate(x):

        if x['last_reading_at'] is None: return False
        if x['added_at'] is None: return False
        if any(x['coordinates']) is None or any(
            [isnan(item) for item in x['coordinates']]):
            return False
        if map_type == 'dynamic':
            if x['date_list'] == []: return False

        return True

    def range_list(x):

        date_r = date_range(start=x['added_at'],
                            end=x['last_reading_at'],
                            normalize=True,
                            freq=options['period']).strftime('%Y-%m-%d')
        date_l = list()
        for item in date_r.values:
            date_l.append(str(item))

        return date_l

    dataframe['color'] = dataframe.apply(lambda x: color(x), axis=1)
    dataframe['coordinates'] = dataframe.apply(lambda x: coordinates(x),
                                               axis=1)

    options = dict_fmerge(config._map_def_opt, options)

    # Make map
    m = Map(
        location=options['location'],
        tiles=options['tiles'],
        zoom_start=options['zoom'],
    )

    if map_type == 'dynamic':
        dataframe['date_list'] = dataframe.apply(lambda x: range_list(x),
                                                 axis=1)
        dataframe['valid'] = dataframe.apply(lambda x: validate(x), axis=1)

        dataframe = dataframe[(dataframe['valid'] == True)]
        features = list()

        for sensor in dataframe.index:
            features.append({
                'type': 'Feature',
                'geometry': {
                    'type':
                    'LineString',
                    'coordinates':
                    [dataframe.loc[sensor, 'coordinates'][::-1]] *
                    len(dataframe.loc[sensor, 'date_list']),
                    'popup':
                    f'<a href="http://smartcitizen.me/kits/{sensor}">{sensor}</a>',
                },
                'properties': {
                    'times': dataframe.loc[sensor, 'date_list'],
                    'icon': options['icon'],
                    'iconstyle': {
                        'fillOpacity': options['fillOpacity'],
                        'fillColor': dataframe.loc[sensor, 'color'],
                        'stroke': options['stroke'],
                        'radius': options['radius']
                    },
                    'style': {
                        'weight': '0'
                    },
                    'id': 'man'
                }
            })

        TimestampedGeoJson(
            {
                'type': 'FeatureCollection',
                'features': features
            },
            period='P' + convert_rollup(options['period']),
            add_last_point=True,
            auto_play=False,
            loop=False,
            max_speed=options['max_speed'],
            loop_button=True,
            # date_options='YYYY/MM/DD',
            time_slider_drag_update=True,
            duration='P' + options['period']).add_to(m)

    elif map_type == 'static':

        dataframe['valid'] = dataframe.apply(lambda x: validate(x), axis=1)
        dataframe = dataframe[(dataframe['valid'] == True)]

        for sensor in dataframe.index:
            Circle(
                location=dataframe.loc[sensor, 'coordinates'],
                radius=options['radius'],
                color=dataframe.loc[sensor, 'color'],
                fill=True,
                fillOpacity=options['fillOpacity'],
                fillColor=dataframe.loc[sensor, 'color'],
                popup=
                f'<a href="http://smartcitizen.me/kits/{sensor}">{sensor}</a>'
            ).add_to(m)

    return m
def ts_dispersion_uplot(self, **kwargs):
    '''
    Plots dispersion timeseries in uplot plot
    Parameters
    ----------
        channel: string
            Channel
        options: dict
            Options including data processing prior to plot. Defaults in config._plot_def_opt
        formatting: dict
            Formatting dict. Defaults in config._ts_plot_def_fmt
    Returns
    -------
        Matplotlib figure
    '''

    head_template = '''
        <link rel="stylesheet" href="https://leeoniya.github.io/uPlot/dist/uPlot.min.css">
        <script src="https://leeoniya.github.io/uPlot/dist/uPlot.iife.js"></script>

        <div style="text-align:center">
            <h2 style="font-family: Roboto"> {{title}} </h2>
        </div>

        '''

    uplot_template = '''
        <div id="plot{{subplot}}"></div>
        <script>
            data = {{data}};
            options = {{options}};

            if (typeof options.scatter == 'undefined') {
                options.scatter = false
            }

            if (options.scatter) {
                for (i=1; i<data.length; i++) {
                    options['series'][i]["paths"] = u => null;
                }
            }

            u = new uPlot(options, data, document.getElementById("plot{{subplot}}"))
        </script>
        '''

    if 'channel' not in kwargs:
        std_out('Needs at least one channel to plot')
        return None
    else:
        channel = kwargs['channel']

    if 'options' not in kwargs:
        std_out('Using default options')
        options = config._plot_def_opt
    else:
        options = dict_fmerge(config._plot_def_opt, kwargs['options'])

    if 'formatting' not in kwargs:
        std_out('Using default formatting')
        formatting = config._ts_plot_def_fmt['uplot']
    else:
        formatting = dict_fmerge(config._ts_plot_def_fmt['uplot'],
                                 kwargs['formatting'])

    # Size sanity check
    if formatting['width'] < 100:
        std_out('Setting width to 800')
        formatting['width'] = 800
    if formatting['height'] < 100:
        std_out('Reducing height to 600')
        formatting['height'] = 600

    if 'html' not in options:
        options['html'] = False

    if self.dispersion_df is None:
        std_out('Perform dispersion analysis first!', 'ERROR')
        return None

    if self.common_channels == []: self.get_common_channels()

    if channel not in self.common_channels:
        std_out(f'Channel {channel} not in common_channels')
        return None
    if channel in config._dispersion['ignore_channels']:
        std_out(f'Channel {channel} ignored per config')
        return None

    if len(self.devices) > config._dispersion['nt_threshold']:
        distribution = 'normal'
        std_out('Using normal distribution')
        std_out(f"Using limit for sigma confidence:\
                {config._dispersion['limit_confidence_sigma']}")
    else:
        distribution = 't-student'
        std_out(f'Using t-student distribution.')

    ch_index = self.common_channels.index(channel) + 1
    total_number = len(self.common_channels)
    h = Template(head_template).render(
        title=f'({ch_index}/{total_number}) - {channel}')

    dispersion_avg = self._dispersion_summary[channel]

    if distribution == 'normal':
        limit_confidence = config._dispersion['limit_confidence_sigma']
        # Calculate upper and lower bounds
        if (config._dispersion['instantatenous_dispersion']):
            # For sensors with high variability in the measurements, it's better to use this
            upper_bound = self.dispersion_df[channel + '_AVG']\
                        + limit_confidence * self.dispersion_df[channel + '_STD']
            lower_bound = self.dispersion_df[channel + '_AVG']\
                        - abs(limit_confidence * self.dispersion_df[channel + '_STD'])
        else:
            upper_bound = self.dispersion_df[channel + '_AVG']\
                        + limit_confidence * dispersion_avg
            lower_bound = self.dispersion_df[channel + '_AVG']\
                        - abs(limit_confidence * dispersion_avg)
    else:
        limit_confidence = t.interval(
            config._dispersion['t_confidence_level'] / 100.0,
            len(self.devices),
            loc=self.dispersion_df[channel + '_AVG'],
            scale=dispersion_avg)
        upper_bound = limit_confidence[1]
        lower_bound = limit_confidence[0]

    udf = self.dispersion_df.copy()
    udf['upper_bound'] = upper_bound
    udf['lower_bound'] = lower_bound

    udf = udf.fillna('null')
    # List containing subplots. First list for TBR, second for OK
    subplots = [[], []]

    if formatting['join_sbplot']: n_subplots = 1
    else: n_subplots = 2
    udf.index = udf.index.astype(int) / 10**9

    # Compose subplots lists
    for device in self.devices:
        ncol = channel + '-' + device

        if ncol in self.dispersion_df.columns:

            # Count how many times we go above the upper bound or below the lower one
            count_problems_up = self.dispersion_df[ncol] > upper_bound
            count_problems_down = self.dispersion_df[ncol] < lower_bound

            # Count them
            count_problems = [1 if (count_problems_up[i] or count_problems_down[i])\
                                else 0 for i in range(len(count_problems_up))]

            # Add the trace in either
            number_errors = np.sum(count_problems)
            max_number_errors = len(count_problems)

            # TBR
            if number_errors / max_number_errors > config._dispersion[
                    'limit_errors'] / 100:
                std_out(
                    f"Device {device} out of {config._dispersion['limit_errors']}% limit\
                         - {np.round(number_errors/max_number_errors*100, 1)}% out",
                    'WARNING')
                subplots[0].append(ncol)
            #OK
            else:
                subplots[n_subplots - 1].append(ncol)

    # Add upper and low bound bound to subplot 0
    subplots[0].append(channel + '_AVG')
    subplots[0].append('upper_bound')
    subplots[0].append('lower_bound')

    if n_subplots > 1:
        # Add upper and low bound bound to subplot 1
        subplots[n_subplots - 1].append(channel + '_AVG')
        subplots[n_subplots - 1].append('upper_bound')
        subplots[n_subplots - 1].append('lower_bound')

        ylabels = [channel + '_TBR', channel + '_OK']
    else:
        ylabels = [channel]

    # Make subplots
    for isbplt in range(n_subplots):

        sdf = udf.loc[:, subplots[isbplt]]
        sdf = sdf.reset_index()
        data = sdf.values.T.tolist()

        labels = sdf.columns
        useries = [{'label': labels[0]}]

        ylabel = ylabels[isbplt]

        uaxes = [{
            'label': formatting['xlabel'],
            'labelSize': formatting['fontsize'],
        }, {
            'label': ylabel,
            'labelSize': formatting['fontsize']
        }]

        color_idx = 0

        for label in labels:
            if label == labels[0]: continue
            if color_idx + 1 > len(colors): color_idx = 0
            # Gray bounds and averages
            if '_bound' in label or '_AVG' in label:
                stroke = 'gray'
                point = {'space': 50, 'size': min([formatting['size'] - 2, 1])}
            else:
                stroke = colors[color_idx]
                point = {'space': 0, 'size': formatting['size']}

            nser = {'label': label, 'stroke': stroke, 'points': point}

            useries.append(nser)
            color_idx += 1

        u_options = {
            'width': formatting['width'],
            'height': formatting['height'],
            'legend': {
                'isolate': True
            },
            'cursor': {
                'lock': True,
                'focus': {
                    'prox': 16,
                },
                'sync': {
                    'key': 'moo',
                    'setSeries': True,
                },
                'drag': {
                    'x': True,
                    'y': True,
                    'uni': 50,
                    'dist': 10,
                }
            },
            'scales': {
                'x': {
                    'time': True
                },
                'y': {
                    'auto': True
                },
            },
            'series': useries,
            'axes': uaxes
        }

        h2 = Template(uplot_template).render(data=json.dumps(data),
                                             options=json.dumps(u_options),
                                             subplot=isbplt)

        h += h2

    h = h.replace('"', "'")
    h = h.replace("'null'", "null")

    if options['html']:
        return h
    else:
        iframe = f'''<iframe srcdoc="{h}" src=""
            frameborder="0" width={formatting['width'] + formatting['padding-right']}
            height={formatting['height'] + formatting['padding-bottom']}
            sandbox="allow-scripts">
            </iframe>'''

        return HTML(iframe)
Exemplo n.º 21
0
def device_metric_map(self, channel, start_date, end_date, options=dict()):
    '''
    Creates a folium map showing the evolution of a metric dynamically with colors
    Parameters
    -------
    channel: String
        The channel to make the map from
    start_date, end_date: String
        Date convertible string
    options: dict()
        Possible keys are (default otherwise)
            location: list
                [41.400818, 2.1825157] 
                Center map location
            tiles: (String)
                'Stamen Toner'
                Tiles for the folium.Map
            zoom: (float)
                2.5
                Zoom to start with in folium.Map
            period: 'String'
                '1W'
                Period for 'dynamic' map
            radius: float 
                10
                Circle radius for icon
            fillOpacity: float
                1
                (<1) Fill opacity for the icon
            stroke: 'String'
                'false'
                'true' or 'false'. For icon's stroke
            icon: 'String' 
                'circle'
                A valid folium.Map icon style
    Returns
    -------    
        Folium.Map object
    '''

    # Map color bins
    poll_colors_palette = array([
        '#053061', '#2166ac', '#4393c3', '#92c5de', '#d1e5f0', '#fddbc7',
        '#f4a582', '#d6604d', '#b2182b', '#67001f'
    ])

    channel_bins = {
        'NOISE': [-inf, 52, 54, 56, 58, 60, 62, 64, 66, 68, inf],
        'PM': [0, 10, 20, 30, 40, 50, 75, 100, 150, 200, inf]
    }

    # Set defaults
    options = dict_fmerge(config._map_def_opt, options)

    # Make date range
    date_r = date_range(start=start_date,
                        end=end_date,
                        normalize=True,
                        freq=options['period']).strftime('%Y-%m-%d')
    date_l = list()
    for item in date_r.values:
        date_l.append(str(item))

    # Get bins
    for bname in channel_bins.keys():
        if bname in channel:
            bins = channel_bins[bname]
            break

    # Make features
    features = []
    for device in self.devices:

        # Get lat, long
        try:
            self.devices[str(device)].api_device.get_device_lat_long()
            lat = self.devices[str(device)].api_device.lat
            long = self.devices[str(device)].api_device.long
        except AttributeError:
            pass
            continue

        if lat is None or long is None: continue

        # Resample
        try:
            dfc = self.devices[str(device)].readings.resample(
                options['period']).mean()
        except:
            pass
            continue

        if channel not in dfc.columns: continue
        # Make color column
        dfc['color'] = cut(dfc[channel], bins, labels=poll_colors_palette)

        # Add point for each date
        for date in date_l:
            if date not in dfc.index: continue
            if date_l.index(date) > len(date_l) - 2: continue
            features.append({
                'type': 'Feature',
                'geometry': {
                    'type': 'LineString',
                    'coordinates': [[str(long), str(lat)]] * 2,
                    'popup': str(device),
                },
                'properties': {
                    'times': [date, date_l[date_l.index(date) + 1]],
                    'icon': options['icon'],
                    'iconstyle': {
                        'fillColor': str(dfc.loc[date, 'color']),
                        'fillOpacity': options['fillOpacity'],
                        'stroke': options['stroke'],
                        'radius': options['radius']
                    },
                    'style': {
                        'weight': '0'
                    },
                    'id': 'man'
                }
            })

    # Make map
    m = make_map(map_type='dynamic', features=features, options=options)

    return m