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)
Beispiel #2
0
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
Beispiel #3
0
    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)
Beispiel #4
0
 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
     ])
Beispiel #5
0
 def _predict(self, t, period):
     model = ssm.SuperSmoother(period=period).fit(self.t, self.y, self.dy)
     return model.predict(t)
Beispiel #6
0
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)