add more functions and tests

This commit is contained in:
ryan
2019-03-19 21:19:58 -07:00
parent 37bb5ef790
commit 7e56514c5a
3 changed files with 139 additions and 47 deletions

View File

@@ -36,10 +36,10 @@ export class StatsPicker extends PureComponent<Props> {
const current = getStatsCalculators(stats);
if (current.length !== stats.length) {
const found = current.map(v => v.value);
const found = current.map(v => v.id);
const notFound = difference(stats, found);
console.warn('Unknown stats', notFound, stats);
onChange(current.map(stat => stat.value));
onChange(current.map(stat => stat.id));
}
// Make sure there is only one
@@ -65,15 +65,31 @@ export class StatsPicker extends PureComponent<Props> {
render() {
const { width, stats, allowMultiple, defaultStat, placeholder } = this.props;
const current = getStatsCalculators(stats);
const options = getStatsCalculators().map(s => {
return {
value: s.id,
label: s.name,
desctipiton: s.description,
};
});
const value: SelectOptionItem[] = [];
stats.forEach(s => {
const o = options.find(v => v.value === s);
if (o) {
value.push(o);
}
});
//getStatsCalculators(stats);
return (
<Select
width={width}
value={current}
value={value}
isClearable={!defaultStat}
isMulti={allowMultiple}
isSearchable={true}
options={getStatsCalculators()}
options={options}
placeholder={placeholder}
onChange={this.onSelectionChange}
/>

View File

@@ -32,14 +32,14 @@ describe('Stats Calculators', () => {
const stats = getStatsCalculators(names);
expect(stats.length).toBe(2);
const found = stats.map(v => v.value);
const found = stats.map(v => v.id);
const notFound = _.difference(names, found);
expect(notFound.length).toBe(2);
expect(notFound[0]).toBe('not a stat');
});
it('should calculate stats', () => {
it('should calculate basic stats', () => {
const stats = calculateStats({
data: basicTable,
columnIndex: 0,
@@ -60,16 +60,22 @@ describe('Stats Calculators', () => {
const stats = calculateStats({
data: basicTable,
columnIndex: 0,
stats: ['first', 'last', 'mean'],
stats: ['first'],
});
// First
// Should do the simple version that just looks up value
expect(Object.keys(stats).length).toEqual(1);
expect(stats.first).toEqual(10);
});
// Last
expect(stats.last).toEqual(20);
it('should get non standard stats', () => {
const stats = calculateStats({
data: basicTable,
columnIndex: 0,
stats: [StatID.distinctCount, StatID.changeCount],
});
// Mean
expect(stats.mean).toEqual(15);
expect(stats.distinctCount).toEqual(2);
expect(stats.changeCount).toEqual(1);
});
});

View File

@@ -17,6 +17,9 @@ export enum StatID {
delta = 'delta',
step = 'step',
changeCount = 'changeCount',
distinctCount = 'distinctCount',
allIsZero = 'allIsZero',
allIsNull = 'allIsNull',
}
@@ -29,9 +32,10 @@ export interface ColumnStats {
type StatCalculator = (data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
export interface StatCalculatorInfo {
value: string; // The ID - value maps directly to select component
label: string; // The name - label for Select component
id: string;
name: string;
description: string;
alias?: string; // optional secondary key. 'avg' vs 'mean', 'total' vs 'sum'
// Internal details
@@ -83,7 +87,7 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
if (!data.rows || data.rows.length < 1) {
const stats = {} as ColumnStats;
queue.forEach(stat => {
stats[stat.value] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
});
return stats;
}
@@ -93,13 +97,13 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
// Avoid calculating all the standard stats if possible
if (queue.length === 1 && queue[0].calculator) {
return [queue[0].calculator(data, columnIndex, ignoreNulls, nullAsZero)];
return queue[0].calculator(data, columnIndex, ignoreNulls, nullAsZero);
}
// For now everything can use the standard stats
let values = standardStatsStat(data, columnIndex, ignoreNulls, nullAsZero);
queue.forEach(calc => {
if (!values.hasOwnProperty(calc.value) && calc.calculator) {
if (!values.hasOwnProperty(calc.id) && calc.calculator) {
values = {
...values,
...calc.calculator(data, columnIndex, ignoreNulls, nullAsZero),
@@ -127,75 +131,89 @@ function getById(id: string): StatCalculatorInfo | undefined {
if (!hasBuiltIndex) {
[
{
value: StatID.last,
label: 'Last',
id: StatID.last,
name: 'Last',
description: 'Last Value (current)',
standard: true,
alias: 'current',
stat: calculateLast,
calculator: calculateLast,
},
{ value: StatID.first, label: 'First', description: 'First Value', standard: true, stat: calculateFirst },
{ value: StatID.min, label: 'Min', description: 'Minimum Value', standard: true },
{ value: StatID.max, label: 'Max', description: 'Maximum Value', standard: true },
{ value: StatID.mean, label: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
{ id: StatID.first, name: 'First', description: 'First Value', standard: true, calculator: calculateFirst },
{ id: StatID.min, name: 'Min', description: 'Minimum Value', standard: true },
{ id: StatID.max, name: 'Max', description: 'Maximum Value', standard: true },
{ id: StatID.mean, name: 'Mean', description: 'Average Value', standard: true },
{
value: StatID.sum,
label: 'Total',
id: StatID.sum,
name: 'Total',
description: 'The sum of all values',
emptyInputResult: 0,
standard: true,
alias: 'total',
},
{
value: StatID.count,
label: 'Count',
id: StatID.count,
name: 'Count',
description: 'Number of values in response',
emptyInputResult: 0,
standard: true,
},
{
value: StatID.range,
label: 'Range',
id: StatID.range,
name: 'Range',
description: 'Difference between minimum and maximum values',
standard: true,
},
{
value: StatID.delta,
label: 'Delta',
id: StatID.delta,
name: 'Delta',
description: 'Cumulative change in value (??? help not really sure ???)',
standard: true,
},
{
value: StatID.step,
label: 'Step',
id: StatID.step,
name: 'Step',
description: 'Minimum interval between values',
standard: true,
},
{
value: StatID.diff,
label: 'Difference',
id: StatID.diff,
name: 'Difference',
description: 'Difference between first and last values',
standard: true,
},
{
value: StatID.logmin,
label: 'Min (above zero)',
id: StatID.logmin,
name: 'Min (above zero)',
description: 'Used for log min scale',
standard: true,
},
].forEach(calc => {
const { value, alias } = calc;
if (index.hasOwnProperty(value)) {
console.warn('Duplicate Stat', value, calc, index);
{
id: StatID.changeCount,
name: 'Change Count',
description: 'Number of times the value changes',
standard: false,
calculator: calculateChangeCount,
},
{
id: StatID.distinctCount,
name: 'Distinct Count',
description: 'Number of distinct values',
standard: false,
calculator: calculateDistinctCount,
},
].forEach(info => {
const { id, alias } = info;
if (index.hasOwnProperty(id)) {
console.warn('Duplicate Stat', id, info, index);
}
index[value] = calc;
index[id] = info;
if (alias) {
if (index.hasOwnProperty(alias)) {
console.warn('Duplicate Stat (alias)', alias, calc, index);
console.warn('Duplicate Stat (alias)', alias, info, index);
}
index[alias] = calc;
index[alias] = info;
}
listOfStats.push(calc);
listOfStats.push(info);
});
hasBuiltIndex = true;
}
@@ -324,9 +342,61 @@ function standardStatsStat(
}
function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
console.log('FIRST', data);
return { first: data.rows[0][columnIndex] };
}
function calculateLast(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
return { last: data.rows[data.rows.length - 1][columnIndex] };
}
function calculateChangeCount(
data: TableData,
columnIndex: number,
ignoreNulls: boolean,
nullAsZero: boolean
): ColumnStats {
let count = 0;
let first = true;
let last: any = null;
for (let i = 0; i < data.rows.length; i++) {
let currentValue = data.rows[i][columnIndex];
if (currentValue === null) {
if (ignoreNulls) {
continue;
}
if (nullAsZero) {
currentValue = 0;
}
}
if (!first && last !== currentValue) {
count++;
}
first = false;
last = currentValue;
}
return { changeCount: count };
}
function calculateDistinctCount(
data: TableData,
columnIndex: number,
ignoreNulls: boolean,
nullAsZero: boolean
): ColumnStats {
const distinct = new Set<any>();
for (let i = 0; i < data.rows.length; i++) {
let currentValue = data.rows[i][columnIndex];
if (currentValue === null) {
if (ignoreNulls) {
continue;
}
if (nullAsZero) {
currentValue = 0;
}
}
distinct.add(currentValue);
}
return { distinctCount: distinct.size };
}