UNPKG

data-forge

Version:

JavaScript data transformation and analysis toolkit inspired by Pandas and LINQ.

1,276 lines (1,275 loc) 165 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __values = (this && this.__values) || function (o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spread = (this && this.__spread) || function () { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var empty_iterable_1 = require("./iterables/empty-iterable"); var count_iterable_1 = require("./iterables/count-iterable"); var multi_iterable_1 = require("./iterables/multi-iterable"); var select_iterable_1 = require("./iterables/select-iterable"); var select_many_iterable_1 = require("./iterables/select-many-iterable"); var take_iterable_1 = require("./iterables/take-iterable"); var take_while_iterable_1 = require("./iterables/take-while-iterable"); var where_iterable_1 = require("./iterables/where-iterable"); var concat_iterable_1 = require("./iterables/concat-iterable"); var series_window_iterable_1 = require("./iterables/series-window-iterable"); var reverse_iterable_1 = require("./iterables/reverse-iterable"); var zip_iterable_1 = require("./iterables/zip-iterable"); var distinct_iterable_1 = require("./iterables/distinct-iterable"); var series_rolling_window_iterable_1 = require("./iterables/series-rolling-window-iterable"); var series_variable_window_iterable_1 = require("./iterables/series-variable-window-iterable"); var ordered_iterable_1 = require("./iterables/ordered-iterable"); var index_1 = require("./index"); var extract_element_iterable_1 = require("./iterables/extract-element-iterable"); var skip_iterable_1 = require("./iterables/skip-iterable"); var skip_while_iterable_1 = require("./iterables/skip-while-iterable"); // @ts-ignore var easy_table_1 = __importDefault(require("easy-table")); var dataframe_1 = require("./dataframe"); // @ts-ignore var dayjs_1 = __importDefault(require("dayjs")); // @ts-ignore var customParseFormat_1 = __importDefault(require("dayjs/plugin/customParseFormat")); dayjs_1.default.extend(customParseFormat_1.default); var utils_1 = require("./utils"); var __1 = require(".."); var numeral_1 = __importDefault(require("numeral")); var cached_iterator_iterable_1 = require("./iterables/cached-iterator-iterable"); /** * Specifies where from a data window the index is pulled from: the start of the window, the end or from the middle. */ var WhichIndex; (function (WhichIndex) { WhichIndex["Start"] = "start"; WhichIndex["End"] = "end"; })(WhichIndex = exports.WhichIndex || (exports.WhichIndex = {})); /** * Class that represents a series containing a sequence of indexed values. */ var Series = /** @class */ (function () { /** * Create a series. * * @param config This can be an array, a configuration object or a function that lazily produces a configuration object. * * It can be an array that specifies the values that the series contains. * * It can be a {@link ISeriesConfig} that defines the values and configuration of the series. * * Or it can be a function that lazily produces a {@link ISeriesConfig}. * * @example * <pre> * * const series = new Series(); * </pre> * * @example * <pre> * * const series = new Series([10, 20, 30, 40]); * </pre> * * @example * <pre> * * const series = new Series({ index: [1, 2, 3, 4], values: [10, 20, 30, 40]}); * </pre> * * @example * <pre> * * const lazyInit = () => ({ index: [1, 2, 3, 4], values: [10, 20, 30, 40] }); * const series = new Series(lazyInit); * </pre> */ function Series(config) { // // Function to lazy evaluate the configuration of the series. // this.configFn = null; // // The content of the series. // When this is null it means the series is yet to be lazy initialised. // this.content = null; // // Indexed content of the dataframe. // this.indexedContent = null; if (config) { var configAsAny = config; if (configAsAny.getTypeCode !== undefined) { var typeCode = configAsAny.getTypeCode(); if (typeCode === "dataframe" || typeCode === "series") { if (configAsAny.content !== undefined) { this.content = configAsAny.content; } else { this.configFn = configAsAny.configFn; } return; } } if (utils_1.isFunction(config)) { this.configFn = config; } else if (Series.isIterator(config)) { this.content = Series.initFromIterator(config); } else if (Series.isIterable(config)) { this.content = Series.initFromIterable(config); } else { this.content = Series.initFromConfig(config); } } else { this.content = Series.initEmpty(); } } // // Initialise a series from an iterator (or generator object). // Series.initFromIterator = function (iterator) { return Series.initFromIterable(new cached_iterator_iterable_1.CachedIteratorIterable(iterator)); }; // // Initialise series content from an iterable of values. // Series.initFromIterable = function (arr) { return { index: Series.defaultCountIterable, values: arr, pairs: new multi_iterable_1.MultiIterable([Series.defaultCountIterable, arr]), isBaked: true, }; }; // // Initialise an empty series. // Series.initEmpty = function () { return { index: Series.defaultEmptyIterable, values: Series.defaultEmptyIterable, pairs: Series.defaultEmptyIterable, isBaked: true, }; }; // // Returns true if the input is an iterator. // Series.isIterator = function (input) { return utils_1.isObject(input) && utils_1.isFunction(input.next); }; // // Returns true if the input is an iterable. // Series.isIterable = function (input) { return utils_1.isArray(input) || (utils_1.isObject(input) && utils_1.isFunction(input[Symbol.iterator])); }; // // Check that a value is an interable. // Series.checkIterable = function (input, fieldName) { if (Series.isIterable(input)) { // Assume it's an iterable. // Ok } else { // Not ok throw new Error("Expected '" + fieldName + "' field of Series config object to be an array of values or an iterable of values."); } }; // // Initialise series content from a config object. // Series.initFromConfig = function (config) { var index; var values; var pairs; var isBaked = false; if (config.pairs) { if (Series.isIterator(config.pairs)) { pairs = new cached_iterator_iterable_1.CachedIteratorIterable(config.pairs); } else { Series.checkIterable(config.pairs, "pairs"); pairs = config.pairs; } } if (config.index) { if (Series.isIterator(config.index)) { index = new cached_iterator_iterable_1.CachedIteratorIterable(config.index); } else { Series.checkIterable(config.index, "index"); index = config.index; } } else if (pairs) { index = new extract_element_iterable_1.ExtractElementIterable(pairs, 0); } else { index = Series.defaultCountIterable; } if (config.values) { if (Series.isIterator(config.values)) { values = new cached_iterator_iterable_1.CachedIteratorIterable(config.values); } else { Series.checkIterable(config.values, "values"); values = config.values; } } else if (pairs) { values = new extract_element_iterable_1.ExtractElementIterable(pairs, 1); } else { values = Series.defaultEmptyIterable; } if (!pairs) { pairs = new multi_iterable_1.MultiIterable([index, values]); } if (config.baked !== undefined) { isBaked = config.baked; } return { index: index, values: values, pairs: pairs, isBaked: isBaked, }; }; // // Ensure the series content has been initialised. // Series.prototype.lazyInit = function () { if (this.content === null && this.configFn !== null) { this.content = Series.initFromConfig(this.configFn()); } }; // // Ensure the series content is lazy initalised and return it. // Series.prototype.getContent = function () { this.lazyInit(); return this.content; }; // // Lazy builds content index, does basic hash lookup. // Series.prototype.getRowByIndex = function (index) { if (!this.indexedContent) { this.indexedContent = new Map(); try { for (var _a = __values(this.getContent().pairs), _b = _a.next(); !_b.done; _b = _a.next()) { var pair = _b.value; this.indexedContent.set(pair[0], pair[1]); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_1) throw e_1.error; } } } return this.indexedContent.get(index); var e_1, _c; }; /** * Get an iterator to enumerate the values of the series. * Enumerating the iterator forces lazy evaluation to complete. * This function is automatically called by `for...of`. * * @return An iterator for the series. * * @example * <pre> * * for (const value of series) { * // ... do something with the value ... * } * </pre> */ Series.prototype[Symbol.iterator] = function () { return this.getContent().values[Symbol.iterator](); }; /** * Cast the value of the series to a new type. * This operation has no effect but to retype the values that the series contains. * * @return The same series, but with the type changed. * * @example * <pre> * * const castSeries = series.cast<SomeOtherType>(); * </pre> */ Series.prototype.cast = function () { return this; }; /** * Get the index for the series. * * @return The {@link Index} for the series. * * @example * <pre> * * const index = series.getIndex(); * </pre> */ Series.prototype.getIndex = function () { var _this = this; return new index_1.Index(function () { return ({ values: _this.getContent().index }); }); }; /** * Apply a new {@link Index} to the series. * * @param newIndex The new array or iterable to be the new {@link Index} of the series. Can also be a selector to choose the {@link Index} for each value in the series. * * @return Returns a new series with the specified {@link Index} attached. * * @example * <pre> * * const indexedSeries = series.withIndex([10, 20, 30]); * </pre> * * @example * <pre> * * const indexedSeries = series.withIndex(someOtherSeries); * </pre> * * @example * <pre> * * const indexedSeries = series.withIndex(value => computeIndexFromValue(value)); * </pre> * * @example * <pre> * * const indexedSeries = series.withIndex(value => value + 20); * </pre> */ Series.prototype.withIndex = function (newIndex) { var _this = this; if (utils_1.isFunction(newIndex)) { return new Series(function () { return ({ values: _this.getContent().values, index: _this.select(newIndex), }); }); } else { Series.checkIterable(newIndex, 'newIndex'); return new Series(function () { return ({ values: _this.getContent().values, index: newIndex, }); }); } }; /** * Resets the {@link Index} of the series back to the default zero-based sequential integer index. * * @return Returns a new series with the {@link Index} reset to the default zero-based index. * * @example * <pre> * * const seriesWithResetIndex = series.resetIndex(); * </pre> */ Series.prototype.resetIndex = function () { var _this = this; return new Series(function () { return ({ values: _this.getContent().values // Just strip the index. }); }); }; /** * Merge multiple series into a single series. * Values are merged by index. * Values at each index are combined into arrays in the resulting series. * * @param series An array or series of series to merge. * * @returns The merged series. * * @example * <pre> * * const mergedSeries = Series.merge([series1, series2, etc]); * </pre> */ Series.merge = function (series) { var rowMap = new Map(); var numSeries = Array.from(series).length; //TODO: Be nice not to have to do this. var seriesIndex = 0; try { for (var series_1 = __values(series), series_1_1 = series_1.next(); !series_1_1.done; series_1_1 = series_1.next()) { var workingSeries = series_1_1.value; try { for (var _a = __values(workingSeries.toPairs()), _b = _a.next(); !_b.done; _b = _a.next()) { var pair = _b.value; var index = pair[0].toString(); if (!rowMap.has(index)) { rowMap.set(index, { index: pair[0], values: new Array(numSeries) }); } rowMap.get(index).values[seriesIndex] = pair[1]; } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_2) throw e_2.error; } } ++seriesIndex; } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (series_1_1 && !series_1_1.done && (_d = series_1.return)) _d.call(series_1); } finally { if (e_3) throw e_3.error; } } var mergedPairs = Array.from(rowMap.values()) .map(function (row) { return [row.index, row.values]; }); mergedPairs.sort(function (a, b) { if (a[0] === b[0]) { return 0; } else if (a[0] > b[0]) { return 1; } else { return -1; } }); return new Series({ pairs: mergedPairs, }); var e_3, _d, e_2, _c; }; /** * Merge one or more series into this series. * Values are merged by index. * Values at each index are combined into arrays in the resulting series. * * @param series... One or more other series to merge into the series. * * @returns The merged series. * * @example * <pre> * * const mergedSeries = series1.merge(series2); * </pre> * * <pre> * * const mergedSeries = series1.merge(series2, series3, etc); * </pre> */ Series.prototype.merge = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return Series.merge([this].concat(args)); }; /** * Extract values from the series as an array. * This forces lazy evaluation to complete. * * @return Returns an array of the values contained within the series. * * @example * <pre> * const values = series.toArray(); * </pre> */ Series.prototype.toArray = function (options) { var values = []; try { for (var _a = __values(this.getContent().values), _b = _a.next(); !_b.done; _b = _a.next()) { var value = _b.value; if (options && options.includeNulls && value !== undefined) { values.push(value); } else if (value !== undefined && value !== null) { values.push(value); } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_4) throw e_4.error; } } return values; var e_4, _c; }; /** * Retreive the index, values pairs from the series as an array. * Each pair is [index, value]. * This forces lazy evaluation to complete. * * @return Returns an array of pairs that contains the series values. Each pair is a two element array that contains an index and a value. * * @example * <pre> * const pairs = series.toPairs(); * </pre> */ Series.prototype.toPairs = function () { var pairs = []; try { for (var _a = __values(this.getContent().pairs), _b = _a.next(); !_b.done; _b = _a.next()) { var pair = _b.value; if (pair[1] !== undefined && pair[1] !== null) { pairs.push(pair); } } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_5) throw e_5.error; } } return pairs; var e_5, _c; }; /** * Convert the series to a JavaScript object. * * @param keySelector User-defined selector function that selects keys for the resulting object. * @param valueSelector User-defined selector function that selects values for the resulting object. * * @return Returns a JavaScript object generated from the series by applying the key and value selector functions. * * @example * <pre> * * const someObject = series.toObject( * value => value, // Specify the value to use for field names in the output object. * value => value // Specify the value to use as the value for each field. * ); * </pre> */ Series.prototype.toObject = function (keySelector, valueSelector) { if (!utils_1.isFunction(keySelector)) throw new Error("Expected 'keySelector' parameter to Series.toObject to be a function."); if (!utils_1.isFunction(valueSelector)) throw new Error("Expected 'valueSelector' parameter to Series.toObject to be a function."); return utils_1.toMap(this, keySelector, valueSelector); }; /** * Transforms an input series, generating a new series. * The transformer function is called for each element of the input and the collection of outputs creates the generated series. * * `select` is an alias for {@link Series.map}. * * This is the same concept as the JavaScript function `Array.map` but maps over a data series rather than an array. * * @param transformer A user-defined transformer function that transforms each element from the input to generate the output. * * @return Returns the series generated by calling the transformer function over each value in the input series. * * @example * <pre> * * function transformer (input) { * const output = // ... construct output from input ... * return output; * } * * const transformed = series.select(transformer); * console.log(transformed.toString()); * </pre> */ Series.prototype.select = function (transformer) { if (!utils_1.isFunction(transformer)) throw new Error("Expected 'transformer' parameter to 'Series.select' to be a function."); return this.map(transformer); }; /** * Transforms an input series, generating a new series. * The transformer function is called for each element of the input and the collection of outputs creates the generated series. * * This is the same concept as the JavaScript function `Array.map` but maps over a data series rather than an array. * * @param transformer A user-defined transformer function that transforms each element from the input to generate the output. * * @return Returns a new series generated by calling the transformer function over each element of the input. * * @example * <pre> * * function transformer (input) { * const output = // ... construct output from input ... * return output; * } * * const transformed = series.map(transformer); * console.log(transformed.toString()); * </pre> */ Series.prototype.map = function (transformer) { var _this = this; if (!utils_1.isFunction(transformer)) throw new Error("Expected 'transformer' parameter to 'Series.map' to be a function."); return new Series(function () { var content = _this.getContent(); return { values: new select_iterable_1.SelectIterable(content.values, transformer), index: content.index, }; }); }; /** * Transforms and flattens an input series, generating a new series. * The transformer function is called for each value in the input series and produces an array that is then flattened into the generated series. * * `selectMany` is an alias for {@link Series.flatMap}. * * This is the same concept as the JavaScript function `Array.flatMap` but maps over a data series rather than an array. * * @param transformer A user-defined function that transforms each value into an array that is then flattened into the generated series. * * @return Returns a new series generated by calling the transformer function over each element of the input. * * @example * <pre> * * function transformer (input) { * const output = []; * while (someCondition) { * // ... generate zero or more outputs from a single input ... * output.push(... some generated value ...); * } * return output; * } * * const transformed = series.selectMany(transformer); * console.log(transformed.toString()); * </pre> */ Series.prototype.selectMany = function (transformer) { if (!utils_1.isFunction(transformer)) throw new Error("Expected 'transformer' parameter to 'Series.selectMany' to be a function."); return this.flatMap(transformer); }; /** * Transforms and flattens an input series, generating a new series. * The transformer function is called for each value in the input series and produces an array that is then flattened into the generated series. * * This is the same concept as the JavaScript function `Array.flatMap` but maps over a data series rather than an array. * * @param transformer A user-defined function that transforms each value into an array that is then flattened into the generated series. * * @return Returns a new series generated by calling the transformer function over each element of the input. * * @example * <pre> * * function transformer (input) { * const output = []; * while (someCondition) { * // ... generate zero or more outputs from a single input ... * output.push(... some generated value ...); * } * return output; * } * * const transformed = series.flatMap(transformer); * console.log(transformed.toString()); * </pre> */ Series.prototype.flatMap = function (transformer) { var _this = this; if (!utils_1.isFunction(transformer)) throw new Error("Expected 'transformer' parameter to 'Series.flatMap' to be a function."); return new Series(function () { return ({ pairs: new select_many_iterable_1.SelectManyIterable(_this.getContent().pairs, function (pair, index) { var outputPairs = []; try { for (var _a = __values(transformer(pair[1], index)), _b = _a.next(); !_b.done; _b = _a.next()) { var transformed = _b.value; outputPairs.push([ pair[0], transformed ]); } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_6) throw e_6.error; } } return outputPairs; var e_6, _c; }) }); }); }; /** * Partition a series into a {@link Series} of *data windows*. * Each value in the new series is a chunk of data from the original series. * * @param period The number of values to include in each data window. * @param whichIndex Sets which side of the window the index comes from: start or end. Can be "start" or "end", defaults to "end". * * @return Returns a new series, each value of which is a chunk (data window) of the original series. * * @example * <pre> * * const windows = series.window(2); // Get values in pairs. * const pctIncrease = windows.select(pair => (pair.last() - pair.first()) / pair.first()); * console.log(pctIncrease.toString()); * </pre> * * @example * <pre> * * const salesDf = ... // Daily sales data. * const weeklySales = salesDf.window(7); // Partition up into weekly data sets. * console.log(weeklySales.toString()); * </pre> */ Series.prototype.window = function (period, whichIndex) { var _this = this; if (!utils_1.isNumber(period)) throw new Error("Expected 'period' parameter to 'Series.window' to be a number."); return new Series(function () { return ({ pairs: new series_window_iterable_1.SeriesWindowIterable(_this.getContent().pairs, period, whichIndex || WhichIndex.End), }); }); }; /** * Partition a series into a new series of *rolling data windows*. * Each value in the new series is a rolling chunk of data from the original series. * * @param period The number of data values to include in each data window. * @param whichIndex Sets which side of the window the index comes from: start or end. Can be "start" or "end", defaults to "end". * * @return Returns a new series, each value of which is a rolling chunk of the original series. * * @example * <pre> * * const salesData = ... // Daily sales data. * const rollingWeeklySales = salesData.rollingWindow(7); // Get rolling window over weekly sales data. * console.log(rollingWeeklySales.toString()); * </pre> */ Series.prototype.rollingWindow = function (period, whichIndex) { var _this = this; if (!utils_1.isNumber(period)) throw new Error("Expected 'period' parameter to 'Series.rollingWindow' to be a number."); return new Series(function () { return ({ pairs: new series_rolling_window_iterable_1.SeriesRollingWindowIterable(_this.getContent().pairs, period, whichIndex || WhichIndex.End), }); }); }; /** * Partition a series into a new series of variable-length *data windows* * where the divisions between the data chunks are * defined by a user-provided *comparer* function. * * @param comparer Function that compares two adjacent data values and returns true if they should be in the same window. * * @return Returns a new series, each value of which is a chunk of data from the original series. * * @example * <pre> * * function rowComparer (valueA, valueB) { * if (... valueA should be in the same data window as valueB ...) { * return true; * } * else { * return false; * } * }; * * const variableWindows = series.variableWindow(rowComparer); */ Series.prototype.variableWindow = function (comparer) { var _this = this; if (!utils_1.isFunction(comparer)) throw new Error("Expected 'comparer' parameter to 'Series.variableWindow' to be a function."); return new Series(function () { return ({ values: new series_variable_window_iterable_1.SeriesVariableWindowIterable(_this.getContent().pairs, comparer) }); }); }; /** * Eliminates adjacent duplicate values. * * For each group of adjacent values that are equivalent only returns the last index/row for the group, * thus ajacent equivalent values are collapsed down to the last value. * * @param selector Optional selector function to determine the value used to compare for equivalence. * * @return Returns a new series with groups of adjacent duplicate vlaues collapsed to a single value per group. * * @example * <pre> * * const seriesWithDuplicateRowsRemoved = series.sequentialDistinct(value => value); * * // Or * const seriesWithDuplicateRowsRemoved = series.sequentialDistinct(value => value.someNestedField); * </pre> */ Series.prototype.sequentialDistinct = function (selector) { if (selector) { if (!utils_1.isFunction(selector)) throw new Error("Expected 'selector' parameter to 'Series.sequentialDistinct' to be a selector function that determines the value to compare for duplicates."); } else { selector = function (value) { return value; }; } return this.variableWindow(function (a, b) { return selector(a) === selector(b); }) .select(function (window) { return [window.getIndex().first(), window.first()]; }) .withIndex(function (pair) { return pair[0]; }) .select(function (pair) { return pair[1]; }); }; /** * Reduces the values in the series to a single result. * * `aggregate` is similar to {@link Series.reduce}, but the parameters are reversed. * Please use {@link Series.reduce} in preference to `aggregate`. * * @param seed Optional seed value for producing the aggregation. * @param reducer Function that takes the seed and then each value in the series and produces the reduced value. * * @return Returns a new value that has been reduced from the input series by passing it through the 'reducer' function. * * @example * <pre> * * const dailySales = ... daily sales figures for the past month ... * const totalSalesForthisMonth = dailySales.aggregate( * 0, // Seed - the starting value. * (accumulator, salesAmount) => accumulator + salesAmount // Aggregation function. * ); * </pre> * * @example * <pre> * * const previousSales = 500; // We'll seed the aggregation with this value. * const dailySales = ... daily sales figures for the past month ... * const updatedSales = dailySales.aggregate( * previousSales, * (accumulator, salesAmount) => accumulator + salesAmount * ); * </pre> * * @example * <pre> * * var salesDataSummary = salesData.aggregate({ * TotalSales: series => series.count(), * AveragePrice: series => series.average(), * TotalRevenue: series => series.sum(), * }); * </pre> */ Series.prototype.aggregate = function (seedOrSelector, selector) { if (utils_1.isFunction(seedOrSelector) && !selector) { return this.skip(1).aggregate(this.first(), seedOrSelector); } else { if (!utils_1.isFunction(selector)) throw new Error("Expected 'selector' parameter to aggregate to be a function."); var accum = seedOrSelector; try { for (var _a = __values(this), _b = _a.next(); !_b.done; _b = _a.next()) { var value = _b.value; accum = selector(accum, value); } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_7) throw e_7.error; } } return accum; } var e_7, _c; }; /** * Reduces the values in the series to a single result. * * This is the same concept as the JavaScript function `Array.reduce` but reduces a data series rather than an array. * @param reducer Function that takes the seed and then each value in the series and produces the reduced value. * @param seed Optional initial value, if not specifed the first value in the series is used as the initial value. * * @return Returns a value that has been reduced from the input series by passing each element through the reducer function. * * @example * <pre> * * const dailySales = ... daily sales figures for the past month ... * const totalSales = dailySales.reduce( * (accumulator, salesAmount) => accumulator + salesAmount, // Reducer function. * 0 // Seed value, the starting value. * ); * </pre> * * @example * <pre> * * const previousSales = 500; // We'll seed the reduction with this value. * const dailySales = ... daily sales figures for the past month ... * const updatedSales = dailySales.reduce( * (accumulator, salesAmount) => accumulator + salesAmount, * previousSales * ); * </pre> */ Series.prototype.reduce = function (reducer, seed) { if (!utils_1.isFunction(reducer)) throw new Error("Expected 'reducer' parameter to `Series.reduce` to be a function."); var accum = seed; var series = this; if (accum === undefined) { if (series.any()) { accum = series.first(); series = series.skip(1); } } try { for (var series_2 = __values(series), series_2_1 = series_2.next(); !series_2_1.done; series_2_1 = series_2.next()) { var value = series_2_1.value; accum = reducer(accum, value); } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (series_2_1 && !series_2_1.done && (_a = series_2.return)) _a.call(series_2); } finally { if (e_8) throw e_8.error; } } return accum; var e_8, _a; }; /** * Compute the absolute range of values in each period. * The range for each period is the absolute difference between largest (max) and smallest (min) values in that period. * * @param period - Period for computing the range. * * @returns Returns a new series where each value indicates the absolute range of values for each period in the original series. * * @example * <pre> * * const closingPrice = ... series of closing prices for a particular stock ... * const volatility = closingPrice.amountRange(5); * </pre> */ Series.prototype.amountRange = function (period, whichIndex) { return this // Have to assume this is a number series. .rollingWindow(period, whichIndex) .select(function (window) { return window.max() - window.min(); }); }; /** * Compute the range of values in each period in proportion to the latest value. * The range for each period is the absolute difference between largest (max) and smallest (min) values in that period. * Proportions are expressed as 0-1 values. * * @param period - Period for computing the range. * * @returns Returns a new series where each value indicates the proportion change from the previous number value in the original series. * * @returns Returns a new series where each value indicates the proportionate range of values for each period in the original series. * * @example * <pre> * * const closingPrice = ... series of closing prices for a particular stock ... * const proportionVolatility = closingPrice.proportionRange(5); * </pre> */ Series.prototype.proportionRange = function (period, whichIndex) { return this // Have to assume this is a number series. .rollingWindow(period, whichIndex) .select(function (window) { return (window.max() - window.min()) / window.last(); }); }; /** * Compute the range of values in each period in proportion to the latest value. * The range for each period is the absolute difference between largest (max) and smallest (min) values in that period. * Proportions are expressed as 0-1 values. * * @param period - Period for computing the range. * * @returns Returns a new series where each value indicates the proportion change from the previous number value in the original series. * * @returns Returns a new series where each value indicates the proportionate range of values for each period in the original series. * * @example * <pre> * * const closingPrice = ... series of closing prices for a particular stock ... * const percentVolatility = closingPrice.percentRange(5); * </pre> */ Series.prototype.percentRange = function (period, whichIndex) { return this.proportionRange(period, whichIndex).select(function (v) { return v * 100; }); }; /** * Compute the amount of change between pairs or sets of values in the series. * * @param period Optional period for computing the change - defaults to 2. * * @returns Returns a new series where each value indicates the amount of change from the previous number value in the original series. * * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const amountChanged = salesFigures.amountChanged(); // Amount that sales has changed, day to day. * </pre> * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const amountChanged = salesFigures.amountChanged(7); // Amount that sales has changed, week to week. * </pre> */ Series.prototype.amountChange = function (period, whichIndex) { return this // Have to assume this is a number series. .rollingWindow(period === undefined ? 2 : period, whichIndex) .select(function (window) { return window.last() - window.first(); }); }; /** * Compute the proportion change between pairs or sets of values in the series. * Proportions are expressed as 0-1 values. * * @param period Optional period for computing the proportion - defaults to 2. * * @returns Returns a new series where each value indicates the proportion change from the previous number value in the original series. * * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const proportionChanged = salesFigures.amountChanged(); // Proportion that sales has changed, day to day. * </pre> * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const proportionChanged = salesFigures.amountChanged(7); // Proportion that sales has changed, week to week. * </pre> */ Series.prototype.proportionChange = function (period, whichIndex) { return this // Have to assume this is a number series. .rollingWindow(period === undefined ? 2 : period, whichIndex) .select(function (window) { return (window.last() - window.first()) / window.first(); }); }; /** * Compute the percentage change between pairs or sets of values in the series. * Percentages are expressed as 0-100 values. * * @param period Optional period for computing the percentage - defaults to 2. * * @returns Returns a new series where each value indicates the percent change from the previous number value in the original series. * * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const percentChanged = salesFigures.amountChanged(); // Percent that sales has changed, day to day. * </pre> * @example * <pre> * * const saleFigures = ... running series of daily sales figures ... * const percentChanged = salesFigures.amountChanged(7); // Percent that sales has changed, week to week. * </pre> */ Series.prototype.percentChange = function (period, whichIndex) { return this.proportionChange(period, whichIndex).select(function (v) { return v * 100; }); }; /** * For each period, compute the proportion of values that are less than the last value in the period. * Proportions are expressed as 0-1 values. * * @param period Optional period for computing the proportion rank - defaults to 2. * * @returns Returns a new series where each value indicates the proportion rank value for that period. * * @example * <pre> * * const proportionRank = series.proportionRank(); * </pre> * @example * <pre> * * const proportionRank = series.proportionRank(100); * </pre> */ Series.prototype.proportionRank = function (period) { if (period === undefined) { period = 2; } if (!utils_1.isNumber(period)) { throw new Error("Expected 'period' parameter to 'Series.proportionRank' to be a number that specifies the time period for the ranking."); } return this.rollingWindow(period + 1) // +1 to account for the last value being used. .select(function (window) { var latestValue = window.last(); var numLowerValues = window.head(-1).filter(function (prevMomentum) { return prevMomentum < latestValue; }).count(); var proportionRank = numLowerValues / period; return proportionRank; }); }; /** * For each period, compute the percent of values that are less than the last value in the period. * Percent are expressed as 0-100 values. * * @param period Optional period for computing the percent rank - defaults to 2. * * @returns Returns a new series where each value indicates the percent rank value for that period. * * @example * <pre> * * const percentRank = series.percentRank(); * </pre> * @example * <pre> * * const percentRank = series.percentRank(100); * </pre> */ Series.prototype.percentRank = function (period) { if (period === undefined) { period = 2; } if (!utils_1.isNumber(period)) { throw new Error("Expected 'period' parameter to 'Series.percentRank' to be a number that specifies the time period for the ranking."); } return this.proportionRank(period).select(function (proportion) { return proportion * 100; }); }; /** * Generates a cumulative sum across a series. * * @returns Returns a new series that is the cumulative sum of values across the input series. */ Series.prototype.cumsum = function () { var _this = this; return new Series(function () { var working = 0; var pairs = _this.toPairs(); var output = pairs.map(function (_a) { var _b = __read(_a, 2), index = _b[0], value = _b[1]; return ([index, working += value]); }); return { pairs: output }; }); }; /** * Skip a number of values in the series. * * @param numValues Number of values to skip. * * @return Returns a new series with the specified number of values skipped. * * @example * <pre> * * const seriesWithRowsSkipped = series.skip(10); // Skip 10 rows in the original series. * </pre> */ Series.prototype.skip = function (numValues) { var _this = this; return new Series(function () { return ({ values: new skip_iterable_1.SkipIterable(_this.getContent().values, numValues), index: new skip_iterable_1.SkipIterable(_this.getContent().index, numValues), pairs: new skip_iterable_1.SkipIterable(_this.getContent().pairs, numValues), }); }); }; /** * Skips values in the series while a condition evaluates to true or truthy. * * @param predicate Returns true/truthy to continue to skip values in the original series. * * @return Returns a new series with all initial sequential values removed while the predicate returned true/truthy. * * @example * <pre> * * const seriesWithRowsSkipped = series.skipWhile(salesFigure => salesFigure > 100); // Skip initial sales figure that are less than 100. * </pre> */ Series.prototype.skipWhile = function (predicate) { var _this = this; if (!utils_1.isFunction(predicate)) throw new Error("Expected 'predicate' parameter to 'Series.skipWhile' function to be a predicate function that returns true/false."); return new Series(function () { return ({ values: new skip_while_iterable_1.SkipWhileIterable(_this.getContent().values, predicate), pairs: new skip_while_iterable_1.SkipWhileIterable(_this.getContent().pairs, function (pair) { return predicate(pair[1]); }), })