Source code for astroML.time_series.periodogram

import numpy as np
from sklearn.utils import check_random_state

try:
    # astropy.timeseries is new in v3.2
    from astropy.timeseries import LombScargle
except ImportError:
    from astropy.stats import LombScargle

from ..utils.decorators import deprecated
from ..utils.exceptions import AstroMLDeprecationWarning


[docs]@deprecated('0.4', alternative='astropy.stats.LombScargle', warning_type=AstroMLDeprecationWarning) def lomb_scargle(t, y, dy, omega, generalized=True, subtract_mean=True, significance=None): """ (Generalized) Lomb-Scargle Periodogram with Floating Mean Parameters ---------- t : array_like sequence of times y : array_like sequence of observations dy : array_like sequence of observational errors omega : array_like frequencies at which to evaluate p(omega) generalized : bool if True (default) use generalized lomb-scargle method otherwise, use classic lomb-scargle. subtract_mean : bool if True (default) subtract the sample mean from the data before computing the periodogram. Only referenced if generalized is False significance : None or float or ndarray if specified, then this is a list of significances to compute for the results. Returns ------- p : array_like Lomb-Scargle power associated with each frequency omega z : array_like if significance is specified, this gives the levels corresponding to the desired significance (using the Scargle 1982 formalism) Notes ----- The algorithm is based on reference [1]_. The result for generalized=False is given by equation 4 of this work, while the result for generalized=True is given by equation 20. Note that the normalization used in this reference is different from that used in other places in the literature (e.g. [2]_). For a discussion of normalization and false-alarm probability, see [1]_. To recover the normalization used in Scargle [3]_, the results should be multiplied by (N - 1) / 2 where N is the number of data points. References ---------- .. [1] M. Zechmeister and M. Kurster, A&A 496, 577-584 (2009) .. [2] W. Press et al, Numerical Recipes in C (2002) .. [3] Scargle, J.D. 1982, ApJ 263:835-853 """ # delegate to astropy's Lomb-Scargle ls = LombScargle(t, y, dy, fit_mean=generalized, center_data=subtract_mean) frequency = np.asarray(omega) / (2 * np.pi) p_omega = ls.power(frequency, method='cython') if significance is not None: N = t.size M = 2 * N z = (-2.0 / (N - 1.) * np.log(1 - (1 - np.asarray(significance)) ** (1. / M))) return p_omega, z else: return p_omega
[docs]@deprecated('0.4', alternative='astropy.stats.LombScargle.false_alarm_probability', warning_type=AstroMLDeprecationWarning) def lomb_scargle_bootstrap(t, y, dy, omega, generalized=True, subtract_mean=True, N_bootstraps=100, random_state=None): """Use a bootstrap analysis to compute Lomb-Scargle significance Parameters ---------- The first set of parameters are passed to the lomb_scargle algorithm t : array_like sequence of times y : array_like sequence of observations dy : array_like sequence of observational errors omega : array_like frequencies at which to evaluate p(omega) generalized : bool if True (default) use generalized lomb-scargle method otherwise, use classic lomb-scargle. subtract_mean : bool if True (default) subtract the sample mean from the data before computing the periodogram. Only referenced if generalized is False Remaining parameters control the bootstrap N_bootstraps : int number of bootstraps random_state : None, int, or RandomState object random seed, or random number generator Returns ------- D : ndarray distribution of the height of the highest peak """ random_state = check_random_state(random_state) t = np.asarray(t) y = np.asarray(y) dy = np.asarray(dy) + np.zeros_like(y) D = np.zeros(N_bootstraps) for i in range(N_bootstraps): ind = random_state.randint(0, len(y), len(y)) p = lomb_scargle(t, y[ind], dy[ind], omega, generalized=generalized, subtract_mean=subtract_mean) D[i] = p.max() return D
def lomb_scargle_AIC(P, y, dy, n_harmonics=1): """Compute the AIC for a Lomb-Scargle Periodogram Parameters ---------- P : array_like lomb-scargle power y : array_like observations dy : array_like errors n_harmonics : int (optional) the number of harmonics used in the Lomb-Scargle fit. Default is 1 Returns ------- AIC : ndarray AIC value corresponding to values in P """ P, y, dy = map(np.asarray(P, y, dy)) w = 1. / dy ** 2 mu = np.dot(w, y) / w.sum() return np.sum(((y - mu) / dy) ** 2) * P - (2 * n_harmonics + 1) * 2 def lomb_scargle_BIC(P, y, dy, n_harmonics=1): """Compute the BIC for a Lomb-Scargle Periodogram Parameters ---------- P : array_like lomb-scargle power y : array_like observations dy : array_like errors n_harmonics : int (optional) the number of harmonics used in the Lomb-Scargle fit. Default is 1 Returns ------- BIC : ndarray BIC value corresponding to values in P """ P, y, dy = map(np.asarray, (P, y, dy)) w = 1. / dy ** 2 mu = np.dot(w, y) / w.sum() N = len(y) return np.sum(((y - mu) / dy) ** 2) * P - (2 * n_harmonics + 1) * np.log(N)
[docs]@deprecated('0.4', alternative='astropy.stats.LombScargle', warning_type=AstroMLDeprecationWarning) def multiterm_periodogram(t, y, dy, omega, n_terms=3): """Perform a multiterm periodogram at each omega This calculates the chi2 for the best-fit least-squares solution for each frequency omega. Parameters ---------- t : array_like sequence of times y : array_like sequence of observations dy : array_like sequence of observational errors omega : float or array_like frequencies at which to evaluate p(omega) Returns ------- power : ndarray P = 1. - chi2 / chi2_0 where chi2_0 is the chi-square for a simple mean fit to the data """ # TODO: deprecate this ls = LombScargle(t, y, dy, nterms=n_terms) frequency = np.asarray(omega) / (2 * np.pi) return ls.power(frequency)
[docs]def search_frequencies(t, y, dy, LS_func=lomb_scargle, LS_kwargs=None, initial_guess=25, limit_fractions=[0.04, 0.3, 0.9, 0.99], n_eval=10000, n_retry=5, n_save=50): """Utility Routine to find the best frequencies To find the best frequency with a Lomb-Scargle periodogram requires searching a large range of frequencies at a very fine resolution. This is an iterative routine that searches progressively finer grids to narrow-in on the best result. Parameters ---------- t: array_like observed times y: array_like observed fluxes or magnitudes dy: array_like observed errors on y Other Parameters ---------------- LS_func : function Function used to perform Lomb-Scargle periodogram. The call signature should be LS_func(t, y, dy, omega, **kwargs) (Default is astroML.periodogram.lomb_scargle) LS_kwargs : dict dictionary of keyword arguments to pass to LS_func in addition to (t, y, dy, omega) initial_guess : float the initial guess of the best period limit_fractions : array_like the list of fractions to use when zooming in on peak possibilities. On the i^th iteration, with f_i = limit_fractions[i], the range probed around each candidate will be (candidate * f_i, candidate / f_i). n_eval : integer or list The number of point to evaluate in the range on each iteration. If n_eval is a list, it should have the same length as limit_fractions. n_retry : integer or list Number of top points to search on each iteration. If n_retry is a list, it should have the same length as limit_fractions. n_save : integer or list Number of evaluations to save on each iteration. If n_save is a list, it should have the same length as limit_fractions. Returns ------- omega_top, power_top: ndarrays The saved values of omega and power. These will have size 1 + n_save * (1 + n_retry * len(limit_fractions)) as long as n_save > n_retry """ if LS_kwargs is None: LS_kwargs = dict() omega_best = [initial_guess] power_best = LS_func(t, y, dy, omega_best, **LS_kwargs) for (Ne, Nr, Ns, frac) in np.broadcast(n_eval, n_retry, n_save, limit_fractions): # make sure we explore differing regions log_ob = np.log(omega_best) width = 0.1 * np.log(frac) log_ob = np.floor(-log_ob / width).astype(int) indices = np.arange(len(log_ob)) for i in range(Nr): if len(indices) == 0: break omega_try = omega_best[indices[-1]] non_duplicates = (log_ob != log_ob[-1]) log_ob = log_ob[non_duplicates] indices = indices[non_duplicates] omega = np.linspace(omega_try * frac, omega_try / frac, Ne) power = LS_func(t, y, dy, omega, **LS_kwargs) i = np.argsort(power)[-Ns:] power_best = np.concatenate([power_best, power[i]]) omega_best = np.concatenate([omega_best, omega[i]]) i = np.argsort(power_best) power_best = power_best[i] omega_best = omega_best[i] i = np.argsort(omega_best) return omega_best[i], power_best[i]
[docs]class MultiTermFit: """Multi-term Fourier fit to a light curve Parameters ---------- omega : float angular frequency of the fundamental mode n_terms : int the number of Fourier modes to use in the fit """
[docs] def __init__(self, omega, n_terms): self.omega = omega self.n_terms = n_terms
def _make_X(self, t): t = np.asarray(t) k = np.arange(1, self.n_terms + 1) X = np.hstack([np.ones(t[:, None].shape), np.sin(k * self.omega * t[:, None]), np.cos(k * self.omega * t[:, None])]) return X def fit(self, t, y, dy): """Fit multiple Fourier terms to the data Parameters ---------- t: array_like observed times y: array_like observed fluxes or magnitudes dy: array_like observed errors on y Returns ------- self : The MultiTermFit object is returned """ t = np.asarray(t) y = np.asarray(y) dy = np.asarray(dy) X_scaled = self._make_X(t) / dy[:, None] y_scaled = y / dy self.t_ = t self.w_ = np.linalg.solve(np.dot(X_scaled.T, X_scaled), np.dot(X_scaled.T, y_scaled)) return self def predict(self, Nphase, return_phased_times=False, adjust_offset=True): """Compute the phased fit, and optionally return phased times Parameters ---------- Nphase : int Number of terms to use in the phased fit return_phased_times : bool If True, then return a phased version of the input times adjust_offset : bool If true, then shift results so that the minimum value is at phase 0 Returns ------- phase, y_fit : ndarrays The phase and y value of the best-fit light curve phased_times : ndarray The phased version of the training times. Returned if return_phased_times is set to True. """ phase_fit = np.linspace(0, 1, Nphase + 1)[:-1] X_fit = self._make_X(2 * np.pi * phase_fit / self.omega) y_fit = np.dot(X_fit, self.w_) i_offset = np.argmin(y_fit) if adjust_offset: y_fit = np.concatenate([y_fit[i_offset:], y_fit[:i_offset]]) if return_phased_times: if adjust_offset: offset = phase_fit[i_offset] else: offset = 0 phased_times = (self.t_ * self.omega * 0.5 / np.pi - offset) % 1 return phase_fit, y_fit, phased_times else: return phase_fit, y_fit