/** * Compute a loess fit on the data at the original abscissae. * * @param xval the arguments for the interpolation points * @param yval the values for the interpolation points * @return values of the loess fit at corresponding original abscissae * @throws MathException if some of the following conditions are false: * <ul> * <li> Arguments and values are of the same size that is greater than zero</li> * <li> The arguments are in a strictly increasing order</li> * <li> All arguments and values are finite real numbers</li> * </ul> */ public double[] Smooth(double[] xval, double[] yval, CustomCancellationToken token) { if (xval.Length != yval.Length) { throw new ArgumentException(@"Array lengths must match"); } double[] unitWeights = Enumerable.Repeat(1.0, xval.Length).ToArray(); return(Smooth(xval, yval, unitWeights, token)); }
/** * Compute a weighted loess fit on the data at the original abscissae. * * @param xval the arguments for the interpolation points * @param yval the values for the interpolation points * @param weights point weights: coefficients by which the robustness weight of a point is multiplied * @return values of the loess fit at corresponding original abscissae * @throws MathException if some of the following conditions are false: * <ul> * <li> Arguments and values are of the same size that is greater than zero</li> * <li> The arguments are in a strictly increasing order</li> * <li> All arguments and values are finite real numbers</li> * </ul> * @since 2.1 */ public double[] Smooth(double[] xval, double[] yval, double[] weights, CustomCancellationToken token) { if (xval.Length != yval.Length) { throw new ArgumentException(@"Mismatched array lengths"); } int n = xval.Length; if (n == 0) { throw new ArgumentException(@"Must have at least one point"); } CheckAllFiniteReal(xval); CheckAllFiniteReal(yval); CheckAllFiniteReal(weights); CheckNotDecreasing(xval); if (n == 1) { return(new[] { yval[0] }); } if (n == 2) { return(new[] { yval[0], yval[1] }); } int bandwidthInPoints = (int)(_bandwidth * n); if (bandwidthInPoints < 2) { throw new ArgumentException(@"Bandwidth too small"); } double[] res = new double[n]; double[] residuals = new double[n]; double[] sortedResiduals = new double[n]; // Do an initial fit and 'robustnessIters' robustness iterations. // This is equivalent to doing 'robustnessIters+1' robustness iterations // starting with all robustness weights set to 1. double[] robustnessWeights = Enumerable.Repeat(1.0, n).ToArray(); for (int iter = 0; iter <= _robustnessIters; ++iter) { int[] bandwidthInterval = { 0, bandwidthInPoints - 1 }; // At each x, compute a local weighted linear regression for (int i = 0; i < n; ++i) { ThreadingHelper.CheckCanceled(token); double x = xval[i]; // Find out the interval of source points on which // a regression is to be made. if (i > 0) { UpdateBandwidthInterval(xval, weights, i, bandwidthInterval); } int ileft = bandwidthInterval[0]; int iright = bandwidthInterval[1]; // Compute the point of the bandwidth interval that is // farthest from x int edge = xval[i] - xval[ileft] > xval[iright] - xval[i] ? ileft : iright; // Compute a least-squares linear fit weighted by // the product of robustness weights and the tricube // weight function. // See http://en.wikipedia.org/wiki/Linear_regression // (section "Univariate linear case") // and http://en.wikipedia.org/wiki/Weighted_least_squares // (section "Weighted least squares") double sumWeights = 0; double sumX = 0; double sumXSquared = 0; double sumY = 0; double sumXy = 0; double denom = Math.Abs(1.0 / (xval[edge] - x)); for (int k = ileft; k <= iright; ++k) { double xk = xval[k]; double yk = yval[k]; double dist = (k < i) ? x - xk : xk - x; double w = Tricube(dist * denom) * robustnessWeights[k] * weights[k]; double xkw = xk * w; sumWeights += w; sumX += xkw; sumXSquared += xk * xkw; sumY += yk * w; sumXy += yk * xkw; } double meanX = sumX / sumWeights; double meanY = sumY / sumWeights; double meanXy = sumXy / sumWeights; double meanXSquared = sumXSquared / sumWeights; double beta; if (Math.Sqrt(Math.Abs(meanXSquared - meanX * meanX)) < _accuracy) { beta = 0; } else { beta = (meanXy - meanX * meanY) / (meanXSquared - meanX * meanX); } double alpha = meanY - beta * meanX; res[i] = beta * x + alpha; residuals[i] = Math.Abs(yval[i] - res[i]); } // No need to recompute the robustness weights at the last // iteration, they won't be needed anymore if (iter == _robustnessIters) { break; } // Recompute the robustness weights. // Find the median residual. // An arraycopy and a sort are completely tractable here, // because the preceding loop is a lot more expensive Array.Copy(residuals, 0, sortedResiduals, 0, n); Array.Sort(sortedResiduals); double medianResidual = sortedResiduals[n / 2]; if (Math.Abs(medianResidual) < _accuracy) { break; } for (int i = 0; i < n; ++i) { double arg = residuals[i] / (6 * medianResidual); if (arg >= 1) { robustnessWeights[i] = 0; } else { double w = 1 - arg * arg; robustnessWeights[i] = w * w; } } } return(res); }