UNPKG

@tidyjs/tidy

Version:

Tidy up your data with JavaScript, inspired by dplyr and the tidyverse

220 lines (217 loc) 7.71 kB
import { group } from 'd3-array'; import { assignGroupKeys } from './helpers/assignGroupKeys.js'; import { groupMap } from './helpers/groupMap.js'; import { groupTraversal } from './helpers/groupTraversal.js'; import { identity } from './helpers/identity.js'; import { isObject } from './helpers/isObject.js'; import { singleOrArray } from './helpers/singleOrArray.js'; function groupBy(groupKeys, fns, options) { if (typeof fns === "function") { fns = [fns]; } else if (arguments.length === 2 && fns != null && !Array.isArray(fns)) { options = fns; } const _groupBy = (items) => { const grouped = makeGrouped(items, groupKeys); const results = runFlow(grouped, fns, options == null ? void 0 : options.addGroupKeys); if (options == null ? void 0 : options.export) { switch (options.export) { case "grouped": return results; case "levels": return exportLevels(results, options); case "entries-obj": case "entriesObject": return exportLevels(results, { ...options, export: "levels", levels: ["entries-object"] }); default: return exportLevels(results, { ...options, export: "levels", levels: [options.export] }); } } const ungrouped = ungroup(results, options == null ? void 0 : options.addGroupKeys); return ungrouped; }; return _groupBy; } groupBy.grouped = (options) => ({...options, export: "grouped"}); groupBy.entries = (options) => ({...options, export: "entries"}); groupBy.entriesObject = (options) => ({...options, export: "entries-object"}); groupBy.object = (options) => ({...options, export: "object"}); groupBy.map = (options) => ({...options, export: "map"}); groupBy.keys = (options) => ({...options, export: "keys"}); groupBy.values = (options) => ({...options, export: "values"}); groupBy.levels = (options) => ({...options, export: "levels"}); function runFlow(items, fns, addGroupKeys) { let result = items; if (!(fns == null ? void 0 : fns.length)) return result; for (const fn of fns) { if (!fn) continue; result = groupMap(result, (items2, keys) => { const context = {groupKeys: keys}; let leafItemsMapped = fn(items2, context); if (addGroupKeys !== false) { leafItemsMapped = leafItemsMapped.map((item) => assignGroupKeys(item, keys)); } return leafItemsMapped; }); } return result; } function makeGrouped(items, groupKeys) { const groupKeyFns = singleOrArray(groupKeys).map((key, i) => { const keyFn = typeof key === "function" ? key : (d) => d[key]; const keyCache = new Map(); return (d) => { const keyValue = keyFn(d); const keyValueOf = isObject(keyValue) ? keyValue.valueOf() : keyValue; if (keyCache.has(keyValueOf)) { return keyCache.get(keyValueOf); } const keyWithName = [key, keyValue]; keyCache.set(keyValueOf, keyWithName); return keyWithName; }; }); const grouped = group(items, ...groupKeyFns); return grouped; } function ungroup(grouped, addGroupKeys) { const items = []; groupTraversal(grouped, items, [], identity, (root, keys, values) => { let valuesToAdd = values; if (addGroupKeys !== false) { valuesToAdd = values.map((d) => assignGroupKeys(d, keys)); } root.push(...valuesToAdd); }); return items; } const defaultCompositeKey = (keys) => keys.join("/"); function processFromGroupsOptions(options) { var _a; const { flat, single, mapLeaf = identity, mapLeaves = identity, addGroupKeys } = options; let compositeKey; if (options.flat) { compositeKey = (_a = options.compositeKey) != null ? _a : defaultCompositeKey; } const groupFn = (values, keys) => { return single ? mapLeaf(addGroupKeys === false ? values[0] : assignGroupKeys(values[0], keys)) : mapLeaves(values.map((d) => mapLeaf(addGroupKeys === false ? d : assignGroupKeys(d, keys)))); }; const keyFn = flat ? (keys) => compositeKey(keys.map((d) => d[1])) : (keys) => keys[keys.length - 1][1]; return {groupFn, keyFn}; } function exportLevels(grouped, options) { const {groupFn, keyFn} = processFromGroupsOptions(options); let {mapEntry = identity} = options; const {levels = ["entries"]} = options; const levelSpecs = []; for (const levelOption of levels) { switch (levelOption) { case "entries": case "entries-object": case "entries-obj": case "entriesObject": { const levelMapEntry = (levelOption === "entries-object" || levelOption === "entries-obj" || levelOption === "entriesObject") && options.mapEntry == null ? ([key, values]) => ({key, values}) : mapEntry; levelSpecs.push({ id: "entries", createEmptySubgroup: () => [], addSubgroup: (parentGrouped, newSubgroup, key, level) => { parentGrouped.push(levelMapEntry([key, newSubgroup], level)); }, addLeaf: (parentGrouped, key, values, level) => { parentGrouped.push(levelMapEntry([key, values], level)); } }); break; } case "map": levelSpecs.push({ id: "map", createEmptySubgroup: () => new Map(), addSubgroup: (parentGrouped, newSubgroup, key) => { parentGrouped.set(key, newSubgroup); }, addLeaf: (parentGrouped, key, values) => { parentGrouped.set(key, values); } }); break; case "object": levelSpecs.push({ id: "object", createEmptySubgroup: () => ({}), addSubgroup: (parentGrouped, newSubgroup, key) => { parentGrouped[key] = newSubgroup; }, addLeaf: (parentGrouped, key, values) => { parentGrouped[key] = values; } }); break; case "keys": levelSpecs.push({ id: "keys", createEmptySubgroup: () => [], addSubgroup: (parentGrouped, newSubgroup, key) => { parentGrouped.push([key, newSubgroup]); }, addLeaf: (parentGrouped, key) => { parentGrouped.push(key); } }); break; case "values": levelSpecs.push({ id: "values", createEmptySubgroup: () => [], addSubgroup: (parentGrouped, newSubgroup) => { parentGrouped.push(newSubgroup); }, addLeaf: (parentGrouped, key, values) => { parentGrouped.push(values); } }); break; default: { if (typeof levelOption === "object") { levelSpecs.push(levelOption); } } } } const addSubgroup = (parentGrouped, keys, level) => { var _a, _b; if (options.flat) { return parentGrouped; } const levelSpec = (_a = levelSpecs[level]) != null ? _a : levelSpecs[levelSpecs.length - 1]; const nextLevelSpec = (_b = levelSpecs[level + 1]) != null ? _b : levelSpec; const newSubgroup = nextLevelSpec.createEmptySubgroup(); levelSpec.addSubgroup(parentGrouped, newSubgroup, keyFn(keys), level); return newSubgroup; }; const addLeaf = (parentGrouped, keys, values, level) => { var _a; const levelSpec = (_a = levelSpecs[level]) != null ? _a : levelSpecs[levelSpecs.length - 1]; levelSpec.addLeaf(parentGrouped, keyFn(keys), groupFn(values, keys), level); }; const initialOutputObject = levelSpecs[0].createEmptySubgroup(); return groupTraversal(grouped, initialOutputObject, [], addSubgroup, addLeaf); } export { groupBy }; //# sourceMappingURL=groupBy.js.map