UNPKG

@modelx/modelx

Version:

Construct AI & ML models with JSON using Typescript & Tensorflow

383 lines (364 loc) 18.4 kB
import { DateTime, } from 'luxon'; import { Dimensions, } from './constants'; import * as ModelXData from '@modelx/data/src/index'; import * as ModelXDataTypes from '@modelx/data/src/DataSet'; export type UniqueDateOptions = { start: string; end: string; time_zone: string; weekday?: string | number; } /** * Returns an array of JavaScript Dates by Month within a range of ISO dates * @example getUniqueYears({ start: '2018-01-01', end: '2018-03-01', time_zone: 'America/Los_Angeles', }) => [ 2017-01-01T08:00:00.000Z, 2018-01-01T08:00:00.000Z, 2019-01-01T08:00:00.000Z ] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueYears({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ month: 1, day: 1, hour:0, minute:0, second:0, millisecond:0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ month: 1, day: 1, hour:0, minute:0, second:0, millisecond:0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ years: 1, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an array of JavaScript Dates by Month within a range of ISO dates * @example getUniqueMonths({ start: '2018-01-01', end: '2018-03-01', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T08:00:00.000Z, 2018-02-01T08:00:00.000Z, 2018-03-01T08:00:00.000Z ] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueMonths({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ day: 1, hour:0, minute:0, second:0, millisecond:0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ day: 1, hour:0, minute:0, second:0, millisecond:0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ months: 1, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an array of JavaScript Dates by Weeks within a range of ISO dates * @example getUniqueMonths({ start: '2018-01-01', end: '2018-01-31', weekday:'monday', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T08:00:00.000Z, 2018-01-08T08:00:00.000Z, 2018-01-15T08:00:00.000Z, 2018-01-22T08:00:00.000Z, 2018-01-29T08:00:00.000Z ]] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @param {String|Number} options.weekday - 'monday' or '1' * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueWeeks({ start, end, weekday = 'monday', time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); const weekdayNumbers: { [index: string]: number;} = { monday: 1, '1':1, tuesday: 2, '2':1, wednesday: 3, '3':1, thursday: 4, '4':1, friday: 5, '5':1, saturday: 6, '6':1, sunday: 7, '7':1, default: 1, }; const weekdayString = weekday.toString().toLowerCase(); const weekdayNumber = weekdayNumbers[weekdayString]; if (!weekdayNumber) throw new ReferenceError(`Invalid weekday (${weekday}), must be either 1-7 or monday-sunday`); // start = DateTime.fromJSDate(new Date(start), { zone: time_zone, }); // end = DateTime.fromJSDate(new Date(end), { zone: time_zone, }); let startLuxon = DateTime.fromISO(start, { zone: time_zone, }).set({ hour:0, minute:0, second:0, millisecond:0, }); let endLuxon = DateTime.fromISO(end, { zone: time_zone, }).set({ hour:0, minute:0, second:0, millisecond:0, }); let startDate = (weekdayNumber >= startLuxon.weekday) ? startLuxon.plus({ days: weekdayNumber - startLuxon.weekday, }) : startLuxon.minus({ days: startLuxon.weekday - weekdayNumber, }); const endDate = (weekdayNumber >= endLuxon.weekday) ? endLuxon.plus({ days: weekdayNumber - endLuxon.weekday, }) : endLuxon.minus({ days: endLuxon.weekday - weekdayNumber, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ days: 7, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an array of JavaScript Dates by Days within a range of ISO dates * @example getUniqueMonths({ start: '2018-01-01', end: '2018-01-31', weekday:'monday', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T08:00:00.000Z, 2018-01-02T08:00:00.000Z, ... 2018-01-29T08:00:00.000Z, 2018-01-30T08:00:00.000Z, 2018-01-31T08:00:00.000Z ]] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueDays({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ hour: 0, minute: 0, second: 0, millisecond: 0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ hour: 0, minute: 0, second: 0, millisecond: 0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ days: 1, }); } while (startDate <= endDate); // console.log({ uniqueDates }); return uniqueDates; } /** * Returns an array of JavaScript Dates by Hours within a range of ISO dates * @example getUniqueHours({ start: '2018-01-01', end: '2018-01-31', weekday:'monday', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T00:08:00.000Z, 2018-01-01T01:09:00.000Z, ... 2018-01-01T21:00:00.000Z, 2018-01-01T22:00:00.000Z, 2018-01-01T23:00:00.000Z ]] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueHours({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ minute: 0, second: 0, millisecond: 0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ minute: 0, second: 0, millisecond: 0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ hours: 1, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an array of JavaScript Dates by Hours within a range of ISO dates * @example getUniqueHours({ start: '2018-01-01', end: '2018-01-31', weekday:'monday', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T00:08:00.000Z, 2018-01-01T01:09:00.000Z, ... 2018-01-01T21:00:00.000Z, 2018-01-01T22:00:00.000Z, 2018-01-01T23:00:00.000Z ]] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueMinutes({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ second: 0, millisecond: 0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ second: 0, millisecond: 0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ minutes: 1, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an array of JavaScript Dates by Hours within a range of ISO dates * @example getUniqueSeconds({ start: '2018-01-01', end: '2018-01-31', weekday:'monday', time_zone: 'America/Los_Angeles', }) => [ 2018-01-01T00:08:00.000Z, 2018-01-01T01:09:00.000Z, ... 2018-01-01T21:00:00.000Z, 2018-01-01T22:00:00.000Z, 2018-01-01T23:00:00.000Z ]] * @param {String} options.start - start of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.end - end of date range (ISO Date String) e.g. 2018-05-31 * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date[]} Array of JavaScript Dates */ export function getUniqueSeconds({ start, end, time_zone, }:UniqueDateOptions):Date[] { if (!time_zone) throw new ReferenceError('Missing required timezone'); let startDate = DateTime.fromISO(start, { zone: time_zone, }).set({ millisecond: 0, }); const endDate = DateTime.fromISO(end, { zone: time_zone, }).set({ millisecond: 0, }); const uniqueDates = []; do { if (startDate.isValid === false) throw new SyntaxError('Date format is invalid, must be an ISO Date (ISO 8601 e.g. 2019-03-21T11:42:00 - YYYY-MM-DDTHH:mm:ss)'); uniqueDates.push(startDate.toJSDate()); startDate = startDate.plus({ seconds: 1, }); } while (startDate <= endDate); return uniqueDates; } /** * Returns an end date from a start date by dimension * @example getEndDate({ start: '2018-01-01', dimension: 'monthly', time_zone: 'America/Los_Angeles', }) => 2018-02-01T00:08:00.000Z getEndDate({ start: '2018-01-01', dimension: 'weekly', time_zone: 'America/Los_Angeles', }) => 2018-01-08T00:08:00.000Z getEndDate({ start: '2018-01-01', dimension: 'daily', time_zone: 'America/Los_Angeles', }) => 2018-01-02T00:08:00.000Z getEndDate({ start: '2018-01-01', dimension: 'hourly', time_zone: 'America/Los_Angeles', }) => 2018-01-01T00:09:00.000Z * @param {Date} options.start_date - start of date * @param {String} options.dimension - monthly, weekly, daily, hourly * @param {String} options.time_zone - valid IANA time_zone e.g. America/Los_Angeles * @returns {Date} JavaScript end date */ export function getEndDate(options: { start_date: Date; dimension: string; time_zone:string;}):Date { const { start_date, dimension, time_zone, } = options; if (!time_zone) throw new ReferenceError('Missing required timezone'); const start = DateTime.fromJSDate(start_date, { zone: time_zone, }); let returnDate; switch (dimension) { case 'yearly': returnDate = start.plus({ years: 1, }); break; case 'monthly': returnDate = start.plus({ months: 1, }); break; case 'weekly': returnDate = start.plus({ weeks: 1, }); break; case 'daily': returnDate = start.plus({ days: 1, }); break; case 'hourly': returnDate = start.plus({ hours: 1, }); break; case 'minutely': returnDate = start.plus({ minutes: 1, }); break; case 'secondly': returnDate = start.plus({ seconds: 1, }); break; default: throw new ReferenceError('Invalid dimension'); } if (returnDate.isValid === false) throw new SyntaxError('Date format or time_zone is invalid, Date must be a JavaScript Date Object'); return returnDate.toJSDate(); } export const dimensionDates = { [Dimensions.YEARLY]: getUniqueYears, [Dimensions.MONTHLY]: getUniqueMonths, [Dimensions.WEEKLY]: getUniqueWeeks, [Dimensions.DAILY]: getUniqueDays, [Dimensions.HOURLY]: getUniqueHours, [Dimensions.MINUTELY]: getUniqueMinutes, [Dimensions.SECONDLY]: getUniqueSeconds, }; export function getEncodedFeatures({ DataSet, features = [], }: { DataSet: ModelXDataTypes.DataSet, features?: string[] }) { if (DataSet.encoders.size) { const featuresCopy = new Array().concat(features); const encodedFeatures = []; for (let encode of DataSet.encoders.keys()) { if (features.includes(encode)) { const existingIndex = featuresCopy.indexOf(encode); featuresCopy.splice(existingIndex, 1); encodedFeatures.push(...DataSet.encoders.get(encode).labels.map((label: string) => `${DataSet.encoders.get(encode).prefix}${label}`)); } } return Array.from(new Set(encodedFeatures.concat(featuresCopy))); } return features; } export enum AutoFeatureTypes{ TEXT = 'text-encoded', LABEL = 'text-label', BOOLEAN = 'boolean', NUMBER = 'number', AUTO = 'auto-detect', } export type AutoFeature = { feature_field_type: AutoFeatureTypes; feature_field_name: string; }; export type AutoFeatureAutoAssignmentOptions = { independent_variables?: string[]; dependent_variables?: string[]; datum?: ModelXDataTypes.Datum; input_independent_features?: AutoFeature[]; output_dependent_features?: AutoFeature[]; training_feature_column_options?: ModelXDataTypes.DataSetTransform; preprocessing_feature_column_options?: ModelXDataTypes.DataSetTransform; features?: AutoFeature[]; }; export function getAutoFeatures({ variables, datum }: { variables: string[]; datum: ModelXDataTypes.Datum; }): AutoFeature[]{ return variables.map(variable => { const autofeature = { feature_field_name: variable, feature_field_type: AutoFeatureTypes.AUTO, }; if (typeof datum[variable] === 'number') autofeature.feature_field_type= AutoFeatureTypes.NUMBER; else if (typeof datum[variable] === 'boolean') autofeature.feature_field_type= AutoFeatureTypes.BOOLEAN; else if (typeof datum[variable] === 'string') autofeature.feature_field_type= AutoFeatureTypes.TEXT; return autofeature; }); } export function autoAssignFeatureColumns({ independent_variables, dependent_variables, datum, input_independent_features, output_dependent_features, training_feature_column_options = {}, preprocessing_feature_column_options = {}, }: AutoFeatureAutoAssignmentOptions) { const autoFeatureFilter = (autoFeature: AutoFeature) => Object.keys(training_feature_column_options).includes(autoFeature.feature_field_name) === false; const input_auto_features = new Array().concat( (independent_variables && independent_variables.length && datum) ? getAutoFeatures({ variables: independent_variables, datum, }) : [], input_independent_features || []); const output_auto_features = new Array().concat( (dependent_variables && dependent_variables.length && datum) ? getAutoFeatures({ variables: dependent_variables, datum, }) : [], output_dependent_features || []); // console.log('input_auto_features',input_auto_features,{training_feature_column_options}) // console.log('output_auto_features',output_auto_features) const model_auto_features = new Array().concat(input_auto_features, output_auto_features); model_auto_features .filter(autoFeatureFilter) .forEach((auto_feature: AutoFeature) => { if (auto_feature.feature_field_type === AutoFeatureTypes.TEXT) { training_feature_column_options[auto_feature.feature_field_name] = ['onehot']; } else if (auto_feature.feature_field_type === AutoFeatureTypes.LABEL) { training_feature_column_options[auto_feature.feature_field_name] = ['label']; } else if (auto_feature.feature_field_type === AutoFeatureTypes.BOOLEAN) { training_feature_column_options[auto_feature.feature_field_name] = ['label', { binary: true, },]; } else if ([AutoFeatureTypes.NUMBER, AutoFeatureTypes.AUTO,].includes(auto_feature.feature_field_type)) { training_feature_column_options[auto_feature.feature_field_name] = ['scale', 'standard',]; // if ([['onehot'], 'onehot', ['label'], 'label', ['label', { binary: true, },]].includes(training_feature_column_options[auto_feature.feature_field_name]) === false) { // } // if (training_feature_column_options[auto_feature.feature_field_name]!==['onehot']) { preprocessing_feature_column_options[auto_feature.feature_field_name] = ['median',]; // } } }); return { x_raw_independent_features: input_auto_features.map(af => af.feature_field_name), y_raw_dependent_labels: output_auto_features.map(af => af.feature_field_name), training_feature_column_options, preprocessing_feature_column_options, }; }