Source code for pyActigraphy.filters.filters

import numpy as np
import pandas as pd
import warnings
from ..log import BaseLog


def _create_inactivity_mask(data, duration, threshold):

    if duration is None:
        return None

    # Create the mask filled iwith ones by default.
    mask = np.ones_like(data)

    # If duration is -1, return a mask with 1s for later manual edition.
    if duration == -1:
        return pd.Series(mask, index=data.index)

    # Binary data
    binary_data = np.where(data >= threshold, 1, 0)

    # The first order diff Series indicates the indices of the transitions
    # between series of zeroes and series of ones.
    # Add zero at the beginning of this series to mark the beginning of the
    # first sequence found in the data.
    edges = np.concatenate([[0], np.diff(binary_data)])

    # Test if there is no edge (i.e. no consecutive zeroes).
    if all(e == 0 for e in edges):
        return pd.Series(mask, index=data.index)

    # Indices of upper transitions (zero to one).
    idx_plus_one = (edges > 0).nonzero()[0]
    # Indices of lower transitions (one to zero).
    idx_minus_one = (edges < 0).nonzero()[0]

    # Even number of transitions.
    if idx_plus_one.size == idx_minus_one.size:

        # Start with zeros
        if idx_plus_one[0] < idx_minus_one[0]:
            starts = np.concatenate([[0], idx_minus_one])
            ends = np.concatenate([idx_plus_one, [edges.size]])
        else:
            starts = idx_minus_one
            ends = idx_plus_one
    # Odd number of transitions
    # starting with an upper transition
    elif idx_plus_one.size > idx_minus_one.size:
        starts = np.concatenate([[0], idx_minus_one])
        ends = idx_plus_one
    # starting with an lower transition
    else:
        starts = idx_minus_one
        ends = np.concatenate([idx_plus_one, [edges.size]])

    # Index pairs (start,end) of the sequences of zeroes
    seq_idx = np.c_[starts, ends]
    # Length of the aforementioned sequences
    seq_len = ends - starts

    for i in seq_idx[np.where(seq_len >= duration)]:
        mask[i[0]:i[1]] = 0

    return pd.Series(mask, index=data.index)


[docs]class FiltersMixin(object): """ Mixin Class """
[docs] def create_inactivity_mask(self, duration): """Create a mask for inactivity (count equal to zero) periods. This mask has the same length as its underlying data and can be used to offuscate inactive periods where the actimeter has most likely been removed. Warning: use a sufficiently long duration in order not to mask sleep periods. A minimal duration corresponding to two hours seems reasonable. Parameters ---------- duration: int or str Minimal number of consecutive zeroes for an inactive period. Time offset strings (ex: '90min') can also be used. """ if isinstance(duration, int): nepochs = duration elif isinstance(duration, str): nepochs = int(pd.Timedelta(duration)/self.frequency) else: nepochs = None warnings.warn( 'Inactivity length must be a int and time offset string (ex: ' '\'90min\'). Could not create a mask.', UserWarning ) # Store requested mask duration (and discard current mask) self.inactivity_length = nepochs # Create actual mask self.mask = _create_inactivity_mask(self.raw_data, nepochs, 1)
[docs] def add_mask_period(self, start, stop): """ Add a period to the inactivity mask Parameters ---------- start: str Start time (YYYY-MM-DD HH:MM:SS) of the inactivity period. stop: str Stop time (YYYY-MM-DD HH:MM:SS) of the inactivity period. """ # Check if a mask has already been created # NB : if the inactivity_length is not None, accessing the mask will # trigger its creation. if self.inactivity_length is None: self.inactivity_length = -1 # self.mask = pd.Series( # np.ones(self.length()), # index=self.data.index # ) # Check if start and stop are within the index range if (pd.Timestamp(start) < self.mask.index[0]): raise ValueError(( "Attempting to set the start time of a mask period before " + "the actual start time of the data.\n" + "Mask start time: {}".format(start) + "Data start time: {}".format(self.mask.index[0]) )) if (pd.Timestamp(stop) > self.mask.index[-1]): raise ValueError(( "Attempting to set the stop time of a mask period after " + "the actual stop time of the data.\n" + "Mask stop time: {}".format(stop) + "Data stop time: {}".format(self.mask.index[-1]) )) # Set mask values between start and stop to zeros self.mask.loc[start:stop] = 0
[docs] def add_mask_periods(self, input_fname, *args, **kwargs): """ Add periods to the inactivity mask Function to read start and stop times from a Mask log file. Supports different file format (.ods, .xls(x), .csv). Parameters ---------- input_fname: str Path to the log file. *args Variable length argument list passed to the subsequent reader function. **kwargs Arbitrary keyword arguments passed to the subsequent reader function. """ # Convert the log file into a DataFrame absname, log = BaseLog.from_file(input_fname, 'Mask', *args, **kwargs) # Iterate over the rows of the DataFrame for _, row in log.iterrows(): self.add_mask_period(row['Start_time'], row['Stop_time'])