// 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;
}
}