modules/manifold/src/actions/io-actions.js (110 lines of code) (raw):

import {createAction} from 'redux-actions'; import {parsePromise} from '@mlvis/mlvis-common/utils'; import {loadData, modelsPerformance, featuresDistribution} from '../io'; import { defaultInputDataTransformer, isDatasetIncomplete, validateInputData, computeMetaData, } from '../utils'; // -- remote data source -- // export const FETCH_BACKEND_DATA_START = 'FETCH_BACKEND_DATA_START'; export const FETCH_BACKEND_DATA_SUCCESS = 'FETCH_BACKEND_DATA_SUCCESS'; export const FETCH_MODELS_START = 'FETCH_MODELS_START'; export const FETCH_MODELS_SUCCESS = 'FETCH_MODELS_SUCCESS'; export const FETCH_MODELS_FAILURE = 'FETCH_MODELS_FAILURE'; export const FETCH_FEATURES_START = 'FETCH_FEATURES_START'; export const FETCH_FEATURES_SUCCESS = 'FETCH_FEATURES_SUCCESS'; // -- local data source -- // export const LOAD_LOCAL_DATA_START = 'LOAD_LOCAL_DATA_START'; export const LOAD_LOCAL_DATA_SUCCESS = 'LOAD_LOCAL_DATA_SUCCESS'; export const LOAD_LOCAL_DATA_FAILURE = 'LOAD_LOCAL_DATA_FAILURE'; // -- remote data source -- // export const fetchBackendDataStart = createAction(FETCH_BACKEND_DATA_START); export const fetchBackendDataSuccess = createAction(FETCH_BACKEND_DATA_SUCCESS); export const fetchModelsStart = createAction(FETCH_MODELS_START); export const fetchModelsSuccess = createAction(FETCH_MODELS_SUCCESS); export const fetchModelsFailure = createAction(FETCH_MODELS_FAILURE); export const fetchFeaturesStart = createAction(FETCH_FEATURES_START); export const fetchFeaturesSuccess = createAction(FETCH_FEATURES_SUCCESS); // -- local data source -- // export const loadLocalDataStart = createAction(LOAD_LOCAL_DATA_START); export const loadLocalDataSuccess = createAction(LOAD_LOCAL_DATA_SUCCESS); export const loadLocalDataFailure = createAction(LOAD_LOCAL_DATA_FAILURE); // ---------------- ASYNC DATA I/O ACTIONS ---------------- // // -- local data source -- // /* * Main data action to be exported into applications * Loads model performance and feature data into Manifold * @param: {Object[][]} fileList, a list of files containing all relevant data needed for Manifold, in csv format * @param: {Function} dataTransformer, see `defaultInputDataTransformer` for its signature */ export const loadLocalData = ({ fileList, dataTransformer = defaultInputDataTransformer, }) => dispatch => { dispatch(loadLocalDataStart()); const allParseDataPromises = fileList.map(parsePromise); // 1. read data by parsing csv format Promise.all(allParseDataPromises) // 2. map data files to `x`, `yPred`, `yTrue` fields .then(dataTransformer) // 3. validate data .then(validateInputData) // 4. get metadata from input data, also convert csv data to array of arrays instead of array of objects // TODO: this change should happen in `parsePromse`. But that will break API of `dataTransformer` so push it later .then(computeMetaData) // 5. add data to redux state .then(result => { dispatch(loadLocalDataSuccess(result)); }); // TODO: catch errors // .catch(error => dispatch(loadLocalDataFailure(error))); }; // TODO: combine with the loadLocalData util above, make parsing logic optional. /** * Use this loader when the data has already been loaded via a 3rd party loader * @param {array} data, an array of objects loaded from e.g. .csv or .arrow files * @param {function} dataTransformer, see `defaultInputDataTransformer` for its signature */ export const loadPrefetchedData = ({ data, dataTransformer = defaultInputDataTransformer, }) => dispatch => { dispatch(loadLocalDataStart()); const processedData = dataTransformer(data); const validatedData = validateInputData(processedData); dispatch(loadLocalDataSuccess(validatedData)); }; /** * Convenient wrapper around `loadLocalData`, taking `x`, `yPred`, `yTrue` as input. * @param {Object} userData - {x: <file>, yPred: [<file>], yTrue: <file>} */ export const loadUserData = ({x, yPred, yTrue}) => { if (isDatasetIncomplete({x, yPred, yTrue})) { /* eslint-disable no-console */ console.error('Dataset is incomplete'); /* eslint-enable no-console */ return; } return loadLocalData({ fileList: [x, ...yPred, yTrue], dataTransformer: userDataTransformer, }); }; /** * Load the processed data fully compatible with manifold * Format of the data is specified here: * https://github.com/uber/manifold#prepare-your-data * @param {Object} data - * { * x: [...], // feature data * yPred: [[...], ...] // prediction data * yTrue: [...], // ground truth data * } */ export const loadProcessedData = data => dispatch => { dispatch(loadLocalDataStart()); const validatedData = validateInputData(data); const result = computeMetaData(validatedData); dispatch(loadLocalDataSuccess(result)); }; const userDataTransformer = fileList => { const dataList = fileList.map(v => v.data); // todo: yTrue should accept object as values as well const yTrue = dataList[dataList.length - 1].map(d => Object.values(d)[0]); return { x: dataList[0], yPred: dataList.slice(1, -1), yTrue, }; }; // -- remote data source -- // export const fetchBackendData = params => dispatch => { dispatch(fetchBackendDataStart()); loadData(params) .then(resp => { if (resp.ok) { return resp.json(); } throw new Error('Data fetching failed!'); }) .then(data => dispatch(fetchBackendDataSuccess(data))); }; export const fetchModels = params => dispatch => { dispatch(fetchModelsStart()); modelsPerformance(params) .then(resp => { if (resp.ok) { return resp.json(); } throw new Error('Data fetching failed!'); }) .then(data => dispatch(fetchModelsSuccess(data))); }; export const fetchFeatures = params => dispatch => { dispatch(fetchFeaturesStart()); featuresDistribution(params) .then(resp => { if (resp.ok) { return resp.json(); } throw new Error('Data fetching failed!'); }) .then(data => dispatch(fetchFeaturesSuccess(data))); };