Fraction of a span's variance attributable to its linear trend.
A cheap signal/noise split for a window: high trend-explained ratio means the window looks like real directed change, low means the window looks like jitter around a roughly fixed point.
271{
272 if (samples.size() < 2)
273 return 0.0;
274
275 const double first = samples.front();
276 const double last = samples.back();
277 const auto n = static_cast<double>(samples.size() - 1);
278
279 const double total_mean = std::accumulate(samples.begin(), samples.end(), 0.0) / static_cast<double>(samples.size());
280
281 double total_var = 0.0;
282 double residual_var = 0.0;
283
284 for (size_t i = 0; i < samples.size(); ++i) {
285 const double t = static_cast<double>(i) / n;
286 const double trend_value = first + t * (last - first);
287
288 const double total_dev = samples[i] - total_mean;
289 total_var += total_dev * total_dev;
290
291 const double residual = samples[i] - trend_value;
292 residual_var += residual * residual;
293 }
294
295 if (total_var < 1e-12)
296 return 0.0;
297
298 return std::clamp(1.0 - (residual_var / total_var), 0.0, 1.0);
299}