def score(self, period): """Find the score for a given period.""" period = np.asarray(period) # double-up the data to allow periodicity on the fits N = len(self.t) N4 = N // 4 t = np.concatenate([self.t, self.t]) y = np.concatenate([self.y, self.y]) dy = np.concatenate([self.dy, self.dy]) results = [] for p in period.ravel(): # compute doubled phase and sort phase = t % p phase[N:] += p isort = np.argsort(phase)[N4:N + 3 * N4] phase = phase[isort] yp = y[isort] dyp = dy[isort] # compute model model = ssm.SuperSmoother().fit(phase, yp, dyp, presorted=True) # take middle residuals resids = model.cv_residuals()[N4:N4 + N] results.append(1 - np.mean(np.abs(resids)) / self.baseline_err) return np.asarray(results).reshape(period.shape)
def _decompose_ssmoother(ts: t.Union[np.ndarray, pd.core.series.Series], plot: bool = False) -> t.Tuple[np.ndarray, ...]: """Time-series decomposition using Friedman's Super Smoother. The seasonal component returned is an array full of zeros with the same length as the given time-series. Parameters ---------- ts : :obj:`np.ndarray` or :obj:`pd.core.series.Series` One-dimensional time-series values. plot : bool, optional (default=False) If True, plot the decomposed components. Returns ------- tuple of :obj:`np.ndarray` A tuple with tree components, in the following order: time-series trend component, an array full of zeros (seasonal component), and the residual component. References ---------- .. [1] Friedman, J. H. 1984, A variable span scatterplot smoother Laboratory for Computational Statistics, Stanford University Technical Report No. 5. Available at: https://www.slac.stanford.edu/pubs/slacpubs/3250/slac-pub-3477.pdf Accessed on May 12 2020. """ timestamp = np.arange(ts.size) model = supersmoother.SuperSmoother().fit(timestamp, ts, presorted=True) comp_trend = model.predict(timestamp) comp_resid = ts - comp_trend comp_season = np.zeros(ts.size, dtype=float) if plot: fig, (ax_raw, ax_trend, ax_resid) = plt.subplots(3, 1) fig.suptitle("Friedman's Super Smoother results") ax_raw.plot(ts) ax_raw.set_title("Original time-series") ax_trend.plot(comp_trend) ax_trend.set_title("Trend component") ax_resid.plot(comp_resid) ax_resid.set_title("Residual component") plt.show() return comp_trend, comp_season, comp_resid
def __init__(self, x, y, name='SuperSmoother'): tic = time.time() if name == 'SuperSmoother': min_J = 5 # min points in a window (otherwise no convergence). Does no work with less than 5! s12 = 4 # multiplier from min to mid span, ie mid_span / min_span s23 = 2.5 # multiplier from mid to max span, ie max_span / mid_span # min data points: we must have min_J/len * s12 * s23 <= 1 min_len = min_J * s12 * s23 if len(y) < min_len: print('ERROR: not enough data points: ' + str(len(y)) + '. Should be at least ' + str(min_len) + '. Try Lowess') sys.exit(-1) min_span = max(0.05, min_J / len(y)) self.model = sm.SuperSmoother(primary_spans=(min_span, min_span * s12, min_span * s12 * s23), middle_span=min_span * s12, final_span=min_span) self.model.fit(x, y, dy=np.std(y)) elif name == 'Lowess': self.model = LowessSmooth(x, y) self.model.fit(x, y, dy=1.0) # get BW else: print('ERROR: invalid smoother: ' + str(name) + '. Valid smoothers are: SuperSmoother, Lowess') sys.exit(-1)
def _score(self, periods): return np.asarray([ 1 - (ssm.SuperSmoother(period=p).fit(self.t, self.y, self.dy).cv_error( skip_endpoints=False) / self.baseline_err) for p in periods ])
def _predict(self, t, period): model = ssm.SuperSmoother(period=period).fit(self.t, self.y, self.dy) return model.predict(t)
def pre_whiten(time, flux, unc_flux, period, kind="supersmoother", which="phased", phaser=None): """Phase a lightcurve and then smooth it. Inputs ------ time, flux, unc_flux: array_like period: float period to whiten by kind: string, optional type of smoothing to use. Defaults to "supersmoother." Other types are "boxcar", "linear" which: string, optional whether to smooth the "phased" lightcurve (default) or the "full" lightcurve. phaser: Float, optional (default=None) if kind="boxcar", phaser is the Half-width of the smoothing window. if kind="supersmoother", phaser is alpha (the "bass enhancement"). Outputs ------- white_flux, white_unc, smoothed_flux: arrays """ # print(which, period, phaser) # phase the LC by the period if period is not None: # phased_time = utils.phase(time, period) phased_time = (time % period) else: phased_time = time if kind.lower() == "supersmoother": if phaser is None: logging.info("Phaser not set! " "Set phaser=alpha (bass-enhancement value " "for supersmoother) if desired.") if which.lower() == "phased": # Instantiate the supersmoother model object with the input period model = supersmoother.SuperSmoother(period=period) # Set up base arrays for phase for the fit x_vals = np.linspace(0, max(phased_time) * 1.001, 1000) # Run a fit for the y phase y_fit = model.fit(phased_time, flux, unc_flux).predict(x_vals) elif which.lower() == "full": # Instantiate the supersmoother model object with the input period model = supersmoother.SuperSmoother(alpha=phaser) # Set up base arrays for time for the fit x_vals = np.linspace(min(time), max(time), 1000) # run a fit for the y values y_fit = model.fit(time, flux, unc_flux).predict(x_vals) else: logging.warning("unknown which %s !!!", which) elif kind.lower() == "boxcar": if phaser is None: logging.info("Phaser not set! " "Set phaser to the width of the smoothing " "box in pixels!") if which.lower() == "phased": # sort the phases sort_locs = np.argsort(phased_time) x_vals = phased_time[sort_locs] flux_to_fit = flux[sort_locs] elif which.lower() == "full": x_vals = time flux_to_fit = flux else: logging.warning("unknown which %s !!!", which) # Use astropy's convolution function! boxcar_kernel = convolution.Box1DKernel(width=phaser, mode="linear_interp") y_fit = convolution.convolve(flux_to_fit, boxcar_kernel, boundary="wrap") elif kind == "linear": if which != "full": logging.warning("Linear smoothing only allowed for full " "lightcurve! Switching to full mode.") which = "full" # Fit a line to the data pars = np.polyfit(time, flux, deg=1) m, b = pars smoothed_flux = m * time + b else: logging.warning("unknown kind %s !!!", kind) if (kind == "supersmoother") or (kind == "boxcar"): # Interpolate back onto the original time array interp_func = interpolate.interp1d(x_vals, y_fit) if which.lower() == "phased": # try: smoothed_flux = interp_func(phased_time) # except ValueError: # # print(min(x_vals),max(x_vals)) # # print(min(phased_time),max(phased_time)) # smoothed_flux = np.ones_like(phased_time) # smoothed_flux[1:-1] = interp_func(phased_time[1:-1]) # smoothed_flux[0] = smoothed_flux[1] # smoothed_flux[-1] = smoothed_flux[-2] elif which.lower() == "full": smoothed_flux = interp_func(time) else: logging.warning("unknown which %s !!!", which) # Whiten the input flux by subtracting the smoothed version # The smoothed flux represents the "bulk trend" white_flux = flux - smoothed_flux white_unc = unc_flux return white_flux, white_unc, smoothed_flux
def predict(self, t, filts=None, period=None): """Predict the supersmoother model for the data""" if period is None: raise ValueError("Must provide a period for the prediction") model = ssm.SuperSmoother().fit(self.t % period, self.y, self.dy) return model.predict(t % period)