/** * Provides percentile computation. *

* There are several commonly used methods for estimating percentiles (a.k.a. * quantiles) based on sample data. For large samples, the different methods * agree closely, but when sample sizes are small, different methods will give * significantly different results. The algorithm implemented here works as * follows: *

    *
  1. Let n be the length of the (sorted) array and * 0 < p <= 100 be the desired percentile.
  2. *
  3. If n = 1 return the unique array element (regardless of * the value of p); otherwise
  4. *
  5. Compute the estimated percentile position * pos = p * (n + 1) / 100 and the difference, d * between pos and floor(pos) (i.e. the fractional * part of pos). If pos >= n return the largest * element in the array; otherwise
  6. *
  7. Let lower be the element in position * floor(pos) in the array and let upper be the * next element in the array. Return lower + d * (upper - lower)
  8. *
* * @param p Percentile between 0 and 100 */ morpheus.Percentile = function(vector, p, isSorted) { return morpheus.ArrayPercentile(morpheus.RemoveNaN(vector), p, isSorted); }; /** * @private * @ignore */ morpheus.RemoveNaN = function(values) { var array = []; for (var i = 0, size = values.size(); i < size; i++) { var value = values.getValue(i); if (!isNaN(value)) { array.push(value); } } return array; }; morpheus.Median = function(vector) { return morpheus.ArrayPercentile(morpheus.RemoveNaN(vector), 50, false); }; morpheus.Median.toString = function() { return 'Median'; }; /** * @ignore */ morpheus.ArrayPercentile = function(values, p, isSorted) { if (!isSorted) { values.sort(function(a, b) { return (a < b ? -1 : (a === b ? 0 : 1)); }); } return d3.quantile(values, p / 100); }; /** * @ignore */ morpheus.MaxPercentiles = function(percentiles) { var f = function(vector) { var values = []; for (var i = 0, size = vector.size(); i < size; i++) { var value = vector.getValue(i); if (!isNaN(value)) { values.push(value); } } if (values.length === 0) { return NaN; } values.sort(function(a, b) { return (a < b ? -1 : (a === b ? 0 : 1)); }); var max = 0; for (var i = 0; i < percentiles.length; i++) { var p = morpheus.ArrayPercentile(values, percentiles[i], true); if (Math.abs(p) > Math.abs(max)) { max = p; } } return max; }; f.toString = function() { var s = [ 'Maximum of ' ]; for (var i = 0, length = percentiles.length; i < length; i++) { if (i > 0 && length > 2) { s.push(', '); } if (i === length - 1) { s.push(length == 2 ? ' and ' : 'and '); } s.push(percentiles[i]); } s.push(' percentiles'); return s.join(''); }; return f; }; morpheus.Mean = function(vector) { var sum = 0; var count = 0; for (var i = 0, length = vector.size(); i < length; i++) { var val = vector.getValue(i); if (!isNaN(val)) { sum += val; count++; } } return count === 0 ? NaN : sum / count; }; morpheus.Mean.toString = function() { return 'Mean'; }; morpheus.Sum = function(vector) { var sum = 0; var found = false; for (var i = 0, length = vector.size(); i < length; i++) { var val = vector.getValue(i); if (!isNaN(val)) { found = true; sum += val; } } return !found ? NaN : sum; }; morpheus.Sum.toString = function() { return 'Sum'; }; morpheus.CountNonNaN = function(vector) { var count = 0; for (var i = 0, length = vector.size(); i < length; i++) { var val = vector.getValue(i); if (!isNaN(val)) { count++; } } return count; }; morpheus.CountNonNaN.toString = function() { return 'Count non-NaN'; }; morpheus.Max = function(vector) { var max = -Number.MAX_VALUE; var found = false; for (var i = 0, length = vector.size(); i < length; i++) { var val = vector.getValue(i); if (!isNaN(val)) { found = true; max = Math.max(max, val); } } return !found ? NaN : max; }; morpheus.Max.toString = function() { return 'Max'; }; morpheus.Min = function(vector) { var min = Number.MAX_VALUE; var found = false; for (var i = 0, length = vector.size(); i < length; i++) { var val = vector.getValue(i); if (!isNaN(val)) { found = true; min = Math.min(min, val); } } return !found ? NaN : min; }; morpheus.Min.toString = function() { return 'Min'; }; morpheus.Variance = function(list, mean) { if (mean == undefined) { mean = morpheus.Mean(list); } var sum = 0; var n = 0; for (var j = 0, size = list.size(); j < size; j++) { var x = list.getValue(j); if (!isNaN(x)) { var diff = x - mean; diff = diff * diff; sum += diff; n++; } } if (n <= 1) { return NaN; } n = n - 1; if (n < 1) { n = 1; } var variance = sum / n; return variance; }; var LOG_10 = Math.log(10); morpheus.Log10 = function(x) { return x <= 0 ? 0 : Math.log(x) / LOG_10; }; var LOG_2 = Math.log(2); morpheus.Log2 = function(x) { return x <= 0 ? 0 : Math.log(x) / LOG_2; }; /** * Computes the False Discovery Rate using the BH procedure. * * @param nominalPValues * Array of nominal p-values. */ morpheus.FDR_BH = function(nominalPValues) { var size = nominalPValues.length; var fdr = []; var pValueIndices = morpheus.Util.indexSort(nominalPValues, true); var ranks = morpheus.Util.rankIndexArray(pValueIndices); // check for ties for (var i = pValueIndices.length - 1; i > 0; i--) { var bigPValue = nominalPValues[pValueIndices[i]]; var smallPValue = nominalPValues[pValueIndices[i - 1]]; if (bigPValue == smallPValue) { ranks[pValueIndices[i - 1]] = ranks[pValueIndices[i]]; } } for (var i = 0; i < size; i++) { var rank = ranks[i]; var p = nominalPValues[i]; fdr[i] = (p * size) / rank; } // ensure fdr is monotonically decreasing var pIndices = morpheus.Util.indexSort(nominalPValues, false); for (var i = 0; i < pIndices.length - 1; i++) { var highIndex = pIndices[i]; var lowIndex = pIndices[i + 1]; fdr[lowIndex] = Math.min(fdr[lowIndex], fdr[highIndex]); } for (var i = 0; i < size; i++) { fdr[i] = Math.min(fdr[i], 1); } return fdr; }; morpheus.FDR_BH.tString = function() { return 'FDR(BH)'; }; morpheus.Variance.toString = function() { return 'Variance'; }; morpheus.MAD = function(list, median) { if (median == null) { median = morpheus.Percentile(list, 50); } var temp = []; for (var j = 0, size = list.size(); j < size; j++) { var value = list.getValue(j); if (!isNaN(value)) { temp.push(Math.abs(value - median)); } } var r = morpheus.Percentile(new morpheus.Vector('', temp.length) .setArray(temp), 50); return 1.4826 * r; }; morpheus.MAD.toString = function() { return 'Median absolute deviation'; }; morpheus.CV = function(list) { var mean = morpheus.Mean(list); var stdev = Math.sqrt(morpheus.Variance(list, mean)); return stdev / mean; }; morpheus.CV.toString = function() { return 'Coefficient of variation'; }; morpheus.BoxPlotItem = function(list) { var values = morpheus.RemoveNaN(list); values.sort(function(a, b) { return (a === b ? 0 : (a < b ? -1 : 1)); }); if (values.length === 0) { return { median : NaN, q1 : NaN, q3 : NaN, lowerAdjacentValue : NaN, upperAdjacentValue : NaN }; } var median = morpheus.ArrayPercentile(values, 50, true); var q1 = morpheus.ArrayPercentile(values, 25, true); var q3 = morpheus.ArrayPercentile(values, 75, true); var w = 1.5; var upperAdjacentValue = -Number.MAX_VALUE; var lowerAdjacentValue = Number.MAX_VALUE; // The upper adjacent value (UAV) is the largest observation that is // less than or equal to // the upper inner fence (UIF), which is the third quartile plus // 1.5*IQR. // // The lower adjacent value (LAV) is the smallest observation that is // greater than or equal // to the lower inner fence (LIF), which is the first quartile minus // 1.5*IQR. var upperOutlier = q3 + w * (q3 - q1); var lowerOutlier = q1 - w * (q3 - q1); for (var i = 0, length = values.length; i < length; i++) { var value = values[i]; if (value <= upperOutlier) { upperAdjacentValue = Math.max(upperAdjacentValue, value); } if (value >= lowerOutlier) { lowerAdjacentValue = Math.min(lowerAdjacentValue, value); } // if (value > upperOutlier) { // upperOutliers.add(new Outlier(i, j, value)); // } // if (value < lowerOutlier) { // lowerOutliers.add(new Outlier(i, j, value)); // } } if (lowerAdjacentValue > q1) { lowerAdjacentValue = q1; } if (upperAdjacentValue < q3) { upperAdjacentValue = q3; } return { median : median, q1 : q1, // Lower Quartile q3 : q3, // Upper Quartile lowerAdjacentValue : lowerAdjacentValue, // Lower Whisker upperAdjacentValue : upperAdjacentValue // Upper Whisker }; };