toolkit/javascript/d3/lib/science/science.stats.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 (function(){science.stats = {};
       
     2 // Bandwidth selectors for Gaussian kernels.
       
     3 // Based on R's implementations in `stats.bw`.
       
     4 science.stats.bandwidth = {
       
     5 
       
     6   // Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
       
     7   nrd0: function(x) {
       
     8     var hi = Math.sqrt(science.stats.variance(x));
       
     9     if (!(lo = Math.min(hi, science.stats.iqr(x) / 1.34)))
       
    10       (lo = hi) || (lo = Math.abs(x[1])) || (lo = 1);
       
    11     return .9 * lo * Math.pow(x.length, -.2);
       
    12   },
       
    13 
       
    14   // Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and
       
    15   // Visualization. Wiley.
       
    16   nrd: function(x) {
       
    17     var h = science.stats.iqr(x) / 1.34;
       
    18     return 1.06 * Math.min(Math.sqrt(science.stats.variance(x)), h)
       
    19       * Math.pow(x.length, -1/5);
       
    20   }
       
    21 };
       
    22 science.stats.distance = {
       
    23   euclidean: function(a, b) {
       
    24     var n = a.length,
       
    25         i = -1,
       
    26         s = 0,
       
    27         x;
       
    28     while (++i < n) {
       
    29       x = a[i] - b[i];
       
    30       s += x * x;
       
    31     }
       
    32     return Math.sqrt(s);
       
    33   },
       
    34   manhattan: function(a, b) {
       
    35     var n = a.length,
       
    36         i = -1,
       
    37         s = 0;
       
    38     while (++i < n) s += Math.abs(a[i] - b[i]);
       
    39     return s;
       
    40   },
       
    41   minkowski: function(p) {
       
    42     return function(a, b) {
       
    43       var n = a.length,
       
    44           i = -1,
       
    45           s = 0;
       
    46       while (++i < n) s += Math.pow(Math.abs(a[i] - b[i]), p);
       
    47       return Math.pow(s, 1 / p);
       
    48     };
       
    49   },
       
    50   chebyshev: function(a, b) {
       
    51     var n = a.length,
       
    52         i = -1,
       
    53         max = 0,
       
    54         x;
       
    55     while (++i < n) {
       
    56       x = Math.abs(a[i] - b[i]);
       
    57       if (x > max) max = x;
       
    58     }
       
    59     return max;
       
    60   },
       
    61   hamming: function(a, b) {
       
    62     var n = a.length,
       
    63         i = -1,
       
    64         d = 0;
       
    65     while (++i < n) if (a[i] !== b[i]) d++;
       
    66     return d;
       
    67   },
       
    68   jaccard: function(a, b) {
       
    69     var n = a.length,
       
    70         i = -1,
       
    71         s = 0;
       
    72     while (++i < n) if (a[i] === b[i]) s++;
       
    73     return s / n;
       
    74   },
       
    75   braycurtis: function(a, b) {
       
    76     var n = a.length,
       
    77         i = -1,
       
    78         s0 = 0,
       
    79         s1 = 0,
       
    80         ai,
       
    81         bi;
       
    82     while (++i < n) {
       
    83       ai = a[i];
       
    84       bi = b[i];
       
    85       s0 += Math.abs(ai - bi);
       
    86       s1 += Math.abs(ai + bi);
       
    87     }
       
    88     return s0 / s1;
       
    89   }
       
    90 };
       
    91 // Based on implementation in http://picomath.org/.
       
    92 science.stats.erf = function(x) {
       
    93   var a1 =  0.254829592,
       
    94       a2 = -0.284496736,
       
    95       a3 =  1.421413741,
       
    96       a4 = -1.453152027,
       
    97       a5 =  1.061405429,
       
    98       p  =  0.3275911;
       
    99 
       
   100   // Save the sign of x
       
   101   var sign = x < 0 ? -1 : 1;
       
   102   if (x < 0) {
       
   103     sign = -1;
       
   104     x = -x;
       
   105   }
       
   106 
       
   107   // A&S formula 7.1.26
       
   108   var t = 1 / (1 + p * x);
       
   109   return sign * (
       
   110     1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1)
       
   111     * t * Math.exp(-x * x));
       
   112 };
       
   113 science.stats.phi = function(x) {
       
   114   return .5 * (1 + science.stats.erf(x / Math.SQRT2));
       
   115 };
       
   116 // See <http://en.wikipedia.org/wiki/Kernel_(statistics)>.
       
   117 science.stats.kernel = {
       
   118   uniform: function(u) {
       
   119     if (u <= 1 && u >= -1) return .5;
       
   120     return 0;
       
   121   },
       
   122   triangular: function(u) {
       
   123     if (u <= 1 && u >= -1) return 1 - Math.abs(u);
       
   124     return 0;
       
   125   },
       
   126   epanechnikov: function(u) {
       
   127     if (u <= 1 && u >= -1) return .75 * (1 - u * u);
       
   128     return 0;
       
   129   },
       
   130   quartic: function(u) {
       
   131     if (u <= 1 && u >= -1) {
       
   132       var tmp = 1 - u * u;
       
   133       return (15 / 16) * tmp * tmp;
       
   134     }
       
   135     return 0;
       
   136   },
       
   137   triweight: function(u) {
       
   138     if (u <= 1 && u >= -1) {
       
   139       var tmp = 1 - u * u;
       
   140       return (35 / 32) * tmp * tmp * tmp;
       
   141     }
       
   142     return 0;
       
   143   },
       
   144   gaussian: function(u) {
       
   145     return 1 / Math.sqrt(2 * Math.PI) * Math.exp(-.5 * u * u);
       
   146   },
       
   147   cosine: function(u) {
       
   148     if (u <= 1 && u >= -1) return Math.PI / 4 * Math.cos(Math.PI / 2 * u);
       
   149     return 0;
       
   150   }
       
   151 };
       
   152 // http://exploringdata.net/den_trac.htm
       
   153 science.stats.kde = function() {
       
   154   var kernel = science.stats.kernel.gaussian,
       
   155       sample = [],
       
   156       bandwidth = science.stats.bandwidth.nrd;
       
   157 
       
   158   function kde(points, i) {
       
   159     var bw = bandwidth.call(this, sample);
       
   160     return points.map(function(x) {
       
   161       var i = -1,
       
   162           y = 0,
       
   163           n = sample.length;
       
   164       while (++i < n) {
       
   165         y += kernel((x - sample[i]) / bw);
       
   166       }
       
   167       return [x, y / bw / n];
       
   168     });
       
   169   }
       
   170 
       
   171   kde.kernel = function(x) {
       
   172     if (!arguments.length) return kernel;
       
   173     kernel = x;
       
   174     return kde;
       
   175   };
       
   176 
       
   177   kde.sample = function(x) {
       
   178     if (!arguments.length) return sample;
       
   179     sample = x;
       
   180     return kde;
       
   181   };
       
   182 
       
   183   kde.bandwidth = function(x) {
       
   184     if (!arguments.length) return bandwidth;
       
   185     bandwidth = science.functor(x);
       
   186     return kde;
       
   187   };
       
   188 
       
   189   return kde;
       
   190 };
       
   191 // Based on figue implementation by Jean-Yves Delort.
       
   192 // http://code.google.com/p/figue/
       
   193 science.stats.kmeans = function() {
       
   194   var distance = science.stats.distance.euclidean,
       
   195       maxIterations = 1000,
       
   196       k = 1;
       
   197 
       
   198   function kmeans(vectors) {
       
   199     var n = vectors.length,
       
   200         assignments = [],
       
   201         clusterSizes = [],
       
   202         repeat = 1,
       
   203         iterations = 0,
       
   204         centroids = science_stats_kmeansRandom(k, vectors),
       
   205         newCentroids,
       
   206         i,
       
   207         j,
       
   208         x,
       
   209         d,
       
   210         min,
       
   211         best;
       
   212 
       
   213     while (repeat && iterations < maxIterations) {
       
   214       // Assignment step.
       
   215       j = -1; while (++j < k) {
       
   216         clusterSizes[j] = 0;
       
   217       }
       
   218 
       
   219       i = -1; while (++i < n) {
       
   220         x = vectors[i];
       
   221         min = Infinity;
       
   222         j = -1; while (++j < k) {
       
   223           d = distance.call(this, centroids[j], x);
       
   224           if (d < min) {
       
   225             min = d;
       
   226             best = j;
       
   227           }
       
   228         }
       
   229         clusterSizes[assignments[i] = best]++;
       
   230       }
       
   231 
       
   232       // Update centroids step.
       
   233       newCentroids = [];
       
   234       i = -1; while (++i < n) {
       
   235         x = assignments[i];
       
   236         d = newCentroids[x];
       
   237         if (d == null) newCentroids[x] = vectors[i].slice();
       
   238         else {
       
   239           j = -1; while (++j < d.length) {
       
   240             d[j] += vectors[i][j];
       
   241           }
       
   242         }
       
   243       }
       
   244       j = -1; while (++j < k) {
       
   245         x = newCentroids[j];
       
   246         d = 1 / clusterSizes[j];
       
   247         i = -1; while (++i < x.length) x[i] *= d;
       
   248       }
       
   249 
       
   250       // Check convergence.
       
   251       repeat = 0;
       
   252       j = -1; while (++j < k) {
       
   253         if (!science_stats_kmeansCompare(newCentroids[j], centroids[j])) {
       
   254           repeat = 1;
       
   255           break;
       
   256         }
       
   257       }
       
   258       centroids = newCentroids;
       
   259       iterations++;
       
   260     }
       
   261     return {assignments: assignments, centroids: centroids};
       
   262   }
       
   263 
       
   264   kmeans.k = function(x) {
       
   265     if (!arguments.length) return k;
       
   266     k = x;
       
   267     return kmeans;
       
   268   };
       
   269 
       
   270   kmeans.distance = function(x) {
       
   271     if (!arguments.length) return distance;
       
   272     distance = x;
       
   273     return kmeans;
       
   274   };
       
   275 
       
   276   return kmeans;
       
   277 };
       
   278 
       
   279 function science_stats_kmeansCompare(a, b) {
       
   280   if (!a || !b || a.length !== b.length) return false;
       
   281   var n = a.length,
       
   282       i = -1;
       
   283   while (++i < n) if (a[i] !== b[i]) return false;
       
   284   return true;
       
   285 }
       
   286 
       
   287 // Returns an array of k distinct vectors randomly selected from the input
       
   288 // array of vectors. Returns null if k > n or if there are less than k distinct
       
   289 // objects in vectors.
       
   290 function science_stats_kmeansRandom(k, vectors) {
       
   291   var n = vectors.length;
       
   292   if (k > n) return null;
       
   293   
       
   294   var selected_vectors = [];
       
   295   var selected_indices = [];
       
   296   var tested_indices = {};
       
   297   var tested = 0;
       
   298   var selected = 0;
       
   299   var i,
       
   300       vector,
       
   301       select;
       
   302 
       
   303   while (selected < k) {
       
   304     if (tested === n) return null;
       
   305     
       
   306     var random_index = Math.floor(Math.random() * n);
       
   307     if (random_index in tested_indices) continue;
       
   308     
       
   309     tested_indices[random_index] = 1;
       
   310     tested++;
       
   311     vector = vectors[random_index];
       
   312     select = true;
       
   313     for (i = 0; i < selected; i++) {
       
   314       if (science_stats_kmeansCompare(vector, selected_vectors[i])) {
       
   315         select = false;
       
   316         break;
       
   317       }
       
   318     }
       
   319     if (select) {
       
   320       selected_vectors[selected] = vector;
       
   321       selected_indices[selected] = random_index;
       
   322       selected++;
       
   323     }
       
   324   }
       
   325   return selected_vectors;
       
   326 }
       
   327 science.stats.hcluster = function() {
       
   328   var distance = science.stats.distance.euclidean,
       
   329       linkage = "simple"; // simple, complete or average
       
   330 
       
   331   function hcluster(vectors) {
       
   332     var n = vectors.length,
       
   333         dMin = [],
       
   334         cSize = [],
       
   335         distMatrix = [],
       
   336         clusters = [],
       
   337         c1,
       
   338         c2,
       
   339         c1Cluster,
       
   340         c2Cluster,
       
   341         p,
       
   342         root,
       
   343         i,
       
   344         j;
       
   345 
       
   346     // Initialise distance matrix and vector of closest clusters.
       
   347     i = -1; while (++i < n) {
       
   348       dMin[i] = 0;
       
   349       distMatrix[i] = [];
       
   350       j = -1; while (++j < n) {
       
   351         distMatrix[i][j] = i === j ? Infinity : distance(vectors[i] , vectors[j]);
       
   352         if (distMatrix[i][dMin[i]] > distMatrix[i][j]) dMin[i] = j;
       
   353       }
       
   354     }
       
   355 
       
   356     // create leaves of the tree
       
   357     i = -1; while (++i < n) {
       
   358       clusters[i] = [];
       
   359       clusters[i][0] = {
       
   360         left: null,
       
   361         right: null,
       
   362         dist: 0,
       
   363         centroid: vectors[i],
       
   364         size: 1,
       
   365         depth: 0
       
   366       };
       
   367       cSize[i] = 1;
       
   368     }
       
   369 
       
   370     // Main loop
       
   371     for (p = 0; p < n-1; p++) {
       
   372       // find the closest pair of clusters
       
   373       c1 = 0;
       
   374       for (i = 0; i < n; i++) {
       
   375         if (distMatrix[i][dMin[i]] < distMatrix[c1][dMin[c1]]) c1 = i;
       
   376       }
       
   377       c2 = dMin[c1];
       
   378 
       
   379       // create node to store cluster info 
       
   380       c1Cluster = clusters[c1][0];
       
   381       c2Cluster = clusters[c2][0];
       
   382 
       
   383       newCluster = {
       
   384         left: c1Cluster,
       
   385         right: c2Cluster,
       
   386         dist: distMatrix[c1][c2],
       
   387         centroid: calculateCentroid(c1Cluster.size, c1Cluster.centroid,
       
   388           c2Cluster.size, c2Cluster.centroid),
       
   389         size: c1Cluster.size + c2Cluster.size,
       
   390         depth: 1 + Math.max(c1Cluster.depth, c2Cluster.depth)
       
   391       };
       
   392       clusters[c1].splice(0, 0, newCluster);
       
   393       cSize[c1] += cSize[c2];
       
   394 
       
   395       // overwrite row c1 with respect to the linkage type
       
   396       for (j = 0; j < n; j++) {
       
   397         switch (linkage) {
       
   398           case "single":
       
   399             if (distMatrix[c1][j] > distMatrix[c2][j])
       
   400               distMatrix[j][c1] = distMatrix[c1][j] = distMatrix[c2][j];
       
   401             break;
       
   402           case "complete":
       
   403             if (distMatrix[c1][j] < distMatrix[c2][j])
       
   404               distMatrix[j][c1] = distMatrix[c1][j] = distMatrix[c2][j];
       
   405             break;
       
   406           case "average":
       
   407             distMatrix[j][c1] = distMatrix[c1][j] = (cSize[c1] * distMatrix[c1][j] + cSize[c2] * distMatrix[c2][j]) / (cSize[c1] + cSize[j]);
       
   408             break;
       
   409         }
       
   410       }
       
   411       distMatrix[c1][c1] = Infinity;
       
   412 
       
   413       // infinity ­out old row c2 and column c2
       
   414       for (i = 0; i < n; i++)
       
   415         distMatrix[i][c2] = distMatrix[c2][i] = Infinity;
       
   416 
       
   417       // update dmin and replace ones that previous pointed to c2 to point to c1
       
   418       for (j = 0; j < n; j++) {
       
   419         if (dMin[j] == c2) dMin[j] = c1;
       
   420         if (distMatrix[c1][j] < distMatrix[c1][dMin[c1]]) dMin[c1] = j;
       
   421       }
       
   422 
       
   423       // keep track of the last added cluster
       
   424       root = newCluster;
       
   425     }
       
   426 
       
   427     return root;
       
   428   }
       
   429 
       
   430   hcluster.distance = function(x) {
       
   431     if (!arguments.length) return distance;
       
   432     distance = x;
       
   433     return hcluster;
       
   434   };
       
   435 
       
   436   return hcluster;
       
   437 };
       
   438 
       
   439 function calculateCentroid(c1Size, c1Centroid, c2Size, c2Centroid) {
       
   440   var newCentroid = [],
       
   441       newSize = c1Size + c2Size,
       
   442       n = c1Centroid.length,
       
   443       i = -1;
       
   444   while (++i < n) {
       
   445     newCentroid[i] = (c1Size * c1Centroid[i] + c2Size * c2Centroid[i]) / newSize;
       
   446   }
       
   447   return newCentroid;
       
   448 }
       
   449 science.stats.iqr = function(x) {
       
   450   var quartiles = science.stats.quantiles(x, [.25, .75]);
       
   451   return quartiles[1] - quartiles[0];
       
   452 };
       
   453 // Based on org.apache.commons.math.analysis.interpolation.LoessInterpolator
       
   454 // from http://commons.apache.org/math/
       
   455 science.stats.loess = function() {    
       
   456   var bandwidth = .3,
       
   457       robustnessIters = 2,
       
   458       accuracy = 1e-12;
       
   459 
       
   460   function smooth(xval, yval, weights) {
       
   461     var n = xval.length,
       
   462         i;
       
   463 
       
   464     if (n !== yval.length) throw {error: "Mismatched array lengths"};
       
   465     if (n == 0) throw {error: "At least one point required."};
       
   466 
       
   467     if (arguments.length < 3) {
       
   468       weights = [];
       
   469       i = -1; while (++i < n) weights[i] = 1;
       
   470     }
       
   471 
       
   472     science_stats_loessFiniteReal(xval);
       
   473     science_stats_loessFiniteReal(yval);
       
   474     science_stats_loessFiniteReal(weights);
       
   475     science_stats_loessStrictlyIncreasing(xval);
       
   476 
       
   477     if (n == 1) return [yval[0]];
       
   478     if (n == 2) return [yval[0], yval[1]];
       
   479 
       
   480     var bandwidthInPoints = Math.floor(bandwidth * n);
       
   481 
       
   482     if (bandwidthInPoints < 2) throw {error: "Bandwidth too small."};
       
   483 
       
   484     var res = [],
       
   485         residuals = [],
       
   486         robustnessWeights = [];
       
   487 
       
   488     // Do an initial fit and 'robustnessIters' robustness iterations.
       
   489     // This is equivalent to doing 'robustnessIters+1' robustness iterations
       
   490     // starting with all robustness weights set to 1.
       
   491     i = -1; while (++i < n) {
       
   492       res[i] = 0;
       
   493       residuals[i] = 0;
       
   494       robustnessWeights[i] = 1;
       
   495     }
       
   496 
       
   497     var iter = -1;
       
   498     while (++iter <= robustnessIters) {
       
   499       var bandwidthInterval = [0, bandwidthInPoints - 1];
       
   500       // At each x, compute a local weighted linear regression
       
   501       var x;
       
   502       i = -1; while (++i < n) {
       
   503         x = xval[i];
       
   504 
       
   505         // Find out the interval of source points on which
       
   506         // a regression is to be made.
       
   507         if (i > 0) {
       
   508           science_stats_loessUpdateBandwidthInterval(xval, weights, i, bandwidthInterval);
       
   509         }
       
   510 
       
   511         var ileft = bandwidthInterval[0],
       
   512             iright = bandwidthInterval[1];
       
   513 
       
   514         // Compute the point of the bandwidth interval that is
       
   515         // farthest from x
       
   516         var edge = (xval[i] - xval[ileft]) > (xval[iright] - xval[i]) ? ileft : iright;
       
   517 
       
   518         // Compute a least-squares linear fit weighted by
       
   519         // the product of robustness weights and the tricube
       
   520         // weight function.
       
   521         // See http://en.wikipedia.org/wiki/Linear_regression
       
   522         // (section "Univariate linear case")
       
   523         // and http://en.wikipedia.org/wiki/Weighted_least_squares
       
   524         // (section "Weighted least squares")
       
   525         var sumWeights = 0,
       
   526             sumX = 0,
       
   527             sumXSquared = 0,
       
   528             sumY = 0,
       
   529             sumXY = 0,
       
   530             denom = Math.abs(1 / (xval[edge] - x));
       
   531 
       
   532         for (var k = ileft; k <= iright; ++k) {
       
   533           var xk   = xval[k],
       
   534               yk   = yval[k],
       
   535               dist = k < i ? x - xk : xk - x,
       
   536               w    = science_stats_loessTricube(dist * denom) * robustnessWeights[k] * weights[k],
       
   537               xkw  = xk * w;
       
   538           sumWeights += w;
       
   539           sumX += xkw;
       
   540           sumXSquared += xk * xkw;
       
   541           sumY += yk * w;
       
   542           sumXY += yk * xkw;
       
   543         }
       
   544 
       
   545         var meanX = sumX / sumWeights,
       
   546             meanY = sumY / sumWeights,
       
   547             meanXY = sumXY / sumWeights,
       
   548             meanXSquared = sumXSquared / sumWeights;
       
   549 
       
   550         var beta = (Math.sqrt(Math.abs(meanXSquared - meanX * meanX)) < accuracy)
       
   551             ? 0 : ((meanXY - meanX * meanY) / (meanXSquared - meanX * meanX));
       
   552 
       
   553         var alpha = meanY - beta * meanX;
       
   554 
       
   555         res[i] = beta * x + alpha;
       
   556         residuals[i] = Math.abs(yval[i] - res[i]);
       
   557       }
       
   558 
       
   559       // No need to recompute the robustness weights at the last
       
   560       // iteration, they won't be needed anymore
       
   561       if (iter === robustnessIters) {
       
   562         break;
       
   563       }
       
   564 
       
   565       // Recompute the robustness weights.
       
   566 
       
   567       // Find the median residual.
       
   568       var sortedResiduals = residuals.slice();
       
   569       sortedResiduals.sort();
       
   570       var medianResidual = sortedResiduals[Math.floor(n / 2)];
       
   571 
       
   572       if (Math.abs(medianResidual) < accuracy)
       
   573         break;
       
   574 
       
   575       var arg,
       
   576           w;
       
   577       i = -1; while (++i < n) {
       
   578         arg = residuals[i] / (6 * medianResidual);
       
   579         robustnessWeights[i] = (arg >= 1) ? 0 : ((w = 1 - arg * arg) * w);
       
   580       }
       
   581     }
       
   582 
       
   583     return res;
       
   584   }
       
   585 
       
   586   smooth.bandwidth = function(x) {
       
   587     if (!arguments.length) return x;
       
   588     bandwidth = x;
       
   589     return smooth;
       
   590   };
       
   591 
       
   592   smooth.robustnessIterations = function(x) {
       
   593     if (!arguments.length) return x;
       
   594     robustnessIters = x;
       
   595     return smooth;
       
   596   };
       
   597 
       
   598   smooth.accuracy = function(x) {
       
   599     if (!arguments.length) return x;
       
   600     accuracy = x;
       
   601     return smooth;
       
   602   };
       
   603 
       
   604   return smooth;
       
   605 };
       
   606 
       
   607 function science_stats_loessFiniteReal(values) {
       
   608   var n = values.length,
       
   609       i = -1;
       
   610 
       
   611   while (++i < n) if (!isFinite(values[i])) return false;
       
   612 
       
   613   return true;
       
   614 }
       
   615 
       
   616 function science_stats_loessStrictlyIncreasing(xval) {
       
   617   var n = xval.length,
       
   618       i = 0;
       
   619 
       
   620   while (++i < n) if (xval[i - 1] >= xval[i]) return false;
       
   621 
       
   622   return true;
       
   623 }
       
   624 
       
   625 // Compute the tricube weight function.
       
   626 // http://en.wikipedia.org/wiki/Local_regression#Weight_function
       
   627 function science_stats_loessTricube(x) {
       
   628   return (x = 1 - x * x * x) * x * x;
       
   629 }
       
   630 
       
   631 // Given an index interval into xval that embraces a certain number of
       
   632 // points closest to xval[i-1], update the interval so that it embraces
       
   633 // the same number of points closest to xval[i], ignoring zero weights.
       
   634 function science_stats_loessUpdateBandwidthInterval(
       
   635   xval, weights, i, bandwidthInterval) {
       
   636 
       
   637   var left = bandwidthInterval[0],
       
   638       right = bandwidthInterval[1];
       
   639 
       
   640   // The right edge should be adjusted if the next point to the right
       
   641   // is closer to xval[i] than the leftmost point of the current interval
       
   642   var nextRight = science_stats_loessNextNonzero(weights, right);
       
   643   if ((nextRight < xval.length) && (xval[nextRight] - xval[i]) < (xval[i] - xval[left])) {
       
   644     var nextLeft = science_stats_loessNextNonzero(weights, left);
       
   645     bandwidthInterval[0] = nextLeft;
       
   646     bandwidthInterval[1] = nextRight;
       
   647   }
       
   648 }
       
   649 
       
   650 function science_stats_loessNextNonzero(weights, i) {
       
   651   var j = i + 1;
       
   652   while (j < weights.length && weights[j] === 0) j++;
       
   653   return j;
       
   654 }
       
   655 // Welford's algorithm.
       
   656 science.stats.mean = function(x) {
       
   657   var n = x.length;
       
   658   if (n === 0) return NaN;
       
   659   var m = 0,
       
   660       i = -1;
       
   661   while (++i < n) m += (x[i] - m) / (i + 1);
       
   662   return m;
       
   663 };
       
   664 science.stats.median = function(x) {
       
   665   return science.stats.quantiles(x, [.5])[0];
       
   666 };
       
   667 science.stats.mode = function(x) {
       
   668   x = x.slice().sort(science.ascending);
       
   669   var mode,
       
   670       n = x.length,
       
   671       i = -1,
       
   672       l = i,
       
   673       last = null,
       
   674       max = 0,
       
   675       tmp,
       
   676       v;
       
   677   while (++i < n) {
       
   678     if ((v = x[i]) !== last) {
       
   679       if ((tmp = i - l) > max) {
       
   680         max = tmp;
       
   681         mode = last;
       
   682       }
       
   683       last = v;
       
   684       l = i;
       
   685     }
       
   686   }
       
   687   return mode;
       
   688 };
       
   689 // Uses R's quantile algorithm type=7.
       
   690 science.stats.quantiles = function(d, quantiles) {
       
   691   d = d.slice().sort(science.ascending);
       
   692   var n_1 = d.length - 1;
       
   693   return quantiles.map(function(q) {
       
   694     if (q === 0) return d[0];
       
   695     else if (q === 1) return d[n_1];
       
   696 
       
   697     var index = 1 + q * n_1,
       
   698         lo = Math.floor(index),
       
   699         h = index - lo,
       
   700         a = d[lo - 1];
       
   701 
       
   702     return h === 0 ? a : a + h * (d[lo] - a);
       
   703   });
       
   704 };
       
   705 // Unbiased estimate of a sample's variance.
       
   706 // Also known as the sample variance, where the denominator is n - 1.
       
   707 science.stats.variance = function(x) {
       
   708   var n = x.length;
       
   709   if (n < 1) return NaN;
       
   710   if (n === 1) return 0;
       
   711   var mean = science.stats.mean(x),
       
   712       i = -1,
       
   713       s = 0;
       
   714   while (++i < n) {
       
   715     var v = x[i] - mean;
       
   716     s += v * v;
       
   717   }
       
   718   return s / (n - 1);
       
   719 };
       
   720 })()