data-forge
Version:
JavaScript data transformation and analysis toolkit inspired by Pandas and LINQ.
1,276 lines (1,275 loc) • 165 kB
JavaScript
"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]); }),
})