Skip to main content

Time Series Analysis

The CSharpNumerics.Statistics.TimeSeriesAnalysis namespace provides specialised algorithms for periodic signal detection, detrending, phase folding, and peak/event fitting in time-series data — including support for unevenly sampled observations.

📡 Lomb–Scargle Periodogram

Spectral analysis for unevenly sampled data. The classical Lomb (1976) / Scargle (1982) algorithm with the Townsend τ-offset for numerical stability.

using CSharpNumerics.Statistics.TimeSeriesAnalysis;
using CSharpNumerics.Numerics.Objects;

// Observations at irregular times
var times = new VectorN(new[] { 0.0, 1.1, 2.3, 3.0, 4.7, 5.2, /* … */ });
var values = new VectorN(new[] { 0.1, 0.9, 0.0, -0.8, 0.2, 0.95, /* … */ });

// Frequency-range scan
PeriodogramResult ls = LombScarglePeriodogram.Compute(
times, values,
minFrequency: 0.01, maxFrequency: 1.0, numFrequencies: 1000);

double bestFreq = ls.BestFrequency; // strongest spectral peak
double bestPeriod = ls.BestPeriod; // 1 / bestFreq
double bestPower = ls.BestPower;

// Period-range scan (linearly spaced in period)
PeriodogramResult lsP = LombScarglePeriodogram.ComputeByPeriod(
times, values, minPeriod: 1, maxPeriod: 50, numPeriods: 500);

// False-alarm probability
double fap = LombScarglePeriodogram.FalseAlarmProbability(
bestPower, numObservations: times.Length, numFrequencies: 1000);

📦 Box-fitting Least Squares (BLS)

Transit detection via the Kovács, Zucker & Mazeh (2002) algorithm. Phase-folds the light curve at each trial period and searches for the optimal box-shaped dip.

// Search for transits between 2 and 20 days
BLSResult bls = BoxFittingLeastSquares.Compute(
times, flux,
minPeriod: 2, maxPeriod: 20, numPeriods: 500,
minDurationFraction: 0.01, maxDurationFraction: 0.15, numBins: 200);

double period = bls.BestPeriod;
double epoch = bls.BestEpoch; // mid-transit time
double depth = bls.BestDepth; // flux decrement
double durFrac = bls.BestDurationFraction;

// Or supply explicit trial periods
var trialPeriods = new VectorN(new[] { 3.0, 5.0, 7.0, 10.0 });
BLSResult bls2 = BoxFittingLeastSquares.Compute(times, flux, trialPeriods);

🔄 Phase Folding

Fold a time series onto a single cycle of a trial period — essential for visualising periodic signals.

// Raw fold (sorted by phase)
var (phases, vals) = PhaseFolding.Fold(times, values, period: 12.5, epoch: 0);

// Binned fold (average per bin)
var (binCenters, binMeans) = PhaseFolding.FoldAndBin(
times, values, period: 12.5, numBins: 20);

// Binned fold with per-bin standard deviations
var (centers, means, stds) = PhaseFolding.FoldAndBinWithErrors(
times, values, period: 12.5, numBins: 20);

📐 Trend Fitting / Detrending

Remove systematic trends before period analysis. All methods return both the detrended signal and the estimated trend.

// Polynomial detrending (uses LeastSquaresFitter internally)
var (detrended, trend) = TimeSeriesDetrending.PolynomialDetrend(times, values, degree: 2);

// Moving-average (centred window, handles edges)
var (det, ma) = TimeSeriesDetrending.MovingAverageDetrend(values, windowSize: 15);

// Median filter (robust to outliers)
var (det2, med) = TimeSeriesDetrending.MedianFilterDetrend(values, windowSize: 15);

// Savitzky–Golay smoothing (local polynomial least squares)
var (det3, sg) = TimeSeriesDetrending.SavitzkyGolayDetrend(values, windowSize: 11, polyOrder: 3);

// Differencing (output length = n − order)
VectorN diff1 = TimeSeriesDetrending.Difference(values, order: 1);
VectorN diff2 = TimeSeriesDetrending.Difference(values, order: 2);

⛰️ Peak / Event Fitting

Detect and fit peaks (or valleys) in a time series with Gaussian or Lorentzian profiles via Levenberg-Marquardt.

// Find peaks with prominence and distance filtering
List<int> peaks = PeakFitting.FindPeaks(values, prominence: 0.5, minDistance: 5);
List<int> dips = PeakFitting.FindValleys(values, prominence: 0.3);

// Fit a Gaussian to a region around a known peak
PeakResult g = PeakFitting.FitGaussian(xRegion, yRegion,
centerEstimate: 10.0, widthEstimate: 2.0);
// g.Center, g.Amplitude, g.Width, g.Baseline, g.RSquared

// Fit a Lorentzian
PeakResult lor = PeakFitting.FitLorentzian(xRegion, yRegion,
centerEstimate: 10.0, widthEstimate: 2.0);

// Find and fit all peaks automatically
List<PeakResult> fitted = PeakFitting.FindAndFitPeaks(
x, y, prominence: 0.5, shape: PeakShape.Gaussian, fitRadius: 15);
PropertyDescription
CenterFitted peak centre (x-axis)
AmplitudeFitted height above baseline
Widthσ (Gaussian) or γ (Lorentzian)
BaselineConstant offset
ShapePeakShape.Gaussian or PeakShape.Lorentzian
RSquaredGoodness-of-fit for the peak region
PeakIndexIndex in the original series

🎰 False Alarm Probability

Estimate the statistical significance of periodogram peaks using analytical (Baluev 2008) or bootstrap methods.

using CSharpNumerics.Statistics.TimeSeriesAnalysis;

// Analytical FAP (fast) — Baluev 2008 approximation
double fap = FalseAlarmProbability.AnalyticalFAP(
peakPower: 12.5, numFrequencies: 1000, numDataPoints: 500);
// fap ≈ 0.0001 → highly significant detection

// Bootstrap FAP (accurate, slower) — shuffles values and re-evaluates periodogram
double fapBoot = FalseAlarmProbability.BootstrapFAP(
times, values, peakPower: 12.5,
nBootstrap: 1000, minPeriod: 1.0, maxPeriod: 50.0,
numFrequencies: 500, seed: 42);

🪟 Sliding Window Statistics

Running (sliding window) descriptive statistics with centered windows. Edge windows are computed with fewer points. Even window sizes are incremented to the next odd value for symmetric centering.

using CSharpNumerics.Statistics.TimeSeriesAnalysis;

var data = new[] { 1.0, 3.0, 5.0, 7.0, 2.0, 4.0, 6.0 };

double[] mean = SlidingWindowStatistics.RunningMean(data, windowSize: 3);
double[] std = SlidingWindowStatistics.RunningStd(data, windowSize: 3);
double[] mad = SlidingWindowStatistics.RunningMAD(data, windowSize: 5);
double[] median = SlidingWindowStatistics.RunningMedian(data, windowSize: 3);

// All outputs have the same length as the input array
// MAD × 1.4826 ≈ standard deviation for normally distributed data