def commit(self): data = self.data if not data or not len(self.selected): self.Outputs.time_series.send(None) return X = [] attrs = [] invert = self.invert_direction shift = self.shift_period order = self.diff_order op = self.chosen_operation for var in self.selected: col = np.ravel(data[:, var]) if invert: col = col[::-1] out = np.empty(len(col)) if op == self.Operation.DIFF and shift == 1: out[order:] = np.diff(col, order) out[:order] = np.nan else: if op == self.Operation.DIFF: out[shift:] = col[shift:] - col[:-shift] else: out[shift:] = np.divide(col[shift:], col[:-shift]) if op == self.Operation.PERC: out = (out - 1) * 100 out[:shift] = np.nan if invert: out = out[::-1] X.append(out) if op == self.Operation.DIFF and shift == 1: details = f'order={order}' else: details = f'shift={shift}' template = f'{var} ({op[:4].lower()}; {details})' name = available_name(data.domain, template) attrs.append(ContinuousVariable(name)) ts = Timeseries.from_numpy(Domain(data.domain.attributes + tuple(attrs), data.domain.class_vars, data.domain.metas), np.column_stack((data.X, np.column_stack(X))), data.Y, data.metas) ts.time_variable = data.time_variable self.Outputs.time_series.send(ts)
def commit(self): data = self.data if not data or not len(self.selected): self.send(Output.TIMESERIES, None) return X = [] attrs = [] invert = self.invert_direction shift = self.shift_period order = self.diff_order for var in self.selected: col = np.ravel(data[:, var]) if invert: col = col[::-1] out = np.empty(len(col)) if shift == 1: out[:-order] = np.diff(col, order) out[-order:] = np.nan else: out[:-shift] = col[shift:] - col[:-shift] out[-shift:] = np.nan if invert: out = out[::-1] X.append(out) template = '{} (diff; {})'.format(var, 'order={}'.format(order) if shift == 1 else 'shift={}'.format(shift)) name = available_name(data.domain, template) attrs.append(ContinuousVariable(name)) ts = Timeseries(Domain(data.domain.attributes + tuple(attrs), data.domain.class_vars, data.domain.metas), np.column_stack((data.X, np.column_stack(X))), data.Y, data.metas) ts.time_variable = data.time_variable self.send(Output.TIMESERIES, ts)
def commit(self): data = self.data if not data or not len(self.selected): self.send(Output.TIMESERIES, None) return X = [] attrs = [] invert = self.invert_direction shift = self.shift_period order = self.diff_order for var in self.selected: col = np.ravel(data[:, var]) if invert: col = col[::-1] out = np.empty(len(col)) if shift == 1: out[:-order] = np.diff(col, order) out[-order:] = np.nan else: out[:-shift] = col[shift:] - col[:-shift] out[-shift:] = np.nan if invert: out = out[::-1] X.append(out) template = '{} (diff; {})'.format( var, 'order={}'.format(order) if shift == 1 else 'shift={}'.format(shift)) name = available_name(data.domain, template) attrs.append(ContinuousVariable(name)) ts = Timeseries( Domain(data.domain.attributes + tuple(attrs), data.domain.class_vars, data.domain.metas), np.column_stack((data.X, np.column_stack(X))), data.Y, data.metas) ts.time_variable = data.time_variable self.send(Output.TIMESERIES, ts)
def moving_transform(data, spec, fixed_wlen=0): """ Return data transformed according to spec. Parameters ---------- data : Timeseries A table with features to transform. spec : list of lists A list of lists [feature:Variable, window_length:int, function:callable]. fixed_wlen : int If not 0, then window_length in spec is disregarded and this length is used. Also the windows don't shift by one but instead align themselves side by side. Returns ------- transformed : Timeseries A table of original data its transformations. """ from itertools import chain from Orange.data import ContinuousVariable, Domain from orangecontrib.timeseries import Timeseries from orangecontrib.timeseries.widgets.utils import available_name from orangecontrib.timeseries.agg_funcs import Cumulative_sum, Cumulative_product X = [] attrs = [] for var, wlen, func in spec: col = np.ravel(data[:, var]) if fixed_wlen: wlen = fixed_wlen if func in (Cumulative_sum, Cumulative_product): out = list(chain.from_iterable(func(col[i:i + wlen]) for i in range(0, len(col), wlen))) else: # In reverse cause lazy brain. Also prefer informative ends, not beginnings as much col = col[::-1] out = [func(col[i:i + wlen]) for i in range(0, len(col), wlen if bool(fixed_wlen) else 1)] out = out[::-1] X.append(out) template = '{} ({}; {})'.format(var.name, wlen, func.__name__.lower().replace('_', ' ')) name = available_name(data.domain, template) attrs.append(ContinuousVariable(name)) dataX, dataY, dataM = data.X, data.Y, data.metas if fixed_wlen: n = len(X[0]) dataX = dataX[::-1][::fixed_wlen][:n][::-1] dataY = dataY[::-1][::fixed_wlen][:n][::-1] dataM = dataM[::-1][::fixed_wlen][:n][::-1] ts = Timeseries(Domain(data.domain.attributes + tuple(attrs), data.domain.class_vars, data.domain.metas), np.column_stack( (dataX, np.column_stack(X))) if X else dataX, dataY, dataM) ts.time_variable = data.time_variable return ts
def seasonal_decompose(data, model='multiplicative', period=12, *, callback=None): """ Return table of decomposition components of original features and original features seasonally adjusted. Parameters ---------- data : Timeseries A table of featres to decompose/adjust. model : str {'additive', 'multiplicative'} A decompostition model. See: https://en.wikipedia.org/wiki/Decomposition_of_time_series period : int The period length of season. callback : callable Optional callback to call (with no parameters) after each iteration. Returns ------- table : Timeseries Table with columns: original series seasonally adjusted, original series' seasonal components, trend components, and residual components. """ from operator import sub, truediv from Orange.data import Domain, ContinuousVariable from orangecontrib.timeseries import Timeseries from orangecontrib.timeseries.widgets.utils import available_name import statsmodels.api as sm def _interp_trend(trend): first = next(i for i, val in enumerate(trend) if val == val) last = trend.size - 1 - next( i for i, val in enumerate(trend[::-1]) if val == val) d = 3 first_last = min(first + d, last) last_first = max(first, last - d) k, n = np.linalg.lstsq( np.column_stack((np.arange(first, first_last), np.ones(first_last - first))), trend[first:first_last])[0] trend[:first] = np.arange(0, first) * k + n k, n = np.linalg.lstsq( np.column_stack((np.arange(last_first, last), np.ones(last - last_first))), trend[last_first:last])[0] trend[last + 1:] = np.arange(last + 1, trend.size) * k + n return trend attrs = [] X = [] recomposition = sub if model == 'additive' else truediv interp_data = data.interp() for var in data.domain.variables: decomposed = sm.tsa.seasonal_decompose(np.ravel(interp_data[:, var]), model=model, freq=period) adjusted = recomposition(decomposed.observed, decomposed.seasonal) season = decomposed.seasonal trend = _interp_trend(decomposed.trend) resid = recomposition(adjusted, trend) # Re-apply nans isnan = np.isnan(data[:, var]).ravel() adjusted[isnan] = np.nan trend[isnan] = np.nan resid[isnan] = np.nan attrs.extend( ContinuousVariable( available_name(data.domain, var.name + ' ({})'.format(transform))) for transform in ('season. adj.', 'seasonal', 'trend', 'residual') ) X.extend((adjusted, season, trend, resid)) if callback: callback() ts = Timeseries(Domain(attrs), np.column_stack(X)) return ts
def moving_transform(data, spec, fixed_wlen=0): """ Return data transformed according to spec. Parameters ---------- data : Timeseries A table with features to transform. spec : list of lists A list of lists [feature:Variable, window_length:int, function:callable]. fixed_wlen : int If not 0, then window_length in spec is disregarded and this length is used. Also the windows don't shift by one but instead align themselves side by side. Returns ------- transformed : Timeseries A table of original data its transformations. """ from itertools import chain from Orange.data import ContinuousVariable, Domain from orangecontrib.timeseries import Timeseries from orangecontrib.timeseries.widgets.utils import available_name from orangecontrib.timeseries.agg_funcs import Cumulative_sum, Cumulative_product X = [] attrs = [] for var, wlen, func in spec: col = np.ravel(data[:, var]) if fixed_wlen: wlen = fixed_wlen if func in (Cumulative_sum, Cumulative_product): out = list( chain.from_iterable( func(col[i:i + wlen]) for i in range(0, len(col), wlen))) else: # In reverse cause lazy brain. Also prefer informative ends, not beginnings as much col = col[::-1] out = [ func(col[i:i + wlen]) for i in range(0, len(col), wlen if bool(fixed_wlen) else 1) ] out = out[::-1] X.append(out) template = '{} ({}; {})'.format( var.name, wlen, func.__name__.lower().replace('_', ' ')) name = available_name(data.domain, template) attrs.append(ContinuousVariable(name)) dataX, dataY, dataM = data.X, data.Y, data.metas if fixed_wlen: n = len(X[0]) dataX = dataX[::-1][::fixed_wlen][:n][::-1] dataY = dataY[::-1][::fixed_wlen][:n][::-1] dataM = dataM[::-1][::fixed_wlen][:n][::-1] ts = Timeseries.from_numpy( Domain(data.domain.attributes + tuple(attrs), data.domain.class_vars, data.domain.metas), np.column_stack((dataX, np.column_stack(X))) if X else dataX, dataY, dataM) ts.time_variable = data.time_variable return ts
def seasonal_decompose(data, model='multiplicative', period=12, *, callback=None): """ Return table of decomposition components of original features and original features seasonally adjusted. Parameters ---------- data : Timeseries A table of featres to decompose/adjust. model : str {'additive', 'multiplicative'} A decompostition model. See: https://en.wikipedia.org/wiki/Decomposition_of_time_series period : int The period length of season. callback : callable Optional callback to call (with no parameters) after each iteration. Returns ------- table : Timeseries Table with columns: original series seasonally adjusted, original series' seasonal components, trend components, and residual components. """ from operator import sub, truediv from Orange.data import Domain, ContinuousVariable from orangecontrib.timeseries import Timeseries from orangecontrib.timeseries.widgets.utils import available_name import statsmodels.api as sm def _interp_trend(trend): first = next(i for i, val in enumerate(trend) if val == val) last = trend.size - 1 - next( i for i, val in enumerate(trend[::-1]) if val == val) d = 3 first_last = min(first + d, last) last_first = max(first, last - d) k, n = np.linalg.lstsq( np.column_stack( (np.arange(first, first_last), np.ones(first_last - first))), trend[first:first_last])[0] trend[:first] = np.arange(0, first) * k + n k, n = np.linalg.lstsq( np.column_stack((np.arange(last_first, last), np.ones(last - last_first))), trend[last_first:last])[0] trend[last + 1:] = np.arange(last + 1, trend.size) * k + n return trend attrs = [] X = [] recomposition = sub if model == 'additive' else truediv interp_data = data.interp() for var in data.domain.variables: decomposed = sm.tsa.seasonal_decompose(np.ravel(interp_data[:, var]), model=model, freq=period) adjusted = recomposition(decomposed.observed, decomposed.seasonal) season = decomposed.seasonal trend = _interp_trend(decomposed.trend) resid = recomposition(adjusted, trend) # Re-apply nans isnan = np.isnan(data[:, var]).ravel() adjusted[isnan] = np.nan trend[isnan] = np.nan resid[isnan] = np.nan attrs.extend( ContinuousVariable( available_name(data.domain, var.name + ' ({})'.format(transform))) for transform in ('season. adj.', 'seasonal', 'trend', 'residual')) X.extend((adjusted, season, trend, resid)) if callback: callback() ts = Timeseries.from_numpy(Domain(attrs), np.column_stack(X)) return ts