All files / src Aggregation.js

100% Statements 156/156
100% Branches 31/31
100% Functions 11/11
100% Lines 156/156

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 1801x   1x   1x 1x 1x 1x 1x 1x 11x 11x   11x 11x 11x 11x 11x 11x 11x 1x 1x 10x 11x 1x 1x 11x 1x 1x 8x   8x 11x   11x 11x 11x 11x 11x 11x 3x 3x   11x 11x 11x 11x 11x 11x 3x 3x   11x 11x 11x 11x 11x 11x 2x 2x   11x 11x 11x 11x 11x 11x 3x 3x 3x 12x 12x   3x 3x 3x   11x 11x 11x 11x 11x 11x 3x 3x 3x 3x   3x 3x 3x 2x 2x   1x 1x 3x   11x 11x 11x 11x 11x 11x 6x 6x   11x 11x 11x 11x 11x 11x 11x 1x 1x 1x 1x 1x 4x 4x   1x 1x 1x   11x 11x 11x 11x 11x 11x 1x 1x 1x 1x 4x 4x   1x 1x 1x   11x 11x 11x 11x 11x 11x 11x 8x 8x 8x 1x 1x 7x 8x 1x 1x   6x 6x   6x 6x 6x   6x 6x 6x   6x 8x   5x 5x 5x 8x 11x  
// src/Aggregation.js
 
import { convertToNumber } from './utils/conversions.js';
 
/**
 * This class is responsible for aggregating functions on a column.
 * It provides functions such as min, max, range, mean, median, count,
 * sample standard deviation, and population standard deviation.
 */
export class Aggregation {
  // Instance Variables
  column;
 
  /**
   * The class responsible for aggregating functions on a column
   * @param {Array} column The column to perform aggregation functions for
   */
  constructor(column) {
    // Check for if column is provided
    if (!column) {
      throw new TypeError('Column must be provided');
    }
    // Check for if column is an array of all numbers
    if (!Array.isArray(column)) {
      throw new TypeError('Column must be an array');
    }
    if (column.length == 0) {
      throw new TypeError('Column must not be empty');
    }
    column = column.map((value) => convertToNumber(value));
 
    this.column = column;
  }
 
  /**
   * The minimum value of the column
   * @function
   * @returns {number} The minimum value of the column
   */
  min() {
    return Math.min.apply(Math, this.column);
  }
 
  /**
   * The maximum value of the column
   * @function
   * @returns {number} The maximum value of the column
   */
  max() {
    return Math.max.apply(Math, this.column);
  }
 
  /**
   * The range of the column
   * @function
   * @returns {number} The range of the column
   */
  range() {
    return Math.abs(this.max() - this.min());
  }
 
  /**
   * The mean of the column
   * @function
   * @returns {number} The mean value of the column
   */
  mean() {
    let sum = 0;
    // Find the sum of the columns
    for (let i = 0; i < this.column.length; i++) {
      sum += this.column[i];
    }
 
    // Devide the sum by the count
    return sum / this.count();
  }
 
  /**
   * The median of the column
   * @function
   * @returns {number} The median value of the column
   */
  median() {
    // Sort the column from smallest to biggest
    const sorted = Array.from(this.column).sort((a, b) => a - b);
    // Find the halfway point's floor
    const middle = Math.floor(sorted.length / 2);
 
    // Check if the length is even. If it is, get the mean of the middle two
    // values
    if (sorted.length % 2 === 0) {
      return (sorted[middle - 1] + sorted[middle]) / 2;
    }
 
    // If odd, return the middle value
    return sorted[middle];
  }
 
  /**
   * The number of elements present in the column
   * @function
   * @returns {number} The count of the column
   */
  count() {
    return this.column.length;
  }
 
  /**
   * Sample Standard Deviation of the column. Use this to estimate the
   * variability for the population this sample is a subset of
   * @function
   * @returns {number} The sample standard deviation of the column
   */
  stds() {
    // Get the mean of the column
    const columnMean = this.mean();
    // Find (xi - xbar)^2 of every value and add it
    let deltaSquaredSum = 0;
    for (let i = 0; i < this.column.length; i++) {
      deltaSquaredSum += Math.pow(this.column[i] - columnMean, 2);
    }
 
    // Use the sample standard deviation formula
    return Math.sqrt(deltaSquaredSum / (this.count() - 1));
  }
 
  /**
   * Population Standard Deviation. Use this if the column is the population.
   * @function
   * @returns {number} The population standard deviation of the column
   */
  stdp() {
    const populationMean = this.mean();
    // Find (xi - populationMean)^2 of every value and add it
    let deltaSquaredSum = 0;
    for (let i = 0; i < this.column.length; i++) {
      deltaSquaredSum += Math.pow(this.column[i] - populationMean, 2);
    }
 
    // Use the population standard deviation formula
    return Math.sqrt(deltaSquaredSum / this.count());
  }
 
  /**
   * Get the number that is at the given percentile based on the given column
   * @param {number} percentile The percentile of the data to get
   * @returns {number} A number that is at the given percentile based on the
   * column
   */
  percentile(percentile) {
    // Error handling
    // if percentile isn't given
    if (!percentile) {
      throw new TypeError('Percentile must be given!');
    }
    // If given percentile is not a number
    if (typeof percentile != 'number') {
      throw new TypeError('Percentile must be a number!');
    }
 
    // Step 1: Make a copy of the array and sort it in ascending order
    const sorted = [...this.column].sort((a, b) => a - b);
 
    // Step 2: Calculate the index for the desired percentile using:
    // index = (percentile / 100) * (n - 1)
    const index = (percentile / 100) * (sorted.length - 1);
 
    // Step 3: Get the lower and upper indices around the index
    const lower = Math.floor(index);
    const upper = Math.ceil(index);
 
    // If index is an integer, return the value at that index
    if (lower === upper) return sorted[lower];
 
    // Otherwise, interpolate between the two surrounding values
    const weight = index - lower;
    return sorted[lower] * (1 - weight) + sorted[upper] * weight;
  }
}