From 4bebbf2510eb1cde51ddd253260ccff390519dc7 Mon Sep 17 00:00:00 2001 From: cristianvogel Date: Tue, 8 Feb 2022 20:57:55 +0100 Subject: [PATCH 1/3] added class and classAlt options --- src/format/to-html.js | 75 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/src/format/to-html.js b/src/format/to-html.js index 82fd1c8f..1ef3940b 100644 --- a/src/format/to-html.js +++ b/src/format/to-html.js @@ -20,11 +20,25 @@ import mapObject from '../util/map-object'; * @return {string} A CSS style string. */ +/** + * Class function. + * @callback ClassFunction + * @param {string} name The column name. + * @param {number} row The table row index. + * @return {string} A classname string. + */ + /** * CSS style options. * @typedef {Object.} StyleOptions */ +/** + * Class options. + * @typedef {Object.} ClassOptions + * @typedef {Object.} AltClassOptions + */ + /** * Options for HTML formatting. * @typedef {object} HTMLFormatOptions @@ -50,6 +64,18 @@ import mapObject from '../util/map-object'; * 'tbody', 'tr', 'th', or 'td'. The object values should be strings of * valid CSS style directives (such as "font-weight: bold;") or functions * that take a column name and row as inputs and return a CSS string. + * @property {ClassOptions} [class] class property to include in HTML output. + * The object keys should be HTML table tag names: 'table', 'thead', + * 'tbody', 'tr', 'th', or 'td'. The object values should be strings of + * valid class names or functions that take a column name and row as + * inputs and return a class string + * @property {AltClassOptions} [classAlt] alternating class property to include + * in HTML output. When used in conjuntion with 'class' will apply these class + * names to every other element. For example, to create alternate background colors + * on every other row, spreadsheet style. The object keys should be HTML table tag + * names: 'table', 'thead', 'tbody', 'tr', 'th', or 'td'. The object values should be strings of + * valid class names or functions that take a column name and row as + * inputs and return a class string * @property {number} [maxdigits=6] The maximum number of fractional digits * to include when formatting numbers. This option is passed to the format * inference method and is overridden by any explicit format options. @@ -65,16 +91,18 @@ export default function(table, options = {}) { const names = columns(table, options.columns); const { align, format } = formats(table, names, options); const style = styles(options); + const elClass = classes(options); + const classAlt = classesAlt(options); const nullish = options.null; const alignValue = a => a === 'c' ? 'center' : a === 'r' ? 'right' : 'left'; const escape = s => s.replace(/&/g, '&') - .replace(//g, '>'); + .replace(//g, '>'); const baseFormat = (value, opt) => escape(formatValue(value, opt)); const formatter = nullish - ? (value, opt) => value == null ? nullish(value) : baseFormat(value, opt) - : baseFormat; + ? (value, opt) => value == null ? nullish(value) : baseFormat(value, opt) + : baseFormat; let r = -1; let idx = -1; @@ -82,16 +110,19 @@ export default function(table, options = {}) { const tag = (tag, name, shouldAlign) => { const a = shouldAlign ? alignValue(align[name]) : ''; const s = style[tag] ? (style[tag](name, idx, r) || '') : ''; + const c = elClass[tag] ? (elClass[tag](name, idx, r) || '') : ''; + const altc = classAlt[tag] ? (classAlt[tag](name, idx, r) || '') : ''; const css = (a ? (`text-align: ${a};` + (s ? ' ' : '')) : '') + s; - return `<${tag}${css ? ` style="${css}"` : ''}>`; + const clss = (idx % 2) ? (c ? ` class="${c}"` : '') : (altc ? ` class="${altc}"` : (c ? ` class="${c}"` : '')); + return `<${tag}${css ? ` style="${css}"` : ''}${clss}>`; }; let text = tag('table') - + tag('thead') - + tag('tr', r) - + names.map(name => `${tag('th', name, 1)}${name}`).join('') - + '' - + tag('tbody'); + + tag('thead') + + tag('tr', r) + + names.map(name => `${tag('th', name, 1)}${name}`).join('') + + '' + + tag('tbody'); scan(table, names, options.limit, options.offset, { row(row) { @@ -100,8 +131,8 @@ export default function(table, options = {}) { }, cell(value, name) { text += tag('td', name, 1) - + formatter(value, format[name]) - + ''; + + formatter(value, format[name]) + + ''; } }); @@ -110,7 +141,21 @@ export default function(table, options = {}) { function styles(options) { return mapObject( - options.style, - value => isFunction(value) ? value : () => value + options.style, + value => isFunction(value) ? value : () => value ); -} \ No newline at end of file +} + +function classes(options) { + return mapObject( + options.class, + value => isFunction(value) ? value : () => value + ); +} + +function classesAlt(options) { + return mapObject( + options.classAlt, + value => isFunction(value) ? value : () => value + ); +} From 487703160bec99529eaa9433106b4189fe925355 Mon Sep 17 00:00:00 2001 From: cristianvogel Date: Fri, 11 Feb 2022 12:21:49 +0100 Subject: [PATCH 2/3] added custom arquero module --- docs/api/expressions.md | 155 ---- docs/api/extensibility.md | 280 ------- docs/api/index.md | 833 ------------------- docs/api/op.md | 1261 ----------------------------- docs/api/table.md | 701 ---------------- docs/api/verbs.md | 807 ------------------ docs/assets/logo-1280-640.png | Bin 14717 -> 0 bytes docs/assets/logo-1280.png | Bin 30286 -> 0 bytes docs/assets/logo-1280.svg | 1 - docs/assets/logo.svg | 1 - docs/index.md | 120 --- perf/arrow-perf.js | 138 ---- perf/csv-perf.js | 40 - perf/data-gen.js | 88 -- perf/derive-perf.js | 42 - perf/escape-perf.js | 28 - perf/filter-perf.js | 44 - perf/rollup-perf.js | 70 -- perf/table-perf.js | 34 - perf/time.js | 7 - test/arrow/data-from-test.js | 167 ---- test/arrow/profiler-test.js | 85 -- test/expression/params-test.js | 106 --- test/expression/parse-test.js | 738 ----------------- test/format/data/beers.csv | 1204 --------------------------- test/format/data/cols-only.json | 5 - test/format/data/cols-schema.json | 14 - test/format/data/flights.arrow | Bin 120504 -> 0 bytes test/format/data/rows.json | 5 - test/format/format-value-test.js | 134 --- test/format/from-arrow-test.js | 150 ---- test/format/from-csv-test.js | 124 --- test/format/from-fixed-test.js | 108 --- test/format/from-json-test.js | 152 ---- test/format/load-file-test.js | 59 -- test/format/load-file-url-test.js | 55 -- test/format/load-url-test.js | 57 -- test/format/to-arrow-test.js | 231 ------ test/format/to-csv-test.js | 74 -- test/format/to-html-test.js | 135 --- test/format/to-json-test.js | 80 -- test/format/to-markdown-test.js | 62 -- test/groupby-equal.js | 14 - test/helpers/agg-test.js | 18 - test/helpers/escape-test.js | 109 --- test/helpers/names-test.js | 27 - test/op/array-test.js | 196 ----- test/op/date-test.js | 42 - test/op/json-test.js | 29 - test/op/math-test.js | 18 - test/op/object-test.js | 90 -- test/op/op-test.js | 46 -- test/op/row-object-test.js | 44 - test/op/string-test.js | 236 ------ test/query/query-test.js | 1217 ---------------------------- test/query/util.js | 7 - test/query/verb-test.js | 612 -------------- test/query/verb-to-ast-test.js | 880 -------------------- test/register-test.js | 236 ------ test/table-equal.js | 29 - test/table/bitset-test.js | 103 --- test/table/column-table-test.js | 544 ------------- test/table/columns-from-test.js | 130 --- test/verbs/concat-test.js | 42 - test/verbs/dedupe-test.js | 31 - test/verbs/derive-test.js | 359 -------- test/verbs/filter-test.js | 55 -- test/verbs/fold-test.js | 37 - test/verbs/groupby-test.js | 173 ---- test/verbs/impute-test.js | 183 ----- test/verbs/join-filter-test.js | 159 ---- test/verbs/join-test.js | 473 ----------- test/verbs/lookup-test.js | 54 -- test/verbs/orderby-test.js | 50 -- test/verbs/pivot-test.js | 130 --- test/verbs/reduce-test.js | 41 - test/verbs/reify-test.js | 50 -- test/verbs/relocate-test.js | 91 --- test/verbs/rename-test.js | 49 -- test/verbs/rollup-test.js | 289 ------- test/verbs/sample-test.js | 157 ---- test/verbs/select-test.js | 214 ----- test/verbs/slice-test.js | 88 -- test/verbs/spread-test.js | 130 --- test/verbs/union-test.js | 65 -- test/verbs/unroll-test.js | 180 ---- 86 files changed, 16122 deletions(-) delete mode 100644 docs/api/expressions.md delete mode 100644 docs/api/extensibility.md delete mode 100644 docs/api/index.md delete mode 100644 docs/api/op.md delete mode 100644 docs/api/table.md delete mode 100644 docs/api/verbs.md delete mode 100644 docs/assets/logo-1280-640.png delete mode 100644 docs/assets/logo-1280.png delete mode 100644 docs/assets/logo-1280.svg delete mode 100644 docs/assets/logo.svg delete mode 100644 docs/index.md delete mode 100644 perf/arrow-perf.js delete mode 100644 perf/csv-perf.js delete mode 100644 perf/data-gen.js delete mode 100644 perf/derive-perf.js delete mode 100644 perf/escape-perf.js delete mode 100644 perf/filter-perf.js delete mode 100644 perf/rollup-perf.js delete mode 100644 perf/table-perf.js delete mode 100644 perf/time.js delete mode 100644 test/arrow/data-from-test.js delete mode 100644 test/arrow/profiler-test.js delete mode 100644 test/expression/params-test.js delete mode 100644 test/expression/parse-test.js delete mode 100644 test/format/data/beers.csv delete mode 100644 test/format/data/cols-only.json delete mode 100644 test/format/data/cols-schema.json delete mode 100644 test/format/data/flights.arrow delete mode 100644 test/format/data/rows.json delete mode 100644 test/format/format-value-test.js delete mode 100644 test/format/from-arrow-test.js delete mode 100644 test/format/from-csv-test.js delete mode 100644 test/format/from-fixed-test.js delete mode 100644 test/format/from-json-test.js delete mode 100644 test/format/load-file-test.js delete mode 100644 test/format/load-file-url-test.js delete mode 100644 test/format/load-url-test.js delete mode 100644 test/format/to-arrow-test.js delete mode 100644 test/format/to-csv-test.js delete mode 100644 test/format/to-html-test.js delete mode 100644 test/format/to-json-test.js delete mode 100644 test/format/to-markdown-test.js delete mode 100644 test/groupby-equal.js delete mode 100644 test/helpers/agg-test.js delete mode 100644 test/helpers/escape-test.js delete mode 100644 test/helpers/names-test.js delete mode 100644 test/op/array-test.js delete mode 100644 test/op/date-test.js delete mode 100644 test/op/json-test.js delete mode 100644 test/op/math-test.js delete mode 100644 test/op/object-test.js delete mode 100644 test/op/op-test.js delete mode 100644 test/op/row-object-test.js delete mode 100644 test/op/string-test.js delete mode 100644 test/query/query-test.js delete mode 100644 test/query/util.js delete mode 100644 test/query/verb-test.js delete mode 100644 test/query/verb-to-ast-test.js delete mode 100644 test/register-test.js delete mode 100644 test/table-equal.js delete mode 100644 test/table/bitset-test.js delete mode 100644 test/table/column-table-test.js delete mode 100644 test/table/columns-from-test.js delete mode 100644 test/verbs/concat-test.js delete mode 100644 test/verbs/dedupe-test.js delete mode 100644 test/verbs/derive-test.js delete mode 100644 test/verbs/filter-test.js delete mode 100644 test/verbs/fold-test.js delete mode 100644 test/verbs/groupby-test.js delete mode 100644 test/verbs/impute-test.js delete mode 100644 test/verbs/join-filter-test.js delete mode 100644 test/verbs/join-test.js delete mode 100644 test/verbs/lookup-test.js delete mode 100644 test/verbs/orderby-test.js delete mode 100644 test/verbs/pivot-test.js delete mode 100644 test/verbs/reduce-test.js delete mode 100644 test/verbs/reify-test.js delete mode 100644 test/verbs/relocate-test.js delete mode 100644 test/verbs/rename-test.js delete mode 100644 test/verbs/rollup-test.js delete mode 100644 test/verbs/sample-test.js delete mode 100644 test/verbs/select-test.js delete mode 100644 test/verbs/slice-test.js delete mode 100644 test/verbs/spread-test.js delete mode 100644 test/verbs/union-test.js delete mode 100644 test/verbs/unroll-test.js diff --git a/docs/api/expressions.md b/docs/api/expressions.md deleted file mode 100644 index 39870f81..00000000 --- a/docs/api/expressions.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Expressions \| Arquero API Reference ---- -# Arquero API Reference - -[Top-Level](/arquero/api) | [Table](table) | [Verbs](verbs) | [Op Functions](op) | [**Expressions**](expressions) | [Extensibility](extensibility) - -* [Table Expressions](#table) - * [Limitations](#limitations) - * [Column Shorthands](#column-shorthands) - * [Aggregate & Window Shorthands](#aggregate-window-shorthands) -* [Two-Table Expressions](#two-table) - * [Column Shorthands](#two-table-column-shorthands) -* [Why are only `op` functions supported?](#why-op-functions-only) - -
- -## Table Expressions - -Most Arquero [verbs](./verbs) accept *table expressions*: functions defined over table column values. For example, the `derive` verb creates new columns based on the provided expressions: - -```js -table.derive({ - raise: d => op.pow(d.col1, d.col2), - 'col diff': d => d.col1 - d['base col'] -}) -``` - -In the example above, the two [arrow function expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) are table expressions. The input argument `d` represents a row of the data table, whose properties are column names. Table expressions can include standard JavaScript expressions and invoke functions defined on the [`op` object](op), which, depending on the context, may include [standard](op#functions), [aggregate](op#aggregate-functions), or [window](op#window-functions) functions. - -At first glance table expressions look like normal JavaScript functions... but hold on! Under the hood, Arquero takes a set of function definitions, maps them to strings, then parses, rewrites, and compiles them to efficiently manage data internally. From Arquero's point of view, the following examples are all equivalent: - -1. `function(d) { return op.sqrt(d.value); }` -2. `d => op.sqrt(d.value)` -3. `({ value }) => op.sqrt(value)` -4. `d => sqrt(d.value)` -5. `d => aq.op.sqrt(d.value)` -6. `"d => op.sqrt(d.value)"` -7. `"sqrt(d.value)"` - -Examples 1 through 5 are function definitions, while examples 6 and 7 are string literals. Let's walk through each: - -* *Examples 1-3*: These are just different variants of writing standard JavaScript functions: traditional function definitions, arrow function definitions, and [destructured arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). -* *Examples 4-5*: While it is conventional to use the [`op` object](./op) to invoke functions, it is not required. For *any* function invocation, the function name will be looked up on the `op` object, even if the function is called directly (as in Example 4) or as the result of a nested property lookup (Example 5). Internally, Arquero's parser doesn't care if you call `sqrt()`, `op.sqrt()`, or `aq.op.sqrt()`; any will work. That said, using an explicit `op` object avoids errors and allows linting and auto-complete to proceed unimpeded. -* *Examples 6-7*: To parse table expressions, Arquero first maps input functions to source code strings. We can simply skip this step and pass a string directly, as in Example 6. For Example 7, the string contains an expression but not a function definition. In this case, an implicit function definition is assumed and the row identifier defaults to `d`; using an identifier other than `d` will fail. In contrast, with an explicit function definition you are free to rename the argument as you see fit. - -### Limitations - -A number of JavaScript features are not allowed in table expressions, including internal function definitions, variable updates, and `for` loops. The *only* function calls allowed are those provided by the `op` object. ([Why? Read below for more...](#why-op-functions-only)) Most notably, parsed table expressions **do not support closures**. As a result, table expressions can not access variables defined in the enclosing scope. - -To include external variables in a table expression, use the [`params()` method](table#params) method to bind a parameter value to a table context. Parameters can then be accessed by including a second argument to a table expression; all bound parameters are available as properties of that argument (default name `$`): - -```js -table - .params({ threshold: 5 }) - .filter((d, $) => d.value < $.threshold) -``` - -To pass in a standard JavaScript function that will be called directly (rather than parsed and rewritten), use the [`escape()` expression helper](./#escape). Escaped functions *do* support closures and so can refer to variables defined in an enclosing scope. However, escaped functions do not support aggregate or window operations; they also sidestep internal optimizations and result in an error when attempting to serialize Arquero queries (for example, to pass transformations to a worker thread). - -```js -const threshold = 5; -table.filter(aq.escape(d => d.value < threshold)) -``` - -Alternatively, for programmatic generation of table expressions one can fallback to a generating a string – rather than a proper function definition – and use that instead: - -```js -// note: threshold must safely coerce to a string! -const threshold = 5; -table.filter(`d => d.value < ${threshold}`) -``` - -### Column Shorthands - -Some verbs – including [`groupby()`](verbs#groupby), [`orderby()`](verbs#orderby), [`fold()`](verbs#fold), [`pivot()`](verbs#pivot), and [`join()`](verbs#join) – accept shorthands such as column name strings. Given a table with columns `colA` and `colB` (in that order), the following are equivalent: - -1. `table.groupby('colA', 'colB')` - Refer to columns by name -2. `table.groupby(['colA', 'colB'])` - Use an explicit array of names -3. `table.groupby(0, 1)` - Refer to columns by index -4. `table.groupby(aq.range(0, 1))` - Use a column [range](index#range) helper -5. `table.groupby({ colA: d => d.colA, colB: d => d.colB })` - Explicit table expressions - -Underneath the hood, all of these variants are grounded down to table expressions. - -### Aggregate & Window Shorthands - -For [aggregate](op#aggregate-functions) and [window](op#window-functions) functions, use of the `op` object outside of a table expression allows the use of shorthand references. The following examples are equivalent: - -1. `d => op.mean(d.value)` - Standard table expression -2. `op.mean('value')` - Shorthand table expression generator - -The second example produces an object that, when coerced to a string, generates `'d => op.mean(d["value"])'` as a result. - -
- -## Two-Table Expressions - -For [join](verbs#joins) verbs, Arquero also supports *two-table* table expressions. Two-table expressions have an expanded signature that accepts two rows as input, one from the "left" table and one from the "right" table. - -```js -table.join(otherTable, (a, b) => op.equal(a.key, b.key)) -``` - -The use of aggregate and window functions is not allowed within two-table expressions. Otherwise, two-table expressions have the same capabilities and limitations as normal (single-table) table expressions. - -Bound parameters can be accessed by including a third argument: - -```js -table - .params({ threshold: 1.5 }) - .join(otherTable, (a, b, $) => op.abs(a.value - b.value) < $.threshold) -``` - -### Two-Table Column Shorthands - -Rather than writing explicit two-table expressions, join verbs can also accept column shorthands in the form of a two-element array: the first element of the array is either a string or string array with columns in the first (left) table, whereas the second element indicates columns in the second (right) table. - -Given two tables – one with columns `x`, `y` and the other with columns `u`, `v` – the following examples are equivalent: - -1. `table.join(other, ['x', 'u'], [['x', 'y'], 'v'])` -2. `table.join(other, [['x'], ['u']], [['x', 'y'], ['v']])` -3. `table.join(other, ['x', 'u'], [aq.all(), aq.not('u')])` - -All of which are in turn equivalent to using the following two-table expressions: - -```js -table.join(other, ['x', 'u'], { - x: (a, b) => a.x, - y: (a, b) => a.y, - v: (a, b) => b.v -}) -``` - -## Why are only `op` functions supported? - -Any function that is callable within an Arquero table expression must be defined on the `op` object, either as a built-in function or added via the [extensibility API](extensibility). Why is this the case? - -As [described earlier](#table), Arquero table expressions can look like normal JavaScript functions, but are treated specially: their source code is parsed and new custom functions are generated to process data. This process prevents the use of [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures), such as referencing functions or values defined externally to the expression. - -So why do we do this? Here are a few reasons: - -* **Performance**. After parsing an expression, Arquero performs code generation, often creating more performant code in the process. This level of indirection also allows us to generate optimized expressions for certain inputs, such as Apache Arrow data. - -* **Flexibility**. Providing our own parsing also allows us to introduce new kinds of backing data without changing the API. For example, we could add support for different underlying data formats and storage layouts. - -* **Portability**. While a common use case of Arquero is to query data directly in the same JavaScript runtime, Arquero verbs can also be [*serialized as queries*](./#queries): one can specify verbs in one environment, but then send them to another environment for processing. For example, the [arquero-worker](https://github.com/uwdata/arquero-worker) package sends queries to a worker thread, while the [arquero-sql](https://github.com/chanwutk/arquero-sql) package sends them to a backing database server. As custom methods may not be defined in those environments, Arquero is designed to make this translation between environments possible and easier to reason about. - -* **Safety**. Arquero table expressions do not let you call methods defined on input data values. For example, to trim a string you must call `op.trim(str)`, not `str.trim()`. Again, this aids portability: otherwise unsupported methods defined on input data elements might "sneak" in to the processing. Invoking arbitrary methods may also lead to security vulnerabilities when allowing untrusted third parties to submit queries into a system. - -* **Discoverability**. Defining all functions on a single object provides a single catalog of all available operations. In most IDEs, you can simply type `op.` (and perhaps hit the tab key) to the see a list of all available functions and benefit from auto-complete! - -Of course, one might wish to make different trade-offs. Arquero is designed to support common use cases while also being applicable to more complex production setups. This goal comes with the cost of more rigid management of functions. However, Arquero can be extended with custom variables, functions, and even new table methods or verbs! As starting points, see the [params](table#params), [addFunction](extensibility#addFunction), and [addTableMethod](extensibility#addTableMethod) functions to introduce external variables, register new `op` functions, or extend tables with new methods. - -All that being said, not all use cases require portability, safety, etc. For such cases Arquero provides an escape hatch: use the [`escape()` expression helper](./#escape) to apply a standard JavaScript function *as-is*, skipping any internal parsing and code generation. \ No newline at end of file diff --git a/docs/api/extensibility.md b/docs/api/extensibility.md deleted file mode 100644 index c54e0249..00000000 --- a/docs/api/extensibility.md +++ /dev/null @@ -1,280 +0,0 @@ ---- -title: Extensibility \| Arquero API Reference ---- -# Arquero API Reference - -[Top-Level](/arquero/api) | [Table](table) | [Verbs](verbs) | [Op Functions](op) | [Expressions](expressions) | [**Extensibility**](extensibility) - -* [Op Functions](#op-functions) - * [addFunction](#addFunction) - * [addAggregateFunction](#addAggregateFunction) - * [addWindowFunction](#addWindowFunction) -* [Table Methods](#table-methods) - * [addTableMethod](#addTableMethod) - * [addVerb](#addVerb) -* [Package Bundles](#packages) - * [addPackage](#addPackage) - -
- -## Op Functions - -Add new functions for use in table expressions. - -
# -aq.addFunction([name,] fn[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a function for use within table expressions. If only a single argument is provided, it will be assumed to be a function and the system will try to extract its name. Throws an error if a function with the same name is already registered and the override option is not specified, or if no name is provided and the input function is anonymous. After registration, the function will be accessible via the [`op`](#op) object. - -Also see the [`escape()` expression helper](./#escape) for a lightweight alternative that allows access to functions defined in an enclosing scope. - -* *name*: The name to use for the function. -* *fn*: A standard JavaScript function. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added function is allowed to override an existing function with the same name. - -*Examples* - -```js -// add a function named square, which is then available as op.square() -// if a 'square' function already exists, this results in an error -aq.addFunction('square', x => x * x); -``` - -```js -// add a function named square, override any previous 'square' definition -aq.addFunction('square', x => x * x, { override: true }); -``` - -```js -// add a function using its existing name -aq.addFunction(function square(x) { return x * x; }); -``` - - -
# -aq.addAggregateFunction(name, def[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a custom aggregate function. Throws an error if a function with the same name is already registered and the override option is not specified. After registration, the operator will be accessible via the [`op`](#op) object. - -In addition to column values, internally aggregate functions are passed a `state` object that tracks intermediate values throughout the aggregation. Each [groupby](verbs/#groupby) group receives a different state object. The state object always has a `count` property (total number of values, including invalid values) and a `valid` property (number of values that are not `null`, `undefined`, or `NaN`). Each aggregate operator may write intermediate values to the `state` object. Follow the property naming convention of using the aggregate function name as a property name prefix to avoid namespace collisions! For example, the `mean` aggregate function writes to the properties `state.mean` and `state.mean_d`. - -The `rem` function of an aggregate definition is used to support rolling window calculations. It is safe to define `rem` as a no-op (`() => {}`) if the aggregate is never used in the context of a rolling window frame. - -* *name*: The name to use for the aggregate function. -* *def*: An aggregate operator definition object: - * *create*: A creation function that takes non-field parameter values as input and returns a new aggregate operator instance. An aggregate operator instance should have four methods: *init(state)* to initialize any operator state, *add(state, value)* to add a value to the aggregate, *rem(state, value)* to remove a value from the aggregate, and *value(state)* to retrieve the current operator output value. The *state* parameter is a normal object for tracking all state information for a shared set of input field values. - * *param*: Two-element array containing the counts of input fields and additional parameters, respectively. - * *req*: Names of aggregate operators required by this one. - * *stream*: Names of operators required by this one for streaming operations (value removes), used during windowed aggregations. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added function is allowed to override an existing function with the same name. - -*Examples* - -```js -// add an aggregate function that computes a sum of squares -// the "rem" method is only needed for windowed aggregates -aq.addAggregateFunction('sumsq', { - create: () => ({ - init: state => state.sumsq = 0, - add: (state, value) => state.sumsq += value * value, - rem: (state, value) => state.sumsq -= value * value, - value: state => state.sumsq - }), - param: [1, 0] // 1 field input, 0 extra parameters -}); - -aq.table({ x: [1, 2, 3] }) - .rollup({ ssq: op.sumsq('x') } // { ssq: [14] } -``` - - -
# -aq.addWindowFunction(name, def[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a custom window function. Throws an error if a function with the same name is already registered and the override option is not specified. After registration, the operator will be accessible via the [`op`](#op) object. - -* *name*: The name to use for the window function. -* *def*: A window operator definition object: - * *create*: A creation function that takes non-field parameter values as input and returns a new window operator instance. A window operator instance should have two methods: *init(state)* to initialize any operator state, and *value(state, field)* to retrieve the current operator output value. The *state* parameter is a [window state](https://github.com/uwdata/arquero/blob/master/src/engine/window/window-state.js) instance that provides access to underlying values and the sliding window frame. - * *param*: Two-element array containing the counts of input fields and additional parameters, respectively. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added function is allowed to override an existing function with the same name. - -*Examples* - -```js -// add a window function that outputs the minimum of a field value -// and the current sorted window index, plus an optional offset -aq.addWindowFunction('idxmin', { - create: (offset = 0) => ({ - init: () => {}, // do nothing - value: (w, f) => Math.min(w.value(w.index, f), w.index) + offset - }), - param: [1, 1] // 1 field input, 1 extra parameter -}); - -aq.table({ x: [4, 3, 2, 1] }) - .derive({ x: op.idxmin('x', 1) }) // { x: [1, 2, 3, 2] } -``` - -
- -## Table Methods - -Add new table-level methods or verbs. The [addTableMethod](#addTableMethod) function registers a new function as an instance method of tables only. The [addVerb](#addVerb) method registers a new transformation verb with both tables and serializable [queries](./#query). - -
# -aq.addTableMethod(name, method[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a custom table method, adding a new method with the given *name* to all table instances. The provided *method* must take a table as its first argument, followed by any additional arguments. - -This method throws an error if the *name* argument is not a legal string value. -To protect Arquero internals, the *name* can not start with an underscore (`_`) character. If a custom method with the same name is already registered, the override option must be specified to overwrite it. In no case may a built-in method be overridden. - -* *name*: The name to use for the table method. -* *method*: A function implementing the table method. This function should accept a table as its first argument, followed by any additional arguments. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added method is allowed to override an existing method with the same name. Built-in table methods can **not** be overridden; this flag applies only to methods previously added using the extensibility API. - -*Examples* - -```js -// add a table method named size, returning an array of row and column counts -aq.addTableMethod('size', table => [table.numRows(), table.numCols()]); -aq.table({ a: [1,2,3], b: [4,5,6] }).size() // [3, 2] -``` - -
# -aq.addVerb(name, method, params[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a custom transformation verb with the given *name*, adding both a table method and serializable [query](./#query) support. The provided *method* must take a table as its first argument, followed by any additional arguments. The required *params* argument describes the parameters the verb accepts. If you wish to add a verb to tables but do not require query serialization support, use [addTableMethod](#addTableMethod). - -This method throws an error if the *name* argument is not a legal string value. -To protect Arquero internals, the *name* can not start with an underscore (`_`) character. If a custom method with the same name is already registered, the override option must be specified to overwrite it. In no case may a built-in method be overridden. - -* *name*: The name to use for the table method. -* *method*: A function implementing the table method. This function should accept a table as its first argument, followed by any additional arguments. -* *params*: An array of schema descriptions for the verb parameters. These descriptors are needed to support query serialization. Each descriptor is an object with *name* (string-valued parameter name) and *type* properties (string-valued parameter type, see below). If a parameter has type `"Options"`, the descriptor can include an additional object-valued *props* property to describe any non-literal values, for which the keys are property names and the values are parameter types. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added method is allowed to override an existing method with the same name. Built-in verbs can **not** be overridden; this flag applies only to methods previously added using the extensibility API. - -*Parameter Types*. The supported parameter types are: - -* `"Expr"`: A single table expression, such as the input to [`filter()`](verbs/#filter). -* `"ExprList"`: A list of column references or expressions, such as the input to [`groupby()`](verbs/#groupby). -* `"ExprNumber"`: A number literal or numeric table expression, such as the *weight* option of [`sample()`](verbs/#sample). -* `"ExprObject"`: An object containing a set of expressions, such as the input to [`rollup()`](verbs/#rollup). -* `"JoinKeys"`: Input join keys, as in [`join()`](verbs/#join). -* `"JoinValues"`: Output join values, as in [`join()`](verbs/#join). -* `"Options"`: An options object of key-value pairs. If any of the option values are column references or table expressions, the descriptor should include a *props* property with property names as keys and parameter types as values. -* `"OrderKeys"`: A list of ordering criteria, as in [`orderby`](verbs/#orderby). -* `"SelectionList"`: A set of columns to select and potentially rename, as in [`select`](verbs/#select). -* `"TableRef"`: A reference to an additional input table, as in [`join()`](verbs/#join). -* `"TableRefList"`: A list of one or more additional input tables, as in [`concat()`](verbs/#concat). - -*Examples* - -```js -// add a bootstrapped confidence interval verb that -// accepts an aggregate expression plus options -aq.addVerb( - 'bootstrap_ci', - (table, expr, options = {}) => table - .params({ frac: options.frac || 1000 }) - .sample((d, $) => op.round($.frac * op.count()), { replace: true }) - .derive({ id: (d, $) => op.row_number() % $.frac }) - .groupby('id') - .rollup({ bs: expr }) - .rollup({ - lo: op.quantile('bs', options.lo || 0.025), - hi: op.quantile('bs', options.hi || 0.975) - }), - [ - { name: 'expr', type: 'Expr' }, - { name: 'options', type: 'Options' } - ] -); - -// apply the new verb -aq.table({ x: [1, 2, 3, 4, 6, 8, 9, 10] }) - .bootstrap_ci(op.mean('x')) -``` - -
- -## Package Bundles - -Extend Arquero with a bundle of functions, table methods, and/or verbs. - -
# -aq.addPackage(bundle[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/register.js) - -Register a *bundle* of extensions, which may include standard functions, aggregate functions, window functions, table methods, and verbs. If the input *bundle* has a key named `"arquero_package"`, the value of that property is used; otherwise the *bundle* object is used directly. This method is particularly useful for publishing separate packages of Arquero extensions and then installing them with a single method call. - -A package bundle has the following structure: - -```js -const bundle = { - functions: { ... }, - aggregateFunctions: { ... }, - windowFunctions: { ... }, - tableMethods: { ... }, - verbs: { ... } -}; -``` - -All keys are optional. For example, `functions` or `verbs` may be omitted. Each sub-bundle is an object of key-value pairs, where the key is the name of the function and the value is the function to add. - -The lone exception is the `verbs` bundle, which instead uses an object format with *method* and *params* keys, corresponding to the *method* and *params* arguments of [addVerb](#addVerb): - -```js -const bundle = { - verbs: { - name: { - method: (table, expr) => { ... }, - params: [ { name: 'expr': type: 'Expr' } ] - } - } -}; -``` - -The package method performs validation prior to adding any package content. The method will throw an error if any of the package items fail validation. See the [addFunction](#addFunction), [addAggregateFunction](#addAggregateFunction), [addWindowFunction](#windowFunction), [addTableMethod](#addTableMethod), and [addVerb](#addVerb) methods for specific validation criteria. The *options* argument can be used to specify if method overriding is permitted, as supported by each of the aforementioned methods. - -* *bundle*: The package bundle of extensions. -* *options*: Function registration options. - * *override*: Boolean flag (default `false`) indicating if the added method is allowed to override an existing method with the same name. Built-in table methods or verbs can **not** be overridden; for table methods and verbs this flag applies only to methods previously added using the extensibility API. - -*Examples* - -```js -// add a package -aq.addPackage({ - functions: { - square: x => x * x, - }, - tableMethods: { - size: table => [table.numRows(), table.numCols()] - } -}); -``` - -```js -// add a package, ignores any content outside of "arquero_package" -aq.addPackage({ - arquero_package: { - functions: { - square: x => x * x, - }, - tableMethods: { - size: table => [table.numRows(), table.numCols()] - } - } -}); -``` - -```js -// add a package from a separate library -aq.addPackage(require('arquero-arrow')); -``` \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md deleted file mode 100644 index b0ef702f..00000000 --- a/docs/api/index.md +++ /dev/null @@ -1,833 +0,0 @@ ---- -title: Arquero API Reference ---- -# Arquero API Reference - -[**Top-Level**](/arquero/api) | [Table](table) | [Verbs](verbs) | [Op Functions](op) | [Expressions](expressions) | [Extensibility](extensibility) - -* [Table Constructors](#table-constructors) - * [table](#table), [from](#from), [fromArrow](#fromArrow), [fromCSV](#fromCSV), [fromFixed](#fromFixed), [fromJSON](#fromJSON) -* [Table Input](#input) - * [load](#load), [loadArrow](#loadArrow), [loadCSV](#loadCSV), [loadFixed](#loadFixed), [loadJSON](#loadJSON) -* [Table Output](#output) - * [toArrow](#toArrow) -* [Expression Helpers](#expression-helpers) - * [op](#op), [agg](#agg), [escape](#escape) - * [bin](#bin), [desc](#desc), [frac](#frac), [rolling](#rolling), [seed](#seed) -* [Selection Helpers](#selection-helpers) - * [all](#all), [not](#not), [range](#range) - * [matches](#matches), [startswith](#startswith), [endswith](#endswith) - * [names](#names) -* [Queries](#queries) - * [query](#query), [queryFrom](#queryFrom) - - -
- -## Table Constructors - -Methods for creating new table instances. - -
# -aq.table(columns[, names]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/index.js) - -Create a new table for a set of named *columns*, optionally including an array of ordered column *names*. The *columns* input can be an object or Map with names for keys and columns for values, or an entry array of `[name, values]` tuples. - -JavaScript objects have specific key ordering rules: keys are enumerated in the order they are assigned, except for integer keys, which are enumerated first in sorted order. As a result, when using a standard object any *columns* entries with integer keys are listed first regardless of their order in the object definition. Use the *names* argument to ensure proper column ordering is respected. Map and entry arrays will preserve name ordering, in which case the *names* argument is only needed if you wish to specify an ordering different from the *columns* input. - -To bind together columns from multiple tables with the same number of rows, use the table [assign](table/#assign) method. To transform the table, use the various [verb](verbs) methods. - -* *columns*: An object or Map providing a named set of column arrays, or an entries array of the form `[[name, values], ...]`. Keys are column name strings; the enumeration order of the keys determines the column indices if the *names* argument is not provided. Column values should be arrays (or array-like values) of identical length. -* *names*: An array of column names, specifying the index order of columns in the table. - -*Examples* - -```js -// create a new table with 2 columns and 3 rows -aq.table({ colA: ['a', 'b', 'c'], colB: [3, 4, 5] }) -``` - -```js -// create a new table, preserving column order for integer names -aq.table({ key: ['a', 'b'], 1: [9, 8], 2: [7, 6] }, ['key', '1', '2']) -``` - -```js -// create a new table from a Map instance -const map = new Map() - .set('colA', ['a', 'b', 'c']) - .set('colB', [3, 4, 5]); -aq.table(map) -``` - - -
# -aq.from(values[, names]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/index.js) - -Create a new table from an existing object, such as an array of objects or a set of key-value pairs. - -* *values*: Data values to populate the table. If array-valued or iterable, imports rows for each non-null value, using the provided column names as keys for each row object. If no *names* are provided, the first non-null object's own keys are used. If an object or a Map, create a two-column table with columns for the input keys and values. -* *names*: Column names to include. For object or Map inputs, specifies the key and value column names. Otherwise, specifies the keys to look up on each row object. - -*Examples* - -```js -// from an array, create a new table with two columns and two rows -// akin to table({ colA: [1, 3], colB: [2, 4] }) -aq.from([ { colA: 1, colB: 2 }, { colA: 3, colB: 4 } ]) -``` - -```js -// from an object, create a new table with 'key' and 'value columns -// akin to table({ key: ['a', 'b', 'c'], value: [1, 2, 3] }) -aq.from({ a: 1, b: 2, c: 3 }) -``` - -```js -// from a Map, create a new table with 'key' and 'value' columns -// akin to table({ key: ['d', 'e', 'f'], value: [4, 5, 6] }) -aq.from(new Map([ ['d', 4], ['e', 5], ['f', 6] ]) -``` - - -
# -aq.fromArrow(arrowTable[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/from-arrow.js) - -Create a new table backed by an [Apache Arrow](https://arrow.apache.org/docs/js/) table instance. The input *arrowTable* can either be an instantiated Arrow table instance or a byte array in the Arrow IPC format. - -For most data types, Arquero uses binary-encoded Arrow columns as-is with zero data copying. For columns containing string, list (array), or struct values, Arquero additionally memoizes value lookups to amortize access costs. For dictionary columns, Arquero unpacks columns with `null` entries or containing multiple record batches to optimize query performance. - -This method performs parsing only. To both load and parse an Arrow file, use [loadArrow](#loadArrow). - -* *arrowTable*: An [Apache Arrow](https://arrow.apache.org/docs/js/) data table or a byte array (e.g., [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) or [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)) in the Arrow IPC format. -* *options*: An Arrow import options object: - * *columns*: An ordered set of columns to import. The input may consist of: column name strings, column integer indices, objects with current column names as keys and new column names as values (for renaming), or a selection helper function such as [all](#all), [not](#not), or [range](#range)). - * *unpack*: _As of v2.3.0, this option is deprecated and ignored if specified. Instead, Arquero now efficiently handles Arrow columns internally._ A boolean flag (default `false`) to unpack binary-encoded Arrow data to standard JavaScript values. Unpacking can incur an upfront time and memory cost to extract data to new arrays, but can speed up later query processing by enabling faster data access. - -*Examples* - -```js -// encode input array-of-objects data as an Arrow table -const arrowTable = aq.toArrow([ - { x: 1, y: 3.4 }, - { x: 2, y: 1.6 }, - { x: 3, y: 5.4 }, - { x: 4, y: 7.1 }, - { x: 5, y: 2.9 } -]); - -// now access the Arrow-encoded data as an Arquero table -const dt = aq.fromArrow(arrowTable); -``` - - -
# -aq.fromCSV(text[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/from-csv.js) - -Parse a comma-separated values (CSV) *text* string into a table. Delimiters other than commas, such as tabs or pipes ('\|'), can be specified using the *options* argument. By default, automatic type inference is performed for input values; string values that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior set *options.autoType* to `false`, which will cause all columns to be loaded as strings. To perform custom parsing of input column values, use *options.parse*. - -This method performs parsing only. To both load and parse a CSV file, use [loadCSV](#loadCSV). - -* *text*: A string in a delimited-value format. -* *options*: A CSV format options object: - * *delimiter*: A single-character delimiter string between column values (default `','`). - * *decimal*: A single-character numeric decimal separator (default `'.'`). - * *header*: Boolean flag (default `true`) to specify the presence of a header row. If `true`, indicates the CSV contains a header row with column names. If `false`, indicates the CSV does not contain a header row and the columns are given the names `'col1'`, `'col2'`, etc unless the *names* option is specified. - * *names*: An array of column names to use for header-less CSV files. This option is ignored if the *header* option is `true`. - * *skip*: The number of lines to skip (default `0`) before reading data. - * *comment*: A string used to identify comment lines. Any lines that start with the comment pattern are skipped. - * *autoType*: Boolean flag (default `true`) for automatic type inference. - * *autoMax*: Maximum number of initial rows (default `1000`) to use for type inference. - * *parse*: Object of column parsing options. The object keys should be column names. The object values should be parsing functions to invoke to transform values upon input. - -*Examples* - -```js -// create table from an input CSV string -// akin to table({ a: [1, 3], b: [2, 4] }) -aq.fromCSV('a,b\n1,2\n3,4') -``` - -```js -// skip commented lines -aq.fromCSV('# a comment\na,b\n1,2\n3,4', { comment: '#' }) -``` - -```js -// skip the first line -aq.fromCSV('# a comment\na,b\n1,2\n3,4', { skip: 1 }) -``` - -```js -// override autoType with custom parser for column 'a' -// akin to table({ a: ['00152', '30219'], b: [2, 4] }) -aq.fromCSV('a,b\n00152,2\n30219,4', { parse: { a: String } }) -``` - -```js -// parse semi-colon delimited text with comma as decimal separator -aq.fromCSV('a;b\nu;-1,23\nv;3,45e5', { delimiter: ';', decimal: ',' }) -``` - -```js -// create table from an input CSV loaded from 'url' -// alternatively, use the loadCSV method -aq.fromCSV(await fetch(url).then(res => res.text())) -``` - - -
# -aq.fromFixed(text[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/from-fixed-width.js) - -Parse a fixed-width file *text* string into a table. By default, automatic type inference is performed for input values; string values that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior set *options.autoType* to `false`, which will cause all columns to be loaded as strings. To perform custom parsing of input column values, use *options.parse*. - -This method performs parsing only. To both load and parse a fixed-width file, use [loadFixed](#loadFixed). - -* *text*: A string in a fixed-width format. -* *options*: A format options object: - * *positions*: Array of [start, end] indices for fixed-width columns. - * *widths*: Array of fixed column widths. This option is ignored if the *positions* property is specified. - * *names*: An array of column names. The array length should match the length of the *positions* or *widths* array. If not specified or shorter than the other array, default column names are generated. - * *decimal*: A single-character numeric decimal separator (default `'.'`). - * *skip*: The number of lines to skip (default `0`) before reading data. - * *comment*: A string used to identify comment lines. Any lines that start with the comment pattern are skipped. - * *autoType*: Boolean flag (default `true`) for automatic type inference. - * *autoMax*: Maximum number of initial rows (default `1000`) to use for type inference. - * *parse*: Object of column parsing options. The object keys should be column names. The object values should be parsing functions to invoke to transform values upon input. - -*Examples* - -```js -// create table from an input fixed-width string -// akin to table({ u: ['a', 'b'], v: [1, 2] }) -aq.fromFixed('a1\nb2', { widths: [1, 1], names: ['u', 'v'] }) -``` - - -
# -aq.fromJSON(data) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/from-json.js) - -Parse JavaScript Object Notation (JSON) *data* into a table. String values in JSON column arrays that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior, set *options.autoType* to `false`. To perform custom parsing of input column values, use *options.parse*. Auto-type Date parsing is not performed for columns with custom parse options. - -This method performs parsing only. To both load and parse a JSON file, use [loadJSON](#loadJSON). - -The expected JSON data format is an object with column names for keys and column value arrays for values, like so: - -```json -{ - "colA": ["a", "b", "c"], - "colB": [1, 2, 3] -} -``` - -The data payload can also be provided as the *data* property of an enclosing object, with an optional *schema* property containing table metadata such as a *fields* array of ordered column information: - -```json -{ - "schema": { - "fields": [ - { "name": "colA" }, - { "name": "colB" } - ] - }, - "data": { - "colA": ["a", "b", "c"], - "colB": [1, 2, 3] - } -} -``` - -* *data*: A string in a supported JSON format, or a corresponding Object instance. -* *options*: A JSON format options object: - * *autoType*: Boolean flag (default `true`) for automatic type inference. If `false`, automatic date parsing for input JSON strings is disabled. - * *parse*: Object of column parsing options. The object keys should be column names. The object values should be parsing functions to invoke to transform values upon input. - -*Examples* - -```js -// create table from an input JSON string -// akin to table({ a: [1, 3], b: [2, 4] }) -aq.fromJSON('{"a":[1,3],"b":[2,4]}') -``` - -```js -// create table from an input JSON string -// akin to table({ a: [1, 3], b: [2, 4] }, ['a', 'b']) -aq.fromJSON(`{ - "schema":{"fields":[{"name":"a"},{"name":"b"}]}, - "data":{"a":[1,3],"b":[2,4]} -}`) -``` - -```js -// create table from an input JSON string loaded from 'url' -aq.fromJSON(await fetch(url).then(res => res.text())) -``` - -```js -// create table from an input JSON object loaded from 'url' -// disable autoType Date parsing -aq.fromJSON(await fetch(url).then(res => res.json()), { autoType: false }) -``` - - -
- -## Table Input - -Methods for loading files and creating new table instances. - -
# -aq.load(url[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/load-url.js) - -Load data from a *url* or file and return a Promise for a table. A specific format parser can be provided with the *using* option, otherwise CSV format is assumed. This method's *options* are also passed as the second argument to the format parser. - -When invoked in the browser, the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is used to load the *url*. When invoked in node.js, the *url* argument can also be a local file path. If the input *url* string has a network protocol at the beginning (e.g., `'http://'`, `'https://'`, *etc*.) it is treated as a URL and the [node-fetch](https://github.com/node-fetch/node-fetch) library is used. If the `'file://'` protocol is used, the rest of the string should be an absolute file path, from which a local file is loaded. Otherwise the input is treated as a path to a local file and loaded using the node.js `fs` module. - -This method provides a generic base for file loading and parsing, and can be used to customize table parsing. To load CSV data, use the more specific [loadCSV](#loadCSV) method. Similarly,to load JSON data use [loadJSON](#loadJSON) and to load Apache Arrow data use [loadArrow](#loadArrow). - -* *url*: The url or local file (node.js only) to load. -* *options*: File loading options. - * *using*: A function that accepts loaded data and an optional options object as input and returns an Arquero table. - * *as*: A string indicating the data type of the file. One of `'arrayBuffer'`, `'json'`, or `'text'` (the default). - * *fetch*: Options to pass to the HTTP [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) method when loading from a URL. - -*Examples* - -```js -// load table from a CSV file -const dt = await aq.load('data/table.csv', { using: aq.fromCSV }); -``` - -```js -// load table from a JSON file with column format -const dt = await aq.load('data/table.json', { as: 'json', using: aq.fromJSON }) -``` - -```js -// load table from a JSON file with array-of-objects format -const dt = await aq.load('data/table.json', { as: 'json', using: aq.from }) -``` - - -
# -aq.loadArrow(url[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/load-file.js) - -Load a file in the [Apache Arrow](https://arrow.apache.org/docs/js/) IPC format from a *url* and return a Promise for a table. - -This method performs both loading and parsing, and is equivalent to `aq.load(url, { as: 'arrayBuffer', using: aq.fromArrow })`. To instead create an Arquero table for an Apache Arrow dataset that has already been loaded, use [fromArrow](#fromArrow). - -When invoked in the browser, the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is used to load the *url*. When invoked in node.js, the *url* argument can also be a local file path. If the input *url* string has a network protocol at the beginning (e.g., `'http://'`, `'https://'`, *etc*.) it is treated as a URL and the [node-fetch](https://github.com/node-fetch/node-fetch) library is used. If the `'file://'` protocol is used, the rest of the string should be an absolute file path, from which a local file is loaded. Otherwise the input is treated as a path to a local file and loaded using the node.js `fs` module. - -* *url*: The url or local file (node.js only) to load. -* *options*: File loading and Arrow formatting options. Accepts the options of both the [load](#load) and [fromArrow](#fromArrow) methods, but ignores any settings for the load *as* and *using* options. - -*Examples* - -```js -// load table from an Apache Arrow file -const dt = await aq.loadArrow('data/table.arrow'); -``` - - -
# -aq.loadCSV(url[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/load-file.js) - -Load a comma-separated values (CSV) file from a *url* and return a Promise for a table. Delimiters other than commas, such as tabs or pipes ('\|'), can be specified using the *options* argument. By default, automatic type inference is performed for input values; string values that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior set *options.autoType* to `false`, which will cause all columns to be loaded as strings. To perform custom parsing of input column values, use *options.parse*. - -This method performs both loading and parsing, and is equivalent to `aq.load(url, { using: aq.fromCSV })`. To instead parse a CSV string that has already been loaded, use [fromCSV](#fromCSV). - -When invoked in the browser, the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is used to load the *url*. When invoked in node.js, the *url* argument can also be a local file path. If the input *url* string has a network protocol at the beginning (e.g., `'http://'`, `'https://'`, *etc*.) it is treated as a URL and the [node-fetch](https://github.com/node-fetch/node-fetch) library is used. If the `'file://'` protocol is used, the rest of the string should be an absolute file path, from which a local file is loaded. Otherwise the input is treated as a path to a local file and loaded using the node.js `fs` module. - -* *url*: The url or local file (node.js only) to load. -* *options*: File loading and CSV formatting options. Accepts the options of both the [load](#load) and [fromCSV](#fromCSV) methods, but ignores any settings for the load *as* and *using* options. - -*Examples* - -```js -// load table from a CSV file -const dt = await aq.loadCSV('data/table.csv'); -``` - -```js -// load table from a tab-delimited file -const dt = await aq.loadCSV('data/table.tsv', { delimiter: '\t' }) -``` - - -
# -aq.loadFixed(url[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/load-file.js) - -Load a fixed-width file from a *url* and return a Promise for a table. By default, automatic type inference is performed for input values; string values that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior set *options.autoType* to `false`, which will cause all columns to be loaded as strings. To perform custom parsing of input column values, use *options.parse*. - -This method performs both loading and parsing, and is equivalent to `aq.load(url, { using: aq.fromFixed })`. To instead parse a fixed width string that has already been loaded, use [fromFixed](#fromFixed). - -When invoked in the browser, the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is used to load the *url*. When invoked in node.js, the *url* argument can also be a local file path. If the input *url* string has a network protocol at the beginning (e.g., `'http://'`, `'https://'`, *etc*.) it is treated as a URL and the [node-fetch](https://github.com/node-fetch/node-fetch) library is used. If the `'file://'` protocol is used, the rest of the string should be an absolute file path, from which a local file is loaded. Otherwise the input is treated as a path to a local file and loaded using the node.js `fs` module. - -* *url*: The url or local file (node.js only) to load. -* *options*: File loading and fixed-width formatting options. Accepts the options of both the [load](#load) and [fromFixed](#fromFixed) methods, but ignores any settings for the load *as* and *using* options. - -*Examples* - -```js -// load table from a fixed-width file -const dt = await aq.loadFixed('a1\nb2', { widths: [1, 1], names: ['u', 'v'] }); -``` - - -
# -aq.loadJSON(url[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/load-file.js) - -Load a JavaScript Object Notation (JSON) file from a *url* and return a Promise for a table. If the loaded JSON is array-valued, an array-of-objects format is assumed and the [from](#from) method is used to construct the table. Otherwise, a column object format (as produced by [toJSON](table/#toJSON)) is assumed and the [fromJSON](#fromJSON) method is applied. - -This method performs both loading and parsing, similar to `aq.load(url, { as: 'json', using: aq.fromJSON })`. To instead parse JSON data that has already been loaded, use either [from](#from) or [fromJSON](#fromJSON). - -When parsing using [fromJSON](#fromJSON), string values in JSON column arrays that match the ISO standard date format are parsed into JavaScript Date objects. To disable this behavior, set *options.autoType* to `false`. To perform custom parsing of input column values, use *options.parse*. Auto-type Date parsing is not performed for columns with custom parse options. - -When invoked in the browser, the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is used to load the *url*. When invoked in node.js, the *url* argument can also be a local file path. If the input *url* string has a network protocol at the beginning (e.g., `'http://'`, `'https://'`, *etc*.) it is treated as a URL and the [node-fetch](https://github.com/node-fetch/node-fetch) library is used. If the `'file://'` protocol is used, the rest of the string should be an absolute file path, from which a local file is loaded. Otherwise the input is treated as a path to a local file and loaded using the node.js `fs` module. - -* *url*: The url or local file (node.js only) to load. -* *options*: File loading and JSON formatting options. Accepts the options of both the [load](#load) and [fromJSON](#fromJSON) methods, but ignores any settings for the load *as* and *using* options. Options for [fromJSON](#fromJSON) are ignored when [from](#from) is used for parsing. - -*Examples* - -```js -// load table from a JSON file -const dt = await aq.loadJSON('data/table.json'); -``` - -```js -// load table from a JSON file, disable Date autoType -const dt = await aq.loadJSON('data/table.json', { autoType: false }) -``` - - -
- -## Table Output - -Methods for writing table data to an output format. Most output methods are defined as [table methods](table#output), not in the top level namespace. - -
# -aq.toArrow(data[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/arrow/encode/index.js) - -Create an [Apache Arrow](https://arrow.apache.org/docs/js/) table for the input *data*. The input data can be either an [Arquero table](#table) or an array of standard JavaScript objects. This method will throw an error if type inference fails or if the generated columns have differing lengths. For Arquero tables, this method can instead be invoked as [table.toArrow()](table#toArrow). - -* *data*: An input dataset to convert to Arrow format. If array-valued, the data should consist of an array of objects where each entry represents a row and named properties represent columns. Otherwise, the input data should be an [Arquero table](#table). -* *options*: Options for Arrow encoding. - * *columns*: Ordered list of column names to include. If function-valued, the function should accept the input *data* as a single argument and return an array of column name strings. - * *limit*: The maximum number of rows to include (default `Infinity`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *types*: An optional object indicating the [Arrow data type](https://arrow.apache.org/docs/js/enums/type.html) to use for named columns. If specified, the input should be an object with column names for keys and Arrow data types for values. If a column's data type is not explicitly provided, type inference will be performed. - - Type values can either be instantiated Arrow [DataType](https://arrow.apache.org/docs/js/classes/datatype.html) instances (for example, `new Float64()`,`new DateMilliseconds()`, *etc.*) or type enum codes (`Type.Float64`, `Type.Date`, `Type.Dictionary`). For convenience, arquero re-exports the apache-arrow `Type` enum object (see examples below). High-level types map to specific data type instances as follows: - - * `Type.Date` → `new DateMilliseconds()` - * `Type.Dictionary` → `new Dictionary(new Utf8(), new Int32())` - * `Type.Float` → `new Float64()` - * `Type.Int` → `new Int32()` - * `Type.Interval` → `new IntervalYearMonth()` - * `Type.Time` → `new TimeMillisecond()` - - Types that require additional parameters (including `List`, `Struct`, and `Timestamp`) can not be specified using type codes. Instead, use data type constructors from apache-arrow, such as `new List(new Int32())`. - -*Examples* - -Encode Arrow data from an input Arquero table: - -```js -const { table, toArrow, Type } = require('arquero'); - -// create Arquero table -const dt = table({ - x: [1, 2, 3, 4, 5], - y: [3.4, 1.6, 5.4, 7.1, 2.9] -}); - -// encode as an Arrow table (infer data types) -// here, infers Uint8 for 'x' and Float64 for 'y' -// equivalent to dt.toArrow() -const at1 = toArrow(dt); - -// encode into Arrow table (set explicit data types) -// equivalent to dt.toArrow({ types: { ... } }) -const at2 = toArrow(dt, { - types: { - x: Type.Uint16, - y: Type.Float32 - } -}); - -// serialize Arrow table to a transferable byte array -const bytes = at1.serialize(); -``` - -Encode Arrow data from an input object array: - -```js -const { toArrow } = require('arquero-arrow'); - -// encode object array as an Arrow table (infer data types) -const at = toArrow([ - { x: 1, y: 3.4 }, - { x: 2, y: 1.6 }, - { x: 3, y: 5.4 }, - { x: 4, y: 7.1 }, - { x: 5, y: 2.9 } -]); -``` - - -
- -## Expression Helpers - -Methods for invoking or modifying table expressions. - -
# -aq.op · [Source](https://github.com/uwdata/arquero/blob/master/src/op/op-api.js) - -All table expression operations, including standard functions, aggregate functions, and window functions. See the [Operations API Reference](op) for documentation of all available functions. - -
# -aq.agg(table, expression) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/helpers/agg.js) - -Compute a single aggregate value for a table. This method is a convenient shortcut for ungrouping a table, applying a [rollup](verbs/#rollup) verb for a single aggregate expression, and extracting the resulting aggregate value. - -* *table*: An Arquero table. -* *expression*: An aggregate-valued table expression. Aggregate functions are permitted, and will take into account any [orderby](#orderby) settings. Window functions are not permitted and any [groupby](#groupby) settings will be ignored. - -*Examples* - -```js -aq.agg(aq.table({ a: [1, 2, 3] }), op.max('a')) // 3 -``` - -```js -aq.agg(aq.table({ a: [1, 3, 5] }), d => [op.min(d.a), op.max('a')]) // [1, 5] -``` - - -
# -aq.escape(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/escape.js) - -Annotate a JavaScript function or *value* to bypass Arquero's default table expression handling. Escaped values enable the direct use of JavaScript functions to process row data: no internal parsing or code generation is performed, and so [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) and arbitrary function invocations are supported. Escaped values provide a lightweight alternative to [table params](https://uwdata.github.io/arquero/api/table#params) and [function registration](https://uwdata.github.io/arquero/api/extensibility#addFunction) to access variables in enclosing scopes. - -An escaped value can be applied anywhere Arquero accepts [single-table table expressions](https://uwdata.github.io/arquero/api/expressions#table), including the [derive](verbs/#derive), [filter](verbs/#filter), and [spread](verbs/#spread) verbs. In addition, any of the [standard `op` functions](https://uwdata.github.io/arquero/api/op#functions) can be used within an escaped function. However, aggregate and window `op` functions are not supported. Also note that using escaped values will break [serialization of Arquero queries to worker threads](https://github.com/uwdata/arquero-worker). - -* *value*: A literal value or a function that is passed a row object and params object as input. Aggregate and window `op` functions are not permitted. - -*Examples* - -```js -// filter based on a variable defined in the enclosing scope -const thresh = 5; -aq.table({ a: [1, 4, 9], b: [1, 2, 3] }) - .filter(aq.escape(d => d.a < thresh)) - // { a: [1, 4], b: [1, 2] } -``` - -```js -// apply a parsing function defined in the enclosing scope -const parseMDY = d3.timeParse('%m/%d/%Y'); -aq.table({ date: ['1/1/2000', '06/01/2010', '12/10/2020'] }) - .derive({ date: aq.escape(d => parseMDY(d.date)) }) - // { date: [new Date(2000,0,1), new Date(2010,5,1), new Date(2020,11,10)] } -``` - -```js -// spread results from an escaped function that returns an array -const denom = 4; -aq.table({ a: [1, 4, 9] }) - .spread( - { a: aq.escape(d => [Math.floor(d.a / denom), d.a % denom]) }, - { as: ['div', 'mod'] } - ) - // { div: [0, 1, 2], mod: [1, 0, 1] } -``` - - -
# -aq.bin(name[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/bin.js) - -Generate a table expression that performs uniform binning of number values. The resulting string can be used as part of the input to table transformation verbs. - -* *name*: The name of the column to bin. -* *options*: A binning scheme options object: - * *maxbins*: The maximum number of bins. - * *minstep*: The minimum step size between bins. - * *nice*: Boolean flag (default `true`) indicating if bins should snap to "nice" human-friendly values such as multiples of ten. - * *offset*: Step offset for bin boundaries. The default (`0`) floors to the lower bin boundary. A value of `1` snaps one step higher to the upper bin boundary, and so on. - * *step*: The exact step size to use between bins. If specified, the *maxbins* and *minstep* options are ignored. - - *Examples* - -```js - aq.bin('colA', { maxbins: 20 }) - ``` - - -
# -aq.desc(expr) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/desc.js) - -Annotate a table expression (*expr*) to indicate descending sort order. - -* *expr*: The table expression to annotate. - -*Examples* - -```js -// sort colA in descending order -aq.desc('colA') -``` - -```js -// sort colA in descending order of lower case values -aq.desc(d => op.lower(d.colA)) -``` - -
# -aq.frac(fraction) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/frac.js) - -Generate a table expression that computes the number of rows corresponding to a given fraction for each group. The resulting string can be used as part of the input to the [sample](verbs/#sample) verb. - -* *fraction*: The fractional value. - - *Examples* - -```js - aq.frac(0.5) - ``` - -
# -aq.rolling(expr[, frame, includePeers]) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/rolling.js) - -Annotate a table expression to compute rolling aggregate or window functions within a sliding window frame. For example, to specify a rolling 7-day average centered on the current day, call *rolling* with a frame value of [-3, 3]. - -* *expr*: The table expression to annotate. -* *frame*:The sliding window frame offsets. Each entry indicates an offset from the current value. If an entry is non-finite, the frame will be unbounded in that direction, including all preceding or following values. If unspecified or `null`, the default frame `[-Infinity, 0]` includes the current values and all preceding values. -* *includePeers*: Boolean flag indicating if the sliding window frame should ignore peer (tied) values. If `false` (the default), the window frame boundaries are insensitive to peer values. If `true`, the window frame expands to include all peers. This parameter only affects operations that depend on the window frame: namely [aggregate functions](op/#aggregate-functions) and the [first_value](op/#first_value), [last_value](op/#last_value), and [nth_value](op/#last_values) window functions. - -*Examples* - -```js -// cumulative sum, with an implicit frame of [-Infinity, 0] -aq.rolling(d => op.sum(d.colA)) -``` - -```js -// centered 7-day moving average, assuming one value per day -aq.rolling(d => op.mean(d.colA), [-3, 3]) -``` - -```js -// retrieve last value in window frame, including peers (ties) -aq.rolling(d => op.last_value(d.colA), [-3, 3], true) -``` - -
# -aq.seed(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/util/random.js) - -Set a seed value for random number generation. If the seed is a valid number, a 32-bit [linear congruential generator](https://en.wikipedia.org/wiki/Linear_congruential_generator) with the given seed will be used to generate random values. If the seed is `null`, `undefined`, or not a valid number, the random number generator will revert to `Math.random`. - -* *seed*: The random seed value. Should either be an integer or a fraction between 0 and 1. - -*Examples* - -```js -// set random seed as an integer -aq.seed(12345) -``` - -```js -// set random seed as a fraction, maps to floor(fraction * (2 ** 32)) -aq.seed(0.5) -``` - -```js -// revert to using Math.random -aq.seed(null) -``` - -
- -## Selection Helpers - -Methods for selecting columns. The result of these methods can be passed as arguments to [select](verbs/#select), [groupby](verbs/#groupby), [join](verbs/#join) and other transformation verbs. - -
# -aq.all() · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Select all columns in a table. Returns a function-valued selection compatible with [select](verbs/#select). - -*Examples* - -```js -aq.all() -``` - - -
# -aq.not(selection) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Negate a column *selection*, selecting all other columns in a table. Returns a function-valued selection compatible with [select](verbs/#select). - -* *selection*: The selection to negate. May be a column name, column index, array of either, or a selection function (e.g., from [range](#range)). - -*Examples* - -```js -aq.not('colA', 'colB') -``` - -```js -aq.not(aq.range(2, 5)) -``` - - -
# -aq.range(start, stop) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Select a contiguous range of columns. Returns a function-valued selection compatible with [select](verbs/#select). - -* *start*: The name or integer index of the first selected column. -* *stop*: The name or integer index of the last selected column. - -*Examples* - -```js -aq.range('colB', 'colE') -``` - -```js -aq.range(2, 5) -``` - -
# -aq.matches(pattern) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Select all columns whose names match a pattern. Returns a function-valued selection compatible with [select](verbs/#select). - -* *pattern*: A string or [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) pattern to match. - -*Examples* - -```js -// contains the string 'col' -aq.matches('col') -``` - -```js -// has 'a', 'b', or 'c' as the first character (case-insensitve) -aq.matches(/^[abc]/i) -``` - -
# -aq.startswith(string) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Select all columns whose names start with a string. Returns a function-valued selection compatible with [select](verbs/#select). - -* *string*: The string to match at the start of the column name. - -*Examples* - -```js -aq.startswith('prefix_') -``` - -
# -aq.endswith(string) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/selection.js) - -Select all columns whose names end with a string. Returns a function-valued selection compatible with [select](verbs/#select). - -* *string*: The string to match at the end of the column name. - -*Examples* - -```js -aq.endswith('_suffix') -``` - -
# -aq.names(...names) · [Source](https://github.com/uwdata/arquero/blob/master/src/helpers/names.js) - -Select columns by index and rename them to the provided *names*. Returns a selection helper function that takes a table as input and produces a rename map as output. If the number of provided names is less than the number of table columns, the rename map will include entries for the provided names only. If the number of table columns is less than then number of provided names, the rename map will include only entries that cover the existing columns. - -* *names*: An ordered set of strings to use as the new column names. - -*Examples* - -```js -// helper to rename the first three columns to 'a', 'b', 'c' -aq.names('a', 'b', 'c') -``` - -```js -// names can also be passed as arrays -aq.names(['a', 'b', 'c']) -``` - -```js -// rename the first three columns, all other columns remain as-is -table.rename(aq.names(['a', 'b', 'c'])) -``` - -```js -// select and rename the first three columns, all other columns are dropped -table.select(aq.names(['a', 'b', 'c'])) -``` - - -
- - -## Queries - -Queries allow deferred processing. Rather than process a sequence of verbs immediately, they can be stored as a query. The query can then be *serialized* to be stored or transferred, or later *evaluated* against an Arquero table. - -
# -aq.query([tableName]) · [Source](https://github.com/uwdata/arquero/blob/master/src/query/query.js) - -Create a new query builder instance. The optional *tableName* string argument indicates the default name of a table the query should process, and is used only when evaluating a query against a catalog of tables. The resulting query builder includes the same [verb](verbs) methods as a normal Arquero table. However, rather than evaluating verbs immediately, they are stored as a list of verbs to be evaluated later. - -The method *query.evaluate(table, catalog)* will evaluate the query against an Arquero table. If provided, the optional *catalog* argument should be a function that takes a table name string as input and returns a corresponding Arquero table instance. The catalog will be used to lookup tables referenced by name for multi-table operations such as joins, or to lookup the primary table to process when the *table* argument to evaluate is `null` or `undefined`. - -Use the query *toObject()* method to serialize a query to a JSON-compatible object. Use the top-level [queryFrom](#queryFrom) method to parse a serialized query and return a new "live" query instance. - -*Examples* - -```js -// create a query, then evaluate it on an input table -const q = aq.query() - .derive({ add1: d => d.value + 1 }) - .filter(d => d.add1 > 5 ); - -const t = q.evaluate(table); -``` - -```js -// serialize a query to a JSON-compatible object -// the query can be reconstructed using aq.queryFrom -aq.query() - .derive({ add1: d => d.value + 1 }) - .filter(d => d.add1 > 5 ) - .toObject(); -``` - - -
# -aq.queryFrom(object) · [Source](https://github.com/uwdata/arquero/blob/master/src/query/query.js) - -Parse a serialized query *object* and return a new query instance. The input *object* should be a serialized query representation, such as those generated by the query *toObject()* method. - -*Examples* - -```js -// round-trip a query to a serialized form and back again -aq.queryFrom( - aq.query() - .derive({ add1: d => d.value + 1 }) - .filter(d => d.add1 > 5 ) - .toObject() -) -``` diff --git a/docs/api/op.md b/docs/api/op.md deleted file mode 100644 index 39a31fed..00000000 --- a/docs/api/op.md +++ /dev/null @@ -1,1261 +0,0 @@ ---- -title: Operations \| Arquero API Reference ---- -# Arquero API Reference - -[Top-Level](/arquero/api) | [Table](table) | [Verbs](verbs) | [**Op Functions**](op) | [Expressions](expressions) | [Extensibility](extensibility) - -* [Standard Functions](#functions) - * [Array Functions](#array-functions) - * [Date Functions](#date-functions) - * [JSON Functions](#json-functions) - * [Math Functions](#math-functions) - * [Object Functions](#object-functions) - * [String Functions](#string-functions) -* [Aggregate Functions](#aggregate-functions) - * [any](#any), [bins](#bins) - * [count](#count), [distinct](#distinct), [valid](#valid), [invalid](#invalid) - * [max](#max), [min](#min), [sum](#sum), [product](#product) - * [mean](#mean), [average](#average), [mode](#mode), [median](#median), [quantile](#quantile) - * [stdev](#stdev), [stdevp](#stdevp), [variance](#variance), [variancep](#variance) - * [corr](#corr), [covariance](#covariance), [covariancep](#covariancep) - * [array_agg](#array_agg), [array_agg_distinct](#array_agg_distinct), [object_agg](#object_agg), [map_agg](#map_agg), [entries_agg](#entries_agg) -* [Window Functions](#window-functions) - * [row_number](#row_number), [rank](#rank), [avg_rank](#avg_rank), [dense_rank](#dense_rank) - * [percent_rank](#percent_rank), [cume_dist](#cume_dist), [ntile](#ntile) - * [lag](#lag), [lead](#lead), [first_value](#first_value), [last_value](#last_value), [nth_value](#nth_value) - * [fill_down](#fill_down), [fill_up](#fill_up) - -
- -## Standard Functions - -Standard library of table expression functions. The [`op` object](./#op) exports these as standard JavaScript functions that behave the same whether invoked inside or outside a table expression context. - -### Array Functions - -
# -op.compact(array) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns a new compacted array with invalid values (`null`, `undefined`, `NaN`) removed. - -* *array*: The array to compact. - -*Examples* - -```js -op.compact([1, null, 2, undefined, NaN, 3]) // [ 1, 2, 3 ] -``` - -
# -op.concat(...values) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Merges two or more arrays in sequence, returning a new array. - -* *values*: The arrays to merge. - -
# -op.join(array[, delimiter]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Creates and returns a new string by concatenating all of the elements in an *array* (or an array-like object), separated by commas or a specified *delimiter* string. If the *array* has only one item, then that item will be returned without using the delimiter. - -* *array*: The input array value. -* *join*: The delimiter string (default `','`). - -
# -op.includes(array, value[, index]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Determines whether an *array* includes a certain *value* among its entries, returning `true` or `false` as appropriate. - -* *array*: The input array value. -* *value*: The value to search for. -* *index*: The integer index to start searching from (default `0`). - -
# -op.indexof(sequence, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns the first index at which a given *value* can be found in the *sequence* (array or string), or -1 if it is not present. - -* *sequence*: The input array or string value. -* *value*: The value to search for. - -
# -op.lastindexof(sequence, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns the last index at which a given *value* can be found in the *sequence* (array or string), or -1 if it is not present. - -* *sequence*: The input array or string value. -* *value*: The value to search for. - -
# -op.length(sequence) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns the length of the input *sequence* (array or string). - -* *sequence*: The input array or string value. - -
# -op.pluck(array, property) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns a new array in which the given *property* has been extracted for each element in the input *array*. - -* *array*: The input array value. -* *property*: The property name string to extract. Nested properties are not supported: the input `"a.b"` will indicates a property with that exact name, *not* a nested property `"b"` of the object `"a"`. - -
# -op.slice(sequence[, start, end]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns a copy of a portion of the input *sequence* (array or string) selected from *start* to *end* (*end* not included) where *start* and *end* represent the index of items in the sequence. - -* *sequence*: The input array or string value. -* *start*: The starting integer index to copy from (inclusive, default `0`). -* *end*: The ending integer index to copy from (exclusive, default `sequence.length`). - -
# -op.reverse(array) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/array.js) - -Returns a new array with the element order reversed: the first *array* element becomes the last, and the last *array* element becomes the first. The input *array* is unchanged. - -* *array*: The input array value. - -
# -op.sequence([start,] stop[, step]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/sequence.js) - -Returns an array containing an arithmetic sequence from the *start* value to the *stop* value, in *step* increments. If *step* is positive, the last element is the largest _start + i * step_ less than *stop*; if *step* is negative, the last element is the smallest _start + i * step_ greater than *stop*. If the returned array would contain an infinite number of values, an empty range is returned. - -* *start*: The starting value of the sequence (default `0`). -* *stop*: The stopping value of the sequence. The stop value is exclusive; it is not included in the result. -* *step*: The step increment between sequence values (default `1`). - - -
- -### Date Functions - -
# -op.now() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the current time as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. - -
# -op.timestamp(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the timestamp for a *date* as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. - -* *date*: The input Date value. - -
# -op.datetime([year, month, date, hours, minutes, seconds, milliseconds]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Creates and returns a new Date value. If no arguments are provided, the current date and time are used. - -* *year*: The year. -* *month* The (zero-based) month (default `0`). -* *date* The date within the month (default `1`). -* hours: The hour within the day (default `0`). -* *minutes*: The minute within the hour (default `0`). -* *seconds*: The second within the minute (default `0`). -* *milliseconds*: The milliseconds within the second (default `0`). - -
# -op.year(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the year of the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.quarter(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the zero-based quarter of the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.month(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the zero-based month of the specified *date* according to local time. A value of `0` indicates January, `1` indicates February, and so on. - -* *date*: The input Date or timestamp value. - -
# -op.week(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the Sunday-based week number of the year (0-53) for the specified *date* according to local time. All days in a new year preceding the first Sunday are considered to be in week 0. - -* *date*: The input Date or timestamp value. - -
# -op.date(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the date (day of month) of the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.dayofyear(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the day of the year (1-366) of the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.dayofweek(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the Sunday-based day of the week (0-6) of the specified *date* according to local time. A value of `0` indicates Sunday, `1` indicates Monday, and so on. - -* *date*: The input Date or timestamp value. - -
# -op.hours(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the hour of the day for the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.minutes(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the minute of the hour for the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.seconds(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the seconds of the minute for the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.milliseconds(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the milliseconds of the second for the specified *date* according to local time. - -* *date*: The input Date or timestamp value. - -
# -op.utcdatetime([year, month, date, hours, minutes, seconds, milliseconds]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Creates and returns a new Date value using [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). If no arguments are provided, the current date and time are used. - -* *year*: The year. -* *month* The (zero-based) month (default `0`). -* *date* The date within the month (default `1`). -* hours: The hour within the day (default `0`). -* *minutes*: The minute within the hour (default `0`). -* *seconds*: The second within the minute (default `0`). -* *milliseconds*: The milliseconds within the second (default `0`). - -
# -op.utcyear(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the year of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcquarter(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the zero-based quarter of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcmonth(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the zero-based month of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). A value of `0` indicates January, `1` indicates February, and so on. - -* *date*: The input Date or timestamp value. - -
# -op.utcweek(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the Sunday-based week number of the year (0-53) for the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). All days in a new year preceding the first Sunday are considered to be in week 0. - -* *date*: The input Date or timestamp value. - -
# -op.utcdate(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the date (day of month) of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcdayofyear(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the day of the year (1-366) of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcdayofweek(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the Sunday-based day of the week (0-6) of the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). A value of `0` indicates Sunday, `1` indicates Monday, and so on. - -* *date*: The input Date or timestamp value. - -
# -op.utchours(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the hour of the day for the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcminutes(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the minute of the hour for the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcseconds(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the seconds of the minute for the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.utcmilliseconds(date) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns the milliseconds of the second for the specified *date* according to [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). - -* *date*: The input Date or timestamp value. - -
# -op.format_date(date[, shorten]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted string for the given *date* in local timezone. The resulting string is compatible with [parse_date](#parse_date) and JavaScript's built-in [Date.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). - -* *date*: The input Date or timestamp value. -* *shorten*: A boolean flag (default `false`) indicating if the formatted string should be shortened if possible. For example, the local date `2001-01-01` will shorten from `"2001-01-01T00:00:00.000"` to `"2001-01-01T00:00"`. - -
# -op.format_utcdate(date[, shorten]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/date.js) - -Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted string for the given *date* in [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). The resulting string is compatible with [parse_date](#parse_date) and JavaScript's built-in [Date.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). - -* *date*: The input Date or timestamp value. -* *shorten*: A boolean flag (default `false`) indicating if the formatted string should be shortened if possible. For example, the UTC date `2001-01-01` will shorten from `"2001-01-01T00:00:00.000Z"` to `"2001-01-01"`. - -
- -### JSON Functions - -Functions for parsing and generating strings formatted using [JavaScript Object Notation (JSON)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON). - -
# -op.parse_json(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/json.js) - -Parses a string *value* in [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) format, constructing the JavaScript value or object described by the string. - -* *value*: The input string value. - -
# -op.to_json(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/json.js) - -Converts a JavaScript object or value to a [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) string. - -* *value*: The value to convert to a JSON string. - -
- -### Math Functions - -
# -op.bin(value, min, max, step[, offset]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/bin.js) - -Truncate a *value* to a bin boundary. Useful for creating equal-width histograms. Values outside the `[min, max]` range will be mapped to `-Infinity` (*value < min*) or `+Infinity` (*value > max*). - -* *value*: The number value to bin. -* *min*: The minimum bin boundary value. -* *max*: The maximum bin boundary value. -* *step*: The step size between bin boundaries. -* *offset*: Offset in steps (default `0`) by which to adjust the returned bin value. An offset of `1` returns the next boundary. - -
# -op.random() · [Source](https://github.com/uwdata/arquero/blob/master/src/util/random.js) - -Return a random floating point number between 0 (inclusive) and 1 (exclusive). By default uses [Math.random](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random). Use the [seed](./#seed) method to instead use a seeded random number generator. - -
# -op.is_nan(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Tests if the input *value* is not a number (`NaN`); equivalent to [Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN). The method will return `true` only if the input *value* is an actual numeric `NaN` value; it will return `false` for other types (booleans, strings, _etc_.). - -* *value*: The value to test. - -*Examples* - -```js -op.is_nan(NaN) // true -op.is_nan(0/0) // true -op.is_nan(op.sqrt(-1)) // true -``` - -```js -op.is_nan('foo') // false -op.is_nan(+'foo') // true, coerce to number first -``` - -```js -op.is_nan(true) // false -op.is_nan(+true) // false, booleans coerce to numbers -``` - -```js -op.is_nan(undefined) // false -op.is_nan(+undefined) // true, coerce to number first -``` - -```js -op.is_nan(null) // false -op.is_nan(+null) // false, null coerces to zero -``` - -
# -op.is_finite(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Tests if the input *value* is finite; equivalent to [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite). - -* *value*: The value to test. - -
# -op.abs(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the absolute value of the input *value*; equivalent to [Math.abs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs). - -* *value*: The input number value. - -
# -op.cbrt(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the cube root value of the input *value*; equivalent to [Math.cbrt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt). - -* *value*: The input number value. - -
# -op.ceil(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the ceiling of the input *value*, the nearest integer equal to or greater than the input; equivalent to [Math.ceil](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil). - -* *value*: The input number value. - -
# -op.clz32(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the number of leading zero bits in the 32-bit binary representation of a number *value*; equivalent to [Math.clz32](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32). - -* *value*: The input number value. - -
# -op.exp(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns *evalue*, where *e* is Euler's number, the base of the natural logarithm; equivalent to [Math.exp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp). - -* *value*: The input number value. - -
# -op.expm1(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns *evalue - 1*, where *e* is Euler's number, the base of the natural logarithm; equivalent to [Math.expm1](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1). - -* *value*: The input number value. - -
# -op.floor(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the floor of the input *value*, the nearest integer equal to or less than the input; equivalent to [Math.floor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor). - -* *value*: The input number value. - -
# -op.fround(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the nearest 32-bit single precision float representation of the input number *value*; equivalent to [Math.fround](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround). Useful for translating between 64-bit `Number` values and values from a `Float32Array`. - -* *value*: The input number value. - -
# -op.greatest(...values) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the greatest (maximum) value among the input *values*; equivalent to [Math.max](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max). This is _not_ an aggregate function, see [op.max](#max) to compute a maximum value across multiple rows. - -* *values*: Zero or more input values. - -
# -op.least(...values) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the least (minimum) value among the input *values*; equivalent to [Math.min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min). This is _not_ an aggregate function, see [op.min](#min) to compute a minimum value across multiple rows. - -* *values*: Zero or more input values. - -
# -op.log(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the natural logarithm (base *e*) of a number *value*; equivalent to [Math.log](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log). - -* *value*: The input number value. - -
# -op.log10(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the base 10 logarithm of a number *value*; equivalent to [Math.log10](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10). - -* *value*: The input number value. - -
# -op.log1p(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the natural logarithm (base *e*) of 1 + a number *value*; equivalent to [Math.log1p](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p). - -* *value*: The input number value. - -
# -op.log2(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the base 2 logarithm of a number *value*; equivalent to [Math.log2](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2). - -* *value*: The input number value. - -
# -op.pow(base, exponent) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the *base* raised to the *exponent* power, that is, *base**exponent*; equivalent to [Math.pow](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow). - -* *base*: The base number value. -* *exponent*: The exponent number value. - -
# -op.round(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the value of a number rounded to the nearest integer; ; equivalent to [Math.round](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round). - -* *value*: The input number value. - -
# -op.sign(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns either a positive or negative +/- 1, indicating the sign of the input *value*; equivalent to [Math.sign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign). - -* *value*: The input number value. - -
# -op.sqrt(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the square root of the input *value*; equivalent to [Math.sqrt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt). - -* *value*: The input number value. - -
# -op.trunc(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the integer part of a number by removing any fractional digits; equivalent to [Math.trunc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc). - -* *value*: The input number value. - -
# -op.degrees(radians) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Converts the input *radians* value to degrees. - -* *value*: The input number value in radians. - -
# -op.radians(radians) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Converts the input *degrees* value to radians. - -* *value*: The input number value in degrees. - -
# -op.acos(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the arc-cosine (in radians) of a number *value*; equivalent to [Math.acos](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos). - -* *value*: The input number value. - -
# -op.acosh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic arc-cosine of a number *value*; equivalent to [Math.acosh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh). - -* *value*: The input number value. - -
# -op.asin(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the arc-sine (in radians) of a number *value*; equivalent to [Math.asin](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin). - -* *value*: The input number value. - -
# -op.asinh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic arc-sine of a number *value*; equivalent to [Math.asinh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh). - -* *value*: The input number value. - -
# -op.atan(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the arc-tangent (in radians) of a number *value*; equivalent to [Math.atan](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan). - -* *value*: The input number value. - -
# -op.atan2(y, x) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (*x*, *y*); ; equivalent to [Math.atan2](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2). - -* *y*: The y coordinate of the point. -* *x*: The x coordinate of the point. - -
# -op.atanh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic arc-tangent of a number *value*; equivalent to [Math.atanh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh). - -* *value*: The input number value. - -
# -op.cos(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the cosine (in radians) of a number *value*; equivalent to [Math.cos](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos). - -* *value*: The input number value. - -
# -op.cosh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic cosine of a number *value*; equivalent to [Math.cosh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh). - -* *value*: The input number value. - -
# -op.sin(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the sine (in radians) of a number *value*; equivalent to [Math.sin](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin). - -* *value*: The input number value. - -
# -op.sinh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic sine of a number *value*; equivalent to [Math.sinh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh). - -* *value*: The input number value. - -
# -op.tan(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the tangent (in radians) of a number *value*; equivalent to [Math.tan](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan). - -* *value*: The input number value. - -
# -op.tanh(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/math.js) - -Returns the hyperbolic tangent of a number *value*; equivalent to [Math.tanh](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh). - -* *value*: The input number value. - - -
- -### Object Functions - -
# -op.equal(a, b) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/equal.js) - -Compare two values for equality, using join semantics in which `null !== null`. If the inputs are object-valued, a deep equality check of array entries or object key-value pairs is performed. The method is helpful within custom [join](verbs/#join) condition expressions. - -* *a*: The first input to compare. -* *b*: The second input to compare. - -
# -op.has(object, key) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js) - -Returns a boolean indicating whether the *object* has the specified *key* as its own property (as opposed to inheriting it). If the *object* is a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance, the `has` method will be invoked directly on the object, otherwise `Object.hasOwnProperty` is used. - -* *object*: The object, Map, or Set to test for property membership. -* *property*: The string property name to test for. - -
# -op.keys(object) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js) - -Returns an array of a given *object*'s own enumerable property names. If the *object* is a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instance, the `keys` method will be invoked directly on the object, otherwise `Object.keys` is used. - -* *object*: The input object or Map value. - -
# -op.values(object) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js) - -Returns an array of a given *object*'s own enumerable property values. If the *object* is a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance, the `values` method will be invoked directly on the object, otherwise `Object.values` is used. - -* *object*: The input object, Map, or Set value. - -
# -op.entries(object) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js) - -Returns an array of a given *object*'s own enumerable string-keyed property `[key, value]` pairs. If the *object* is a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance, the `entries` method will be invoked directly on the object, otherwise `Object.entries` is used. - -* *object*: The input object, Map, or Set value. - -
# -op.object(entries) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/object.js) - -Returns a new object given an iterable *entries* argument of `[key, value]` pairs. This method is Arquero's version of the standard [Object.fromEntries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) method. - -* *entries*: An iterable collection of `[key, value]` pairs, such as an array of two-element arrays or a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). - -
# -op.recode(value, map[, fallback]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/recode.js) - -Recodes an input *value* to an alternative value, based on a provided value *map* object. If a *fallback* value is specified, it will be returned when the input value is not found in the map; otherwise, the input value is returned unchanged. - -* *value*: The value to recode. The value must be safely coercible to a string for lookup against the value map. -* *map*: An object or [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) with input values for keys and recoded values for values. If a non-Map object, only the object's own properties will be considered; inherited properties on the prototype chain are ignored. -* *fallback*: An optional fallback value to use if the input value is not found in the value map. If a fallback is not specified, the input value will be returned unchanged when not found in the map. - -*Examples* - -```js -// recode values in a derive statement -table.derive({ val: d => op.recode(d.val, { 'opt:a': 'A', 'opt:b': 'B' }) }) -``` - -```js -// define value map externally, bind as parameter -const map = { 'opt:a': 'A', 'opt:b': 'B' }; -table - .params({ map }) - .derive({ val: (d, $) => op.recode(d.val, $.map, '?') }) -``` - -```js -// using a Map object, bind as parameter -const map = new Map().set('opt:a', 'A').set('opt:b', 'B'); -table - .params({ map }) - .derive({ val: (d, $) => op.recode(d.val, $.map, '?') }) -``` - -
# -op.row_object([...columns]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/op-api.js) - -Generate a new object containing the data for the current table row. The new object maps from column name keys to table values for the current row. The optional *columns* list indicates which columns to include in the object; if unspecified, all columns are included by default. - -This method can only be invoked within a single-table expression. Calling this method in a multi-table expression (such as for a join) results in an error. An error will also result if any provided column names are specified using dynamic lookups of table column values. - -* *columns*: A list of column names or indices to include in the object. - -*Examples* - -```js -aq.table({ a: [1, 3], b: [2, 4] }) - .derive({ row: op.row_object() }) - .get('row', 0); // { a: 1, b: 2 } -``` - -```js -// rollup a table into an array of row objects -table.rollup({ rows: d => op.array_agg(op.row_object()) }) -``` - -
- -### String Functions - -
# -op.parse_date(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Parses a string *value* and returns a Date instance. Beware: this method uses JavaScript's [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) functionality, which is inconsistently implemented across browsers. That said, [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted strings such as those produced by [format_date](#format_date) and [format_utcdate](#format_utcdate) should be supported across platforms. Note that "bare" ISO date strings such as `"2001-01-01"` are interpreted by JavaScript as indicating midnight of that day in [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), *not* local time. To indicate the local timezone, an ISO string can include additional time components and no `Z` suffix: `"2001-01-01T00:00"`. - -* *value*: The input value. - -
# -op.parse_float(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Parses a string *value* and returns a floating point number. - -* *value*: The input value. - -
# -op.parse_int(value[, radix]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Parses a string *value* and returns an integer of the specified radix (the base in mathematical numeral systems). - -* *value*: The input value. -* *radix*: An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful: this does not default to 10! If *radix* is `undefined`, `0`, or unspecified, JavaScript assumes the following: If the input string begins with `"0x"` or `"0X"` (a zero, followed by lowercase or uppercase X), the radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number. If the input string begins with `"0"` (a zero), the radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. If the input string begins with any other value, the radix is 10 (decimal). - -
# -op.endswith(value, search[, length]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Determines whether a string *value* ends with the characters of a specified *search* string, returning `true` or `false` as appropriate. - -* *value*: The input string value. -* *search*: The search string to test for. -* *length*: If provided, used as the length of *value* (default `value.length`). - -
# -op.match(value, regexp[, index]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Retrieves the result of matching a string *value* against a regular expression *regexp*. If no *index* is specified, returns an array whose contents depend on the presence or absence of the regular expression global (`g`) flag, or `null` if no matches are found. If the `g` flag is used, all results matching the complete regular expression will be returned, but capturing groups will not. If the `g` flag is not used, only the first complete match and its related capturing groups are returned. - -If specified, the *index* looks up a value of the resulting match. If *index* is a number, the corresponding index of the result array is returned. If *index* is a string, the value of the corresponding [named capture group](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges) is returned, or `null` if there is no such group. - -* *value*: The input string value. -* *regexp*: The [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) to match against. - -*Examples* - -```js -// returns ['1', '2', '3'] -op.match('1 2 3', /\d+/g) -``` - -```js -// returns '2' (index into match array) -op.match('1 2 3', /\d+/g, 1) -``` - -```js -// returns '3' (index of capture group) -op.match('1 2 3', /\d+ \d+ (\d+)/, 1) -``` - -```js -// returns '2' (named capture group) -op.match('1 2 3', /\d+ (?\d+)/, 'digit') -``` - -
# -op.normalize(value[, form]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns the Unicode normalization form of the string *value*. - -* *value*: The input string to normalize. -* *form*: The Unicode normalization form, one of `'NFC'` (default, canonical decomposition, followed by canonical composition), `'NFD'` (canonical decomposition), `'NFKC'` (compatibility decomposition, followed by canonical composition), or `'NFKD'` (compatibility decomposition). - -
# -op.padend(value, length[, fill]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Pad a string *value* with a given *fill* string (applied from the end of *value* and repeated, if needed) so that the resulting string reaches a given *length*. - -* *value*: The input string to pad. -* *length*: The length of the resulting string once the *value* string has been padded. If the length is lower than `value.length`, the *value* string will be returned as-is. -* *fill*: The string to pad the *value* string with (default `''`). If *fill* is too long to stay within the target *length*, it will be truncated: for left-to-right languages the left-most part and for right-to-left languages the right-most will be applied. - -
# -op.padstart(value, length[, fill]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Pad a string *value* with a given *fill* string (applied from the start of *value* and repeated, if needed) so that the resulting string reaches a given *length*. - -* *value*: The input string to pad. -* *length*: The length of the resulting string once the *value* string has been padded. If the length is lower than `value.length`, the *value* string will be returned as-is. -* *fill*: The string to pad the *value* string with (default `''`). If *fill* is too long to stay within the target *length*, it will be truncated: for left-to-right languages the left-most part and for right-to-left languages the right-most will be applied. - -
# -op.lower(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns the string *value* converted to lower case. - -* *value*: The input string value. - -
# -op.upper(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns the string *value* converted to upper case. - -* *value*: The input string value. - -
# -op.repeat(value, number) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns a new string which contains the specified *number* of copies of the *value* string concatenated together. - -* *value*: The input string to repeat. -* *number*: An integer between `0` and `+Infinity`, indicating the number of times to repeat the string. - -
# -op.replace(value, pattern, replacement) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns a new string with some or all matches of a *pattern* replaced by a *replacement*. The *pattern* can be a string or a regular expression, and the *replacement* must be a string. If *pattern* is a string, only the first occurrence will be replaced; to make multiple replacements, use a regular expression *pattern* with a `g` (global) flag. - -* *value*: The input string value. -* *pattern*: The pattern string or regular expression to replace. -* *replacement*: The replacement string to use. - -
# -op.split(value, separator[, limit]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Divides a string *value* into an ordered list of substrings based on a *separator* pattern, puts these substrings into an array, and returns the array. - -* *value*: The input string value. -* *separator*: A string or regular expression pattern describing where each split should occur. -* *limit*: An integer specifying a limit on the number of substrings to be included in the array. - -
# -op.startswith(value, search[, position]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Determines whether a string *value* starts with the characters of a specified *search* string, returning `true` or `false` as appropriate. - -* *value*: The input string value. -* *search*: The search string to test for. -* *position*: The position in the *value* string at which to begin searching (default `0`). - -
# -op.substring(value[, start, end]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns the part of the string *value* between the *start* and *end* indexes, or to the end of the string. - -* *value*: The input string value. -* *start*: The index of the first character to include in the returned substring (default `0`). -* *end*: The index of the first character to exclude from the returned substring (default `value.length`). - -
# -op.trim(value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/functions/string.js) - -Returns a new string with whitespace removed from both ends of the input *value* string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). - -* *value*: The input string value to trim. - - -
- -## Aggregate Functions - -Aggregate table expression functions for summarizing values. If invoked outside a table expression context, column (field) inputs must be column name strings, and the operator will return a corresponding table expression. - -
# -op.any(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function returning an arbitrary observed value (typically the first encountered). - -* *field*: The data column or derived field. - -
# -op.bins(field[, maxbins, nice, minstep, step]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for calculating a binning scheme in terms of the minimum bin boundary, maximum bin boundary, and step size. - -* *field*: The data column or derived field. -* *maxbins*: The maximum number of allowed bins (default `15`). -* *nice*: Boolean flag (default `true`) indicating if the bin min and max should snap to "nice" human-friendly values such as multiples of 10. -* *minstep*: The minimum allowed step size between bins. -* *step*: The exact step size to use between bins. If specified, the *maxbins* and *minstep* arguments are ignored. - -
# -op.count() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to count the number of records (rows). - -
# -op.distinct(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to count the number of distinct values. - -* *field*: The data column or derived field. - -
# -op.valid(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to count the number of valid values. Invalid values are `null`, `undefined`, or `NaN`. - -* *field*: The data column or derived field. - -
# -op.invalid(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to count the number of invalid values. Invalid values are `null`, `undefined`, or `NaN`. - -* *field*: The data column or derived field. - -
# -op.max(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the maximum value. For a non-aggregate version, see [op.greatest](#greatest). - -* *field*: The data column or derived field. - -
# -op.min(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the minimum value. For a non-aggregate version, see [op.least](#least). - -* *field*: The data column or derived field. - -
# -op.sum(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to sum values. - -* *field*: The data column or derived field. - -
# -op.product(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to multiply values. - -* *field*: The data column or derived field. - -
# -op.mean(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the mean (average) value. This operator is a synonym for [average](#average). - -* *field*: The data column or derived field. - -
# -op.average(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the average (mean) value. This operator is a synonym for [mean](#mean). - -* *field*: The data column or derived field. - -
# -op.mode(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to determine the mode (most frequent) value. - -* *field*: The data column or derived field. - -
# -op.median(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the median value. This operation is a shorthand for the [quantile](#quantile) value at p = 0.5. - -* *field*: The data column or derived field. - -
# -op.quantile(field, p) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to compute the quantile boundary of a data field for a probability threshold. The [median](#median) is the value of quantile at p = 0.5. - -* *field*: The data column or derived field. -* *p*: The probability threshold. - -
# -op.stdev(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the sample standard deviation. - -* *field*: The data column or derived field. - -
# -op.stdevp(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the population standard deviation. - -* *field*: The data column or derived field. - -
# -op.variance(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the sample variance. - -* *field*: The data column or derived field. - -
# -op.variancep(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the population variance. - -
# -op.corr(field1, field2) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the [product-moment correlation](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient) between two variables. To instead compute a [rank correlation](https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient), compute the average ranks for each variable and then apply this function to the result. - -* *field1*: The first data column or derived field. -* *field2*: The second data column or derived field. - -
# -op.covariance(field1, field2) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the sample covariance between two variables. - -* *field1*: The first data column or derived field. -* *field2*: The second data column or derived field. - -
# -op.covariancep(field1, field2) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function for the population covariance between two variables. - -* *field1*: The first data column or derived field. -* *field2*: The second data column or derived field. - -
# -op.array_agg(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to collect an array of *field* values. The resulting aggregate is an array (one per group) containing all observed values. The order of values is sensitive to any [orderby](verbs#orderby) criteria. - -* *field*: The data column or derived field. - -*Examples* - -```js -aq.table({ v: [1, 2, 3, 1] }) - .rollup({ a: op.array_agg('v') }) // a: [ [1, 2, 3, 1] ] -``` - -
# -op.array_agg_distinct(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to collect an array of distinct (unique) *field* values. The resulting aggregate is an array (one per group) containing all unique values. The order of values is sensitive to any [orderby](verbs#orderby) criteria. - -* *field*: The data column or derived field. - -*Examples* - -```js -aq.table({ v: [1, 2, 3, 1] }) - .rollup({ a: op.array_agg_distinct('v') }) // a: [ [1, 2, 3] ] -``` - -
# -op.object_agg(key, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to create an object given input *key* and *value* fields. The resulting aggregate is an object (one per group) with keys and values defined by the input fields. For any keys that occur multiple times in a group, the most recently observed value is used. The order in which keys and values are observed is sensitive to any [orderby](verbs#orderby) criteria. - -* *key*: The object key field, should be a string or string-coercible value. -* *value* The object value field. - -*Examples* - -```js -aq.table({ k: ['a', 'b', 'a'], v: [1, 2, 3] }) - .rollup({ o: op.object_agg('k', 'v') }) // o: [ { a: 3, b: 2 } ] -``` - -
# -op.map_agg(key, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to create a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) given input *key* and *value* fields. The resulting aggregate is a Map (one per group) with keys and values defined by the input fields. For any keys that occur multiple times in a group, the most recently observed value is used. The order in which keys and values are observed is sensitive to any [orderby](verbs#orderby) criteria. - -* *key*: The key field. -* *value* The value field. - -*Examples* - -```js -aq.table({ k: ['a', 'b', 'a'], v: [1, 2, 3] }) - .rollup({ m: op.map_agg('k', 'v') }) // m: [ new Map([['a', 3], ['b', 2]]) ] -``` - -
# -op.entries_agg(key, value) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/aggregate-functions.js) - -Aggregate function to create an array in the style of [Object.entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) given input *key* and *value* fields. The resulting aggregate is an array (one per group) with [key, value] arrays defined by the input fields, and may include duplicate keys. The order of entries is sensitive to any [orderby](verbs#orderby) criteria. - -* *key*: The key field. -* *value* The value field. - -*Examples* - -```js -aq.table({ k: ['a', 'b', 'a'], v: [1, 2, 3] }) - .rollup({ e: op.entries_agg('k', 'v') }) // e: [ [['a', 1], ['b', 2], ['a', 3]] ] -``` - -
- -## Window Functions - -Window table expression functions applicable over ordered table rows. If invoked outside a table expression context, column (field) inputs must be column name strings, and the operator will return a corresponding table expression. - -
# -op.row_number() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign consecutive row numbers, starting from 1. - -
# -op.rank() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a rank to each value in a group, starting from 1. Peer values are assigned the same rank. Subsequent ranks reflect the number of prior values: if the first two values tie for rank 1, the third value is assigned rank 3. - -
# -op.avg_rank() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a fractional (average) rank to each value in a group, starting from 1. Peer values are assigned the average of their indices: if the first two values tie, both will be assigned rank 1.5. - -
# -op.dense_rank() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a dense rank to each value in a group, starting from 1. Peer values are assigned the same rank. Subsequent ranks do not reflect the number of prior values: if the first two values tie for rank 1, the third value is assigned rank 2. - -
# -op.percent_rank() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a percentage rank to each value in a group. The percent is calculated as *(rank - 1) / (group_size - 1)*. - -
# -op.cume_dist() · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a cumulative distribution value between 0 and 1 to each value in a group. - -
# -op.ntile(num) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a quantile (e.g., percentile) value to each value in a group. Accepts an integer parameter indicating the number of buckets to use (e.g., 100 for percentiles, 5 for quintiles). - -* *num*: The number of buckets for ntile calculation. - -
# -op.lag(field[, offset, defaultValue]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a value that precedes the current value by a specified number of positions. If no such value exists, returns a default value instead. - -* *field*: The data column or derived field. -* *offset*: The lag offset (default `1`) from the current value. -* *defaultValue*: The default value (default `undefined`). - -
# -op.lead(field[, offset, defaultValue]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign a value that follows the current value by a specified number of positions. If no such value exists, returns a default value instead. - -* *field*: The data column or derived field. -* *offset*: The lead offset (default `1`) from the current value. -* *defaultValue*: The default value (default `undefined`). - -
# -op.first_value(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign the first value in a sliding window frame. - -* *field*: The data column or derived field. - -
# -op.last_value(field) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign the last value in a sliding window frame. - -* *field*: The data column or derived field. - -
# -op.nth_value(field[, nth]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to assign the nth value in a sliding window frame (counting from 1), or `undefined` if no such value exists. - -* *field*: The data column or derived field. -* *nth*: The nth position, starting from 1. - -
# -op.fill_down(field[, defaultValue]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to fill in missing values with preceding values. Returns the value at the current window position if it is valid (not `null`, `undefined`, or `NaN`), otherwise returns the first preceding valid value. If no such value exists, returns the default value. - -* *field*: The data column or derived field. -* *defaultValue*: The default value (default `undefined`). - -
# -op.fill_up(field[, defaultValue]) · [Source](https://github.com/uwdata/arquero/blob/master/src/op/window-functions.js) - -Window function to fill in missing values with subsequent values. Returns the value at the current window position if it is valid (not `null`, `undefined`, or `NaN`), otherwise returns the first subsequent valid value. If no such value exists, returns the default value. - -* *field*: The data column or derived field. -* *defaultValue*: The default value (default `undefined`). diff --git a/docs/api/table.md b/docs/api/table.md deleted file mode 100644 index 02f08ad4..00000000 --- a/docs/api/table.md +++ /dev/null @@ -1,701 +0,0 @@ ---- -title: Table \| Arquero API Reference ---- -# Arquero API Reference - -[Top-Level](/arquero/api) | [**Table**](table) | [Verbs](verbs) | [Op Functions](op) | [Expressions](expressions) | [Extensibility](extensibility) - -* [Table Metadata](#metadata) - * [numCols](#numCols), [numRows](#numRows), [size](#size), [totalRows](#totalRows) - * [isFiltered](#isFiltered), [isGrouped](#isGrouped), [isOrdered](#isOrdered) - * [comparator](#foo), [groups](#groups), [mask](#mask) - * [params](#params) -* [Table Transformation](#transformation) - * [assign](#assign) - * [transform](#transform) -* [Table Columns](#columns) - * [column](#column), [columnAt](#columnAt), [columnArray](#columnArray) - * [columnIndex](#columnIndex), [columnName](#columnName), [columnNames](#columnNames) -* [Table Values](#table-values) - * [array](#array), [values](#values) - * [data](#data), [get](#get), [getter](#getter) - * [indices](#indices), [partitions](#partitions), [scan](#scan) -* [Table Output](#output) - * [objects](#objects), [object](#object), [Symbol.iterator](#@@iterator) - * [print](#print), [toHTML](#toHTML), [toMarkdown](#toMarkdown) - * [toArrow](#toArrow), [toArrowBuffer](#toArrowBuffer), [toCSV](#toCSV), [toJSON](#toJSON) - - -
- -## Table Metadata - -
# -table.numCols() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The number of columns in this table. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .numCols() // 2 -``` - -
# -table.numRows() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The number of active (non-filtered) rows in this table. This number may be less than the [total rows](#totalRows) if the table has been filtered. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .numRows() // 3 -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .filter(d => d.a > 2) - .numRows() // 1 -``` - -
# -table.size · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The number of active (non-filtered) rows in this table. This number is the same as [numRows()](#numRows), and may be less than the [total rows](#totalRows) if the table has been filtered. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .size // 3 -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .filter(d => d.a > 2) - .size // 1 -``` - -
# -table.totalRows() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The total number of rows in this table, including both filtered and unfiltered rows. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .totalRows() // 3 -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .filter(d => d.a > 2) - .totalRows() // 3 -``` - -
# -table.isFiltered() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Indicates if the table has a filter applied. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .isFiltered() // false -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .filter(d => d.a > 2) - .isFiltered() // true -``` - -
# -table.isGrouped() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Indicates if the table has a groupby specification. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .isGrouped() // false -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .groupby('a') - .isGrouped() // true -``` - -
# -table.isOrdered() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Indicates if the table has a row order comparator. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .isOrdered() // false -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .orderby(aq.desc('b')) - .isOrdered() // true -``` - -
# -table.comparator() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns the row order comparator function, if specified. - -
# -table.groups() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns the groupby specification, if defined. A groupby specification is an object with the following properties: - -* *names*: Output column names for each group field. -* *get*: Value accessor functions for each group field. -* *rows*: Row indices of example table rows for each group. -* *size*: The total number of groups. -* *keys*: Per-row group indices for every row in the table. - -
# -table.mask() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns the bitset mask for filtered rows, or null if there is no filter. - -
# -table.params() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/transformable.js) - -Get or set table expression parameter values. If called with no arguments, returns the current parameter values as an object. Otherwise, adds the provided parameters to this table's parameter set and returns the table. Any prior parameters with names matching the input parameters are overridden. - -Also see the [`escape()` expression helper](./#escape) for a lightweight alternative that allows access to variables defined in an enclosing scope. - -* *values*: A set of parameter values to add as an object of name-value pairs. - -*Examples* - -```js -table.params({ hi: 5 }).filter((d, $) => abs(d.value) < $.hi) -``` - - -
- -## Table Transformation - -For a variety of additional transformations, see the [Verbs API Reference](verbs). - -
# -table.assign(...tables) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Create a new table with additional columns drawn from one or more input *tables*. All tables must have the same numer of rows and will be [reified](verbs/#reify) prior to assignment. In the case of repeated column names, input table columns overwrite existing columns. - -* *tables*: The input tables to merge. - -*Examples* - -```js -const t1 = aq.table({ a: [1, 2], b: [3, 4] }); -const t2 = aq.table({ c: [5, 6], b: [7, 8] }); -t1.assign(t2); // { a: [1, 2], b: [7, 8], c: [5, 6] } -``` - -
# -table.transform(...transforms) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Apply a sequence of transformations to this table. The output of each transform is passed as input to the next transform, and the output of the last transform is then returned. This method provides a lightweight mechanism for applying custom transformations to a table. - -* *transforms*: Transformation functions to apply to the table in sequence. Each function should take a single table as input and return a table as output. - -*Examples* - -```js -aq.table({ a: [1, 2], b: [3, 4] }) - .transform( - table => table.filter(d => d.b > 3), - table => table.select('a') - ) // { a: [2] } -``` - - -
- -## Table Columns - -
# -table.column(name) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Get the column instance with the given *name*, or `undefined` if does not exist. The returned column object provides a lightweight abstraction over the column storage (such as a backing array), providing a *length* property and *get(row)* method. - -A column instance may be used across multiple tables and so does _not_ track a table's filter or orderby critera. To access filtered or ordered values, use the table [get](#get), [getter](#getter), or [columnArray](#columnArray) methods. - -* *name*: The column name. - -*Examples* - -```js -const dt = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) -dt.column('b').get(1) // 5 -``` - -
# -table.columnAt(index) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Get the column instance at the given index position, or `undefined` if does not exist. The returned column object provides a lightweight abstraction over the column storage (such as a backing array), providing a *length* property and *get(row)* method. - -* *index*: The zero-based column index. - -*Examples* - -```js -const dt = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) -dt.columnAt(1).get(1) // 5 -``` - -
# -table.columnArray(name[, constructor]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -_This method is a deprecated alias for the table [array()](#array) method. Please use [array()](#array) instead._ - -Get an array of values contained in the column with the given *name*. Unlike direct access through the table [column](#column) method, the array returned by this method respects any table filter or orderby criteria. By default, a standard [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) is returned; use the *constructor* argument to specify a [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). - -* *name*: The column name. -* *constructor*: An optional array constructor (default [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Array)) to use to instantiate the output array. Note that errors or truncated values may occur when assigning to a typed array with an incompatible type. - -
# -table.columnIndex(name) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The column index for the given name, or `-1` if the name is not found. - -* *name*: The column name. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .columnIndex('b'); // 1 -``` - -
# -table.columnName(index) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -The column name at the given index, or `undefined` if the index is out of range. - -* *index*: The column index. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .columnName(1); // 'b' -``` - -
# -table.columnNames([filter]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns an array of table column names, optionally filtered. - -* *filter*: An optional filter callback function. If unspecified, all column names are returned. If *filter* is provided, it will be invoked for each column name and only those for which the callback returns a [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) value will be kept. The filter callback function is called with the following arguments: - * *name*: The column name. - * *index*: The column index. - * *array*: The backing array of names. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .columnNames(); // [ 'a', 'b' ] -``` - - -
- -## Table Values - -
# -table.array(name[, constructor]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Get an array of values contained in the column with the given *name*. Unlike direct access through the table [column](#column) method, the array returned by this method respects any table filter or orderby criteria. By default, a standard [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) is returned; use the *constructor* argument to specify a [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). - -* *name*: The column name. -* *constructor*: An optional array constructor (default [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Array)) to use to instantiate the output array. Note that errors or truncated values may occur when assigning to a typed array with an incompatible type. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .array('b'); // [ 4, 5, 6 ] -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .filter(d => d.a > 1) - .array('b'); // [ 5, 6 ] -``` - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .array('b', Int32Array); // Int32Array.of(4, 5, 6) -``` - -
# -table.values(name) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns an iterator over values in the column with the given *name*. The iterator returned by this method respects any table filter or orderby criteria. - -* *name*: The column name. - -*Examples* - -```js -for (const value of table.values('colA')) { - // do something with ordered values from column A -} -``` - -```js -// slightly less efficient version of table.columnArray('colA') -const colValues = Array.from(table.values('colA')); -``` - -
# -table.data() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns the internal table storage data structure. - -
# -table.get(name[, row]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Get the value for the given column and row. Row indices are relative to any filtering and ordering criteria, not the internal data layout. - -* *name*: The column name. -* *row*: The row index (default `0`), relative to any filtering or ordering criteria. - -*Examples* - -```js -const dt = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }); -dt.get('a', 0) // 1 -dt.get('a', 2) // 3 -``` - -```js -const dt = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .orderby(aq.desc('b')); -dt.get('a', 0) // 3 -dt.get('a', 2) // 1 -``` - -
# -table.getter(name) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Returns an accessor ("getter") function for a column. The returned function takes a row index as its single argument and returns the corresponding column value. Row indices are relative to any filtering and ordering criteria, not the internal data layout. - -* *name*: The column name. - -*Examples* - -```js -const get = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).getter('a'); -get(0) // 1 -get(2) // 3 -``` - -```js -const dt = aq.table({ a: [1, 2, 3], b: [4, 5, 6] }) - .orderby(aq.desc('b')) - .getter('a'); -get(0) // 3 -get(2) // 1 -``` - -
# -table.indices([order]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns an array of indices for all rows passing the table filter. - -* *order*: A boolean flag (default `true`) indicating if the returned indices should be sorted if this table is ordered. If `false`, the returned indices may or may not be sorted. - -
# -table.partitions([order]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Returns an array of indices for each group in the table. If the table is not grouped, the result is the same as [indices](#indices), but wrapped within an array. Otherwise returns an array of row index arrays, one per group. The indices will be filtered if the table has been filtered. - -* *order*: A boolean flag (default `true`) indicating if the returned indices should be sorted if this table is ordered. If `false`, the returned indices may or may not be sorted. - -
# -table.scan(callback[, order]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Perform a table scan, invoking the provided *callback* function for each row of the table. If this table is filtered, only rows passing the filter are visited. - -* *callback*: Function invoked for each row of the table. The callback is invoked with the following arguments: - * *row*: The table row index. - * *data*: The backing table data store. - * *stop*: A function to stop the scan early. The callback can invoke *stop()* to prevent future scan calls. -* *order*: A boolean flag (default `false`), indicating if the table should be scanned in the order determined by [orderby](verbs#orderby). This argument has no effect if the table is unordered. - - -
- -## Table Output - -
# -table.objects([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Returns an array of objects representing table rows. A new set of objects will be created, copying the backing table data. - -* *options*: Options for row generation: - * *limit*: The maximum number of objects to create (default `Infinity`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *columns*: An ordered set of columns to include. The input may consist of: column name strings, column integer indices, objects with current column names as keys and new column names as values (for renaming), or a selection helper function such as [all](#all), [not](#not), or [range](#range)). - * *grouped*: The export format for groups of rows. This option only applies to tables with groups set with the [groupby](verbs/#groupby) verb. The default (`false`) is to ignore groups, returning a flat array of objects. The valid values are `true` or `'map'` (for [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instances), `'object'` (for standard [Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)), or `'entries'` (for arrays in the style of [Object.entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries)). For the `'object'` format, groupby keys are coerced to strings to use as object property names; note that this can lead to undesirable behavior if the groupby key values do not coerce to unique strings. The `'map'` and `'entries'` options preserve the groupby key values. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).objects() -// [ { a: 1, b: 4 }, { a: 2, b: 5 }, { a: 3, b: 6 } ] -``` - -```js -aq.table({ k: ['a', 'b', 'a'], v: [1, 2, 3] }) - .groupby('k') - .objects({ grouped: true }) -// new Map([ -// [ 'a', [ { k: 'a', v: 1 }, { k: 'a', v: 3 } ] ], -// [ 'b', [ { k: 'b', v: 2 } ] ] -// ]) -``` - -
# -table.object([row]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Returns an object representing a single table row. The *row* index is relative to any filtering and ordering criteria, not the internal data layout. If the *row* index is not specified, the first row in the table (index `0`) is returned. - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).object(1) // { a: 2, b : 5} -``` - -```js -const { min, max } = aq.table({ v: [1, 2, 3] }) - .rollup({ min: op.min('v'), max: op.max('v') }) - .object(); // { min: 1, max: 3 } -``` - -
# -table\[Symbol.iterator\]() · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Returns an iterator over generated row objects. Similar to the [objects](#objects) method, this method generates new row object instances; however, rather than returning an array, this method provides an iterator over row objects for each non-filtered row in the table. - -*Examples* - -```js -for (const rowObject of table) { - // do something with row object -} -``` - -```js -// slightly less efficient version of table.objects() -const objects = [...table]; -``` - -
# -table.print([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Print the contents of this table using the `console.table()` method. - -* *options*: Options for printing. If number-valued, specifies the row limit (equivalent to `{ limit: value }`). - * *limit*: The maximum number of rows to print (default `10`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *columns*: An ordered set of columns to print. The input may consist of: column name strings, column integer indices, objects with current column names as keys and new column names as values (for renaming), or a selection helper function such as [all](#all), [not](#not), or [range](#range)). - -*Examples* - -```js -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).print() -// ┌─────────┬───┬───┐ -// │ (index) │ a │ b │ -// ├─────────┼───┼───┤ -// │ 0 │ 1 │ 4 │ -// │ 1 │ 2 │ 5 │ -// │ 2 │ 3 │ 6 │ -// └─────────┴───┴───┘ -``` - -
# -table.toHTML([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/to-html.js) - -Format this table as an HTML table string. - -* *options*: A formatting options object: - * *limit*: The maximum number of rows to print (default `100`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *columns*: Ordered list of column names to print. If function-valued, the function should accept a table as input and return an array of column name strings. Otherwise, should be an array of name strings. - * *align*: Object of column alignment options. The object keys should be column names. The object values should be aligment strings, one of `'l'` (left), `'c'` (center), or `'r'` (right). If specified, these override any automatically inferred options. - * *format*: Object of column format options. If specified, these override any automatically inferred options. The object keys should be column names. The object values should either be formatting functions or objects with any of the following properties: - * *utc*: A boolean flag indicating if UTC date formatting should be used rather than the local time zone. - * *digits*: Number of fractional digits to include for numbers. - * *maxdigits*: The maximum number of fractional digits to include when inferring a number format (default `6`). This option is passed to the format inference method and is ignored when explicit format options are specified. - * *null*: Optional format function for `null` and `undefined` values. If specified, this function be invoked with the `null` or `undefined` value as the sole input argument. The return value is then used as the HTML output for the input value. - * *style*: CSS styles to include in HTML output. The object keys can be HTML table tag names: `'table'`, `'thead'`, `'tbody'`, `'tr'`, `'th'`, or `'td'`. The object values should be strings of valid CSS style directives (such as `"font-weight: bold;"`) or functions that take a column name and row as input and return a CSS string. - -*Examples* - -```js -// serialize a table as HTML-formatted text -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).toHTML() -``` - -
# -table.toMarkdown([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/to-markdown.js) - -Format this table as a [GitHub-Flavored Markdown table](https://github.github.com/gfm/#tables-extension-) string. - -* *options*: A formatting options object: - * *limit*: The maximum number of rows to print (default `100`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *columns*: Ordered list of column names to print. If function-valued, the function should accept a table as input and return an array of column name strings. Otherwise, should be an array of name strings. - * *align*: Object of column alignment options. The object keys should be column names. The object values should be aligment strings, one of `'l'` (left), `'c'` (center), or `'r'` (right). If specified, these override any automatically inferred options. - * *format*: Object of column format options. If specified, these override any automatically inferred options. The object keys should be column names. The object values should either be formatting functions or objects with any of the following properties: - * *utc*: A boolean flag indicating if UTC date formatting should be used rather than the local time zone. - * *digits*: Number of fractional digits to include for numbers. - * *maxdigits*: The maximum number of fractional digits to include when inferring a number format (default `6`). This option is passed to the format inference method and is ignored when explicit format options are specified. - -*Examples* - -```js -// serialize a table as Markdown-formatted text -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).toMarkdown() -// '|a|b|\n|-:|-:|\n|1|4|\n|2|5|\n|3|6|\n' -``` - -
# -table.toArrow([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/arrow/encode/index.js) - -Format this table as an [Apache Arrow](https://arrow.apache.org/docs/js/) table instance. This method will throw an error if type inference fails or if the generated columns have differing lengths. - -* *options*: Options for Arrow encoding. - * *columns*: Ordered list of column names to include. If function-valued, the function should accept this table as a single argument and return an array of column name strings. - * *limit*: The maximum number of rows to include (default `Infinity`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *types*: An optional object indicating the [Arrow data type](https://arrow.apache.org/docs/js/enums/type.html) to use for named columns. If specified, the input should be an object with column names for keys and Arrow data types for values. If a column's data type is not explicitly provided, type inference will be performed. - - Type values can either be instantiated Arrow [DataType](https://arrow.apache.org/docs/js/classes/datatype.html) instances (for example, `new Float64()`,`new DateMilliseconds()`, *etc.*) or type enum codes (`Type.Float64`, `Type.Date`, `Type.Dictionary`). For convenience, arquero re-exports the apache-arrow `Type` enum object (see examples below). High-level types map to specific data type instances as follows: - - * `Type.Date` → `new DateMilliseconds()` - * `Type.Dictionary` → `new Dictionary(new Utf8(), new Int32())` - * `Type.Float` → `new Float64()` - * `Type.Int` → `new Int32()` - * `Type.Interval` → `new IntervalYearMonth()` - * `Type.Time` → `new TimeMillisecond()` - - Types that require additional parameters (including `List`, `Struct`, and `Timestamp`) can not be specified using type codes. Instead, use data type constructors from apache-arrow, such as `new List(new Int32())`. - -*Examples* - -Encode Arrow data from an input Arquero table: - -```js -const { table, Type } = require('arquero'); - -// create Arquero table -const dt = table({ - x: [1, 2, 3, 4, 5], - y: [3.4, 1.6, 5.4, 7.1, 2.9] -}); - -// encode as an Arrow table (infer data types) -// here, infers Uint8 for 'x' and Float64 for 'y' -const at1 = dt.toArrow(); - -// encode into Arrow table (set explicit data types) -const at2 = dt.toArrow({ - types: { - x: Type.Uint16, - y: Type.Float32 - } -}); -``` - -
# -table.toArrowBuffer([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/arrow/encode/index.js) - -Format this table as binary data in the [Apache Arrow](https://arrow.apache.org/docs/js/) IPC format. The binary data may be saved to disk or passed between processes or tools. For example, when using [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), the output of this method can be passed directly between threads (no data copy) as a [Transferable](https://developer.mozilla.org/en-US/docs/Web/API/Transferable) object. Additionally, Arrow binary data can be loaded in other language environments such as [Python](https://arrow.apache.org/docs/python/) or [R](https://arrow.apache.org/docs/r/). - -This method will throw an error if type inference fails or if the generated columns have differing lengths. This method is a shorthand for `table.toArrow().serialize()`. - -* *options*: Options for Arrow encoding, same as [toArrow](#toArrow). - -*Examples* - -Encode Arrow data from an input Arquero table: - -```js -const { table } = require('arquero'); - -const dt = table({ - x: [1, 2, 3, 4, 5], - y: [3.4, 1.6, 5.4, 7.1, 2.9] -}); - -// encode table as a transferable Arrow byte buffer -// here, infers Uint8 for 'x' and Float64 for 'y' -const bytes = dt.toArrowBuffer(); -``` - -
# -table.toCSV([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/to-csv.js) - -Format this table as a comma-separated values (CSV) string. Other delimiters, such as tabs or pipes ('\|'), can be specified using the *options* argument. - -* *options*: A formatting options object: - * *delimiter*: The delimiter between values (default `","`). - * *limit*: The maximum number of rows to print (default `Infinity`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *columns*: Ordered list of column names to include. If function-valued, the function should accept a table as input and return an array of column name strings. Otherwise, should be an array of name strings. - * *format*: Object of column format options. The object keys should be column names. The object values should be formatting functions that transform column values prior to output. If specified, a formatting function overrides any automatically inferred options. - -*Examples* - -```js -// serialize a table as CSV-formatted text -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).toCSV() -// 'a,b\n1,4\n2,5\n3,6' -``` - -
# -table.toJSON([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/format/to-json.js) - -Format this table as a JavaScript Object Notation (JSON) string compatible with the [fromJSON](/#fromJSON) method. - -* *options*: A formatting options object: - * *limit*: The maximum number of rows to print (default `Infinity`). - * *offset*: The row offset indicating how many initial rows to skip (default `0`). - * *schema*: Boolean flag (default `true`) indicating if table schema metadata should be included in the JSON output. If `false`, only the data payload is included. - * *columns*: Ordered list of column names to print. If function-valued, the function should accept a table as input and return an array of column name strings. Otherwise, should be an array of name strings. - * *format*: Object of column format options. The object keys should be column names. The object values should be formatting functions that transform column values prior to output. If specified, a formatting function overrides any automatically inferred options. - -*Examples* - -```js -// serialize a table as a JSON string with schema metadata -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).toJSON() -// '{"schema":{"fields":[{"name":"a"},{"name":"b"}]},"data":{"a":[1,2,3],"b":[4,5,6]}}' -``` - -```js -// serialize a table as a JSON string without schema metadata -aq.table({ a: [1, 2, 3], b: [4, 5, 6] }).toJSON({ schema: false }) -// '{"a":[1,2,3],"b":[4,5,6]}' -``` diff --git a/docs/api/verbs.md b/docs/api/verbs.md deleted file mode 100644 index 58b373b4..00000000 --- a/docs/api/verbs.md +++ /dev/null @@ -1,807 +0,0 @@ ---- -title: Verbs \| Arquero API Reference ---- -# Arquero API Reference - -[Top-Level](/arquero/api) | [Table](table) | [**Verbs**](verbs) | [Op Functions](op) | [Expressions](expressions) | [Extensibility](extensibility) - -* [Core Verbs](#verbs) - * [derive](#derive) - * [filter](#filter), [slice](#slice) - * [groupby](#groupby), [ungroup](#ungroup) - * [orderby](#orderby), [unorder](#unorder) - * [rollup](#rollup), [count](#count) - * [sample](#sample) - * [select](#select), [relocate](#relocate), [rename](#rename) - * [reify](#reify) -* [Join Verbs](#joins) - * [cross](#cross) - * [join](#join), [join_left](#join_left), [join_right](#join_right), [join_full](#join_full) - * [lookup](#lookup) - * [semijoin](#semijoin), [antijoin](#antijoin) -* [Cleaning Verbs](#cleaning) - * [dedupe](#dedupe), [impute](#impute) -* [Reshape Verbs](#reshape) - * [fold](#fold), [pivot](#pivot) - * [spread](#spread), [unroll](#unroll) -* [Set Verbs](#sets) - * [concat](#concat), [union](#union) - * [intersect](#intersect), [except](#except) - -
- -## Core Verbs - -
# -table.derive(values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/derive.js) - -Derive new column values based on the provided expressions. - -* *values*: Object of name-value pairs defining the columns to derive. The input object should have output column names for keys and table expressions for values. -* *options*: An options object for dropping or relocating derived columns. Use either the *before* or *after* property to indicate where to place derived columns. Specifying both before and after is an error. Unlike the [relocate](#relocate) verb, this option affects only new columns; overwritten columns with existing names are excluded from relocation. - * *drop*: A boolean (default `false`) indicating if the original columns should be dropped, leaving only the derived columns. If `true`, the *before* and *after* options are ignored. - * *before*: An anchor column that relocated columns should be placed before. The value can be any legal column selection. If multiple columns are selected, only the *first* column will be used as an anchor. - * *after*: An anchor column that relocated columns should be placed after. The value can be any legal column selection. If multiple columns are selected, only the *last* column will be used as an anchor. - -*Examples* - -```js -table.derive({ sumXY: d => d.x + d.y }) -``` - -```js -table.derive({ z: d => d.x * d.y }, { before: 'x' }) -``` - -
# -table.filter(criteria) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/filter.js) - -Filter a table to a subset of rows based on the input criteria. The resulting table provides a filtered view over the original data; no data copy is made. To create a table that copies only filtered data to new data structures, call [reify](#reify) on the output table. - -* *criteria*: The filter criteria as a table expression. Both aggregate and window functions are permitted, and will take into account any [groupby](#groupby) or [orderby](#orderby) settings. - -*Examples* - -```js -table.filter(d => op.abs(d.value) < 5) -``` - -
# -table.slice([start, end]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/table.js) - -Extract rows with indices from *start* to *end* (*end* not included), where *start* and *end* represent per-group ordered row numbers in the table. The table row indices are determined by the current [orderby](#orderby) settings. The *start* and *end* arguments are applied separately to each group, as determined by [groupby](#groupby). - -* *start*: Zero-based index at which to start extraction. A negative index indicates an offset from the end of the group. If start is undefined, slice starts from the index 0. -* *end*: Zero-based index before which to end extraction. A negative index indicates an offset from the end of the group. If end is omitted, slice extracts through the end of the group. - -*Examples* - -```js -// slice the table to include all rows except for the first and last -table.slice(1, -1) -``` - -```js -// extract (up to) the first two rows of each group -table.groupby('colA').slice(0, 2) -``` - -
# -table.groupby(...keys) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/groupby.js) - -Group table rows based on a set of column values. Subsequent operations that are sensitive to grouping (such as aggregate functions) will operate over the grouped rows. To undo grouping, use [ungroup](#ungroup). - -* *keys*: Key column values to group by. Keys may be column name strings, column index numbers, or value objects with output column names for keys and table expressions for values. - -*Examples* - -```js -table.groupby('colA', 'colB') -``` - -```js -table.groupby({ key: d => d.colA + d.colB }) -``` - -
# -table.ungroup() · [Source](https://github.com/uwdata/arquero/blob/master/src/engine/ungroup.js) - -Ungroup a table, removing any grouping criteria. Undoes the effects of [groupby](#groupby). - -*Examples* - -```js -table.ungroup() -``` - - -
# -table.orderby(...keys) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/orderby.js) - -Order table rows based on a set of column values. Subsequent operations sensitive to ordering (such as window functions) will operate over sorted values. The resulting table provides an view over the original data, without any copying. To create a table with sorted data copied to new data strucures, call [reify](#reify) on the result of this method. To undo ordering, use [unorder](#unorder). - -* *keys*: Key values to sort by, in precedence order. By default, sorting is done in ascending order. To sort in descending order, wrap values using [desc](./#desc). If a string, order by the column with that name. If a number, order by the column with that index. If a function, must be a valid table expression; aggregate functions are permitted, but window functions are not. If an object, object values must be valid values parameters with output column names for keys and table expressions for values (the output names will be ignored). If an array, array values must be valid key parameters. - -*Examples* - -```js -// order by column 'a' in ascending order, than 'b' in descending order -table.orderby('a', aq.desc('b')) -``` - -```js -// same as above, but with object syntax -// key order is significant, but the key names are ignored -table.orderby({ a: 'a', b: aq.desc('b') )}) -``` - -```js -// orderby accepts table expressions as well as column names -table.orderby(aq.desc(d => d.a)) -``` - -
# -table.unorder() · [Source](https://github.com/uwdata/arquero/blob/master/src/engine/unorder.js) - -Unorder a table, removing any sorting criteria. Undoes the effects of [orderby](#orderby). - -*Examples* - -```js -table.unorder() -``` - - -
# -table.rollup(values) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/rollup.js) - -Rollup a table to produce an aggregate summary. Often used in conjunction with [groupby](#groupby). To produce counts only, [count](#count) provides a convenient shortcut. - -* *values*: Object of name-value pairs defining aggregated output columns. The input object should have output column names for keys and table expressions for values. - -*Examples* - -```js -table.groupby('colA').rollup({ mean: d => op.mean(d.colB) }) -``` - -```js -table.groupby('colA').rollup({ mean: op.median('colB') }) -``` - -
# -table.count([options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/index.js) - -Count the number of values in a group. This method is a shorthand for [rollup](#rollup) with a [count](op#count) aggregate function. - -* *options*: An options object: - * *as*: The name of the output count column (default `'count'`). - -*Examples* - -```js -table.groupby('colA').count() -``` - -```js -table.groupby('colA').count({ as: 'num' }) -``` - - -
# -table.sample(size[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/sample.js) - -Generate a table from a random sample of rows. If the table is grouped, perform [stratified sampling](https://en.wikipedia.org/wiki/Stratified_sampling) by sampling separately from each group. - -* *size*: The number of samples to draw per group. If number-valued, the same sample size is used for each group. If function-valued, the input should be an aggregate table expression compatible with [rollup](#rollup). -* *options*: An options object: - * *replace*: Boolean flag (default `false`) to sample with replacement. - * *shuffle*: Boolean flag (default `true`) to ensure randomly ordered rows. - * *weight*: Column values to use as weights for sampling. Rows will be sampled with probability proportional to their relative weight. The input should be a column name string or a table expression compatible with [derive](#derive). - -*Examples* - -```js -// sample 50 rows without replacement -table.sample(50) -``` - -```js -// sample 100 rows with replacement -table.sample(100, { replace: true }) -``` - -```js -// stratified sampling with dynamic sample size -table.groupby('colA').sample(aq.frac(0.5)) -``` - -```js -// sample twice the number of records in each group, with replacement -table.groupby('colA').sample(aq.frac(2), { replace: true }) -``` - -
# -table.select(...columns) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/select.js) - -Select a subset of columns into a new table, potentially renaming them. - -* *columns*: The columns to select. The input may consist of: column name strings, column integer indices, objects with current column names as keys and new column names as values (for renaming), or functions that take a table as input and return a valid selection parameter (typically the output of the selection helper functions [all](./#all), [not](./#not), or [range](./#range)). - -*Examples* - -```js -table.select('colA', 'colB') -``` - -```js -table.select(aq.not('colB', 'colC')) -``` - -```js -table.select({ colA: 'newA', colB: 'newB' }) -``` - - -
# -table.relocate(columns, options) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/relocate.js) - -Relocate a subset of columns to change their positions, also potentially renaming them. - -* *columns*: An ordered selection of columns to relocate. The input may consist of: column name strings, column integer indices, objects with current column names as keys and new column names as values (for renaming), or functions that take a table as input and return a valid selection parameter (typically the output of the selection helper functions [all](./#all), [not](./#not), or [range](./#range)). -* *options*: An options object for specifying where columns should be relocated. The options must include either the *before* or *after* property to indicate where to place the selected columns. Specifying both *before* and *after* is an error. - * *before*: An anchor column that relocated columns should be placed before. The value can be any legal column selection. If multiple columns are selected, only the *first* column will be used as an anchor. - * *after*: An anchor column that relocated columns should be placed after. The value can be any legal column selection. If multiple columns are selected, only the *last* column will be used as an anchor. - -*Examples* - -```js -// place colY and colZ immediately after colX -table.relocate(['colY', 'colZ'], { after: 'colX' }) -``` - -```js -// place all columns but colB and colC immediately before -// the position of colA prior to relocation -table.relocate(not('colB', 'colC'), { before: 'colA' }) -``` - -```js -// place colA and colB immediately after colC, while also -// respectively renaming them as newA and newB -table.relocate({ colA: 'newA', colB: 'newB' }, { after: 'colC' }) -``` - - -
# -table.rename(columns) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/rename.js) - -Rename one or more columns, preserving column order. The *columns* input should be an object or Map instance that maps existing column names to new column names. Use the [`names()` helper function](./#names) to create a rename map based on integer column indices. - -* *columns*: A rename object or [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) with current column names as keys and new column names as values, or a selection helper function that takes a table as input and returns a rename map as output. - -*Examples* - -```js -// rename colA to colA2 -table.rename({ colA: 'colA2' }) -``` - -```js -// rename 'old col' to 'new col' -table.rename({ 'old col': 'new col' }) -``` - -```js -// rename colA and colB -table.rename({ colA: 'colA2', colB: 'colB2' }) -``` - -```js -// rename colA and colB, alternate syntax -table.rename({ colA: 'colA2' }, { colB: 'colB2' }) -``` - -```js -// rename the first two columns (by index) to 'colA2' and 'colB2' -table.rename(aq.names('colA2', 'colB2')) -``` - - -
# -table.reify([indices]) · [Source](https://github.com/uwdata/arquero/blob/master/src/table/column-table.js) - -Create a new fully-materialized instance of this table. All filter and orderby settings are removed from the new table. Instead, the data itself is filtered and ordered as needed to produce new backing data columns. - -* *indices*: An array of ordered row indices to materialize. If unspecified, all rows passing the table filter are used. - -*Examples* - -```js -// materialize any internal filtering and ordering -table.reify() -``` - - -
- -## Join Verbs - -
# -table.cross(other[, values, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/index.js) - -Produce the [Cartesian cross product](https://en.wikipedia.org/wiki/Join_%28SQL%29#Cross_join) of two tables. The output table has one row for every pair of input table rows. Beware that outputs may be quite large, as the number of output rows is the product of the input row counts. This method is a convenient shorthand for a [join](#join) in which the join criteria is always true. - -* *other*: The other (right) table to join with. -* *values*: The columns to include in the join output. If unspecified, all columns from both tables are included. If array-valued, a two element array should be provided, containing column selections to include from the left and right tables, respectively. Array input may consist of column name strings, objects with output names as keys and single-table table expressions as values, or the selection helper functions [all](./#all), [not](./#not), or [range](./#range). If object-valued, specifies the key-value pairs for each output, defined using two-table table expressions. -* *options*: An options object: - * *suffix*: Column name suffixes to append, for the left and right tables, respectively, when two columns with the same name are produced by the join (default `['_1', '_2']`). - -*Examples* - -```js -table.cross(other) -``` - -```js -table.cross(other, [['leftKey', 'leftVal'], ['rightVal']]) -``` - -
# -table.join(other[, on, values, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join.js) - -Join two tables, extending the columns of one table with values from the *other* table. The current table is considered the "left" table in the join, and the new table input is considered the "right" table in the join. By default an [inner join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Inner_join) is performed, removing all rows that do not match the join criteria. To perform left, right, or full outer joins, use the [join_left](#join_left}), [join_right](#join_right), or [join_full](#join_full) methods, or provide an *options* argument. - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. -* *values*: The columns to include in the join output. If unspecified, all columns from both tables are included; paired join keys sharing the same column name are included only once. If array-valued, a two element array should be provided, containing column selections to include from the left and right tables, respectively. Array input may consist of column name strings, objects with output names as keys and single-table table expressions as values, or the selection helper functions [all](./#all), [not](./#not), or [range](./#range). If object-valued, specifies the key-value pairs for each output, defined using two-table table expressions. -* *options*: An options object: - * *left*: Boolean flag (default `false`) indicating a [left outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Left_outer_join). If both *left* and *right* are true, indicates a [full outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Full_outer_join). - * *right* Boolean flag (default `false`) indicating a [right outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Right_outer_join). If both the *left* and *right* are true, indicates a [full outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Full_outer_join). - * *suffix*: Column name suffixes to append, for the left and right tables, respectively, when two columns with the same name are produced by the join (default `['_1', '_2']`). - -*Examples* - -```js -table.join(other, 'keyShared') -``` - -```js -table.join(other, ['keyL', 'keyR']) -``` - -```js -table.join(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - - -
# -table.join_left(other[, on, values, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join.js) - -Perform a [left outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Left_outer_join) on two tables. Rows in the left table that do not match a row in the right table will be preserved. This method is a convenient shorthand with fixed options `{left: true, right: false}` passed to [join](#join). - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. -* *values*: The columns to include in the join output. If unspecified, all columns from both tables are included; paired join keys sharing the same column name are included only once. If array-valued, a two element array should be provided, containing column selections to include from the left and right tables, respectively. Array input may consist of column name strings, objects with output names as keys and single-table table expressions as values, or the selection helper functions [all](./#all), [not](./#not), or [range](./#range). If object-valued, specifies the key-value pairs for each output, defined using two-table table expressions. -* *options*: An options object: - * *suffix*: Column name suffixes to append, for the left and right tables, respectively, when two columns with the same name are produced by the join (default `['_1', '_2']`). - -*Examples* - -```js -table.join_left(other, 'keyShared') -``` - -```js -table.join_left(other, ['keyL', 'keyR']) -``` - -```js -table.join_left(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - - -
# -table.join_right(other[, on, values, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join.js) - -Perform a [right outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Right_outer_join) on two tables. Rows in the right table that do not match a row in the left table will be preserved. This method is a convenient shorthand with fixed options `{left: false, right: true}` passed to [join](#join). - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. -* *values*: The columns to include in the join output. If unspecified, all columns from both tables are included; paired join keys sharing the same column name are included only once. If array-valued, a two element array should be provided, containing column selections to include from the left and right tables, respectively. Array input may consist of column name strings, objects with output names as keys and single-table table expressions as values, or the selection helper functions [all](./#all), [not](./#not), or [range](./#range). If object-valued, specifies the key-value pairs for each output, defined using two-table table expressions. -* *options*: An options object: - * *suffix*: Column name suffixes to append, for the left and right tables, respectively, when two columns with the same name are produced by the join (default `['_1', '_2']`). - -*Examples* - -```js -table.join_right(other, 'keyShared') -``` - -```js -table.join_right(other, ['keyL', 'keyR']) -``` - -```js -table.join_right(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - -
# -table.join_full(other[, on, values, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join.js) - -Perform a [full outer join](https://en.wikipedia.org/wiki/Join_%28SQL%29#Full_outer_join) on two tables. Rows in either the left or right table that do not match a row in the other will be preserved. This method is a convenient shorthand with fixed options `{left: true, right: true}` passed to [join](#join). - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. -* *values*: The columns to include in the join output. If unspecified, all columns from both tables are included; paired join keys sharing the same column name are included only once. If array-valued, a two element array should be provided, containing column selections to include from the left and right tables, respectively. Array input may consist of column name strings, objects with output names as keys and single-table table expressions as values, or the selection helper functions [all](./#all), [not](./#not), or [range](./#range). If object-valued, specifies the key-value pairs for each output, defined using two-table table expressions. -* *options*: An options object: - * *suffix*: Column name suffixes to append, for the left and right tables, respectively, when two columns with the same name are produced by the join (default `['_1', '_2']`). - -*Examples* - -```js -table.join_full(other, 'keyShared') -``` - -```js -table.join_full(other, ['keyL', 'keyR']) -``` - -```js -table.join_full(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - -
# -table.lookup(other, on, ...values) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/lookup.js) - -Lookup values from a secondary table and add them as new columns. A lookup occurs upon matching key values for rows in both tables. If the secondary table has multiple rows with the same key, only the last observed instance will be considered in the lookup. Lookup is similar to [join_left](#join_left), but with a streamlined syntax and the added constraint of allowing at most one match only. - -* *other*: The secondary table to look up values from. -* *on*: A two-element array of lookup keys (column name strings or table expressions) for this table and the secondary table, respectively. -* *values*: The column values to add from the secondary table. Can be column name strings or objects with column names as keys and table expressions as values. - -*Example* - -```js -table.lookup(other, ['key1', 'key2'], 'value1', 'value2') -``` - -
# -table.semijoin(other[, on]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join-filter.js) - -Perform a [semi-join](https://en.wikipedia.org/wiki/Relational_algebra#Semijoin), filtering the left table to only rows that match a row in the right table. - -Similar to the [filter](#filter) verb, the resulting table provides a filtered view over the original data; no data copy is made. To create a table that copies only semi-joined data to new data structures, call [reify](#reify) on the output table. - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. - -*Examples* - -```js -table.semijoin(other) -``` - -```js -table.semijoin(other, 'keyShared') -``` - -```js -table.semijoin(other, ['keyL', 'keyR']) -``` - -```js -table.semijoin(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - - -
# -table.antijoin(other[, on]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/join-filter.js) - -Perform an [anti-join](https://en.wikipedia.org/wiki/Relational_algebra#Antijoin), filtering the left table to only rows that do *not* match a row in the right table. - -Similar to the [filter](#filter) verb, the resulting table provides a filtered view over the original data; no data copy is made. To create a table that copies only anti-joined data to new data structures, call [reify](#reify) on the output table. - -* *other*: The other (right) table to join with. -* *on*: The join criteria for matching table rows. If unspecified, the values of all columns with matching names are compared. If array-valued, a two-element array should be provided, containing the columns to compare for the left and right tables, respectively. If a one-element array or a string value is provided, the same column names will be drawn from both tables. If function-valued, should be a two-table table expression that returns a boolean value. When providing a custom predicate, note that join key values can be arrays or objects, and that normal join semantics do not consider null or undefined values to be equal (that is, `null !== null`). Use the [op.equal](op#equal) function to handle these cases. - -*Examples* - -```js -table.antijoin(other) -``` - -```js -table.antijoin(other, 'keyShared') -``` - -```js -table.antijoin(other, ['keyL', 'keyR']) -``` - -```js -table.antijoin(other, (a, b) => op.equal(a.keyL, b.keyR)) -``` - - -
- -## Cleaning Verbs - -
# -table.dedupe(...keys) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/dedupe.js) - -De-duplicate table rows by removing repeated row values. - -* *keys*: Key columns to check for duplicates. Two rows are considered duplicates if they have matching values for all keys. If keys are unspecified, all columns are used. Keys may be column name strings, column index numbers, or value objects with output column names for keys and table expressions for values. - -*Examples* - -```js -// remove rows that duplicate all column values -table.dedupe() -``` - -```js -// remove rows that duplicate the 'a' and 'b' columns -table.dedupe('a', 'b') -``` - -```js -// remove rows that duplicate the absolute value of column 'a' -table.dedupe({ abs: d => op.abs(d.a) }) -``` - - -
# -table.impute(values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/impute.js) - -Impute missing values or rows. Any of `null`, `undefined`, or `NaN` are considered missing values. - -The *expand* option additionally imputes new rows for missing combinations of values. All combinations of expand values (the full cross product) are considered for each group (if specified by [groupby](#groupby)). New rows are added for any combination of expand and groupby values not already contained in the table; the additional columns are populated with imputed values (if specified in *values*) or are otherwise `undefined`. - -The output table persists a [groupby](#groupby) specification. If the *expand* option is specified, a reified table is returned without any [filter](#filter) or [orderby](#orderby) settings. - -* *values*: Object of name-value pairs for the column values to impute. The input object should have existing column names for keys and table expressions for values. The expressions will be evaluated to determine replacements for any missing values (`null`, `undefined`, or `NaN`). -* *options*: An options object: - * *expand*: Impute new rows for any missing combinations of the provided expansion values. Accepts column names, column indices, or an object of name-expression pairs. Table expressions must be valid inputs to [rollup](#rollup). All combinations of values will be checked for each unique set of groupby values. - -*Examples* - -```js -// replace missing values in column 'v' with zeros -table.impute({ v: () => 0 }) -``` - -```js -// replace missing values in column 'v' with the mean of non-missing values -table.impute({ v: d => op.mean(d.v) }) -``` - -```js -// replace missing values in column 'v' with zeros -// impute rows based on all combinations of values in columns 'x' and 'y' -table.impute({ v: () => 0 }, { expand: ['x', 'y'] }) -``` - - -
- -## Reshape Verbs - -
# -table.fold(values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/fold.js) - -Fold one or more columns into two key-value pair columns. The fold transform is an inverse of the [pivot](#pivot) transform. The resulting table has two new columns, one containing the column names (named "key") and the other the column values (named "value"). The number of output rows equals the original row count multiplied by the number of folded columns. - -* *values*: The columns to fold. The input may consist of an array with column name strings, objects with output names as keys and current names as values (output names will be ignored), or the output of the selection helper functions [all](./#all), [not](./#not), or [range](./#range)). -* *options*: An options object: - * *as*: A two-element array indicating the output column names to use for the key and value columns, respectively. The default is `['key', 'value']`. - -*Examples* - -```js -table.fold('colA') -``` - -```js -table.fold(['colA', 'colB']) -``` - -```js -table.fold(aq.range(5, 8)) -``` - - -
# -table.pivot(keys, values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/pivot.js) - -Pivot columns into a cross-tabulation. The pivot transform is an inverse of the [fold](#fold) transform. The resulting table has new columns for each unique combination of the provided *keys*, populated with the provided *values*. The provided *values* must be aggregates, as a single set of keys may include more than one row. If string-valued, the [any](op#any) aggregate is used. If only one *values* column is defined, the new pivoted columns are named using key values directly. Otherwise, input value column names are included as a component of the output column names. - -* *keys*: Key values to map to new column names. Keys may be an array of column name strings, column index numbers, or value objects with output column names for keys and table expressions for values. -* *values*: Output values for pivoted columns. Column string names will be wrapped in any [any](op#any) aggregate. If object-valued, the input object should have output value names for keys and aggregate table expressions for values. -* *options*: An options object: - * *limit*: The maximum number of new columns to generate (default `Infinity`). - * *keySeparator*: A string to place between multiple key names (default `'_'`). - * *valueSeparator*: A string to place between key and value names (default `'_'`). - * *sort*: A boolean flag (default `true`) for alphabetical sorting of new column names. - -*Examples* - -```js -// pivot the values in the 'key' column to be new column names -// using the 'value' column as the new column values -// the any() aggregate combines multiple values with the same key -table.pivot('key', 'value') -``` - -```js -// pivot lowercase values of the 'key' column to be new column names -// use the sum of corresponding 'value' entris as new column values -table.pivot( - { key: d => op.lower(d.key) }, - { value: d => op.sum(d.value) } -) -``` - -```js -// pivot on key column 'type' and value columns ['x', 'y'] -// generates: { x_a: [1], x_b: [2], y_a: [3], y_b: [4] } -aq.table({ type: ['a', 'b'], x: [1, 2], y: [3, 4 ]}) - .pivot('type', ['x', 'y']) -``` - -```js -// pivot on the combination of the keys 'foo' and 'bar' for the values of 'x' and 'y' -aq.table({ foo: ['a', 'b'], bar: ['u', 'v'], x: [1, 2], y: [3, 4 ]}) - .pivot(['foo', 'bar'], ['x', 'y']) -``` - -
# -table.spread(values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/spread.js) - -Spread array elements into a set of new columns. Output columns are named either according to the *as* option or using a combination of the input colum names and array index. - -* *values*: The columns to spread, as either an array of column names or a key-value object of table expressions. -* *options*: An options object: - * *drop*: Boolean flag (default `true`) indicating if input columns to the spread operation should be dropped in the output table. - * *limit*: The maximum number of new columns to generate (default `Infinity`). - * *as*: String array of output column names to use. This option only applies when a single column is spread. If the given array of names is shorter than the number of generated columns and no *limit* option is specified, the additional generated columns will be dropped (in other words, the length of the *as* array then serves as the limit value). - -*Examples* - -```js -// generate new columns 'text_1', 'text_2', etc. by splitting on whitespace -// the input column 'text' is dropped from the output -table.spread({ text: d => op.split(d.text, ' ') }) -``` - -```js -// generate new columns 'text_1', 'text_2', etc. by splitting on whitespace -// the input column 'text' is retained in the output -table.spread({ text: d => op.split(d.text, ' ') }, { drop: false }) -``` - -```js -// spread the 'arrayCol' column across a maximum of 100 new columns -table.spread('arrayCol', { limit: 100 }) -``` - -```js -// extract the first two 'arrayCol' entries into 'value1', 'value2' columns -table.spread('arrayCol', { as: ['value1', 'value2'] }) -``` - - -
# -table.unroll(values[, options]) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/unroll.js) - -Unroll one or more array-valued columns into new rows. If more than one array value is used, the number of new rows is the smaller of the limit and the largest length. Values for all other columns are copied over. - -* *values*: The columns to unroll, as either an array of column names or a key-value object of table expressions. -* *options*: An options object: - * *limit*: The maximum number of new columns to generate per array value (default `Infinity`). - * *index*: Boolean flag (default `false`) or column name for adding zero-based array index values as an output column. If `true`, a new column named "index" will be added. If string-valued, a new column with the given name will be added. - * *drop*: A selection of columns to drop (exclude) from the unrolled output. The input may consist of column name strings, column integer indices, objects with output names as keys (object values will be ignored), or the output of the selection helper functions [all](./#all), [not](./#not), or [range](./#range)). - -*Examples* - -```js -table.unroll('colA', { limit: 1000 }) -``` - -```js -table.unroll('colA', { limit: 1000, index: 'idxnum' }) -``` - - -
- -## Set Verbs - -
# -table.concat(...tables) · [Source](https://github.com/uwdata/arquero/blob/master/src/engine/concat.js) - -Concatenate multiple tables into a single table, preserving all rows. This transformation mirrors the [UNION_ALL](https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#UNION_operator) operation in SQL. It is similar to [union](#union) but retains all rows, without removing duplicates. Only named columns in this table are included in the output. - -* *tables*: A list of tables to concatenate. - -*Examples* - -```js -table.concat(other) -``` - -```js -table.concat(other1, other2) -``` - -```js -table.concat([other1, other2]) -``` - - -
# -table.union(...tables) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/union.js) - -Union multiple tables into a single table, deduplicating all rows. This transformation mirrors the [UNION](https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#UNION_operator) operation in SQL. It is similar to [concat](#concat) but suppresses duplicate rows with values identical to another row. Only named columns in this table are included in the output. - -* *tables*: A list of tables to union. - -*Examples* - -```js -table.union(other) -``` - -```js -table.union(other1, other2) -``` - -```js -table.union([other1, other2]) -``` - - -
# -table.intersect(...tables) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/intersect.js) - -[Intersect](https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#INTERSECT_operator) multiple tables, keeping only rows with matching values for all columns in all tables, and deduplicates the rows. This transformation is similar to a series of one or more [semijoin](#semijoin) calls, but additionally suppresses duplicate rows. - -* *tables*: A list of tables to intersect. - -*Examples* - -```js -table.intersect(other) -``` - -```js -table.intersect(other1, other2) -``` - -```js -table.intersect([other1, other2]) -``` - - -
# -table.except(...tables) · [Source](https://github.com/uwdata/arquero/blob/master/src/verbs/except.js) - -Compute the [set difference](https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#EXCEPT_operator) with multiple tables, keeping only rows in this table whose values do not occur in the other tables. This transformation is similar to a series of one or more [antijoin](#antijoin) calls, but additionally suppresses duplicate rows. - -* *tables*: A list of tables to difference. - -*Examples* - -```js -table.except(other) -``` - -```js -table.except(other1, other2) -``` - -```js -table.except([other1, other2]) -``` diff --git a/docs/assets/logo-1280-640.png b/docs/assets/logo-1280-640.png deleted file mode 100644 index b00940e1ae67bfbe59e7fdacece1d334a1c4e005..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14717 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 DNq2vq diff --git a/docs/assets/logo-1280.svg b/docs/assets/logo-1280.svg deleted file mode 100644 index 261d3ca1..00000000 --- a/docs/assets/logo-1280.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg deleted file mode 100644 index a1577fbd..00000000 --- a/docs/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 7ee2a338..00000000 --- a/docs/index.md +++ /dev/null @@ -1,120 +0,0 @@ -# Arquero - -**Arquero** is a JavaScript library for query processing and transformation of array-backed data tables. Following the [relational algebra](https://en.wikipedia.org/wiki/Relational_algebra) and inspired by the design of [dplyr](https://dplyr.tidyverse.org/), Arquero provides a fluent API for manipulating column-oriented data frames. Arquero supports a range of data transformation tasks, including filter, sample, aggregation, window, join, and reshaping operations. - -* Fast: process data tables with million+ rows. -* Flexible: query over arrays, typed arrays, array-like objects, or [Apache Arrow](https://arrow.apache.org/) columns. -* Full-Featured: perform a variety of wrangling and analysis tasks. -* Extensible: add new column types or functions, including aggregate & window operations. -* Lightweight: small size, minimal dependencies. - -To get up and running, start with the [Introducing Arquero](https://observablehq.com/@uwdata/introducing-arquero) tutorial, part of the [Arquero notebook collection](https://observablehq.com/collection/@uwdata/arquero). - -Arquero is Spanish for "archer": if datasets are [arrows](https://arrow.apache.org/), Arquero helps their aim stay true. 🏹 Arquero also refers to a goalkeeper: safeguard your data from analytic "own goals"! 🥅 ✋ ⚽ - -## API Documentation - -* [Top-Level API](api) - All methods in the top-level Arquero namespace. -* [Table](api/table) - Table access and output methods. -* [Verbs](api/verbs) - Table transformation verbs. -* [Op Functions](api/op) - All functions, including aggregate and window functions. -* [Expressions](api/expressions) - Parsing and generation of table expressions. -* [Extensibility](api/extensibility) - Extend Arquero with new expression functions or table verbs. - -## Example - -The core abstractions in Arquero are *data tables*, which model each column as an array of values, and *verbs* that transform data and return new tables. Verbs are table methods, allowing method chaining for multi-step transformations. Though each table is unique, many verbs reuse the underlying columns to limit duplication. - -```js -import { all, desc, op, table } from 'arquero'; - -// Average hours of sunshine per month, from https://usclimatedata.com/. -const dt = table({ - 'Seattle': [69, 108, 178, 207, 253, 268, 312, 281, 221, 142, 72, 52], - 'Chicago': [135, 136, 187, 215, 281, 311, 318, 283, 226, 193, 113, 106], - 'San Francisco': [165, 182, 251, 281, 314, 330, 300, 272, 267, 243, 189, 156] -}); - -// Sorted differences between Seattle and Chicago. -// Table expressions use arrow function syntax. -dt.derive({ - month: d => op.row_number(), - diff: d => d.Seattle - d.Chicago - }) - .select('month', 'diff') - .orderby(desc('diff')) - .print(); - -// Is Seattle more correlated with San Francisco or Chicago? -// Operations accept column name strings outside a function context. -dt.rollup({ - corr_sf: op.corr('Seattle', 'San Francisco'), - corr_chi: op.corr('Seattle', 'Chicago') - }) - .print(); - -// Aggregate statistics per city, as output objects. -// Reshape (fold) the data to a two column layout: city, sun. -dt.fold(all(), { as: ['city', 'sun'] }) - .groupby('city') - .rollup({ - min: d => op.min(d.sun), // functional form of op.min('sun') - max: d => op.max(d.sun), - avg: d => op.average(d.sun), - med: d => op.median(d.sun), - // functional forms permit flexible table expressions - skew: ({sun: s}) => (op.mean(s) - op.median(s)) / op.stdev(s) || 0 - }) - .objects() -``` - -## Usage - -### In Browser - -To use in the browser, you can load Arquero from a content delivery network: - -```html - -``` - -Arquero will be imported into the `aq` global object. The default browser bundle does not include the [Apache Arrow](https://arrow.apache.org/) library. To perform Arrow encoding using [toArrow()](api/#toArrow) or binary file loading using [loadArrow()](api/#loadArrow), import Apache Arrow first: - -```html - - -``` - -Alternatively, you can build and import `arquero.min.js` from the `dist` directory, or build your own application bundle. When building custom application bundles for the browser, the module bundler should draw from the `browser` property of Arquero's `package.json` file. For example, if using [rollup](https://rollupjs.org/), pass the `browser: true` option to the [node-resolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve) plugin. - -Arquero uses modern JavaScript features, and so will not work with some outdated browsers. To use Arquero with older browsers including Internet Explorer, set up your project with a transpiler such as [Babel](https://babeljs.io/). - -### In Node.js or Application Bundles - -First install `arquero` as a dependency, via `npm install arquero --save` or `yarn add arquero`. Arquero assumes Node version 12 or higher. - -Import using CommonJS module syntax: - -```js -const aq = require('arquero'); -``` - -Import using ES module syntax, import all exports into a single object: - -```js -import * as aq from 'arquero'; -``` - -Import using ES module syntax, with targeted imports: - -```js -import { op, table } from 'arquero'; -``` - -## Build Instructions - -To build and develop Arquero locally: - -- Clone [https://github.com/uwdata/arquero](https://github.com/uwdata/arquero). -- Run `yarn` to install dependencies for all packages. If you don't have yarn installed, see [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install). -- Run `yarn test` to run test cases, `yarn perf` to run performance benchmarks, and `yarn build` to build output files. \ No newline at end of file diff --git a/perf/arrow-perf.js b/perf/arrow-perf.js deleted file mode 100644 index c0bcb1ac..00000000 --- a/perf/arrow-perf.js +++ /dev/null @@ -1,138 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { bools, floats, ints, sample, strings } = require('./data-gen'); -const { fromArrow, table } = require('..'); -const { - Bool, Dictionary, Float64, Int32, Table, Uint32, Utf8, Vector, predicate -} = require('apache-arrow'); - -function process(N, nulls, msg) { - const vectors = [ - Vector.from({ - type: new Dictionary(new Utf8(), new Int32()), - values: sample(N, strings(100), nulls), - highWaterMark: 1e12 - }), - Vector.from({ - type: new Int32(), - values: ints(N, -10000, 10000, nulls), - highWaterMark: 1e12 - }) - ]; - const at = Table.new(vectors, ['k', 'v']); - const dt = fromArrow(at); - - const arrowFilterDict = val => time(() => { - const p = new predicate.Equals( - new predicate.Col('k'), - new predicate.Literal(val) - ); - at.filter(p).count(); - }); - - const arqueroFilterDict = val => time(() => { - dt.filter(`d.k === '${val}'`).numRows(); - }); - - const arrowFilterValue = val => time(() => { - const p = new predicate.GTeq( - new predicate.Col('v'), - new predicate.Literal(val) - ); - at.filter(p).count(); - }); - - const arqueroFilterValue = val => time(() => { - dt.filter(`d.v >= ${val}`).numRows(); - }); - - tape(`arrow processing: ${msg}`, t => { - const k = at.getColumn('k').get(50); - console.table([ // eslint-disable-line - { - operation: 'init table', - 'arrow-js': time(() => Table.new(vectors, ['k', 'v'])), - arquero: time(() => fromArrow(at)) - }, - { - operation: 'count dictionary', - 'arrow-js': time(() => at.countBy('k')), - arquero: time(() => dt.groupby('k').count()) - }, - { - operation: 'filter dictionary', - 'arrow-js': arrowFilterDict(k), - arquero: arqueroFilterDict(k) - }, - { - operation: 'filter numbers 0', - 'arrow-js': arrowFilterValue(0), - arquero: arqueroFilterValue(0) - }, - { - operation: 'filter numbers 1', - 'arrow-js': arrowFilterValue(1), - arquero: arqueroFilterValue(1) - } - ]); - t.end(); - }); -} - -function serialize(N, nulls, msg) { - tape(`arrow serialization: ${msg}`, t => { - console.table([ // eslint-disable-line - encode('boolean', new Bool(), bools(N, nulls)), - encode('integer', new Int32(), ints(N, -10000, 10000, nulls)), - encode('float', new Float64(), floats(N, -10000, 10000, nulls)), - encode('dictionary', - new Dictionary(new Utf8(), new Uint32(), 0), - sample(N, strings(100), nulls) - ) - ]); - t.end(); - }); -} - -function encode(name, type, values) { - const dt = table({ values }); - - // measure encoding times - const qt = time(() => dt.toArrow({ types: { values: type } }).serialize()); - const at = time(() => Table.new( - [Vector.from({ type, values, highWaterMark: 1e12 })], - ['values'] - ).serialize()); - const jt = time(() => JSON.stringify(values)); - - // measure serialized byte size - const ab = Table.new( - [Vector.from({ type, values, highWaterMark: 1e12 })], - ['values'] - ).serialize().length; - const qb = dt.toArrow({ types: { values: type }}).serialize().length; - const jb = (new TextEncoder().encode(JSON.stringify(values))).length; - - // check that arrow and arquero produce the same result - if (qb !== ab) { - // eslint-disable-next-line - console.warn(`Arrow and Arquero bytes don't match: ${ab} vs. ${qb}`); - } - - return { - 'data type': name, - 'arrow-js': at, - 'arquero': qt, - 'json': jt, - 'size-arrow': ab, - 'size-json': jb - }; -} - -// run arrow processing benchmarks -process(5e6, 0, '5M values, 0% nulls'); -process(5e6, 0.05, '5M values, 5% nulls'); - -// run arrow serialization benchmarks -serialize(1e6, 0, '1M values'); -serialize(1e6, 0.05, '1M values, 5% nulls'); \ No newline at end of file diff --git a/perf/csv-perf.js b/perf/csv-perf.js deleted file mode 100644 index 65f59a07..00000000 --- a/perf/csv-perf.js +++ /dev/null @@ -1,40 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { bools, dates, floats, ints, sample, strings } = require('./data-gen'); -const { fromCSV, table } = require('..'); - -function toCSV(...values) { - const cols = values.map((v, i) => [`col${i}`, v]); - return table(cols).toCSV(); -} - -function parse(csv, opt) { - return time(() => fromCSV(csv, opt)); -} - -function run(N, nulls, msg) { - const opt = { - parse: [0,1,2,3,4].reduce((p, i) => (p[`col${i}`] = x => x, p), {}) - }; - const bv = bools(N, nulls); - const iv = ints(N, -10000, 10000, nulls); - const fv = floats(N, -10000, 10000, nulls); - const dv = dates(N, nulls); - const sv = sample(N, strings(100), nulls); - const av = [bv, iv, fv, dv, sv]; - - tape(`parse csv: ${msg}`, t => { - console.table([ // eslint-disable-line - { type: 'boolean', raw: parse(toCSV(bv), opt), typed: parse(toCSV(bv)) }, - { type: 'integer', raw: parse(toCSV(iv), opt), typed: parse(toCSV(iv)) }, - { type: 'float', raw: parse(toCSV(fv), opt), typed: parse(toCSV(fv)) }, - { type: 'date', raw: parse(toCSV(dv), opt), typed: parse(toCSV(dv)) }, - { type: 'string', raw: parse(toCSV(sv), opt), typed: parse(toCSV(sv)) }, - { type: 'all', raw: parse(toCSV(...av), opt), typed: parse(toCSV(...av)) } - ]); - t.end(); - }); -} - -run(1e5, 0, '100k values'); -run(1e5, 0.05, '100k values, 5% nulls'); \ No newline at end of file diff --git a/perf/data-gen.js b/perf/data-gen.js deleted file mode 100644 index 26b0570c..00000000 --- a/perf/data-gen.js +++ /dev/null @@ -1,88 +0,0 @@ -function rint(min, max) { - let delta = min; - if (max === undefined) { - min = 0; - } else { - delta = max - min; - } - return (min + delta * Math.random()) | 0; -} - -function ints(n, min, max, nullf) { - const data = []; - for (let i = 0; i < n; ++i) { - const v = nullf && Math.random() < nullf ? null : rint(min, max); - data.push(v); - } - return data; -} - -function floats(n, min, max, nullf) { - const data = []; - const delta = max - min; - for (let i = 0; i < n; ++i) { - const v = nullf && Math.random() < nullf - ? null - : (min + delta * Math.random()); - data.push(v); - } - return data; -} - -function dates(n, nullf) { - const data = []; - for (let i = 0; i < n; ++i) { - const v = nullf && Math.random() < nullf - ? null - : new Date(1970 + rint(0, 41), 0, rint(1, 366)); - data.push(v); - } - return data; -} - -function strings(n) { - const c = 'bcdfghjlmpqrstvwxyz'; - const v = 'aeiou'; - const cn = c.length; - const vn = v.length; - const data = []; - const map = {}; - while (data.length < n) { - const s = c[rint(cn)] + v[rint(vn)] + c[rint(cn)] + c[rint(cn)]; - if (!map[s]) { - data.push(s); - map[s] = 1; - } - } - return data; -} - -function bools(n, nullf) { - const data = []; - for (let i = 0; i < n; ++i) { - const v = nullf && Math.random() < nullf ? null : (Math.random() < 0.5); - data.push(v); - } - return data; -} - -function sample(n, values, nullf) { - const data = []; - for (let i = 0; i < n; ++i) { - const v = nullf && Math.random() < nullf - ? null - : values[~~(values.length * Math.random())]; - data.push(v); - } - return data; -} - -module.exports = { - rint, - ints, - floats, - dates, - strings, - bools, - sample -}; \ No newline at end of file diff --git a/perf/derive-perf.js b/perf/derive-perf.js deleted file mode 100644 index 1a0fb185..00000000 --- a/perf/derive-perf.js +++ /dev/null @@ -1,42 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { floats, sample, strings } = require('./data-gen'); -const { table } = require('..'); - -function run(N, nulls, msg) { - const dt = table({ - k: sample(N, strings(10), nulls), - c: sample(N, strings(100), nulls), - a: floats(N, -10000, 10000, nulls), - b: floats(N, -10000, 10000, nulls) - }); - - const gt = dt.groupby('k'); - const sum2 = { s: 'd.a + d.b' }; - const fill = { p: 'fill_down(d.c)' }; - const zscr = { z: '(d.a - mean(d.a)) / stdev(d.a) || 0' }; - - tape(`derive: ${msg}`, t => { - console.table([ // eslint-disable-line - { - op: 'sum2', - flat: time(() => dt.derive(sum2)), - group: time(() => gt.derive(sum2)) - }, - { - op: 'fill', - flat: time(() => dt.derive(fill)), - group: time(() => gt.derive(fill)) - }, - { - op: 'zscore', - flat: time(() => dt.derive(zscr)), - group: time(() => gt.derive(zscr)) - } - ]); - t.end(); - }); -} - -run(1e6, 0, '1M values'); -run(1e6, 0.05, '1M values, 5% nulls'); \ No newline at end of file diff --git a/perf/escape-perf.js b/perf/escape-perf.js deleted file mode 100644 index 7f19142e..00000000 --- a/perf/escape-perf.js +++ /dev/null @@ -1,28 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { floats, sample, strings } = require('./data-gen'); -const aq = require('..'); - -function run(N, nulls, msg) { - const off = 1; - const dt = aq.table({ - k: sample(N, strings(10), nulls), - c: sample(N, strings(100), nulls), - a: floats(N, -10000, 10000, nulls), - b: floats(N, -10000, 10000, nulls) - }).params({ off }); - - const opt = { s: 'd.a * d.b + off' }; - const esc = { s: aq.escape(d => d.a * d.b + off) }; - - tape(`escape: ${msg}`, t => { - console.table([ // eslint-disable-line - { type: 'opt', flat: time(() => dt.derive(opt)) }, - { type: 'esc', flat: time(() => dt.derive(esc)) } - ]); - t.end(); - }); -} - -run(1e6, 0, '1M values'); -run(1e6, 0.05, '1M values, 5% nulls'); \ No newline at end of file diff --git a/perf/filter-perf.js b/perf/filter-perf.js deleted file mode 100644 index 66151a7a..00000000 --- a/perf/filter-perf.js +++ /dev/null @@ -1,44 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { floats, ints, sample, strings } = require('./data-gen'); -const { table } = require('..'); - -function run(N, nulls, msg) { - const dt = table({ - a: ints(N, -10000, 10000, nulls), - b: floats(N, -10000, 10000, nulls), - c: sample(N, strings(2), nulls) - }); - - const str = dt.get('c', 0); - - tape(`filter: ${msg}`, t => { - console.table([ // eslint-disable-line - { - type: 'integer', - table: time(() => dt.filter('d.a > 0')), - reify: time(() => dt.filter('d.a > 0').reify()), - object: time(a => a.filter(d => d.a > 0), dt.objects()), - array: time(a => a.filter(v => v > 0), dt.column('a').data) - }, - { - type: 'float', - table: time(() => dt.filter('d.b > 0')), - reify: time(() => dt.filter('d.b > 0').reify()), - object: time(a => a.filter(d => d.b > 0), dt.objects()), - array: time(a => a.filter(v => v > 0), dt.column('b').data) - }, - { - type: 'string', - table: time(() => dt.filter(`d.c === '${str}'`)), - reify: time(() => dt.filter(`d.c === '${str}'`).reify()), - object: time(a => a.filter(d => d.c === str), dt.objects()), - array: time(a => a.filter(v => v === str), dt.column('c').data) - } - ]); - t.end(); - }); -} - -run(1e6, 0, '1M values'); -run(1e6, 0.05, '1M values, 5% nulls'); \ No newline at end of file diff --git a/perf/rollup-perf.js b/perf/rollup-perf.js deleted file mode 100644 index 3a2aa96b..00000000 --- a/perf/rollup-perf.js +++ /dev/null @@ -1,70 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { floats, sample, strings } = require('./data-gen'); -const { table, op } = require('..'); - -function run(N, nulls, msg) { - const dt = table({ - k: sample(N, strings(10), nulls), - a: floats(N, -10000, 10000, nulls), - b: floats(N, -10000, 10000, nulls), - c: floats(N, -10000, 10000, nulls) - }); - - const g = time(() => dt.groupby('k')); - const gt = dt.groupby('k'); - const sum1 = { a: op.sum('a') }; - const sum2 = { a: op.sum('a'), b: op.sum('b') }; - const sum3 = { a: op.sum('a'), b: op.sum('b'), c: op.sum('c') }; - - const avg1 = { a: op.mean('a') }; - const avg2 = { a: op.mean('a'), b: op.mean('b') }; - const avg3 = { a: op.mean('a'), b: op.mean('b'), c: op.mean('c') }; - - tape(`rollup: ${msg}`, t => { - const fc = time(() => dt.count()); - const gc = time(() => gt.count()); - console.table([ // eslint-disable-line - { - op: 'group', - 'flat-1': 0, - 'flat-2': 0, - 'flat-3': 0, - 'group-1': g, - 'group-2': g, - 'group-3': g - }, - { - op: 'count', - 'flat-1': fc, - 'flat-2': fc, - 'flat-3': fc, - 'group-1': gc, - 'group-2': gc, - 'group-3': gc - }, - { - op: 'sum', - 'flat-1': time(() => dt.rollup(sum1)), - 'flat-2': time(() => dt.rollup(sum2)), - 'flat-3': time(() => dt.rollup(sum3)), - 'group-1': time(() => dt.rollup(sum1)), - 'group-2': time(() => dt.rollup(sum2)), - 'group-3': time(() => dt.rollup(sum3)) - }, - { - op: 'avg', - 'flat-1': time(() => dt.rollup(avg1)), - 'flat-2': time(() => dt.rollup(avg2)), - 'flat-3': time(() => dt.rollup(avg3)), - 'group-1': time(() => dt.rollup(avg1)), - 'group-2': time(() => dt.rollup(avg2)), - 'group-3': time(() => dt.rollup(avg3)) - } - ]); - t.end(); - }); -} - -run(1e6, 0, '1M values'); -run(1e6, 0.05, '1M values, 5% nulls'); \ No newline at end of file diff --git a/perf/table-perf.js b/perf/table-perf.js deleted file mode 100644 index cc47a0d6..00000000 --- a/perf/table-perf.js +++ /dev/null @@ -1,34 +0,0 @@ -const tape = require('tape'); -const time = require('./time'); -const { floats, ints, sample, strings } = require('./data-gen'); -const { from, table } = require('..'); - -function run(N, nulls, msg) { - const dt = table({ - a: ints(N, -10000, 10000, nulls), - b: floats(N, -10000, 10000, nulls), - c: sample(N, strings(2), nulls), - d: sample(N, strings(50), nulls), - e: sample(N, strings(100), nulls) - }); - - const array = dt.objects(); - const iterable = { - [Symbol.iterator]: () => array[Symbol.iterator]() - }; - - tape(`object serialization: ${msg}`, t => { - console.table([ // eslint-disable-line - { - from_array: time(() => from(array)), - from_iterable: time(() => from(iterable)), - to_objects: time(() => dt.objects()), - to_iterable: time(() => [...dt]) - } - ]); - t.end(); - }); -} - -run(1e5, 0, '100k values'); -run(1e5, 0.05, '100k values, 5% nulls'); \ No newline at end of file diff --git a/perf/time.js b/perf/time.js deleted file mode 100644 index eada5eb8..00000000 --- a/perf/time.js +++ /dev/null @@ -1,7 +0,0 @@ -const { performance } = require('perf_hooks'); - -module.exports = function time(fn, ...args) { - const t0 = performance.now(); - fn(...args); - return Math.round(performance.now() - t0); -}; \ No newline at end of file diff --git a/test/arrow/data-from-test.js b/test/arrow/data-from-test.js deleted file mode 100644 index 17900b7b..00000000 --- a/test/arrow/data-from-test.js +++ /dev/null @@ -1,167 +0,0 @@ -import tape from 'tape'; -import { - Bool, DateDay, DateMillisecond, Dictionary, Field, FixedSizeList, - Float32, Float64, Int16, Int32, Int64, Int8, List, Struct, Table, - Uint16, Uint32, Uint64, Uint8, Utf8, Vector -} from 'apache-arrow'; -import { dataFromScan } from '../../src/arrow/encode/data-from'; -import { scanTable } from '../../src/arrow/encode/scan'; -import { table } from '../../src/table'; - -function dataFromTable(table, column, type, nullable) { - const nrows = table.numRows(); - const scan = scanTable(table, Infinity, 0); - return dataFromScan(nrows, scan, column, type, nullable); -} - -function integerTest(t, type) { - const values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); -} - -function floatTest(t, type) { - const values = [0, NaN, 1/3, Math.PI, 7, Infinity, -Infinity]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); -} - -function bigintTest(t, type) { - const values = [0n, 1n, 10n, 100n, 1000n, 10n ** 10n]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); -} - -function dateTest(t, type) { - const date = (y, m = 0, d = 1) => new Date(Date.UTC(y, m, d)); - const values = [ - date(2000, 0, 1), - date(2004, 10, 12), - date(2007, 3, 14), - date(2009, 6, 26), - date(2000, 0, 1), - date(2004, 10, 12), - date(2007, 3, 14), - date(2009, 6, 26), - date(2000, 0, 1), - date(2004, 10, 12) - ]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); -} - -function valueTest(t, type, values, msg) { - const dt = table({ values }); - const u = dataFromTable(dt, dt.column('values'), type); - const v = Vector.from({ type, values, highWaterMark: Infinity }); - const tu = Table.new([u], ['values']); - const tv = Table.new([v], ['values']); - t.equal( - tu.serialize().join(' '), - tv.serialize().join(' '), - 'serialized data matches' + msg - ); -} - -tape('dataFrom encodes dictionary data', t => { - const type = new Dictionary(new Utf8(), new Uint32(), 0); - const values = ['a', 'b', 'FOO', 'b', 'a']; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); - t.end(); -}); - -tape('dataFrom encodes boolean data', t => { - const type = new Bool(); - const values = [true, false, false, true, false]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); - t.end(); -}); - -tape('dataFrom encodes date millis data', t => { - dateTest(t, new DateMillisecond()); - t.end(); -}); - -tape('dataFrom encodes date day data', t => { - dateTest(t, new DateDay()); - t.end(); -}); - -tape('dataFrom encodes int8 data', t => { - integerTest(t, new Int8()); - t.end(); -}); - -tape('dataFrom encodes int16 data', t => { - integerTest(t, new Int16()); - t.end(); -}); - -tape('dataFrom encodes int32 data', t => { - integerTest(t, new Int32()); - t.end(); -}); - -tape('dataFrom encodes int64 data', t => { - bigintTest(t, new Int64()); - t.end(); -}); - -tape('dataFrom encodes uint8 data', t => { - integerTest(t, new Uint8()); - t.end(); -}); - -tape('dataFrom encodes uint16 data', t => { - integerTest(t, new Uint16()); - t.end(); -}); - -tape('dataFrom encodes uint32 data', t => { - integerTest(t, new Uint32()); - t.end(); -}); - -tape('dataFrom encodes uint64 data', t => { - bigintTest(t, new Uint64()); - t.end(); -}); - -tape('dataFrom encodes float32 data', t => { - floatTest(t, new Float32()); - t.end(); -}); - -tape('dataFrom encodes float64 data', t => { - floatTest(t, new Float64()); - t.end(); -}); - -tape('dataFrom encodes list data', t => { - const field = Field.new({ name: 'value', type: new Int32() }); - const type = new List(field); - const values = [[1, 2], [3], [4, 5, 6], [7]]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); - t.end(); -}); - -tape('dataFrom encodes fixed size list data', t => { - const field = Field.new({ name: 'value', type: new Int32() }); - const type = new FixedSizeList(1, field); - const values = [[1], [2], [3], [4], [5], [6]]; - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); - t.end(); -}); - -tape('dataFrom encodes struct data', t => { - const key = Field.new({ name: 'key', type: new Int32() }); - const type = new Struct([key]); - const values = [1, 2, 3, null, 5, 6].map(key => ({ key })); - valueTest(t, type, values, ', without nulls'); - valueTest(t, type, [null, ...values, undefined], ', with nulls'); - t.end(); -}); \ No newline at end of file diff --git a/test/arrow/profiler-test.js b/test/arrow/profiler-test.js deleted file mode 100644 index 71649340..00000000 --- a/test/arrow/profiler-test.js +++ /dev/null @@ -1,85 +0,0 @@ -import tape from 'tape'; -import { profiler } from '../../src/arrow/encode/profiler'; -import { - Float64, Int16, Int32, Int64, Int8, - Uint16, Uint32, Uint64, Uint8 -} from 'apache-arrow'; - -function profile(array) { - const p = profiler(); - array.forEach(value => p.add(value)); - return p; -} - -function typeCompare(a, b) { - return a.compareTo(b); -} - -tape('profiler infers integer types', t => { - const types = { - uint8: new Uint8(), - uint16: new Uint16(), - uint32: new Uint32(), - int8: new Int8(), - int16: new Int16(), - int32: new Int32() - }; - - const dt = { - uint8: [0, 1 << 7, 1 << 8 - 1], - uint16: [0, 1 << 15, 1 << 16 - 1], - uint32: [0, 2 ** 31 - 1, 2 ** 32 - 1], - int8: [-(1 << 7), 0, (1 << 7) - 1], - int16: [-(1 << 15), 0, (1 << 15) - 1], - int32: [(1 << 31), 0, 2 ** 31 - 1] - }; - - Object.keys(dt).forEach(name => { - const type = profile(dt[name]).type(); - t.ok(typeCompare(types[name], type), `${name} type`); - }); - - const float = new Float64(); - t.ok( - typeCompare(float, profile([0, 1, 2 ** 32]).type()), - 'overflow to float64 type' - ); - t.ok( - typeCompare(float, profile([(1 << 31), 0, 2 ** 32 - 1]).type()), - 'overflow to float64 type' - ); - t.ok( - typeCompare(float, profile([(1 << 31) - 1, 0, 1]).type()), - 'underflow to float64 type' - ); - - t.end(); -}); - -tape('profiler infers bigint types', t => { - const types = { - int64: new Int64(), - uint64: new Uint64() - }; - - const dt = { - int64: [-(2n ** 63n), 0n, (2n ** 63n) - 1n], - uint64: [0n, 1n, 2n ** 64n - 1n] - }; - - Object.keys(dt).forEach(name => { - const type = profile(dt[name]).type(); - t.ok(typeCompare(types[name], type), `${name} type`); - }); - - t.throws( - () => profile([0n, 1n, 2n ** 64n]).type(), - 'throws on overflow' - ); - t.throws( - () => profile([-(2n ** 63n), 0n, 2n ** 63n]).type(), - 'throws on underflow' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/expression/params-test.js b/test/expression/params-test.js deleted file mode 100644 index d2a3c2f9..00000000 --- a/test/expression/params-test.js +++ /dev/null @@ -1,106 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src/table'; - -tape('parse supports table expression with parameter arg', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols) - .params({ lo: 1, hi: 7 }) - .filter((d, $) => $.lo < d.a && d.a < $.hi) - .reify(); - - tableEqual(t, ft, { a: [3, 5], b: [4, 6] }, 'parameter filtered data'); - t.deepEqual(ft.params(), { lo: 1, hi: 7 }); - t.end(); -}); - -tape('parse supports table expression with object pattern parameter arg', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols) - .params({ lo: 1, hi: 7 }) - .filter((d, { lo, hi }) => lo < d.a && d.a < hi) - .reify(); - - tableEqual(t, ft, { a: [3, 5], b: [4, 6] }, 'parameter filtered data'); - t.deepEqual(ft.params(), { lo: 1, hi: 7 }); - t.end(); -}); - -tape('parse throws on table expression with nested object pattern parameter arg', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - t.throws(() => { - table(cols) - .params({ thresh: {lo: 1, hi: 7} }) - .filter((d, { thresh: { lo, hi } }) => lo < d.a && d.a < hi); - }, 'throws on nested argument destructuring'); - - t.end(); -}); - -tape('parse supports table expression without parameter arg', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const lo = 1; - const hi = 7; - - const ft = table(cols) - .params({ lo, hi }) - .filter(d => lo < d.a && d.a < hi) - .reify(); - - tableEqual(t, ft, { a: [3, 5], b: [4, 6] }, 'parameter filtered data'); - t.deepEqual(ft.params(), { lo: 1, hi: 7 }); - t.end(); -}); - -tape('parse supports table expression with object-valued parameter', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const arr = [1, 7]; - const at = table(cols) - .params({ arr }) - .filter(d => arr[0] < d.a && d.a < arr[1]) - .reify(); - tableEqual(t, at, { a: [3, 5], b: [4, 6] }, 'array parameter filtered data'); - t.deepEqual(at.params(), { arr }); - - const obj = { lo: 1, hi: 7 }; - const ot = table(cols) - .params({ obj }) - .filter(d => obj.lo < d.a && d.a < obj.hi) - .reify(); - tableEqual(t, ot, { a: [3, 5], b: [4, 6] }, 'object parameter filtered data'); - t.deepEqual(ot.params(), { obj }); - - t.end(); -}); - -tape('parse throws on invalid parameter', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - t.throws( - () => { table(cols).filter((d, $) => $.lo < d.a && d.a < $.hi); }, - 'throws on undefined parameter' - ); - t.end(); -}); \ No newline at end of file diff --git a/test/expression/parse-test.js b/test/expression/parse-test.js deleted file mode 100644 index 90b795a6..00000000 --- a/test/expression/parse-test.js +++ /dev/null @@ -1,738 +0,0 @@ -import tape from 'tape'; -import parse from '../../src/expression/parse'; -import op from '../../src/op/op-api'; -import rolling from '../../src/helpers/rolling'; - -// pass code through for testing -const compiler = { param: x => x, expr: x => x }; - -function test(t, input) { - const { ops, names, exprs } = parse(input, { compiler }); - - t.deepEqual(ops, [ - { name: 'mean', fields: ['data.a.get(row)'], params: [], id: 0 }, - { name: 'corr', fields: ['data.a.get(row)', 'data.b.get(row)'], params: [], id: 1}, - { name: 'quantile', fields: ['(-data.bar.get(row))'], params: ['(0.5 / 2)'], id: 2}, - { name: 'lag', fields: ['data.value.get(row)'], params: [2], id: 3 }, - { name: 'mean', fields: ['data.value.get(row)'], params: [], frame: [-3, 3], peers: false, id: 4 }, - { name: 'count', fields: [], params: [], frame: [-3, 3], peers: true, id: 5 } - ], 'parsed operators'); - - t.deepEqual(names, [ - 'constant', - 'column', - 'agg1', - 'agg2', - 'agg3', - 'win1', - 'win2', - 'win3' - ], 'parsed output names'); - - t.deepEqual(exprs, [ - '(1 + 1)', - '(data.a.get(row) * data.b.get(row))', - 'op(0,row)', - 'op(1,row)', - '(1 + op(2,row))', - '(data.value.get(row) - op(3,row))', - 'op(4,row)', - 'op(5,row)' - ], 'parsed output expressions'); -} - -tape('parse parses expressions with global operator names', t => { - /* eslint-disable no-undef */ - test(t, { - constant: () => 1 + 1, - column: d => d.a * d.b, - agg1: d => mean(d.a), - agg2: d => corr(d.a, d.b), - agg3: d => 1 + quantile(-d.bar, 0.5/2), - win1: d => d.value - lag(d.value, 2), - win2: rolling(d => mean(d.value), [-3, 3]), - win3: rolling(() => count(), [-3, 3], true) - }); - /* eslint-enable */ - - t.end(); -}); - -tape('parse parses expressions with operator object', t => { - test(t, { - constant: () => 1 + 1, - column: d => d.a * d.b, - agg1: d => op.mean(d.a), - agg2: d => op.corr(d.a, d.b), - agg3: d => 1 + op.quantile(-d.bar, 0.5/2), - win1: d => d.value - op.lag(d.value, 2), - win2: rolling(d => op.mean(d.value), [-3, 3]), - win3: rolling(() => op.count(), [-3, 3], true) - }); - - t.end(); -}); - -tape('parse parses expressions with nested operator object', t => { - const aq = { op }; - - test(t, { - constant: () => 1 + 1, - column: d => d.a * d.b, - agg1: d => aq.op.mean(d.a), - agg2: d => aq.op.corr(d.a, d.b), - agg3: d => 1 + aq.op.quantile(-d.bar, 0.5/2), - win1: d => d.value - aq.op.lag(d.value, 2), - win2: rolling(d => aq.op.mean(d.value), [-3, 3]), - win3: rolling(() => aq.op.count(), [-3, 3], true) - }); - - t.end(); -}); - -tape('parse parses expressions with Math object', t => { - t.equal( - parse({ f: d => Math.sqrt(d.x) }).exprs[0] + '', - '(row,data,op)=>fn.sqrt(data.x.get(row))', - 'parse Math.sqrt' - ); - - t.equal( - parse({ f: d => Math.max(d.x) }).exprs[0] + '', - '(row,data,op)=>fn.greatest(data.x.get(row))', - 'parse Math.max, rewrite as greatest' - ); - - t.equal( - parse({ f: d => Math.min(d.x) }).exprs[0] + '', - '(row,data,op)=>fn.least(data.x.get(row))', - 'parse Math.min, rewrite as least' - ); - - t.end(); -}); - -tape('parse parses expressions with constant values', t => { - function constant(string, result) { - const { exprs } = parse({ f: `d => ${string}` }); - t.equal( - exprs[0] + '', - `(row,data,op)=>${result}`, - `parsed ${string} constant` - ); - } - - constant('undefined', 'void(0)'); - constant('Infinity', 'Number.POSITIVE_INFINITY'); - constant('NaN', 'Number.NaN'); - constant('E', 'Math.E'); - constant('LN2', 'Math.LN2'); - constant('LN10', 'Math.LN10'); - constant('LOG2E', 'Math.LOG2E'); - constant('LOG10E', 'Math.LOG10E'); - constant('PI', 'Math.PI'); - constant('SQRT1_2', 'Math.SQRT1_2'); - constant('SQRT2', 'Math.SQRT2'); - - constant('Math.E', 'Math.E'); - constant('Math.LN2', 'Math.LN2'); - constant('Math.LN10', 'Math.LN10'); - constant('Math.LOG2E', 'Math.LOG2E'); - constant('Math.LOG10E', 'Math.LOG10E'); - constant('Math.PI', 'Math.PI'); - constant('Math.SQRT1_2', 'Math.SQRT1_2'); - constant('Math.SQRT2', 'Math.SQRT2'); - - t.throws(() => constant('Object'), 'throws on constant Object'); - t.throws(() => constant('Object.keys'), 'throws on constant Object.keys'); - t.throws(() => constant('Number.NaN'), 'throws on constant Number.NaN'); - - t.end(); -}); - -tape('parse parses expressions with literal values', t => { - function literal(string, result) { - const { exprs } = parse({ f: `d => ${string}` }); - t.equal( - exprs[0] + '', - `(row,data,op)=>${result}`, - `parsed ${string} literal` - ); - } - - literal('1', '1'); - literal('1e-5', '1e-5'); - literal('true', 'true'); - literal('false', 'false'); - literal('"foo"', '"foo"'); - literal('[1,2,3]', '[1,2,3]'); - literal('({a:1})', '({a:1})'); - literal('({"b":2})', '({"b":2})'); - t.end(); -}); - -tape('parse parses column references with nested properties', t => { - t.equal( - parse({ f: d => d.x.y }).exprs[0] + '', - '(row,data,op)=>data.x.get(row).y', - 'parsed nested members' - ); - - t.equal( - parse({ f: d => d['x'].y }).exprs[0] + '', - '(row,data,op)=>data["x"].get(row).y', - 'parsed nested members' - ); - - t.equal( - parse({ f: d => d['x']['y'] }).exprs[0] + '', - '(row,data,op)=>data["x"].get(row)[\'y\']', - 'parsed nested members' - ); - - t.end(); -}); - -tape('parse parses indirect column names', t => { - // direct expression - t.equal( - parse({ f: d => d['x' + 'y'] }).exprs[0] + '', - '(row,data,op)=>data["xy"].get(row)', - 'parsed indirect member as expression' - ); - - // parameter reference - const opt = { - table: { - params: () => ({ col: 'a' }), - column: (name) => name == 'a' ? {} : null - } - }; - t.equal( - parse({ f: (d, $) => d[$.col] }, opt).exprs[0] + '', - '(row,data,op)=>data["a"].get(row)', - 'parsed indirect member as param' - ); - - // variable reference - t.throws( - () => parse({ - f: d => { - const col = 'a'; - return d[col]; - } - }), - 'throws on indirect variable' - ); - - // variable reference - t.throws( - () => parse({ f: d => d[d.foo] }), - 'throws on nested column reference' - ); - - t.end(); -}); - -tape('parse throws on invalid column names', t => { - const opt = { table: { params: () => ({}), data: () => ({}) } }; - t.throws(() => parse({ f: d => d.foo }, opt)); - t.throws(() => parse({ f: ({ foo }) => foo }, opt)); - t.end(); -}); - -tape('parse parses expressions with op parameter expressions', t => { - const exprs = parse({ - op: d => op.quantile(d.a, op.abs(op.sqrt(0.25))) - }); - t.equal( - exprs.ops[0].params[0], 0.5, 'calculated op param' - ); - t.end(); -}); - -tape('parse throws on invalid op parameter expressions', t => { - t.throws(() => parse({ op: d => op.quantile(d.a, d.b) })); - t.throws(() => parse({ op: d => op.sum(op.mean(d.a)) })); - t.throws(() => parse({ op: d => op.sum(op.lag(d.a)) })); - t.throws(() => parse({ op: d => op.lag(op.sum(d.a)) })); - t.throws(() => parse({ - op: d => { - const value = 0.5; - return op.quantile(d.a, value); - } - })); - t.throws(() => parse({ - op: d => { - const value = 0.5; - return op.quantile(d.a + value, 0.5); - } - })); - t.end(); -}); - -tape('parse parses computed object properties', t => { - const { exprs } = parse({ f: d => ({ [d.x]: d.y }) }); - t.equal( - exprs[0] + '', - '(row,data,op)=>({[data.x.get(row)]:data.y.get(row)})', - 'parsed computed object property' - ); - t.end(); -}); - -tape('parse parses template literals', t => { - const { exprs } = parse({ f: d => `${d.x} + ${d.y}` }); - t.equal( - exprs[0] + '', - '(row,data,op)=>`${data.x.get(row)} + ${data.y.get(row)}`', - 'parsed template literal' - ); - t.end(); -}); - -tape('parse parses expressions with block statements', t => { - const exprs = { - val: d => { const s = op.sum(d.a); return s * s; } - }; - - t.deepEqual( - parse(exprs, { compiler }), - { - names: [ 'val' ], - exprs: [ '{const s=op(0,row);return (s * s);}' ], - ops: [ - { name: 'sum', fields: [ 'data.a.get(row)' ], params: [], id: 0 } - ] - }, - 'parsed block' - ); - - t.equal( - parse(exprs).exprs[0] + '', - '(row,data,op)=>{const s=op(0,row);return (s * s);}', - 'compiled block' - ); - - t.end(); -}); - -tape('parse parses expressions with if statements', t => { - const exprs = { - val1: () => { - const d = 3 - 2; - if (d < 1) { return 1; } else { return 0; } - }, - val2: () => { - const d = 3 - 2; - if (d < 1) { return 1; } - return 0; - } - }; - - t.deepEqual( - parse(exprs, { compiler }), - { - names: ['val1', 'val2'], - exprs: [ - '{const d=(3 - 2);if ((d < 1)){return 1;} else {return 0;};}', - '{const d=(3 - 2);if ((d < 1)){return 1;};return 0;}' - ], - ops: [] - }, - 'parsed if' - ); - - t.end(); -}); - -tape('parse parses expressions with switch statements', t => { - const exprs = { - val: () => { - const v = 'foo'; - switch (v) { - case 'foo': return 1; - case 'bar': return 2; - default: return 3; - } - } - }; - - t.equal( - parse(exprs, { compiler }).exprs[0], - '{const v=\'foo\';switch (v) {case \'foo\': return 1;case \'bar\': return 2;default: return 3;};}', - 'parsed switch' - ); - - t.end(); -}); - -tape('parse parses expressions with destructuring assignments', t => { - const exprs = { - arr: () => { - const [start, stop, step] = op.bins('value'); - return op.bin('value', start, stop, step); - }, - obj: () => { - const { start, stop, step } = op.bins('value'); - return op.bin('value', start, stop, step); - }, - nest: () => { - const { start: [{ baz: bop }], stop, step } = op.bins('value'); - return op.bin('value', bop, stop, step); - } - }; - - t.deepEqual( - parse(exprs, { compiler }), - { - names: ['arr', 'obj', 'nest'], - exprs: [ - '{const [start,stop,step]=op(0,row);return fn.bin(\'value\',start,stop,step);}', - '{const {start:start,stop:stop,step:step}=op(0,row);return fn.bin(\'value\',start,stop,step);}', - '{const {start:[{baz:bop}],stop:stop,step:step}=op(0,row);return fn.bin(\'value\',bop,stop,step);}' - ], - ops: [ - { name: 'bins', fields: [ '\'value\'' ], params: [], id: 0 } - ] - }, - 'parsed destructuring assignmeents' - ); - - t.end(); -}); - -tape('parse throws on expressions with for loops', t => { - const exprs = { - val: () => { - let v = 0; - for (let i = 0; i < 5; ++i) { - v += i; - } - return v; - } - }; - t.throws(() => parse(exprs), 'no for loops'); - t.end(); -}); - -tape('parse throws on expressions with while loops', t => { - const exprs = { - val: () => { - let v = 0; - let i = 0; - while (i < 5) { - v += i++; - } - return v; - } - }; - t.throws(() => parse(exprs), 'no while loops'); - t.end(); -}); - -tape('parse throws on expressions with do-while loops', t => { - const exprs = { - val: () => { - let v = 0; - let i = 0; - do { - v += i; - } while (++i < 5); - return v; - } - }; - t.throws(() => parse(exprs), 'no do-while loops'); - t.end(); -}); - -tape('parse throws on expressions with comma sequences', t => { - const exprs = { val: () => (1, 2) }; - t.throws(() => parse(exprs), 'no comma sequences'); - t.end(); -}); - -tape('parse throws on dirty tricks', t => { - // eslint-disable-next-line no-undef - t.throws(() => parse({ f: () => globalThis }), 'no globalThis access'); - t.throws(() => parse({ f: () => global }), 'no global access'); - t.throws(() => parse({ f: () => window }), 'no window access'); - t.throws(() => parse({ f: () => self }), 'no self access'); - t.throws(() => parse({ f: () => this }), 'no this access'); - t.throws(() => parse({ f: () => Object }), 'no Object access'); - t.throws(() => parse({ f: () => Date }), 'no Date access'); - t.throws(() => parse({ f: () => Array }), 'no Array access'); - t.throws(() => parse({ f: () => Number }), 'no Number access'); - t.throws(() => parse({ f: () => Math }), 'no Math access'); - t.throws(() => parse({ f: () => String }), 'no String access'); - t.throws(() => parse({ f: () => RegExp }), 'no RegExp access'); - - t.throws(() => parse({ - f: () => { const foo = [].constructor; return new foo(3); } - }), 'no instantiation'); - - t.throws(() => parse({ - f: () => [].constructor() - }), 'no property invocation'); - - t.throws(() => parse({ - f: () => [].__proto__.unsafe = 1 - }), 'no __proto__ assignment'); - - t.throws(() => parse({ - f: () => 'abc'.toUpperCase() - }), 'no literal method calls'); - - t.throws(() => parse({ - f: () => { const s = 'abc'; return s.toUpperCase(); } - }), 'no identifier method calls'); - - t.throws(() => parse({ - f: () => ('abc')['toUpperCase']() - }), 'no indirect method calls'); - - t.throws(() => parse({ - f: 'd => op.mean(var foo = d.x)' - }), 'no funny business'); - - t.end(); -}); - -tape('parse supports ast output option', t => { - const ast = parse({ - constant: () => 1 + Math.E, - column: d => d.a * d.b, - agg1: d => op.mean(d.a), - agg2: d => op.corr(d.a, d.b), - agg3: d => 1 + op.quantile(-d.bar, 0.5/2), - win1: d => d.value - op.lag(d.value, 2), - win2: rolling(d => op.mean(d.value), [-3, 3]), - win3: rolling(() => op.count(), [-3, 3], true) - }, { ast: true }); - - t.deepEqual( - JSON.parse(JSON.stringify(ast.exprs)), - [ - { - 'type': 'BinaryExpression', - 'left': { - 'type': 'Literal', - 'value': 1, - 'raw': '1' - }, - 'operator': '+', - 'right': { - 'type': 'Constant', - 'name': 'E', - 'raw': 'Math.E' - } - }, - { - 'type': 'BinaryExpression', - 'left': { - 'type': 'Column', - 'name': 'a' - }, - 'operator': '*', - 'right': { - 'type': 'Column', - 'name': 'b' - } - }, - { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'mean' - }, - 'arguments': [ - { - 'type': 'Column', - 'name': 'a' - } - ] - }, - { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'corr' - }, - 'arguments': [ - { - 'type': 'Column', - 'name': 'a' - }, - { - 'type': 'Column', - 'name': 'b' - } - ] - }, - { - 'type': 'BinaryExpression', - 'left': { - 'type': 'Literal', - 'value': 1, - 'raw': '1' - }, - 'operator': '+', - 'right': { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'quantile' - }, - 'arguments': [ - { - 'type': 'UnaryExpression', - 'operator': '-', - 'prefix': true, - 'argument': { - 'type': 'Column', - 'name': 'bar' - } - }, - { - 'type': 'BinaryExpression', - 'left': { - 'type': 'Literal', - 'value': 0.5, - 'raw': '0.5' - }, - 'operator': '/', - 'right': { - 'type': 'Literal', - 'value': 2, - 'raw': '2' - } - } - ] - } - }, - { - 'type': 'BinaryExpression', - 'left': { - 'type': 'Column', - 'name': 'value' - }, - 'operator': '-', - 'right': { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'lag' - }, - 'arguments': [ - { - 'type': 'Column', - 'name': 'value' - }, - { - 'type': 'Literal', - 'value': 2, - 'raw': '2' - } - ] - } - }, - { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'mean' - }, - 'arguments': [ - { - 'type': 'Column', - 'name': 'value' - } - ] - }, - { - 'type': 'CallExpression', - 'callee': { - 'type': 'Function', - 'name': 'count' - }, - 'arguments': [] - } - ] - ); - - t.end(); -}); - -tape('parse optimizes dictionary references', t => { - const cols = { v: { keyFor() { return 1; } } }; - const dt = { column: name => cols[name] }; - - const optimized = { - l_eq2: d => d.v == 'a', - r_eq2: d => 'a' == d.v, - l_eq3: d => d.v === 'a', - r_eq3: d => 'a' === d.v, - l_ne2: d => d.v != 'a', - r_ne2: d => 'a' != d.v, - l_ne3: d => d.v !== 'a', - r_ne3: d => 'a' !== d.v, - l_eqo: d => op.equal(d.v, 'a'), - r_eqo: d => op.equal('a', d.v), - destr: ({ v }) => v === 'a' - }; - - t.deepEqual( - parse(optimized, { compiler, table: dt }), - { - names: [ - 'l_eq2', 'r_eq2', - 'l_eq3', 'r_eq3', - 'l_ne2', 'r_ne2', - 'l_ne3', 'r_ne3', - 'l_eqo', 'r_eqo', - 'destr' - ], - exprs: [ - '(data.v.key(row) == 1)', - '(1 == data.v.key(row))', - '(data.v.key(row) === 1)', - '(1 === data.v.key(row))', - '(data.v.key(row) != 1)', - '(1 != data.v.key(row))', - '(data.v.key(row) !== 1)', - '(1 !== data.v.key(row))', - 'fn.equal(data.v.key(row),1)', - 'fn.equal(1,data.v.key(row))', - '(data.v.key(row) === 1)' - ], - ops: [] - }, - 'optimized references' - ); - - const unoptimized = { - ref: d => d.v, - nest: d => d.v.x === 'a', - destr: ({ v }) => v.x === 'a', - l_lte: d => d.v <= 'a', - r_lte: d => 'a' <= d.v - }; - - t.deepEqual( - parse(unoptimized, { compiler, table: dt }), - { - names: [ 'ref', 'nest', 'destr', 'l_lte', 'r_lte' ], - exprs: [ - 'data.v.get(row)', - "(data.v.get(row).x === 'a')", - "(data.v.get(row).x === 'a')", - "(data.v.get(row) <= 'a')", - "('a' <= data.v.get(row))" - ], - ops: [] - }, - 'unoptimized references' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/data/beers.csv b/test/format/data/beers.csv deleted file mode 100644 index 95906a1d..00000000 --- a/test/format/data/beers.csv +++ /dev/null @@ -1,1204 +0,0 @@ -name,style,brewery_id,abv,ibu -#002 American I.P.A.,American IPA,211,0.071,60 -#004 Session I.P.A.,American IPA,211,0.048,38 -#9,Fruit / Vegetable Beer,303,0.051,20 -077XX,American Double / Imperial IPA,222,0.078,80 -113 IPA,American IPA,371,0.07,113 -12th Round,American Strong Ale,376,0.076,78 -13 Rebels ESB,Extra Special / Strong Bitter (ESB),433,0.052,42 -1327 Pod's ESB,Extra Special / Strong Bitter (ESB),380,0.056,37 -14° ESB,Extra Special / Strong Bitter (ESB),75,0.056,32 -1554 Black Lager,Euro Dark Lager,82,0.056,21 -"1836",American Blonde Ale,214,0.06,40 -1881 California Red Ale,American Amber / Red Ale,397,0.056,35 -18th Anniversary Gose,Gose,128,0.044,5 -1916 Shore Shiver,American IPA,217,0.069,65 -2012 Black Ale,American Brown Ale,131,0.054,32 -2014 IPA Cicada Series,American IPA,133,0.075,72 -2020 IPA,American IPA,240,0.074,74 -21st Amendment IPA,American IPA,368,0.07,70 -3 Gear Robust Porter,American Porter,17,0.052,50 -3:33 Black IPA,American IPA,24,0.072,86 -312 Urban Pale Ale,American Pale Ale (APA),88,0.054,30 -312 Urban Wheat Ale,American Pale Wheat Ale,88,0.042,18 -33 Select Brown Ale,American Brown Ale,471,0.065,26 -35 K,Milk / Sweet Stout,1,0.077,25 -3Beans,Baltic Porter,46,0.099,85 -40 Mile IPA,American IPA,273,0.06,50 -4000 Footer IPA,American IPA,109,0.065,82 -40th Annual Bix Street Fest Copper Ale (Current),American Amber / Red Ale,365,0.048,25 -483 Pale Ale,American Pale Ale (APA),365,0.053,48 -4Beans,Baltic Porter,46,0.1,52 -5 Day IPA,American IPA,442,0.061,66 -5:00 O'Clock Afternoon Ale,American Blonde Ale,110,0.05,25 -51K IPA,American IPA,12,0.07,51 -541 American Lager,American Pale Lager,198,0.048,13 -8 Barrel,American Strong Ale,226,0.08,69 -"805",American Blonde Ale,48,0.047,20 -834 Happy As Ale,American Pale Ale (APA),380,0.046,35 -98 Problems (Cuz A Hop Ain't One),American IPA,13,0.065,65 -A Beer,American Pale Ale (APA),1,0.042,42 -A Slice of Hefen,Hefeweizen,332,0.054,15 -AARGHtoberfest!,Märzen / Oktoberfest,479,0.06,30 -Abbey's Single Ale,Abbey Single Ale,57,0.049,22 -Abita Amber,American Amber / Red Lager,533,0.045,17 -Abominable Winter Ale,American Strong Ale,80,0.073,70 -Abrasive Ale,American Double / Imperial IPA,61,0.097,120 -Ace IPA,American IPA,509,0.074,83 -Adam's Stout,American Stout,298,0.058,40 -Aftermath Pale Ale,American Pale Ale (APA),112,0.058,44 -Agave Wheat,American Pale Wheat Ale,391,0.042,9 -Alaskan Amber,Altbier,270,0.053,18 -Ale Cider,Fruit / Vegetable Beer,250,0.065,8 -All Day IPA,American IPA,15,0.047,42 -Alloy,American IPA,17,0.058,36 -Almanac IPA,American IPA,265,0.062,72 -Alpha Ale,American Pale Ale (APA),181,0.051,45 -Alphadelic IPA,American IPA,198,0.065,90 -Alter Ego,Saison / Farmhouse Ale,115,0.062,33 -Alteration,Altbier,395,0.051,40 -Amazon Princess IPA,American IPA,205,0.062,62 -Amber Road,American Amber / Red Ale,292,0.055,35 -American Amber,American Amber / Red Lager,129,0.041,8 -American Hero,American Amber / Red Ale,298,0.057,42 -American Idiot Ale,American Pale Ale (APA),30,0.055,45 -American India Red Ale,American Strong Ale,294,0.071,83 -American Lager,American Adjunct Lager,129,0.041,8 -American Light,Light Lager,129,0.032,13 -American Red Porter,American Porter,294,0.071,45 -American Red Saison,Saison / Farmhouse Ale,294,0.078,34 -Ananda India Pale Ale,American IPA,239,0.062,61 -Anti-Hero IPA,American IPA,44,0.065,70 -Apollo,American Pale Wheat Ale,46,0.052,11 -Aprè Shred,American Strong Ale,220,0.081,17 -Apricot Blonde,Fruit / Vegetable Beer,496,0.051,17 -Arcus IPA,American IPA,221,0.069,81 -Are Wheat There Yet?,American Pale Wheat Ale,308,0.055,28 -Ashland Amber Ale,American Amber / Red Ale,155,0.054,24 -Atom Smasher,Märzen / Oktoberfest,74,0.077,23 -Atwater's Lager,Munich Helles Lager,72,0.05,12 -Aurora Lager,Dortmunder / Export Lager,165,0.057,27 -Aurora,American Amber / Red Ale,209,0.067,75 -Autocrat Coffee Milk Stout,Milk / Sweet Stout,143,0.053,30 -Autumnation,Pumpkin Ale,46,0.06,48 -Autumnation,American IPA,46,0.067,74 -Avalanche Ale,American Amber / Red Ale,391,0.054,19 -Avery India Pale Ale,American IPA,37,0.063,69 -Avery Joe’s Premium American Pilsner,German Pilsener,37,0.047,42 -Aviator Raspberry Blonde,American Blonde Ale,169,0.049,25 -Baby Daddy Session IPA,American IPA,78,0.047,35 -Back Bay IPA,American IPA,103,0.068,85 -Back in Black,American Black Ale,368,0.068,65 -Back in the Saddle Rye Pale Ale,American Pale Ale (APA),448,0.037,53 -Bad Axe Imperial IPA,American Double / Imperial IPA,444,0.098,76 -Ballast Point Pale Ale,Kölsch,34,0.052,23 -Ballistic Blonde,Belgian Pale Ale,245,0.051,31 -Ballz Deep Double IPA,American Double / Imperial IPA,481,0.084,82 -Banner American Ale,American Amber / Red Ale,318,0.035,45 -Banner American Rye,Rye Beer,318,0.045,20 -Bark Bite IPA,American IPA,444,0.066,50 -Barn Burner Saison,Saison / Farmhouse Ale,125,0.066,20 -Barney Flats Oatmeal Stout,Oatmeal Stout,171,0.057,13 -Barrel Aged Farmer,American Brown Ale,365,0.07,22 -Barrio Blanco,American IPA,251,0.06,60 -Bat Outta Helles,Munich Helles Lager,66,0.042,20 -Batch 69 IPA,American IPA,253,0.069,69 -Battle LIne,American Brown Ale,215,0.063,23 -Bay of Bengal Double IPA,American Double / Imperial IPA,99,0.089,126 -Be Hoppy IPA,American IPA,339,0.065,69 -Beach Blonde,American Blonde Ale,267,0.049,10 -Beaver Logger,American Pale Lager,420,0.052,19 -Becky's Black Cat Porter,American Porter,494,0.07,55 -Beer Agent Re-Ignition,American Blonde Ale,165,0.053,22 -Belgian Style Session Ale,Belgian Pale Ale,356,0.045,21 -Belgorado,Belgian IPA,424,0.067,45 -Bender,American Brown Ale,61,0.051,45 -Bengali Tiger,American IPA,46,0.064,62 -Bent Hop Golden IPA,American IPA,75,0.062,68 -Bent Paddle Black Ale,American Black Ale,75,0.06,34 -Better Weather IPA,American IPA,240,0.094,92 -Beyond The Pale IPA,American IPA,131,0.063,64 -Big Cock IPA,American IPA,365,0.07,70 -Big Eye India Pale Ale,American IPA,34,0.07,75 -Big Nose,American IPA,447,0.073,50 -Big River Pilsner,Czech Pilsener,519,0.05,32 -Big Rod Coconut Ale,American Blonde Ale,393,0.053,16 -Big Sky IPA,American IPA,336,0.062,65 -Big Star White IPA,American White IPA,105,0.07,70 -Big Swell IPA,American IPA,375,0.062,65 -Bikini Blonde Lager,Munich Helles Lager,375,0.045,18 -Bimini Twist,American IPA,67,0.07,82 -Bitter American,American Pale Ale (APA),368,0.044,42 -Bitter Brewer,English Bitter,61,0.04,37 -Black Adder IBA,American Black Ale,530,0.073,85 -Black Bear Dark Lager,Schwarzbier,326,0.046,24 -Black House,American Stout,209,0.058,40 -Black IPA,American Black Ale,392,0.071,95 -Black Market Hefeweizen,Hefeweizen,112,0.05,8 -Black Me Stout,American Stout,236,0.06,45 -Black Noddy Lager,Schwarzbier,530,0.052,40 -Black Star Double Hopped Golden Lager,American Pale Lager,543,0.045,15 -Black Walnut Wheat,American Dark Wheat Ale,314,0.045,18 -Blackbeary Wheat,Fruit / Vegetable Beer,268,0.04,8 -Blackberry Wheat,American Pale Wheat Ale,87,0.048,11 -Blackmarket Rye IPA,American IPA,112,0.075,35 -Blacktop Blonde,American Blonde Ale,449,0.05,19 -BLAKKR,American Black Ale,61,0.099,85 -Blazing World,American Amber / Red Ale,209,0.065,115 -Block Party Robust Porter,American Porter,287,0.057,40 -Blonde Czich,American Blonde Ale,26,0.049,23 -Blonde Hunny,Belgian Pale Ale,181,0.068,21 -Blood Orange Honey,Fruit / Vegetable Beer,121,0.057,10 -Blood Orange Wit,Witbier,225,0.05,16 -Bloody Show,American Pilsner,1,0.055,17 -Blue Mountain Classic Lager,Euro Pale Lager,382,0.053,22 -Blue Point Summer Ale,American Blonde Ale,489,0.044,16 -Blue Point White IPA,American White IPA,489,0.06,40 -Blueberry Blonde Ale,American Blonde Ale,299,0.05,12 -Blur India Pale Ale,American IPA,432,0.074,60 -Boat Beer,American IPA,222,0.042,35 -Boathouse Blonde,American Blonde Ale,228,0.049,15 -Bohemian Pils,American Pilsner,143,0.052,30 -Boji Beach Golden Rye Ale,Rye Beer,471,0.05,23 -Boji Blue Pale Ale,American Pale Ale (APA),471,0.05,45 -Bomb Lager (Old Recipe),Munich Helles Lager,525,0.045,27 -Bomber Mountain Amber Ale,American Amber / Red Ale,79,0.046,20 -Bombshell Blonde,American Blonde Ale,118,0.05,20 -Booming Rollers,American IPA,209,0.068,75 -Boont Amber Ale,American Amber / Red Ale,171,0.058,15 -Booyah Farmhouse Ale,Saison / Farmhouse Ale,284,0.065,20 -Boston Lager,Vienna Lager,300,0.049,30 -Bottom Up Belgian Wit,Witbier,44,0.05,14 -Bourbon Barrel Aged Coconut Porter,American Porter,165,0.056,33 -Bourbon Barrel Aged Timmie,Russian Imperial Stout,25,0.099,75 -Bourbon Barrel Batch 666: Sympathy for the Devil,Belgian Dark Ale,25,0.099,36 -Bourbon Barrel Johan,English Barleywine,25,0.099,60 -Bourbon Barrel Wee Mac,Scottish Ale,25,0.054,23 -Bourbon's Barrel Stout,American Stout,165,0.075,65 -Bozone Hefe Weizen,Hefeweizen,219,0.06,25 -Bozone HopZone IPA,American IPA,219,0.07,80 -Brass Knuckle Pale Ale,American Pale Ale (APA),105,0.057,36 -Bravo Four Point,American Pale Ale (APA),237,0.044,45 -Brew Free! or Die IPA,American IPA,368,0.07,70 -British Pale Ale,English Pale Ale,481,0.054,30 -Broken Bridge,Dunkelweizen,215,0.056,12 -Bronx Black Pale Ale,American Black Ale,329,0.057,46 -Bronx Pale Ale,American Pale Ale (APA),329,0.063,50 -Bronx Summer Pale Ale,American Pale Ale (APA),329,0.052,16 -Brownstone,American Brown Ale,46,0.059,47 -Brunette Nut Brown Ale,English Brown Ale,337,0.048,15 -Brutus,English India Pale Ale (IPA),215,0.071,69 -Buffalo Sweat,Milk / Sweet Stout,45,0.05,20 -Bunker Hill Blueberry Ale,Other,103,0.048,16 -Buried Hatchet Stout,Foreign / Export Stout,118,0.083,50 -Burning Bush Smoked IPA,American IPA,240,0.08,70 -Cache La Porter,American Porter,441,0.05,24 -Cafe Leche,American Porter,17,0.058,20 -Caldera IPA,American IPA,155,0.061,94 -Caldera Pale Ale,American Pale Ale (APA),155,0.056,55 -Cali Creamin',Cream Ale,111,0.052,21 -California Sunshine Rye IPA,American IPA,491,0.071,85 -Calyptra,American India Pale Lager,2,0.049,45 -Camelback,American IPA,157,0.061,60 -Campside Session IPA,American IPA,202,0.045,50 -Cane and Ebel,American Strong Ale,74,0.07,68 -Cape Cod Red,American Amber / Red Ale,267,0.055,35 -Capital Gold Golden Lager,German Pilsener,482,0.048,22 -Capital Trail Pale Ale,American Pale Ale (APA),343,0.056,55 -CAPT Black IPA,American Black Ale,397,0.073,55 -Captain's Daughter,American Double / Imperial IPA,86,0.085,69 -Car 21,English Bitter,441,0.044,28 -Cardinal Pale Ale,American Pale Ale (APA),337,0.057,29 -Cascadian Dark Ale,American Black Ale,418,0.06,75 -Catch 23,American Black Ale,350,0.075,77 -Category 3 IPA,American IPA,340,0.061,64 -Cedar Point,American Amber / Red Ale,228,0.05,26 -Celestial Meridian Cascadian Dark Lager,Euro Dark Lager,116,0.051,45 -Centennial IPA,American IPA,15,0.072,65 -Chai Ale,Herbed / Spiced Beer,350,0.051,15 -Chaotic Double IPA,American Double / Imperial IPA,346,0.099,93 -Charlie in the Rye,American IPA,351,0.058,55 -Cherry Ale,Fruit / Vegetable Beer,165,0.057,18 -Chickawawa Lemonale,Fruit / Vegetable Beer,365,0.05,5 -Chin Music Amber Lager,American Amber / Red Lager,122,0.045,24 -Choc Beer,American Dark Wheat Ale,505,0.04,9 -Chuli Stout,Irish Dry Stout,453,0.059,55 -Chupahopra,American IPA,38,0.075,63 -Churchkey Pilsner Style Beer,American Pilsner,526,0.049,29 -Circuit Bohemian Pilsner,Czech Pilsener,17,0.045,35 -Citra Ass Down,American Double / Imperial IPA,1,0.08,68 -Citra Faced,American Pale Wheat Ale,21,0.055,64 -Citrafest,American IPA,27,0.05,45 -City of the Sun,American IPA,209,0.075,85 -Clan Warrior,Scotch Ale / Wee Heavy,148,0.087,29 -Claritas,Kölsch,3,0.058,28 -Clean Shave IPA,American IPA,106,0.067,70 -CoCoNut Porter,American Porter,375,0.057,30 -Coffee Bender,American Brown Ale,61,0.051,45 -Coffee Oatmeal Stout,Oatmeal Stout,478,0.06,54 -Cold Smoke Scotch Ale,Scotch Ale / Wee Heavy,510,0.065,11 -Colorado Kölsch,Kölsch,119,0.049,17 -Colorado Native,American Amber / Red Lager,462,0.055,26 -Colorado Red Ale,American Amber / Red Ale,294,0.066,44 -Common Sense Kentucky Common Ale,American Brown Ale,546,0.053,22 -Commotion APA,American Pale Ale (APA),269,0.052,49 -Consilium,American Pale Ale (APA),110,0.05,40 -Contact High,American Pale Wheat Ale,60,0.05,28 -Copper Hook,American Amber / Red Ale,487,0.058,27 -Copperhead Amber,Belgian Dark Ale,27,0.052,18 -Coq de la Marche,Saison / Farmhouse Ale,1,0.051,38 -Cotton Mouth,Witbier,447,0.05,10 -Couch Select Lager,American Pale Lager,197,0.05,14 -Cougar,American Blonde Ale,93,0.048,25 -Count Hopula (Vault Series),American Double / Imperial IPA,30,0.091,99 -Country Boy IPA,American IPA,170,0.062,80 -Country Pale Ale,English Pale Ale,295,0.051,17 -Cow Creek,American Amber / Red Lager,38,0.054,26 -Cowbell,American Porter,25,0.054,23 -Crabtree Oatmeal Stout,Oatmeal Stout,515,0.075,29 -Crank Yanker IPA,American IPA,369,0.078,74 -Crazy Mountain Amber Ale,American Amber / Red Ale,63,0.052,25 -Cream Ale,Cream Ale,238,0.042,35 -Csar,Russian Imperial Stout,17,0.12,90 -Cubano Espresso,Bock,141,0.055,25 -Cutthroat Pale Ale,American Pale Ale (APA),159,0.04,34 -CynicAle,Saison / Farmhouse Ale,61,0.067,33 -Czech Yo Self,Czech Pilsener,36,0.055,45 -Dad,American Amber / Red Ale,93,0.06,60 -Daft Funk,Berliner Weissbier,65,0.043,8 -Dagger Falls IPA,American IPA,309,0.063,100 -Dale's Pale Ale,American Pale Ale (APA),166,0.065,65 -Dallas Blonde,American Blonde Ale,127,0.052,23 -Damascene Apricot Sour,Fruit / Vegetable Beer,17,0.052,12 -Dammit Jim!,American Amber / Red Ale,241,0.052,50 -Dankosaurus,American IPA,29,0.068,70 -Dark Star,American Stout,8,0.08,54 -Dark Voyage Black IPA,American Black Ale,192,0.065,80 -Day Hike Session,American IPA,190,0.041,41 -Day Tripper Pale Ale,American Pale Ale (APA),277,0.054,45 -Dead Armadillo Amber Ale,American Amber / Red Ale,330,0.063,37 -Dead Eye Dunkel,Munich Dunkel Lager,135,0.06,15 -Dead-Eye DIPA,American Double / Imperial IPA,230,0.09,130 -Deadicated Amber,American Amber / Red Ale,491,0.054,27 -Deception,American Blonde Ale,112,0.045,16 -Deduction,Dubbel,28,0.08,22 -Deep Ellum IPA,American IPA,127,0.07,70 -Des Moines IPA,American IPA,482,0.068,75 -Descender IPA,American IPA,464,0.07,70 -Desolation IPA,American IPA,401,0.062,43 -Deviant Dale's IPA,American Double / Imperial IPA,389,0.08,85 -Devil's Harvest Extra Pale Ale,American Pale Ale (APA),133,0.058,60 -Devil’s Ale,American Pale Ale (APA),30,0.055,45 -Devils Tramping Ground Tripel,Tripel,360,0.092,5 -Diesel,American Stout,46,0.063,69 -Dirty Bastard,Scotch Ale / Wee Heavy,15,0.085,50 -Dirty Blonde Ale,American Blonde Ale,72,0.045,8 -Disconnected Red,American Amber / Red Ale,220,0.067,85 -Dodgy Knight Imperial IPA,American Double / Imperial IPA,449,0.08,95 -Dog Days Lager,Dortmunder / Export Lager,74,0.051,17 -Dog Days Summer Ale,Kölsch,164,0.045,28 -Dos Pistolas,Vienna Lager,365,0.048,20 -Dottie Seattle Lager,American Amber / Red Lager,516,0.049,25 -Double Bag,Altbier,268,0.072,33 -Double D Blonde,American Blonde Ale,198,0.049,20 -Double Duckpin,American Double / Imperial IPA,71,0.085,90 -Double Haul IPA,American IPA,510,0.065,65 -Double Trunk,American Double / Imperial IPA,33,0.099,101 -Dream Crusher Double IPA,American Double / Imperial IPA,127,0.085,100 -Dreamin' Double IPA,American Double / Imperial IPA,356,0.085,85 -Driftwood Ale,Extra Special / Strong Bitter (ESB),276,0.06,49 -Dry Dock Amber Ale,American Amber / Red Ale,496,0.058,49 -Dry Dock Hefeweizen,Hefeweizen,496,0.043,12 -Dry-Hopped On The High Seas Caribbean-Style IPA,American IPA,141,0.07,60 -Duluchan India Pale Ale,American IPA,345,0.056,70 -Dundee Summer Wheat Beer,American Pale Wheat Ale,538,0.045,18 -Dunkel Lager,Munich Dunkel Lager,371,0.053,21 -East India Pale Ale,English India Pale Ale (IPA),437,0.068,47 -Easy Day Kolsch,Kölsch,199,0.045,25 -Easy Jack,American IPA,48,0.045,47 -Eclipse Black IPA,American Black Ale,515,0.077,71 -Eddy Out Pale Ale,American Pale Ale (APA),510,0.055,50 -El Chingon IPA,American IPA,287,0.076,73 -El Conquistador Extra Pale Ale,American Pale Ale (APA),98,0.048,44 -El Hefe Speaks,Hefeweizen,227,0.053,11 -El Rojo,American Amber / Red Ale,124,0.065,25 -El Steinber Dark Lager,Vienna Lager,171,0.055,25 -Elder Betty,Hefeweizen,303,0.055,13 -Elephant Rock IPA,American IPA,355,0.07,75 -Elevated IPA,American IPA,332,0.072,100 -Elevation Triple India Pale Ale,American Double / Imperial IPA,110,0.099,100 -Ellie’s Brown Ale,American Brown Ale,37,0.055,17 -Elliott's Phoned Home Pale Ale,American Pale Ale (APA),29,0.051,36 -EOS Hefeweizen,Hefeweizen,337,0.048,10 -Epicenter Amber Ale,American Amber / Red Ale,30,0.055,20 -Epitome,American Black Ale,222,0.099,100 -Ermal's,Cream Ale,94,0.054,20 -ESB Special Ale,Extra Special / Strong Bitter (ESB),264,0.057,58 -Estival Cream Stout,American Stout,264,0.058,15 -Ethos IPA,American IPA,45,0.068,110 -Eugene Porter,American Porter,44,0.068,28 -Even Keel,American IPA,34,0.038,40 -Evil Owl,American Amber / Red Ale,207,0.052,40 -Evo IPA,American IPA,190,0.062,70 -Evolutionary IPA,American IPA,190,0.062,70 -Ex Umbris Rye Imperial Stout,American Double / Imperial Stout,423,0.099,85 -Excess IPL,American India Pale Lager,2,0.072,80 -Extra Pale Ale,American Pale Ale (APA),138,0.053,49 -F5 IPA,American IPA,182,0.068,100 -Face Plant IPA,American IPA,430,0.062,65 -Fairweather IPA,American IPA,493,0.061,64 -Falling Down Brown Ale,American Brown Ale,301,0.065,65 -Farm Girl Saison,Saison / Farmhouse Ale,244,0.06,30 -Farmer Brown Ale,American Brown Ale,365,0.07,22 -Farmer John's Multi-Grain Ale,American Blonde Ale,482,0.056,21 -Farmer Wirtz India Pale Ale,English India Pale Ale (IPA),107,0.07,94 -Farmer's Daughter Blonde,American Blonde Ale,68,0.051,17 -Farmer's Tan Red Ale,American Amber / Red Ale,265,0.06,30 -Farmhouse Wit,Saison / Farmhouse Ale,127,0.048,25 -Fascist Pig Ale,American Amber / Red Ale,256,0.08,72 -Fat Tire Amber Ale,American Amber / Red Ale,82,0.052,18 -Fenway American Pale Ale,American Pale Ale (APA),103,0.058,45 -Ferus Fluxus Wild Belgian Pale Ale,American Wild Ale,51,0.075,30 -Festeroo Winter Ale,American Strong Ale,198,0.078,60 -Festie,Märzen / Oktoberfest,383,0.048,12 -Field 41 Pale Ale,American Pale Ale (APA),483,0.044,38 -Filthy Hoppin' IPA,American IPA,311,0.065,72 -Firemans #4 Blonde Ale,American Blonde Ale,128,0.051,21 -Fireside Chat,Winter Warmer,368,0.079,45 -Firestarter India Pale Ale,American IPA,107,0.066,72 -First Stand,Saison / Farmhouse Ale,215,0.055,35 -Fisherman's Brew,American Amber / Red Ale,230,0.055,30 -Fisherman's IPA,American IPA,230,0.055,64 -Fisherman's Pils,German Pilsener,230,0.054,35 -Fist City,American Pale Ale (APA),44,0.055,40 -Fistful Of Hops Blue,American IPA,25,0.064,75 -Fistful of Hops Green,American IPA,25,0.064,75 -Fistful of Hops Orange,American IPA,25,0.063,75 -Fistful Of Hops Red,American IPA,25,0.064,75 -Fistmas Ale,Herbed / Spiced Beer,44,0.061,31 -Fivepine Chocolate Porter,American Porter,153,0.062,40 -Flagship Ale,Cream Ale,379,0.049,22 -Flaming Damsel Lager,Vienna Lager,284,0.048,18 -Flesh Gourd'n,Pumpkin Ale,1,0.066,21 -Float Trip Ale,American Blonde Ale,314,0.045,18 -Florida Cracker Belgian Wit,Witbier,141,0.05,18 -Flyin' HI.P.Hay,American IPA,375,0.068,68 -Flying Jenny Extra Pale Ale,American Pale Ale (APA),379,0.06,54 -Flying Mouse 4,American IPA,50,0.07,70 -FMB 101,Kölsch,36,0.048,20 -Forest Fire Imperial Smoked Rye,Rye Beer,444,0.099,85 -Fortunate Islands,American Pale Wheat Ale,209,0.047,46 -Four Peaks Peach Ale,Fruit / Vegetable Beer,160,0.042,9 -Four Squared,American Blonde Ale,128,0.06,50 -Fox Tail Gluten Free Ale,American Pale Ale (APA),233,0.05,50 -Frankenlou's IPA,American IPA,494,0.07,105 -Franz,Märzen / Oktoberfest,93,0.052,21 -Freeride APA,American Pale Ale (APA),270,0.053,40 -Fremont Summer Ale,American Pale Ale (APA),460,0.065,45 -Fresh Slice White IPA,American White IPA,275,0.055,45 -Frosted Fields Winter Wheat,American Dark Wheat Ale,165,0.06,25 -Full Boar Scotch Ale,Scotch Ale / Wee Heavy,491,0.074,12 -Full Nelson Pale Ale,American Pale Ale (APA),382,0.059,60 -Furious,American IPA,61,0.062,99 -G'KNIGHT,American Double / Imperial IPA,389,0.087,85 -Galaxy IPA,American IPA,40,0.075,60 -Gandhi-Bot Double IPA,American Double / Imperial IPA,410,0.088,85 -Gangway IPA,American IPA,475,0.062,55 -Gansett Light,Light Lager,143,0.037,10 -Gateway Kolsch Style Ale,Kölsch,433,0.053,32 -Gator Tail Brown Ale,American Brown Ale,393,0.053,30 -General George Patton Pilsner,Czech Pilsener,26,0.054,48 -Get Together,American IPA,0,0.045,50 -Get Up Offa That Brown,American Brown Ale,240,0.055,20 -Getaway,German Pilsener,244,0.052,30 -Ghost Ship White IPA,American IPA,192,0.056,55 -Giant DIPA,American Double / Imperial IPA,252,0.089,88 -Gillespie Brown Ale,American Brown Ale,346,0.095,49 -Ginger Peach Saison,Saison / Farmhouse Ale,45,0.048,20 -Global Warmer,American Strong Ale,46,0.07,70 -Gnarly Brown,American Brown Ale,91,0.07,32 -Gold,American Blonde Ale,13,0.048,15 -Golden Fleece,Belgian Pale Ale,246,0.045,35 -Golden Pilsner,German Pilsener,326,0.05,35 -Golden Ratio IPA,American IPA,183,0.07,68 -Golden Road Hefeweizen,Hefeweizen,240,0.046,15 -Gone A-Rye,American Double / Imperial IPA,29,0.085,90 -Good People American Brown Ale,American Brown Ale,478,0.058,36 -Good People IPA,American IPA,478,0.06,64 -Good People Pale Ale,American Pale Ale (APA),478,0.056,36 -Good Vibes IPA,American IPA,322,0.073,85 -Goose Island India Pale Ale,American IPA,196,0.059,55 -Gordon,American Double / Imperial IPA,503,0.092,85 -Gordon Ale,American Double / Imperial IPA,166,0.087,85 -Gordon Beer,American Double / Imperial IPA,503,0.087,60 -Gordon Imperial Red,American Double / Imperial IPA,166,0.087,85 -Gose,Gose,41,0.046,8 -Gran Met,Belgian Strong Pale Ale,322,0.092,25 -Grand Circus IPA,American IPA,72,0.05,62 -Grand Trunk Bohemian Pils,Czech Pilsener,124,0.05,35 -Grandma's Pecan,English Brown Ale,33,0.069,34 -Grapefruit IPA,American IPA,13,0.05,35 -Grapefruit Jungle (GFJ),American IPA,25,0.075,77 -Grazias,Cream Ale,3,0.063,30 -Great Crescent Belgian Style Wit,Witbier,165,0.051,13 -Great Crescent Blonde Ale,American Blonde Ale,165,0.053,22 -Great Crescent Brown Ale,American Brown Ale,165,0.045,36 -Great Crescent Coconut Porter,American Porter,165,0.056,33 -Great Crescent Dark Lager,Euro Dark Lager,165,0.057,23 -Great Crescent IPA,American IPA,165,0.062,60 -Great Crescent Mild Ale,English Dark Mild Ale,165,0.042,26 -Great Crescent Oktoberfest Lager,Märzen / Oktoberfest,165,0.057,25 -Great Crescent Stout,English Stout,165,0.08,66 -Green Bullet Organic India Pale Ale,American IPA,429,0.07,45 -Green Monsta IPA,American IPA,295,0.06,55 -Greenbelt Farmhouse Ale,Saison / Farmhouse Ale,426,0.051,20 -Greenville Pale Ale,American Pale Ale (APA),539,0.055,52 -GreyBeard IPA,American IPA,324,0.069,51 -Grind Line,American Pale Ale (APA),124,0.05,35 -Grisette,Grisette,371,0.056,25 -Groupe G,Belgian IPA,280,0.076,65 -GUBNA Imperial IPA,American Double / Imperial IPA,166,0.099,100 -Gutch English Style Mild Ale,English Pale Mild Ale,283,0.05,16 -Gyptoberfest,Märzen / Oktoberfest,107,0.056,26 -Habitus IPA,American IPA,423,0.08,86 -Habitus,American Double / Imperial IPA,3,0.08,100 -Halcyon Unfiltered Wheat,American Pale Wheat Ale,45,0.05,20 -Half Cycle IPA,American IPA,16,0.06,104 -Half Full Bright Ale,American Blonde Ale,248,0.052,18 -Hammer & Sickle,Russian Imperial Stout,110,0.09,60 -Hangar 24 Helles Lager,Munich Helles Lager,476,0.043,14 -Hans' Pils,German Pilsener,128,0.053,52 -Happy Amber,American Amber / Red Ale,91,0.06,30 -Harbinger,Saison / Farmhouse Ale,46,0.049,35 -Hardywood Cream Ale,Cream Ale,343,0.044,18 -Harness the Winter,American IPA,75,0.072,87 -Harpoon IPA,American IPA,234,0.059,42 -Harpoon Octoberfest,Märzen / Oktoberfest,234,0.055,30 -Harpoon Summer Beer,Kölsch,234,0.05,28 -Harvest Ale,Saison / Farmhouse Ale,460,0.065,35 -Hawaiian Crow Porter,American Porter,203,0.052,27 -Haywire Hefeweizen,Hefeweizen,544,0.052,18 -Hazed & Infused,American Pale Ale (APA),417,0.049,35 -Hazy Day Belgian-Style Wit,Witbier,379,0.04,20 -Heady Topper,American Double / Imperial IPA,272,0.08,120 -Heal the Bay IPA,American IPA,240,0.068,65 -Heavy Lifting,American IPA,31,0.062,80 -Hefe Black,Hefeweizen,296,0.049,30 -Hefe Lemon,Radler,296,0.049,30 -Hell or High Watermelon Wheat,Fruit / Vegetable Beer,368,0.049,17 -Hell-Diver Pale Ale,American Pale Ale (APA),309,0.052,32 -Hell,Keller Bier / Zwickel Bier,61,0.051,20 -Helles Golden Lager,Munich Helles Lager,371,0.049,18 -Hemlock Double IPA,American Double / Imperial IPA,220,0.095,104 -Here Gose Nothin',Gose,57,0.05,12 -HGH (Home Grown Hops): Part Duh,American Strong Ale,166,0.08,70 -Hi-Res,American Double / Imperial IPA,46,0.099,111 -Hideout Helles,Munich Helles Lager,135,0.069,17 -Hijack,Cream Ale,242,0.055,20 -Hill 88 Double IPA,American Double / Imperial IPA,280,0.088,77 -Hilliard's Amber Ale,American Amber / Red Ale,362,0.055,60 -Hilliard's Blonde,American Blonde Ale,362,0.049,20 -Hilliard's Pils,Czech Pilsener,362,0.055,34 -Hilliard's Saison,Saison / Farmhouse Ale,362,0.066,30 -Hinchtown Hammer Down,American Blonde Ale,16,0.05,27 -Hipster Breakfast,Oatmeal Stout,133,0.058,40 -Hodad Porter,American Porter,172,0.055,30 -Homefront IPA,American IPA,163,0.06,70 -Honey Badger Blonde,American Blonde Ale,69,0.047,19 -Honey Kolsch,Kölsch,27,0.046,15 -Honey Rye,Rye Beer,68,0.058,18 -Honey Wheat,American Pale Wheat Ale,326,0.047,14 -Hoodoo Voodoo IPA,American IPA,153,0.062,82 -Hoopla Pale Ale,American Pale Ale (APA),417,0.057,35 -Hop A Potamus Double Dark Rye Pale Ale,Rye Beer,365,0.09,99 -Hop A-Peel,American Double / Imperial IPA,72,0.075,115 -Hop Abomination,American IPA,496,0.066,100 -Hop Bomber Rye Pale Ale,American Pale Ale (APA),321,0.055,60 -Hop Box Imperial IPA,American Double / Imperial IPA,233,0.093,90 -Hop Crisis,American Double / Imperial IPA,368,0.097,94 -Hop Drop 'N Roll IPA,American IPA,359,0.072,80 -Hop Farm IPA,American IPA,297,0.058,45 -Hop Freak,American Double / Imperial IPA,284,0.087,80 -Hop Happy,American IPA,284,0.075,51 -Hop in the ‘Pool Helles,American Pilsner,116,0.049,22 -Hop Knot IPA,American IPA,160,0.067,47 -Hop Nosh IPA,American IPA,159,0.073,83 -Hop Notch IPA,American IPA,159,0.073,82 -Hop Ottin' IPA,American IPA,171,0.07,80 -Hop Shock IPA,American IPA,30,0.07,85 -Hop Slayer Double IPA,American Double / Imperial IPA,361,0.082,100 -Hop Stalker Fresh Hop IPA,American IPA,224,0.07,80 -Hop Up Offa That Brett,Belgian Pale Ale,25,0.058,20 -Hopadillo India Pale Ale,American IPA,125,0.066,70 -Hoperation Overload,American Double / Imperial IPA,57,0.096,85 -Hopkick Dropkick,American Double / Imperial IPA,24,0.099,115 -Hoponius Union,American India Pale Lager,2,0.067,65 -Hopped on the High Seas (Ahtanum),American IPA,141,0.07,60 -Hopped on the High Seas (Calypso),American IPA,141,0.07,60 -Hopped on the High Seas (Citra),American IPA,141,0.07,60 -Hopped on the High Seas (Hop #529),American IPA,141,0.07,60 -Hopportunity Knocks IPA,American IPA,155,0.068,100 -Hoppy Boy,American IPA,520,0.062,65 -Hoptopus Double IPA,American Double / Imperial IPA,306,0.088,108 -Hopvale Organic Ale,American Pale Ale (APA),58,0.047,55 -Hopworks IPA,American IPA,80,0.066,75 -Horny Monk,Dubbel,11,0.069,20 -Horny Toad Cerveza,American Blonde Ale,182,0.053,25 -Hot Date Ale,Chile Beer,314,0.06,20 -Hot Rod Red,American Amber / Red Ale,360,0.061,41 -Hotbox Brown,American Brown Ale,13,0.055,10 -House Brand IPA,American IPA,519,0.06,55 -House Lager,Keller Bier / Zwickel Bier,2,0.052,18 -HUB Lager,Czech Pilsener,80,0.051,32 -Humbucker Helles,Maibock / Helles Bock,126,0.047,25 -Humidor Series India Pale Ale,American IPA,141,0.075,70 -Humpback Blonde Ale,American Blonde Ale,203,0.042,22 -Humulus Nimbus Super Pale Ale,American Pale Ale (APA),183,0.06,53 -Hunny Do Wheat,American Pale Wheat Ale,142,0.048,18 -Hurricane Amber Ale,American Amber / Red Ale,101,0.052,24 -Hustle,American Amber / Red Ale,93,0.057,42 -Hydraulion Red,Irish Red Ale,273,0.053,22 -I-10 IPA,American IPA,527,0.068,55 -Immersion Amber,American Amber / Red Ale,190,0.052,27 -Imperial Pumpkin Stout,Pumpkin Ale,230,0.099,43 -In-Tents India Pale Lager,American Pale Lager,116,0.068,62 -Independence Pass Ale,American IPA,523,0.07,67 -India Pale Ale,American IPA,87,0.063,65 -Indian Paintbrush IPA,American IPA,457,0.07,75 -Indianapolis Indians Lager,Dortmunder / Export Lager,25,0.052,24 -Indians Victory Lager,Dortmunder / Export Lager,25,0.052,24 -Infamous IPA,American IPA,242,0.07,75 -Initial Point India Pale Ale,American IPA,313,0.071,92 -Interurban IPA,American IPA,460,0.065,80 -IPA & a Half,American IPA,291,0.073,87 -IPA #11,American IPA,121,0.057,58 -Iron Butt Red Ale,American Amber / Red Ale,313,0.058,39 -Iron Horse Pale Ale,American Pale Ale (APA),335,0.05,32 -Ironman,English Strong Ale,27,0.09,50 -Irregardless IPA,American IPA,298,0.065,75 -Isis,American Double / Imperial IPA,25,0.091,91 -Jacaranada Rye IPA,American IPA,320,0.067,60 -Jack Pine Savage,American Pale Ale (APA),444,0.053,43 -Jack Stout,Oatmeal Stout,361,0.06,23 -Jack the Sipper,Extra Special / Strong Bitter (ESB),133,0.053,45 -Jah Mon,American IPA,43,0.05,100 -Jai Alai IPA,American IPA,141,0.075,70 -Jalapeno Pale Ale,American Pale Ale (APA),70,0.055,45 -Jam Session,American Pale Ale (APA),359,0.051,31 -Jammer,Gose,46,0.042,16 -Jockamo IPA,American IPA,533,0.065,52 -Joey Wheat,American Pale Wheat Ale,24,0.068,16 -Johan the Barleywine,English Barleywine,25,0.099,60 -Jon Boat Coastal Ale,American Blonde Ale,527,0.045,20 -José Martí American Porter,American Porter,141,0.08,65 -Joseph James American Lager,American Adjunct Lager,233,0.052,15 -Jucundus,Wheat Ale,3,0.06,24 -Juke Joint IPA,American IPA,152,0.07,60 -Just IPA,American IPA,328,0.046,45 -Kadigan,American Blonde Ale,241,0.056,30 -Karbachtoberfest,Märzen / Oktoberfest,125,0.055,25 -KelSo India Pale Ale,American IPA,342,0.06,64 -KelSo Nut Brown Lager,Euro Dark Lager,342,0.057,19 -KelSo Pilsner,Czech Pilsener,342,0.055,23 -Killer Kolsch,Kölsch,236,0.05,22 -Kilt Dropper Scotch Ale,Scotch Ale / Wee Heavy,107,0.075,22 -Kilt Lifter Scottish-Style Ale,Scottish Ale,160,0.06,21 -Kindler Pale Ale,American Pale Ale (APA),107,0.053,45 -King Street Hefeweizen,Hefeweizen,102,0.057,10 -King Street IPA,American IPA,102,0.06,70 -Klickitat Pale Ale,American Pale Ale (APA),117,0.053,36 -Knotty Blonde Ale,American Blonde Ale,153,0.04,18 -Kodiak Brown Ale,American Brown Ale,223,0.05,24 -Kol' Beer,Kölsch,257,0.05,22 -Köld Lager,German Pilsener,45,0.05,16 -Kölsch 151,Kölsch,414,0.049,16 -KSA,Kölsch,4,0.046,17 -L'il Lucy's Hot Pepper Ale,Chile Beer,132,0.049,28 -La Perouse White,Witbier,375,0.05,12 -Lahaina Town Brown,American Brown Ale,375,0.051,20 -Laimas Kölsch Style Ale,Kölsch,405,0.05,20 -Laka Laka Pineapple,Hefeweizen,120,0.051,17 -Lake House,Munich Helles Lager,192,0.046,18 -Lake Monster,Baltic Porter,184,0.082,25 -Lakefire Rye Pale Ale,American Pale Ale (APA),213,0.055,35 -Lancaster German Style Kölsch,Kölsch,545,0.048,28 -Larry Imperial IPA,American Double / Imperial IPA,295,0.085,85 -Last Stop IPA,American IPA,308,0.072,60 -Laughing Dog Cream Ale,Cream Ale,218,0.05,12 -Laughing Dog IPA,American IPA,218,0.064,66 -Lava Lake Wit,Witbier,63,0.052,15 -Lawnmower Lager,American Adjunct Lager,155,0.039,16 -Le Mort Vivant,Bière de Garde,118,0.069,23 -Leaning Chimney Smoked Porter,American Porter,379,0.06,34 -Lee Hill Series Vol. 2 - Wild Saison,American Wild Ale,51,0.068,24 -Lee Hill Series Vol. 3 - Barrel Aged Imperial Stout,American Double / Imperial Stout,51,0.099,51 -Leisure Time,American Pale Lager,2,0.048,15 -Let It Ride IPA,American IPA,277,0.068,90 -Lewbricator Wheat Dopplebock,Doppelbock,208,0.075,24 -LIFT,Kölsch,91,0.047,11 -Lights Out Vanilla Cream Extra Stout,American Double / Imperial IPA,199,0.077,30 -Lil SIPA,American IPA,321,0.05,55 -Lil' Helper,American IPA,188,0.07,70 -Lily Flagg Milk Stout,Milk / Sweet Stout,412,0.05,30 -Little Red Cap,Altbier,144,0.063,43 -Little Sister India Style Session Ale,American IPA,170,0.043,60 -Lobo Lito,Light Lager,187,0.04,12 -Local Buzz,American Blonde Ale,287,0.052,20 -Loki Red Ale,American Amber / Red Ale,201,0.075,53 -Lomaland,Saison / Farmhouse Ale,209,0.055,30 -London Balling,English Barleywine,1,0.125,80 -London Homesick Ale,English Bitter,184,0.049,27 -Long Hammer IPA,American IPA,487,0.065,44 -Long Leaf,American IPA,69,0.071,75 -Long Trail Ale,American Amber / Red Ale,268,0.046,30 -Long Trail IPA,English India Pale Ale (IPA),268,0.059,42 -Longboard Island Lager,American Amber / Red Lager,439,0.046,18 -Longhop IPA,American IPA,281,0.042,30 -Loose Cannon,American IPA,479,0.072,45 -Lost Meridian Wit,Witbier,116,0.05,20 -Lost Sailor IPA,English India Pale Ale (IPA),278,0.055,40 -Louie’s Demise Immort-Ale,American Amber / Red Ale,284,0.051,24 -Love Street Summer Seasonal,Kölsch,125,0.047,20 -Lower De Boom,American Barleywine,368,0.099,92 -Luchesa Lager,Keller Bier / Zwickel Bier,184,0.048,35 -LuckenBock,Bock,257,0.07,18 -Lucky Buck,Irish Dry Stout,281,0.04,34 -Lucky Day IPA,American IPA,350,0.072,85 -Lucky U IPA,American IPA,391,0.062,68 -Lumberyard Pilsner,American Pilsner,158,0.053,20 -Mac's Scottish Style Amber Ale,American Amber / Red Ale,485,0.051,32 -Mad Beach,American Pale Wheat Ale,360,0.048,23 -Madra Allta,American IPA,345,0.064,95 -Maduro Brown Ale,English Brown Ale,141,0.055,25 -Maggie's Leap,Milk / Sweet Stout,0,0.049,26 -Main St. Virginia Ale,Altbier,122,0.05,40 -Make It So,Extra Special / Strong Bitter (ESB),58,0.053,40 -Mama's Little Yella Pils,Czech Pilsener,166,0.053,35 -Mana Wheat,American Pale Wheat Ale,375,0.055,15 -Manzanita IPA,American IPA,346,0.08,88 -Manzanita Pale Ale,American Pale Ale (APA),346,0.066,44 -Mauna Kea Pale Ale,American Pale Ale (APA),203,0.054,42 -Maylani's Coconut Stout,American Stout,367,0.053,35 -Mazzie,American Pale Ale (APA),55,0.054,45 -McKinney Eddy Amber Ale,American Amber / Red Ale,314,0.055,20 -Mexican Logger,American Pale Lager,264,0.042,18 -Miami Vice IPA,American IPA,393,0.071,62 -Midnight Oil,Oatmeal Stout,447,0.05,38 -Midnight Ryder,American Black Ale,277,0.065,80 -Midwest Charm Farmhouse Ale,Saison / Farmhouse Ale,471,0.06,29 -Might As Well IPL,American Pale Lager,240,0.072,75 -Mind's Eye PA,American IPA,11,0.067,74 -Mirror Pond Pale Ale,American Pale Ale (APA),454,0.05,40 -Missile IPA,American IPA,236,0.07,65 -Mission IPA,American IPA,98,0.068,66 -Mississippi Fire Ant,American Amber / Red Ale,133,0.08,80 -Missouri Mule India Pale Ale,American IPA,314,0.07,70 -Mo's Gose,Gose,461,0.052,10 -Moar,English India Pale Ale (IPA),53,0.044,44 -Modus Hoperandi,American IPA,264,0.068,65 -Moirai India Pale Ale,American IPA,405,0.07,70 -Monarch Classic American Wheat,American Pale Wheat Ale,213,0.043,21 -Monk from the 'Yunk,Tripel,356,0.09,30 -Monk's Blood,Belgian Dark Ale,368,0.083,35 -Monkey Chased the Weasel,Berliner Weissbier,222,0.039,9 -Monkey Fist IPA,American IPA,385,0.069,65 -Monkeynaut IPA,American IPA,412,0.072,70 -Montana Trout Slayer Ale,American Pale Wheat Ale,336,0.05,35 -Montauk Summer Ale,American Blonde Ale,276,0.056,28 -Moose Drool Brown Ale,American Brown Ale,336,0.051,26 -More Cowbell,American Double / Imperial IPA,214,0.09,118 -Morgan Street Oktoberfest,Märzen / Oktoberfest,326,0.049,24 -Morning Wood Wheat (Current),American Pale Wheat Ale,135,0.059,14 -Morning Wood,Oatmeal Stout,444,0.055,35 -Mother Ale,American Blonde Ale,453,0.056,46 -Mother in Lager,Munich Dunkel Lager,125,0.058,25 -Mothman Black IPA,American Black Ale,156,0.067,71 -Mound Builder IPA,American IPA,205,0.065,77 -Mountain Rescue Pale Ale,American Pale Ale (APA),194,0.055,40 -Mr. Blue Sky,American Pale Wheat Ale,124,0.045,6 -Mr. Pineapple,Fruit / Vegetable Beer,30,0.05,20 -Mucho Aloha Hawaiian Pale Ale,American Pale Ale (APA),517,0.056,36 -Murphy's Law,American Amber / Red Ale,115,0.058,35 -Mustang Golden Ale,American Blonde Ale,366,0.053,10 -Mutiny IPA,American IPA,192,0.062,70 -Mutton Buster,American Brown Ale,307,0.055,25 -Na Zdraví Pilsner,Czech Pilsener,436,0.048,32 -Naked Pig Pale Ale,American Pale Ale (APA),286,0.06,43 -Narragansett Bock,Bock,143,0.065,32 -Narragansett Bohemian Pilsner,German Pilsener,143,0.086,35 -Narragansett Cream Ale,Cream Ale,143,0.05,22 -Narragansett Fest Lager,Märzen / Oktoberfest,143,0.055,15 -Narragansett Porter,American Porter,143,0.07,22 -Narragansett Summer Ale,American Pale Wheat Ale,143,0.042,24 -Native Amber,American Amber / Red Ale,182,0.063,35 -Nebraska India Pale Ale,American IPA,337,0.065,65 -Neomexicanus Native,American Pale Ale (APA),63,0.06,46 -Newport Storm IPA,American IPA,101,0.065,75 -Nice Rack IPA,American IPA,436,0.055,65 -Night Cat,American Dark Wheat Ale,74,0.058,43 -Ninja Porter,American Porter,528,0.053,26 -No Wake IPA,American IPA,228,0.072,50 -Noche Dulce,American Porter,231,0.071,16 -Nonstop Hef Hop,American Pale Wheat Ale,80,0.039,20 -Nordic Blonde,American Blonde Ale,53,0.057,27 -Nordskye,American IPA,12,0.048,47 -Norm's Gateway IPA,American IPA,124,0.04,55 -Norns Roggenbier,Roggenbier,405,0.05,20 -North 45 Amber Ale,American Amber / Red Ale,11,0.059,25 -North Third Stout,Foreign / Export Stout,12,0.06,30 -Northern Lights Amber Ale,American Amber / Red Ale,493,0.05,15 -Northern Lights India Pale Ale,American IPA,383,0.065,52 -Nugget Nectar,American Amber / Red Ale,97,0.075,93 -O'Fallon Wheach,Fruit / Vegetable Beer,442,0.051,7 -O’Malley’s IPA,American IPA,132,0.075,89 -Oak Cliff Coffee Ale,American Brown Ale,127,0.075,33 -Oakshire Amber Ale,American Amber / Red Ale,150,0.054,24 -Oasis,American Double / Imperial IPA,45,0.072,93 -Oasis,Extra Special / Strong Bitter (ESB),45,0.072,93 -Oatmeal PSA,American Pale Ale (APA),367,0.05,35 -OB-1 Organic Ale,English Brown Ale,191,0.05,22 -Odyssey Imperial IPA,American Double / Imperial IPA,371,0.084,90 -Oklahoma Suks,American Amber / Red Ale,140,0.048,32 -OktoberBeast,Märzen / Oktoberfest,360,0.072,22 -Oktoberfest Lager,Märzen / Oktoberfest,371,0.058,25 -Oktoberfest Revolution,Märzen / Oktoberfest,44,0.057,25 -Oktoberfest,Märzen / Oktoberfest,226,0.055,40 -Oktoberfest,Märzen / Oktoberfest,365,0.059,25 -OktoberFiesta,,66,0.053,27 -Old Chico Crystal Wheat,American Pale Wheat Ale,83,0.048,26 -Old Chub,Scottish Ale,166,0.08,35 -Old Devil's Tooth,American Barleywine,309,0.099,100 -Old Elephant Foot IPA,American IPA,490,0.07,80 -Old Forge Pumpkin Ale,Pumpkin Ale,301,0.046,20 -Old Grogham Imperial India Pale Ale,American Double / Imperial IPA,387,0.085,86 -Old Potentate,Old Ale,118,0.072,40 -Old Pro,Gose,71,0.042,10 -Old Red Beard Amber Ale,American Amber / Red Ale,374,0.06,35 -Old Soul,Belgian Strong Pale Ale,63,0.075,25 -Old Tom Porter,American Porter,314,0.055,25 -Old Town Ale,Kölsch,441,0.045,22 -On the Count of 3,Hefeweizen,292,0.07,42 -On the Wings of Armageddon,American Double / Imperial IPA,227,0.092,115 -One Hit Wonder,American Double / Imperial IPA,166,0.09,60 -One Nut Brown,American Brown Ale,297,0.047,28 -Oneida,American Pale Ale (APA),209,0.052,50 -Onyx Black Ale,American Black Ale,131,0.052,9 -Operation Homefront,American IPA,141,0.062,65 -Orange Wheat,Fruit / Vegetable Beer,476,0.046,17 -Organic Baba Black Lager,Schwarzbier,159,0.04,32 -Original Orange Blossom Ale (Current),Herbed / Spiced Beer,530,0.058,35 -Original Slacker Ale,English Brown Ale,164,0.056,40 -Ornery Amber Lager,Vienna Lager,291,0.055,33 -Osiris Pale Ale,American Pale Ale (APA),25,0.056,50 -Our Legacy IPA,American IPA,299,0.065,60 -Outlaw IPA,American IPA,307,0.062,65 -Oval Beach Blonde Ale,American Blonde Ale,398,0.05,11 -Over the Rail Pale Ale,American Pale Ale (APA),135,0.057,68 -Overcast Espresso Stout,American Stout,150,0.058,27 -Overgrown American Pale Ale,American Pale Ale (APA),261,0.055,55 -Overlord Imperial IPA,American Double / Imperial IPA,17,0.085,115 -Overrated! West Coast Style IPA,American IPA,61,0.073,69 -Owney Irish Style Red Ale,Irish Red Ale,365,0.05,30 -Ozark American Pale Ale,American Pale Ale (APA),259,0.04,39 -P-51 Porter,American Porter,509,0.08,31 -P-Town Pilsner,American Pilsner,117,0.04,20 -Pablo Beach Pale Ale,American Pale Ale (APA),466,0.05,30 -Pace Setter Belgian Style Wit,Witbier,448,0.037,21 -Paddy Pale Ale,American Pale Ale (APA),361,0.056,41 -Pail Ale,American Pale Ale (APA),250,0.055,30 -Pako’s EyePA,American IPA,191,0.068,60 -Pale Alement,American Pale Ale (APA),24,0.055,40 -Pale Dog,American Pale Ale (APA),395,0.06,50 -Pale,American Pale Ale (APA),21,0.054,37 -Pallavicini Pilsner,Czech Pilsener,135,0.058,21 -Pamola Xtra Pale Ale,American Pale Ale (APA),292,0.049,28 -Panther,American Porter,93,0.058,35 -Parade Ground Coffee Porter,American Porter,152,0.07,35 -Parapet ESB,Extra Special / Strong Bitter (ESB),0,0.056,47 -Parcae Belgian Style Pale Ale,Belgian Pale Ale,405,0.05,20 -Park,American Pale Wheat Ale,4,0.047,19 -Passion Fruit Prussia,Berliner Weissbier,60,0.035,11 -Payette Pale Ale,American Pale Ale (APA),307,0.048,35 -Peacemaker Pale Ale,American Pale Ale (APA),149,0.057,47 -Peacemaker Pilsner,Czech Pilsener,135,0.058,21 -Peach Pale Ale,American Pale Ale (APA),104,0.057,40 -Peck's Porter,American Porter,7,0.065,35 -Peninsula Brewers Reserve (PBR),American Blonde Ale,458,0.05,15 -People's Pale Ale,American Pale Ale (APA),527,0.053,28 -Perfect Tin Amber,American Amber / Red Ale,152,0.045,28 -Perpetual Darkness,Belgian Strong Dark Ale,148,0.092,72 -Perpetual IPA,American IPA,97,0.075,85 -Persnickety Pale,American Pale Ale (APA),190,0.057,36 -Phat Chance,American Blonde Ale,361,0.052,27 -Phoenix Pale Ale,American Pale Ale (APA),371,0.051,40 -Pigwar White India Pale Ale,American White IPA,80,0.06,60 -Pikeland Pils,German Pilsener,371,0.049,44 -Pile of Face,American IPA,1,0.06,65 -Pilzilla,American Double / Imperial Pilsner,322,0.075,85 -Pine Belt Pale Ale,American Pale Ale (APA),118,0.065,45 -Pinner Throwback IPA,American IPA,166,0.049,35 -Pisgah Pale Ale,American Pale Ale (APA),324,0.057,31 -Pit Stop Chocolate Porter,American Porter,448,0.037,34 -Pleasure Town IPA,American IPA,223,0.063,61 -Pleasure Town,American IPA,223,0.063,61 -Plum St. Porter,American Porter,219,0.06,52 -Point Amber Classic,American Amber / Red Lager,131,0.047,14 -Point Cascade Pale Ale,American Pale Ale (APA),131,0.054,33 -Point Nude Beach Summer Wheat,American Pale Wheat Ale,131,0.052,7 -Point Oktoberfest,Märzen / Oktoberfest,131,0.057,15 -Point Special Lager,American Adjunct Lager,131,0.047,9 -Point Special,American Adjunct Lager,131,0.047,9 -Point the Way IPA,American IPA,240,0.059,60 -Polar Pale Ale,American Pale Ale (APA),493,0.052,17 -Pole Barn Stout,Oatmeal Stout,21,0.055,31 -Poleeko Gold Pale Ale,American Pale Ale (APA),171,0.055,28 -Poop Deck Porter,American Porter,374,0.062,35 -Porch Rocker,Radler,300,0.045,8 -Port Barrel Wee Mac,Scotch Ale / Wee Heavy,25,0.053,23 -Porter (a/k/a Black Gold Porter),American Porter,69,0.06,23 -Powder Hound Winter Ale,English Strong Ale,336,0.072,60 -Power & Light,American Pale Ale (APA),140,0.055,42 -Pre Flight Pilsner,American Pilsner,367,0.052,33 -Pretzel Stout,American Stout,161,0.065,47 -Pride of Texas Pale Ale,American Pale Ale (APA),176,0.058,60 -PRO-AM,American Double / Imperial IPA,118,0.099,100 -Provision,Saison / Farmhouse Ale,297,0.042,25 -Proxima IPA,American IPA,198,0.063,70 -Psycho Penguin Vanilla Porter,American Porter,148,0.054,36 -Psychopathy,American IPA,91,0.069,70 -Pt. Bonita Rustic Lager,American Pale Lager,280,0.062,40 -Pub Ale,English Dark Mild Ale,245,0.038,18 -Pump House IPA,American IPA,68,0.055,45 -Pumpion,Pumpkin Ale,0,0.06,38 -Pumpkan,Pumpkin Ale,295,0.052,20 -Pumpkin Beast,Pumpkin Ale,360,0.062,17 -PUNK'N,Pumpkin Ale,159,0.05,10 -Pure Fury,American Pale Ale (APA),93,0.055,42 -Purple Haze,Fruit / Vegetable Beer,533,0.042,13 -Pursuit,American IPA,248,0.07,40 -PV Muckle,Scotch Ale / Wee Heavy,25,0.083,23 -Pyramid Hefeweizen,Hefeweizen,544,0.052,18 -Quakertown Stout,American Double / Imperial Stout,426,0.092,50 -Quarter Mile Double IPA,American Double / Imperial IPA,304,0.08,80 -Rad,Fruit / Vegetable Beer,46,0.032,7 -Ranger IPA,American IPA,82,0.065,70 -Ray Ray’s Pale Ale,American Pale Ale (APA),122,0.052,42 -Rebel IPA,American IPA,300,0.065,45 -RecreationAle,American Pale Ale (APA),469,0.047,42 -Red Cockaded Ale,American Double / Imperial IPA,118,0.085,110 -Redacted Rye IPA,American IPA,110,0.07,100 -Redband Stout,American Stout,365,0.06,36 -Resin,American Double / Imperial IPA,46,0.091,103 -Rhino Chasers Pilsner,Czech Pilsener,430,0.056,55 -Rhode Island Blueberry,Kölsch,101,0.046,11 -Rico Sauvin,American Double / Imperial IPA,1,0.076,68 -Righteous Ale,Rye Beer,46,0.063,57 -Ring of Dingle,Irish Dry Stout,25,0.071,27 -Rise Up Red,American Amber / Red Ale,80,0.058,60 -River House,Saison / Farmhouse Ale,161,0.05,20 -Riverwalk Blonde Ale,American Blonde Ale,346,0.06,25 -Rivet Irish Red Ale,Irish Red Ale,17,0.051,22 -Robert Earl Keen Honey Pils,American Pilsner,187,0.05,17 -Rocket Girl,Kölsch,528,0.032,27 -Rodeo Clown Double IPA,American Double / Imperial IPA,125,0.095,85 -Rodeo Rye Pale Ale,American Pale Ale (APA),307,0.042,35 -Roller Dam Red Ale,Irish Red Ale,365,0.054,30 -Rollin Dirty Red Ale,Irish Red Ale,308,0.05,21 -Rosa Hibiscus Ale,Herbed / Spiced Beer,44,0.058,15 -RoughTail IPA,American IPA,376,0.07,80 -Royal Brat,Extra Special / Strong Bitter (ESB),25,0.065,55 -Royal Weisse Ale,Hefeweizen,371,0.056,11 -Rubberneck Red,American Amber / Red Ale,161,0.05,35 -Rude Parrot IPA,American IPA,481,0.059,75 -Ruhstaller's Gilt Edge Lager Beer,American Amber / Red Lager,397,0.048,42 -Rule G IPA,American IPA,115,0.07,88 -Rules are Rules,German Pilsener,1,0.05,25 -Rumspringa Golden Bock,Maibock / Helles Bock,545,0.066,30 -Rustic Red,Irish Red Ale,441,0.052,23 -Rye Wit,Witbier,66,0.042,10 -Ryeteous Rye IPA,American IPA,110,0.07,100 -Saddle Bronc Brown Ale,American Brown Ale,79,0.048,16 -Saint Archer Blonde,Kölsch,288,0.048,22 -Saint Archer IPA,American IPA,288,0.068,66 -Saint Archer Pale Ale,American Pale Ale (APA),288,0.052,40 -Saint Archer White Ale,Witbier,288,0.05,15 -Saison 88,Saison / Farmhouse Ale,392,0.055,30 -Saison Pamplemousse,Saison / Farmhouse Ale,240,0.058,35 -Salamander Slam,American IPA,180,0.07,73 -Samuel Adams Octoberfest,Märzen / Oktoberfest,300,0.053,15 -Samuel Adams Summer Ale,American Pale Wheat Ale,300,0.053,7 -Sand Island Lighthouse,Kölsch,412,0.051,25 -Sanitas Black IPA,American Black Ale,419,0.068,65 -Sanitas Saison Ale,Saison / Farmhouse Ale,419,0.058,20 -Santa's Secret,Winter Warmer,179,0.059,22 -SanTan HefeWeizen,Hefeweizen,30,0.05,15 -SanTan HefeWeizen,Hefeweizen,30,0.05,15 -Saucy Intruder,Rye Beer,18,0.072,75 -Savannah Brown Ale,American Brown Ale,415,0.062,55 -Scape Goat Pale Ale,English Pale Ale,336,0.05,40 -Scarecrow,American IPA,27,0.069,65 -Schlafly American Brown Ale,American Brown Ale,428,0.05,30 -Schlafly Hefeweizen,Hefeweizen,428,0.041,16 -Schlafly IPA,American IPA,428,0.045,30 -Schlafly Summer Lager,Munich Helles Lager,428,0.045,17 -Schlafly Yakima Wheat Ale,American Pale Wheat Ale,428,0.05,45 -Schuylkill Punch,Fruit / Vegetable Beer,356,0.06,14 -Schweet Ale,Fruit / Vegetable Beer,298,0.052,20 -Screamin’ Pumpkin,Pumpkin Ale,124,0.05,25 -Screaming Eagle Special Ale ESB,Extra Special / Strong Bitter (ESB),107,0.048,38 -Scruffy's Smoked Alt,Smoked Beer,29,0.051,35 -Sculpin IPA,American IPA,34,0.07,70 -Second Fiddle,American Double / Imperial IPA,172,0.082,80 -Seiche Scottish Ale,Scottish Ale,285,0.078,16 -Self Starter,American IPA,94,0.052,67 -Send Help,American Blonde Ale,60,0.045,18 -Sensi Harvest,American Pale Ale (APA),46,0.047,50 -Seventh Son Hopped Red Ale,American Amber / Red Ale,183,0.077,40 -Seventh Son of a Seventh Son,American Strong Ale,183,0.077,40 -Sex Panther,American Porter,30,0.069,20 -Shark Bait,Fruit / Vegetable Beer,393,0.053,11 -Shenanigans Summer Ale,American Pale Wheat Ale,277,0.046,27 -Shift,American Pale Lager,82,0.05,29 -Shipwrecked Double IPA,American Double / Imperial IPA,98,0.092,75 -Shiva IPA,American IPA,528,0.06,69 -Sho'nuff,Belgian Pale Ale,1,0.04,13 -Shotgun Betty,Hefeweizen,149,0.058,11 -Sidekick Extra Pale Ale,American Pale Ale (APA),74,0.051,36 -Sierra Nevada Pale Ale,American Pale Ale (APA),83,0.056,37 -Silverback Pale Ale,American Pale Ale (APA),424,0.055,40 -Single Engine Red,Irish Red Ale,453,0.058,46 -Sinister Minister Black IPA,American IPA,133,0.077,65 -Sir William's English Brown Ale,English Brown Ale,213,0.049,21 -Sky High Rye,American Pale Ale (APA),541,0.06,55 -Sky-Five,American IPA,258,0.067,70 -Skylight,Dunkelweizen,241,0.056,20 -Slow Hand Stout,American Stout,377,0.052,29 -Slow Ride,American IPA,82,0.045,40 -Slow Ride,American Pale Ale (APA),184,0.048,35 -Sly Fox Christmas Ale 2013,Winter Warmer,371,0.055,16 -Smoking Mirror,American Porter,246,0.055,30 -Snake Dog IPA,American IPA,521,0.071,60 -Snake Handler Double IPA,American Double / Imperial IPA,478,0.093,103 -Snake River Lager,Vienna Lager,191,0.05,18 -Snake River Pale Ale,American Pale Ale (APA),191,0.052,32 -Snow King Pale Ale,American Pale Ale (APA),191,0.06,55 -Snowshoe White Ale,Witbier,223,0.048,12 -Sobek & Set,American Black Ale,256,0.08,80 -Sobrehumano Palena'ole,American Amber / Red Ale,375,0.06,24 -Sockeye Red IPA,American IPA,223,0.057,70 -SoDo Brown Ale,American Brown Ale,190,0.054,20 -Sol Drifter,American Blonde Ale,91,0.043,18 -Solis,American IPA,3,0.075,85 -Soul Doubt,American IPA,66,0.059,70 -South Ridge Amber Ale,American Amber / Red Ale,472,0.06,31 -Sparkle,American Pale Lager,11,0.041,12 -Special Amber,Vienna Lager,498,0.05,22 -Special Edition: Allies Win The War!,English Strong Ale,368,0.085,52 -SPRYE,American Pale Ale (APA),91,0.05,40 -Squatters Hop Rising Double IPA,American Double / Imperial IPA,302,0.09,75 -Stargrazer,Schwarzbier,258,0.05,28 -Starr Pils,German Pilsener,383,0.042,20 -Steam Engine Lager,American Amber / Red Lager,119,0.057,25 -Steel Rail Extra Pale Ale,American Pale Ale (APA),278,0.053,20 -Steel Wheels ESB,Extra Special / Strong Bitter (ESB),382,0.065,30 -Stimulator Pale Ale,American Pale Ale (APA),441,0.053,48 -Stir Crazy Winter Ale,Winter Warmer,277,0.065,22 -Stone Fort Brown Ale,English Brown Ale,183,0.053,20 -Stone's Throw IPA,Scottish Ale,335,0.045,19 -Stowaway IPA,American IPA,292,0.069,69 -Striped Bass Pale Ale,American Pale Ale (APA),237,0.052,26 -Stronghold,American Porter,0,0.06,25 -Stump Knocker Pale Ale,American Pale Ale (APA),447,0.056,35 -Stupid Sexy Flanders,Flanders Oud Bruin,25,0.063,23 -Sudice American Stout,American Stout,405,0.07,58 -SUM'R,American Blonde Ale,159,0.04,17 -Summer Brew,American Pilsner,109,0.028,15 -Summer Paradise,American Pale Wheat Ale,356,0.05,18 -Summer Session Ale,American Pale Wheat Ale,266,0.05,61 -Summer Solstice,Cream Ale,171,0.056,4 -Summer's Wit,Witbier,214,0.06,20 -SummerBright Ale,American Pale Wheat Ale,391,0.045,15 -Summerfest,Czech Pilsener,83,0.05,28 -Summertime Ale,Kölsch,472,0.052,23 -Sun King Oktoberfest,Märzen / Oktoberfest,25,0.055,23 -Sunbru Kölsch,Kölsch,160,0.052,17 -Suncaster Summer Wheat,American Pale Wheat Ale,105,0.05,28 -Sundown,Saison / Farmhouse Ale,164,0.071,36 -Sunlight Cream Ale,Cream Ale,25,0.053,20 -Sunshine Pils,American Pilsner,97,0.045,45 -SunSpot Golden Ale,American Blonde Ale,30,0.05,15 -Supergoose IPA,American IPA,290,0.069,67 -Surfrider,American Pale Ale (APA),33,0.052,35 -SurlyFest,Rye Beer,61,0.055,34 -Survival Stout,American Stout,80,0.058,35 -Suzy B Dirty Blonde Ale,American Blonde Ale,133,0.05,20 -Sweet Action,Cream Ale,46,0.052,34 -Sweet As Pacific Ale,American Pale Wheat Ale,464,0.06,18 -Sweet Josie,American Brown Ale,149,0.061,30 -Sweet Potato Ale,Fruit / Vegetable Beer,314,0.06,24 -Sweet Yamma Jamma Ale,Fruit / Vegetable Beer,277,0.05,10 -Sympathy for the Lager,American Amber / Red Lager,125,0.049,45 -TailGate Hefeweizen,Hefeweizen,449,0.049,28 -TailGate IPA,American IPA,449,0.05,44 -Take Two Pils,German Pilsener,49,0.055,35 -Tallgrass Ale,American Brown Ale,45,0.044,22 -Tallgrass IPA,American IPA,45,0.063,60 -Tallgrass Pub Ale,American Brown Ale,45,0.044,12 -Ten Fidy,Russian Imperial Stout,389,0.099,98 -Terrace Hill Double IPA,American Double / Imperial IPA,350,0.095,99 -Texas Pale Ale (TPA),American IPA,257,0.055,40 -Texicali,American Brown Ale,66,0.065,33 -Thai Style White IPA,American White IPA,51,0.065,33 -Thai.p.a,American IPA,20,0.07,46 -The 12th Can™,American Pale Ale (APA),362,0.045,32 -The Brown Note,English Brown Ale,1,0.05,20 -The Corruption,American IPA,227,0.065,80 -The Crisp,German Pilsener,46,0.054,42 -The Gadget,American IPA,22,0.064,90 -The Golden One,American Pilsner,168,0.063,21 -The Great Pumpcan,Fruit / Vegetable Beer,91,0.079,18 -The Great Return,American IPA,343,0.075,70 -The Green Room,American IPA,126,0.06,75 -The Hole in Hadrian's Wall,Scottish Ale,471,0.095,19 -The Lawn Ranger,Cream Ale,29,0.05,18 -The Long Thaw White IPA,American White IPA,234,0.062,45 -The Power of Zeus,American Pale Ale (APA),168,0.07,68 -The Tradition,American Blonde Ale,227,0.05,15 -The Velvet Fog,Quadrupel (Quad),25,0.09,24 -Third Eye Enlightened Pale Ale,American Pale Ale (APA),119,0.065,65 -This Season's Blonde,American Blonde Ale,523,0.056,27 -Thrasher Session India Pale Ale,American IPA,283,0.045,44 -Three Kings Ale,Kölsch,131,0.049,13 -Three Skulls Ale Pale Ale,American Pale Ale (APA),450,0.063,42 -Thunder Ann,American Pale Ale (APA),312,0.055,37 -Tin Roof Blonde Ale,American Blonde Ale,152,0.045,18 -Tin Roof Watermelon Wheat,Fruit / Vegetable Beer,152,0.05,21 -Tiny Bomb,American Pilsner,239,0.045,23 -Tip Off,Altbier,25,0.052,29 -Toasted Lager,Vienna Lager,489,0.055,28 -Tocobaga Red Ale,American Amber / Red Ale,141,0.072,75 -Tonganoxie Honey Wheat,American Pale Wheat Ale,500,0.044,22 -Top Rope Mexican-style Craft Lager,Vienna Lager,51,0.048,15 -Topcutter India Pale Ale,American IPA,483,0.068,70 -Torpedo,American IPA,83,0.072,65 -Totally Radler,Radler,80,0.027,21 -Towhead,American Blonde Ale,188,0.052,21 -Trader Session IPA,American IPA,159,0.04,42 -Trail Head,American Pale Ale (APA),224,0.063,55 -Trailhead India Style Session Ale,American IPA,190,0.048,48 -Trailhead ISA,American IPA,190,0.048,48 -Train Hopper,American IPA,14,0.058,72 -Tribute,American Pale Ale (APA),23,0.058,58 -Troegenator Doublebock,Doppelbock,97,0.082,25 -Trolley Stop Stout,American Stout,374,0.057,40 -Troopers Alley IPA,American IPA,344,0.059,135 -Tropicalia,American IPA,247,0.065,65 -Truth,American IPA,93,0.072,75 -Tsunami IPA,American IPA,203,0.072,75 -Tule Duck Red Ale (Current),American Amber / Red Ale,530,0.062,42 -Twisted Helles Summer Lager,Munich Helles Lager,253,0.055,18 -Twisted X,American Adjunct Lager,38,0.051,19 -Twister Creek India Pale Ale,American IPA,453,0.065,71 -Two-One Niner,American Pilsner,218,0.048,9 -Tybee Island Blonde,American Blonde Ale,415,0.047,17 -UFO Gingerland,Herbed / Spiced Beer,234,0.052,15 -UFO Pumpkin,Pumpkin Ale,234,0.059,20 -UFO White,American Pale Wheat Ale,234,0.048,10 -Ultra Gnar Gnar IPA,American IPA,116,0.067,60 -Unchained #18 Hop Silo,American Double / Imperial IPA,58,0.083,100 -Underdog Atlantic Lager,American Pale Lager,521,0.047,28 -Union Jack,American IPA,48,0.075,75 -Universale Pale Ale,American Pale Ale (APA),460,0.056,30 -Upland Wheat Ale,Witbier,202,0.045,15 -Upslope Belgian Style Pale Ale,Belgian Pale Ale,51,0.075,30 -Upslope Craft Lager,Vienna Lager,51,0.048,22 -Upslope Imperial India Pale Ale,American Double / Imperial IPA,51,0.099,90 -Upstate I.P.W.,American IPA,546,0.065,70 -Valkyrie Double IPA,American Double / Imperial IPA,118,0.092,100 -Vanilla Bean Buffalo Sweat,Oatmeal Stout,45,0.05,20 -Vanilla Java Porter,American Porter,72,0.055,12 -Vanilla Porter,American Porter,39,0.07,11 -Vanilla Porter,American Porter,68,0.047,25 -Venture Pils,German Pilsener,75,0.05,38 -Vermont Pilsner,German Pilsener,41,0.048,20 -Vertex IPA,American IPA,57,0.063,76 -Veteran’s Pale Ale (VPA),American Pale Ale (APA),13,0.05,40 -Villager,American IPA,4,0.063,42 -Volcano Red Ale,American Amber / Red Ale,203,0.052,23 -Voodoo Bengal Pale Ale,American Pale Ale (APA),152,0.055,37 -Voodoo Love Child,Tripel,322,0.092,25 -Vortex IPA,American IPA,206,0.074,97 -Wachusett Blueberry Ale,Fruit / Vegetable Beer,295,0.045,10 -Wachusett IPA,American IPA,295,0.056,50 -Wachusett Light IPA,American IPA,295,0.04,37 -Wagon Box Wheat Beer,American Pale Wheat Ale,457,0.059,15 -Wagon Party,California Common / Steam Beer,258,0.054,55 -Wall's End,English Brown Ale,0,0.048,19 -Wandering Pelican,American Black Ale,141,0.082,65 -Washita Wheat,American Pale Wheat Ale,366,0.053,14 -Watermelon Ale,Fruit / Vegetable Beer,103,0.05,10 -Watermelon Ale,Fruit / Vegetable Beer,108,0.051,11 -Watershed IPA,American IPA,150,0.067,70 -Watership Brown Ale,American Brown Ale,475,0.072,55 -Wavemaker,American Amber / Red Ale,221,0.058,38 -Wee Mac Scottish-Style Ale,Scottish Ale,25,0.054,23 -Wee Muckle,Scotch Ale / Wee Heavy,25,0.09,30 -Wee-Heavy-Er Scotch Ale,Scotch Ale / Wee Heavy,433,0.07,24 -Weekend Warrior Pale Ale,American Pale Ale (APA),125,0.055,40 -Weiss Trash Culture,Berliner Weissbier,410,0.034,6 -Weisse Versa,Hefeweizen,125,0.052,15 -Weissenheimer,Hefeweizen,57,0.052,16 -Weize Guy,Hefeweizen,233,0.05,15 -Westbrook Gose,Gose,384,0.04,5 -Westbrook IPA,American IPA,384,0.068,65 -Westfalia,American Amber / Red Ale,4,0.056,16 -Westfield Octoberfest,Märzen / Oktoberfest,351,0.057,22 -Wet Hot American Wheat Ale,American Pale Wheat Ale,256,0.05,22 -WET,American IPA,61,0.075,90 -What the Butler Saw,Witbier,217,0.05,18 -Wheat the People,American Pale Wheat Ale,418,0.044,13 -When Helles Freezes Over,Munich Helles Lager,326,0.056,18 -Whip Fight,Scotch Ale / Wee Heavy,25,0.09,30 -White,Witbier,128,0.046,25 -White Magick of the Sun,Witbier,322,0.079,23 -White Rabbit,Witbier,140,0.059,27 -White Rascal,Witbier,37,0.056,10 -White Reaper,Belgian IPA,26,0.07,61 -White Thai,Witbier,384,0.05,16 -Whitecap Wit,Witbier,285,0.048,16 -Whitsun,American Pale Wheat Ale,541,0.062,17 -Wick For Brains,Pumpkin Ale,337,0.061,11 -Widespread Wit,Witbier,365,0.055,10 -Widmer Brothers Hefeweizen,Hefeweizen,296,0.049,30 -Wild Night,Cream Ale,447,0.059,18 -Wild Onion Summer Wit,Witbier,361,0.042,13 -Wild Plum Farmhouse Ale,Saison / Farmhouse Ale,45,0.056,20 -Wild Trail Pale Ale,American Pale Ale (APA),156,0.057,44 -Wild Wolf American Pilsner,American Pilsner,181,0.045,25 -Wild Wolf Wee Heavy Scottish Style Ale,Scotch Ale / Wee Heavy,181,0.057,20 -Winter Games Select #32 Stout,American Stout,471,0.057,26 -Winter Solstice,Winter Warmer,171,0.069,6 -Winter Warmer (Vault Series),Winter Warmer,30,0.095,25 -Winterfest,American Strong Ale,309,0.084,90 -Wisco Disco,American Amber / Red Ale,486,0.051,31 -Wisconsin Amber,Vienna Lager,192,0.052,28 -Wobble,American IPA,74,0.063,69 -Wolf Among Weeds IPA,American IPA,240,0.08,70 -Wolverine Premium Lager,American Pale Lager,402,0.047,15 -Wonderstuff,German Pilsener,258,0.054,48 -Wood Chipper India Pale Ale,American IPA,335,0.067,70 -Wood Splitter Pilsner,Czech Pilsener,107,0.048,30 -Wooden Rooster,Tripel,45,0.085,34 -Woolybugger Wheat,American Pale Wheat Ale,309,0.046,12 -Worthy IPA,American IPA,199,0.069,69 -Worthy Pale,American Pale Ale (APA),199,0.06,50 -WYLD Extra Pale Ale,American Pale Ale (APA),159,0.04,29 -Wynona's Big Brown Ale,American Brown Ale,322,0.075,31 -Yard Sale Winter Lager,American Amber / Red Lager,159,0.04,22 -Yellow Wolf Imperial IPA,American Double / Imperial IPA,117,0.082,103 -Yeti Imperial Stout,Russian Imperial Stout,6,0.095,75 -Yo Soy Un Berliner,Berliner Weissbier,66,0.044,5 -Yoshi's Nectar,California Common / Steam Beer,21,0.053,27 -Zen,American Pale Ale (APA),93,0.043,45 -Zombie Monkie,American Porter,45,0.062,35 -Zonker Stout,Foreign / Export Stout,191,0.054,36 \ No newline at end of file diff --git a/test/format/data/cols-only.json b/test/format/data/cols-only.json deleted file mode 100644 index 6a0b690c..00000000 --- a/test/format/data/cols-only.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a": [1, 2, 3], - "b": ["a", "b", "c"], - "c": [1.2, 3.4, 5.6] -} \ No newline at end of file diff --git a/test/format/data/cols-schema.json b/test/format/data/cols-schema.json deleted file mode 100644 index 46648656..00000000 --- a/test/format/data/cols-schema.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "schema": { - "fields": [ - { "name": "a" }, - { "name": "b" }, - { "name": "c" } - ] - }, - "data": { - "a": [1, 2, 3], - "b": ["a", "b", "c"], - "c": [1.2, 3.4, 5.6] - } -} \ No newline at end of file diff --git a/test/format/data/flights.arrow b/test/format/data/flights.arrow deleted file mode 100644 index d3942e9cecb70f1267e3266a074a377169f0933f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120504 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 z zBk$dvR_O-;X%|qRRg7|eDw<7*)`&?4T zz{__6bo>`Np6 z;YC6GIL6H+e(b!O>`QG=;?u@S{MvXf@!vRJPyE&x-y9@$OKis{CHu%ti65H3X4c!p z2dy8sHQA4PoSN)MZBOEV#&gL&)p__|>}|s`*NCq|bq9 zvVS$k&sUM}O`V_hKJjhitn-OaTL*6~*^fp&y}sTjzHH3*1QI_kh#wnIB|aSUtNNbo zJ0nj|{MNW8err2Ex{B-%ozK3oAbx6$UtT1BXFq;d5PvhS$-Xf1RmAsfpGy49@%Y$9 z;&1l1BEA;KtLks!XSUa5Uub(0e=}Z7{LMW0+aU3=IPM7XG5fb-*8fc6e;%(UzGplB zwutO6orixlC4OeUn)sOQ_}E3_Q!&3MK4pwASvRj0@gwu$M;D1N*}selxDg{bzfU`z6Np(YHk3 z65SR=zY(8KGV5rv&KcK4htYoXh2(o@iM|T5Z#L$-7v#G4buz!**TpqTxt&Zl{koKNGa{+?(a)>+B*&ANn}5WSceJ#0#JV}5iqNY+u?(aqO_=rBHKRfm)RpP^{y z`NtT&E+T&Fyd=JAj9wQJojPwV*&jxpzK$k(HBO@2tcSP>`Foamc`lOZxa7pTndm;| zWgX4OkBwPZCAy7xq`oG)jd4BEukli%tC-()65XsKx^X_bk*voNSM@W|)1r3PW9Q+6 z5?yUd^kshZm30uC5SwY)a~}GU=*N15Z<_d+p98%~bT*6lljG4E4#E1QyJU16ax5llAzRfpAbZ`GPavwNuD$%#&(e)tF zx$SF-{>_8#gH!8n_VqQKIc5vM~S~luKOlrJ&nBGtz^Gg5WQGOaWA49kB1KxL`TMRiO)C> zKPiZQjFafdcrEc6^VCFlwxhdEiT=!w{tB|cGftwj=wC>5>3n=jqR&l;?yQTrHQC>J z9P}5QS&x(boAc1wAkmraNpxnsmgp?_Xo(P7qM+?43i{OD2QhXwJ$=--Iw+5Aa-)OeKVC-$S~b?iFl zqg#nyXA!+Q9(_sl)r73a=E3g@qC4Zo?CW}>JM$#bnROdm5j~j?Jq54S=R{wF)w?3TIB2Nck=gzBIY^OIEh}2$B2&X--_tX{7H0Yyp%lOIv?F#BtB?=5*->Z zW?v5!9hwKvvW`Tau8a1@bwgQy%v%$G zupM7`qa3Rr%KNK+t|agE8LuVp0h%X?zZfqi>!9;_f3+ZfWX$`0O~}5^JXtR^iND!C zM(Q-#&-W;U|j>q?`Q}=Ztei?Q7Ce{nZS7(iWbozwr@@R(&ONh zn-E_#ZxY`#UQ7JXJW1XQHcqmyj^p&?Jz?Y4#2?Med-?KB)f>f+J$@2@H0Jw@g7{*5 z&Yt+9@lvvXb3XgF$kY8;;)}D0A38pXFB;>A5C2k__4>ueN&HgckE@7JM!uDGMe$GbOl4h%6hCx6epry_ePjHwDe==d z&a;msep(PeHEzwUGn&LdJr4dU@yoY@?0@|n?0Y4?+LZXF`Pj$Can3)wNWNFJAAjA2 zd>`O(*#8ca`c}5%&y&c$*KzE73*yJdQ;F|79-l3U-x{|j-xrv7jQDUIx33e*zP5<* z-_^tEs+d<;DS(k59vOhIH-xo;ywkhj+qWGuzml7X!eiGj_o=g1DdF(GGKG=l# zoO$rMro^wzmtWt`^JL!oN*GrGhRxbPr1+#U{>pY*u+ zq-1|u5Z^RzP4=&mcV+!j{MJ19 values.forEach(f), options); - const out = values.map(v => formatValue(v, format)); - t.deepEqual(out, strings, `formats [${strings.join(', ')}]`); -} - -const loc = (y, m, d, H, M, S, u) => - new Date(y, m - 1, d, H || 0, M || 0, S || 0, u || 0); - -const utc = (y, m, d, H, M, S, u) => - new Date(Date.UTC(y, m - 1, d, H || 0, M || 0, S || 0, u || 0)); - -tape('formatValue formats invalid values', t => { - formatsAs(t, [NaN], ['NaN']); - formatsAs(t, [null], ['null']); - formatsAs(t, [undefined], ['undefined']); - t.end(); -}); - -tape('formatValue formats boolean values', t => { - formatsAs(t, [true], ['true']); - formatsAs(t, [false], ['false']); - formatsAs(t, [true, false, null], ['true', 'false', 'null']); - t.end(); -}); - -tape('formatValue formats number values', t => { - // integers - formatsAs(t, [0], ['0']); - formatsAs(t, [-0], ['0']); - formatsAs(t, [1], ['1']); - formatsAs(t, [-1], ['-1']); - - // decimals - formatsAs(t, [Math.E], ['2.718282']); - formatsAs(t, [3.14], ['3.14']); - formatsAs(t, [1/3], ['0.3333'], { maxdigits: 4 }); - formatsAs(t, [1/3], ['0.333333'], { maxdigits: 6 }); - formatsAs(t, [1/3], ['0.33333333'], { maxdigits: 8 }); - formatsAs(t, [-1/3], ['-0.3333'], { maxdigits: 4 }); - formatsAs(t, [-1/3], ['-0.333333'], { maxdigits: 6 }); - formatsAs(t, [-1/3], ['-0.33333333'], { maxdigits: 8 }); - - // fixed -> exponential - formatsAs(t, [0.1], ['0.1'], { maxdigits: 4 }); - formatsAs(t, [0.01], ['0.01'], { maxdigits: 4 }); - formatsAs(t, [0.001], ['0.001'], { maxdigits: 4 }); - formatsAs(t, [0.0001], ['0.0001'], { maxdigits: 4 }); - formatsAs(t, [0.00001], ['1.0000e-5'], { maxdigits: 4 }); - formatsAs(t, [0.000001], ['1.0000e-6'], { maxdigits: 4 }); - formatsAs(t, [1e30], ['1e+30']); - formatsAs(t, [-1e30], ['-1e+30']); - formatsAs(t, [1.23e-18], ['1.23e-18']); - formatsAs(t, [-1.23e-18], ['-1.23e-18']); - - // grouped inference - formatsAs(t, [0, 1, 2, 3], ['0', '1', '2', '3']); - formatsAs(t, - [3.14, null, NaN, 2.71828], - ['3.14000', 'null', 'NaN', '2.71828'] - ); - formatsAs(t, - [-4/3, -1, -2/3, 1/3, 1, 4/3, 5/3], - ['-1.333', '-1.000', '-0.667', '0.333', '1.000', '1.333', '1.667'], - { maxdigits: 3} - ); - formatsAs(t, - [-1.23e-18, 9.87654321e24, 1], - ['-1.230000e-18', '9.876543e+24', '1.000000'] - ); - - t.end(); -}); - -tape('formatValue formats date values', t => { - formatsAs(t, [utc(2000, 1, 1)], ['2000-01-01T00:00:00.000Z']); - formatsAs(t, - [utc(2000, 1, 1), utc(2001, 3, 14)], - ['2000-01-01T00:00:00.000Z', '2001-03-14T00:00:00.000Z'] - ); - - formatsAs(t, [loc(2000, 1, 1)], ['2000-01-01T00:00:00.000']); - formatsAs(t, [loc(2005, 2, 3, 7, 11)], ['2005-02-03T07:11:00.000']); - formatsAs(t, [loc(2005, 2, 3, 7, 11, 0, 5)], ['2005-02-03T07:11:00.005']); - formatsAs(t, - [loc(2000, 1, 1), loc(2001, 3, 14)], - ['2000-01-01T00:00:00.000', '2001-03-14T00:00:00.000'] - ); - formatsAs(t, - [loc(2000, 1, 1), loc(2001, 3, 14), loc(2005, 2, 3, 7, 11)], - ['2000-01-01T00:00:00.000', '2001-03-14T00:00:00.000', '2005-02-03T07:11:00.000'] - ); - - formatsAs(t, - [loc(2000, 1, 1), utc(2001, 3, 14)], - ['2000-01-01T00:00:00.000', '2001-03-13T16:00:00.000'] - ); - formatsAs(t, - [loc(2000, 1, 1), loc(2001, 3, 14), utc(2005, 2, 3, 7, 11)], - ['2000-01-01T00:00:00.000', '2001-03-14T00:00:00.000', '2005-02-02T23:11:00.000'] - ); - - t.end(); -}); - -tape('formatValue formats array values', t => { - formatsAs(t, [[1, 2, 3]], ['[1,2,3]']); - formatsAs(t, [Int32Array.of(1, 2, 3)], ['[1,2,3]']); - formatsAs(t, [Float32Array.of(1, 2, 3)], ['[1,2,3]']); - - formatsAs(t, [['foo']], ['["foo"]']); - formatsAs(t, - [['foo boo goo woo soo loo roo']], - ['["foo boo goo woo soo loo ro…]'] - ); - - t.end(); -}); - -tape('formatValue formats object values', t => { - formatsAs(t, [{a:1}], ['{"a":1}']); - formatsAs(t, [{a:1}], ['{"a":1}']); - formatsAs(t, [{a: Int32Array.of(1, 2, 3)}], ['{"a":[1,2,3]}']); - formatsAs(t, - [{key: 'value', 'another key': 'another vlaue'}], - ['{"key":"value","another key"…}'] - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/from-arrow-test.js b/test/format/from-arrow-test.js deleted file mode 100644 index 21d910e0..00000000 --- a/test/format/from-arrow-test.js +++ /dev/null @@ -1,150 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import fromArrow from '../../src/format/from-arrow'; -import { not } from '../../src/helpers/selection'; -import { table } from '../../src'; -import { Type, Utf8 } from 'apache-arrow'; - -function arrowTable(data, types) { - return table(data).toArrow({ types }); -} - -tape('fromArrow imports Apache Arrow tables', t => { - const data = { - u: [1, 2, 3, 4, 5], - v: ['a', 'b', null, 'd', 'e'] - }; - const at = arrowTable(data); - - tableEqual(t, fromArrow(at), data, 'arrow data'); - t.end(); -}); - -tape('fromArrow can unpack Apache Arrow tables', t => { - const data = { - u: [1, 2, 3, 4, 5], - v: ['a', 'b', null, 'd', 'e'], - x: ['cc', 'dd', 'cc', 'dd', 'cc'], - y: ['aa', 'aa', null, 'bb', 'bb'] - }; - const at = arrowTable(data, { v: new Utf8() }); - const dt = fromArrow(at); - - tableEqual(t, dt, data, 'arrow data'); - t.ok(dt.column('x').keyFor, 'create dictionary column without nulls'); - t.ok(dt.column('y').keyFor, 'create dictionary column with nulls'); - t.end(); -}); - -tape('fromArrow can select Apache Arrow columns', t => { - const data = { - u: [1, 2, 3, 4, 5], - v: ['a', 'b', null, 'd', 'e'], - x: ['cc', 'dd', 'cc', 'dd', 'cc'], - y: ['aa', 'aa', null, 'bb', 'bb'] - }; - const at = arrowTable(data); - - const s1 = fromArrow(at, { columns: 'x' }); - t.deepEqual(s1.columnNames(), ['x'], 'select by column name'); - tableEqual(t, s1, { x: data.x }, 'correct columns selected'); - - const s2 = fromArrow(at, { columns: ['u', 'y'] }); - t.deepEqual(s2.columnNames(), ['u', 'y'], 'select by column names'); - tableEqual(t, s2, { u: data.u, y: data.y }, 'correct columns selected'); - - const s3 = fromArrow(at, { columns: not('u', 'y') }); - t.deepEqual(s3.columnNames(), ['v', 'x'], 'select by helper'); - tableEqual(t, s3, { v: data.v, x: data.x }, 'correct columns selected'); - - const s4 = fromArrow(at, { columns: { u: 'a', x: 'b'} }); - t.deepEqual(s4.columnNames(), ['a', 'b'], 'select by helper'); - tableEqual(t, s4, { a: data.u, b: data.x }, 'correct columns selected'); - - t.end(); -}); - -tape('fromArrow can read Apache Arrow lists', t => { - const l = [[1, 2, 3], null, [4, 5]]; - const at = arrowTable({ l }); - - if (at.getColumn('l').typeId !== Type.List) { - t.fail('Arrow column should have List type'); - } - tableEqual(t, fromArrow(at), { l }, 'extract Arrow list'); - t.end(); -}); - -tape('fromArrow can read Apache Arrow fixed-size lists', t => { - const l = [[1, 2], null, [4, 5]]; - const at = arrowTable({ l }); - - if (at.getColumn('l').typeId !== Type.FixedSizeList) { - t.fail('Arrow column should have FixedSizeList type'); - } - tableEqual(t, fromArrow(at), { l }, 'extract Arrow list'); - t.end(); -}); - -tape('fromArrow can read Apache Arrow structs', t => { - const s = [{ foo: 1, bar: [2, 3] }, null, { foo: 2, bar: [4] }]; - const at = arrowTable({ s }); - - if (at.getColumn('s').typeId !== Type.Struct) { - t.fail('Arrow column should have Struct type'); - } - tableEqual(t, fromArrow(at), { s }, 'extract Arrow struct'); - - t.end(); -}); - -tape('fromArrow can read nested Apache Arrow structs', t => { - const s = [{ foo: 1, bar: { bop: 2 } }, { foo: 2, bar: { bop: 3 } }]; - const at = arrowTable({ s }); - - if (at.getColumn('s').typeId !== Type.Struct) { - t.fail('Arrow column should have Struct type'); - } - tableEqual(t, fromArrow(at), { s }, 'extract nested Arrow struct'); - - t.end(); -}); - -tape('fromArrow can read filtered Apache Arrow tables', t => { - const data = { - n: [1, null, null, 4, 5], - u: Int8Array.of(1, 2, 3, 4, 5), - v: Float64Array.of(1.2, 2.3, 3.4, 4.5, 5.6), - w: ['a', 'b', null, 'd', 'e'], - x: ['cc', 'dd', 'cc', 'dd', 'cc'], - y: ['aa', 'aa', null, 'bb', 'bb'] - }; - const at = arrowTable(data, { w: new Utf8() }); - - const valid = [1, 1, 0, 0, 1]; - const ft = at.filter(idx => valid[idx]); - const dt = fromArrow(ft); - const rt = dt.reify(); - - t.equal(dt.totalRows(), 5, 'filtered table total rows'); - t.equal(dt.numRows(), 3, 'filtered table active rows'); - t.deepEqual( - [...dt], - [ - { n: 1, u: 1, v: 1.2, w: 'a', x: 'cc', y: 'aa' }, - { n: null, u: 2, v: 2.3, w: 'b', x: 'dd', y: 'aa' }, - { n: 5, u: 5, v: 5.6, w: 'e', x: 'cc', y: 'bb' } - ], - 'extract filtered Arrow data' - ); - tableEqual(t, rt, { - n: [1, null, 5], - u: [1, 2, 5], - v: [1.2, 2.3, 5.6], - w: ['a', 'b', 'e'], - x: ['cc', 'dd', 'cc'], - y: ['aa', 'aa', 'bb'] - }, 'extract reified filtered Arrow data'); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/from-csv-test.js b/test/format/from-csv-test.js deleted file mode 100644 index cecf9eff..00000000 --- a/test/format/from-csv-test.js +++ /dev/null @@ -1,124 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import fromCSV from '../../src/format/from-csv'; - -function data() { - return { - str: ['a', 'b', 'c'], - int: [1, 2, 3], - num: [12.3, 45.6, 78.9], - bool: [true, null, false], - date: [new Date('2010-01-01'), new Date('2015-04-05'), new Date('2020-02-29')] - }; -} - -const text = [ - 'str,int,num,bool,date', - 'a,1,12.3,true,2010-01-01', - 'b,2,45.6,,2015-04-05', - 'c,3,78.9,false,2020-02-29' -]; - -const tabText = text.map(t => t.split(',').join('\t')); - -tape('fromCSV parses delimited text', t => { - const table = fromCSV(text.join('\n')); - t.equal(table.numRows(), 3, 'num rows'); - t.equal(table.numCols(), 5, 'num cols'); - tableEqual(t, table, data(), 'csv parsed data'); - t.end(); -}); - -tape('fromCSV infers types', t => { - function check(msg, values, test) { - const d = fromCSV('col\n' + values.join('\n')).array('col'); - t.ok(d.every(v => v == null || test(v)), msg); - } - - check('boolean', [true, false, '', true], v => typeof v === 'boolean'); - check('number', [1, Math.PI, '', 'NaN'], v => typeof v === 'number'); - check('string', ['a', 1, '', 'c'], v => typeof v === 'string'); - check('date', [ - new Date().toISOString(), '', - new Date(2000, 0, 1).toISOString(), - new Date(1979, 3, 14, 3, 45).toISOString() - ], v => v instanceof Date); - t.end(); -}); - -tape('fromCSV parses delimited text with delimiter', t => { - const table = fromCSV(tabText.join('\n'), { delimiter: '\t' }); - t.equal(table.numRows(), 3, 'num rows'); - t.equal(table.numCols(), 5, 'num cols'); - tableEqual(t, table, data(), 'csv parsed data with delimiter'); - t.end(); -}); - -tape('fromCSV parses delimited text with header option', t => { - const table = fromCSV(text.slice(1).join('\n'), { header: false }); - const cols = data(); - const d = { - col1: cols.str, - col2: cols.int, - col3: cols.num, - col4: cols.bool, - col5: cols.date - }; - tableEqual(t, table, d, 'csv parsed data with no header'); - t.end(); -}); - -tape('fromCSV parses delimited text with parse option', t => { - const table = fromCSV(text.join('\n'), { parse: { str: d => d + d } }); - const d = { ...data(), str: ['aa', 'bb', 'cc'] }; - tableEqual(t, table, d, 'csv parsed data with custom parse'); - t.end(); -}); - -tape('fromCSV parses delimited text with decimal option', t => { - tableEqual(t, - fromCSV('a;b\nu;-1,23\nv;4,56e5\nw;', { delimiter: ';', decimal: ',' }), - { a: ['u', 'v', 'w'], b: [-1.23, 4.56e5, null] }, - 'csv parsed data with decimal option' - ); - t.end(); -}); - -tape('fromCSV parses delimited text with skip options', t => { - const text = '# line 1\n# line 2\na,b\n1,2\n3,4'; - const data = { a: [1, 3], b: [2, 4] }; - - tableEqual(t, fromCSV(text, { skip: 2 }), data, - 'csv parsed data with skip option' - ); - - tableEqual(t, fromCSV(text, { comment: '#' }), data, - 'csv parsed data with comment option' - ); - - tableEqual(t, fromCSV(text, { skip: 1, comment: '#' }), data, - 'csv parsed data with skip and comment options' - ); - - t.end(); -}); - -tape('fromCSV applies parsers regardless of autoType flag', t => { - const text = 'a,b\r\n00152,01/01/2021\r\n30219,01/01/2021'; - const table = autoType => fromCSV(text, { - autoType, - parse: { - a: v => v, - b: v => v.split('/').reverse().join('-') - } - }); - const data = { - a: ['00152', '30219'], - b: ['2021-01-01', '2021-01-01'] - }; - - tableEqual(t, table(true), data, 'csv parsed data with autoType true'); - tableEqual(t, table(false), data, 'csv parsed data with autoType false'); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/from-fixed-test.js b/test/format/from-fixed-test.js deleted file mode 100644 index 89b120e1..00000000 --- a/test/format/from-fixed-test.js +++ /dev/null @@ -1,108 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import fromFixed from '../../src/format/from-fixed'; - -function data() { - return { - str: ['a', 'b', 'c'], - int: [1, 2, 3], - num: [12.3, 45.6, 78.9], - bool: [true, null, false], - date: [new Date('2010-01-01'), new Date('2015-04-05'), new Date('2020-02-29')] - }; -} - -const names = [ 'str', 'int', 'num', 'bool', 'date' ]; -const widths = [1, 1, 4, 5, 10]; -const positions = [[0, 1], [1, 2], [2, 6], [6, 11], [11, 21]]; -const text = [ - 'a112.3true 2010-01-01', - 'b245.6 2015-04-05', - 'c378.9false2020-02-29' -]; - -tape('fromFixed parses fixed width files using positions', t => { - const table = fromFixed(text.join('\n'), { names, positions }); - t.equal(table.numRows(), 3, 'num rows'); - t.equal(table.numCols(), 5, 'num cols'); - tableEqual(t, table, data(), 'fixed-width parsed data'); - t.end(); -}); - -tape('fromFixed parses fixed width files using widths', t => { - const table = fromFixed(text.join('\n'), { names, widths }); - t.equal(table.numRows(), 3, 'num rows'); - t.equal(table.numCols(), 5, 'num cols'); - tableEqual(t, table, data(), 'fixed-width parsed data'); - t.end(); -}); - -tape('fromFixed infers types', t => { - function check(msg, widths, values, test) { - const d = fromFixed(values.join('\n'), { widths }).array('col1'); - t.ok(d.every(v => v == null || test(v)), msg); - } - - check('boolean', [5], - ['true ', 'false', ' ', 'true '], - v => typeof v === 'boolean' - ); - check('number', [3], - ['1 ', '3.14', ' ', 'NaN'], - v => typeof v === 'number' - ); - check('string', [1], - ['a', '1', ' ', 'c'], - v => typeof v === 'string' - ); - check('date', [24], - [ - new Date().toISOString(), - ' ', - new Date(2000, 0, 1).toISOString(), - new Date(1979, 3, 14, 3, 45).toISOString() - ], - v => v instanceof Date - ); - t.end(); -}); - -tape('fromFixed parses text with parse option', t => { - const table = fromFixed(text.join('\n'), { names, widths, parse: { str: d => d + d } }); - const d = { ...data(), str: ['aa', 'bb', 'cc'] }; - tableEqual(t, table, d, 'fixed-width parsed data with custom parse'); - t.end(); -}); - -tape('fromFixed parses text with decimal option', t => { - tableEqual(t, - fromFixed( - 'u -1,23\nv4,56e5\nw', - { decimal: ',', widths: [1, 6], names: ['a', 'b'] } - ), - { a: ['u', 'v', 'w'], b: [-1.23, 4.56e5, null] }, - 'fixed-width parsed data with decimal option' - ); - t.end(); -}); - -tape('fromFixed parses text with skip options', t => { - const text = '# line 1\n# line 2\n12\n34'; - const data = { a: [1, 3], b: [2, 4] }; - const names = ['a', 'b']; - const widths = [1, 1]; - - tableEqual(t, fromFixed(text, { names, widths, skip: 2 }), data, - 'fixed-width parsed data with skip option' - ); - - tableEqual(t, fromFixed(text, { names, widths, comment: '#' }), data, - 'fixed-width parsed data with comment option' - ); - - tableEqual(t, fromFixed(text, { names, widths, skip: 1, comment: '#' }), data, - 'fixed-width parsed data with skip and comment options' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/from-json-test.js b/test/format/from-json-test.js deleted file mode 100644 index cc850bd6..00000000 --- a/test/format/from-json-test.js +++ /dev/null @@ -1,152 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import ColumnTable from '../../src/table/column-table'; -import fromJSON from '../../src/format/from-json'; -import toJSON from '../../src/format/to-json'; - -function data() { - return { - str: ['a', 'b', 'c'], - int: [1, 2, 3], - num: [12.3, 45.6, 78.9], - bool: [true, null, false], - date: [new Date('2010-01-01'), new Date('2015-04-05'), new Date('2020-02-29')] - }; -} - -function cols() { - return Object.keys(data()); -} - -const text = '{' - + '"str":["a","b","c"],' - + '"int":[1,2,3],' - + '"num":[12.3,45.6,78.9],' - + '"bool":[true,null,false],' - + '"date":["2010-01-01","2015-04-05","2020-02-29"]' - + '}'; - -function schema(names, text) { - return '{"schema":{"fields":' - + JSON.stringify(names.map(name => ({ name }))) - + '},"data":' + text + '}'; -} - -function wrap(text) { - return '{"data":' + text + '}'; -} - -tape('toJSON formats JSON text with schema', t => { - const dt = new ColumnTable(data()); - t.equal(toJSON(dt), schema(cols(), text), 'json text'); - const names = ['str', 'int']; - t.equal( - toJSON(dt, { limit: 2, columns: names }), - schema(names, '{"str":["a","b"],"int":[1,2]}'), - 'json text with limit' - ); - t.end(); -}); - -tape('toJSON formats JSON text with format option with schema', t => { - const dt = new ColumnTable(data()); - const names = ['str']; - t.equal( - toJSON(dt, { limit: 2, columns: names, format: { str: d => d + '!' } }), - schema(names, '{"str":["a!","b!"]}'), - 'json text with custom format' - ); - t.end(); -}); - -tape('toJSON formats JSON text without schema', t => { - const dt = new ColumnTable(data()); - t.equal(toJSON(dt, { schema: false }), text, 'json text'); - t.equal( - toJSON(dt, { limit: 2, columns: ['str', 'int'], schema: false }), - '{"str":["a","b"],"int":[1,2]}', - 'json text with limit' - ); - t.end(); -}); - -tape('toJSON formats JSON text with format option without schema', t => { - const dt = new ColumnTable(data()); - t.equal( - toJSON(dt, { - schema: false, - limit: 2, - columns: ['str'], - format: { str: d => d + '!' } - }), - '{"str":["a!","b!"]}', - 'json text with custom format' - ); - t.end(); -}); - -tape('fromJSON parses JSON text with schema', t => { - const table = fromJSON(schema(cols(), text)); - tableEqual(t, table, data(), 'json parsed data'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses JSON text with parse option with schema', t => { - const table = fromJSON(schema(cols(), text), { parse: { str: d => d + d } }); - const d = { ...data(), str: ['aa', 'bb', 'cc'] }; - tableEqual(t, table, d, 'json parsed data with custom parse'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses JSON text without schema', t => { - const table = fromJSON(wrap(text)); - tableEqual(t, table, data(), 'json parsed data'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses JSON text with parse option without schema', t => { - const table = fromJSON(wrap(text), { parse: { str: d => d + d } }); - const d = { ...data(), str: ['aa', 'bb', 'cc'] }; - tableEqual(t, table, d, 'json parsed data with custom parse'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses JSON text as data only', t => { - const table = fromJSON(text); - tableEqual(t, table, data(), 'json parsed data'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses JSON text with parse option as data only', t => { - const table = fromJSON(text, { parse: { str: d => d + d } }); - const d = { ...data(), str: ['aa', 'bb', 'cc'] }; - tableEqual(t, table, d, 'json parsed data with custom parse'); - t.deepEqual(table.columnNames(), cols(), 'column names'); - t.end(); -}); - -tape('fromJSON parses ISO date strings', t => { - const values = [ - 0, '', '2.1', '2000', - new Date(Date.UTC(2000, 0, 1)), - new Date(Date.UTC(2000, 0, 1)), - new Date(2021, 0, 6, 12), - new Date(2021, 0, 6, 4) - ]; - const str = [ - 0, '', '2.1', '2000', - '2000-01', - '2000-01-01', - '2021-01-06T12:00:00.000', - '2021-01-06T12:00:00.000Z' - ]; - const json = '{"v":' + JSON.stringify(str) + '}'; - const table = fromJSON(json); - t.deepEqual(table.column('v').data, values, 'column values'); - t.end(); -}); \ No newline at end of file diff --git a/test/format/load-file-test.js b/test/format/load-file-test.js deleted file mode 100644 index 720b0ae1..00000000 --- a/test/format/load-file-test.js +++ /dev/null @@ -1,59 +0,0 @@ -import tape from 'tape'; -import { load, loadArrow, loadCSV, loadJSON } from '../../src/format/load-file'; - -const PATH = 'test/format/data'; - -tape('load loads a file using a relative path', async t => { - const dt = await load(`${PATH}/beers.csv`); - t.deepEqual([dt.numRows(), dt.numCols()], [1203, 5], 'load table'); - t.end(); -}); - -tape('load loads a file using a file protocol url', async t => { - const dt = await load(`file://${process.cwd()}/${PATH}/beers.csv`); - t.deepEqual([dt.numRows(), dt.numCols()], [1203, 5], 'load table'); - t.end(); -}); - -tape('loadCSV loads CSV files from disk', async t => { - const dt = await loadCSV(`${PATH}/beers.csv`); - t.deepEqual([dt.numRows(), dt.numCols()], [1203, 5], 'load csv table'); - t.end(); -}); - -tape('loadJSON loads JSON files from disk', async t => { - const rt = await loadJSON(`${PATH}/rows.json`); - t.deepEqual([rt.numRows(), rt.numCols()], [3, 3], 'load json rows'); - - const st = await loadJSON(`${PATH}/cols-schema.json`); - t.deepEqual([st.numRows(), st.numCols()], [3, 3], 'load json cols with schema'); - - const ct = await loadJSON(`${PATH}/cols-only.json`); - t.deepEqual([ct.numRows(), ct.numCols()], [3, 3], 'load json cols no schema'); - - t.end(); -}); - -tape('loadArrow loads Arrow files from disk', async t => { - const dt = await loadArrow(`${PATH}/flights.arrow`); - t.deepEqual([dt.numRows(), dt.numCols()], [9999, 3], 'load arrow table'); - t.end(); -}); - -tape('load fails on non-existent path', t => { - load('/foo/bar/baz/does.not.exist') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadJSON fails on non-JSON file', t => { - loadJSON(`${PATH}/beers.csv`) - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadArrow fails on non-Arrow file', t => { - loadArrow(`${PATH}/beers.csv`) - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); \ No newline at end of file diff --git a/test/format/load-file-url-test.js b/test/format/load-file-url-test.js deleted file mode 100644 index 84951ea4..00000000 --- a/test/format/load-file-url-test.js +++ /dev/null @@ -1,55 +0,0 @@ -import tape from 'tape'; -import { load, loadArrow, loadCSV, loadJSON } from '../../src/format/load-file'; - -tape('load loads from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/airports.csv'; - const dt = await load(url); - t.deepEqual([dt.numRows(), dt.numCols()], [3376, 7], 'load table'); - t.end(); -}); - -tape('loadCSV loads CSV files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/airports.csv'; - const dt = await loadCSV(url); - t.deepEqual([dt.numRows(), dt.numCols()], [3376, 7], 'load csv table'); - t.end(); -}); - -tape('loadJSON loads JSON files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/budgets.json'; - const rt = await loadJSON(url); - t.deepEqual([rt.numRows(), rt.numCols()], [230, 3], 'load json rows'); - - t.end(); -}); - -tape('loadArrow loads Arrow files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/flights-200k.arrow'; - const dt = await loadArrow(url); - t.deepEqual([dt.numRows(), dt.numCols()], [231083, 3], 'load arrow table'); - t.end(); -}); - -tape('load fails on non-existent path', t => { - load('https://foo.bar.baz/does.not.exist') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('load fails on invalid protocol', t => { - load('htsp://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadJSON fails on non-JSON file URL', t => { - loadJSON('https://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadArrow fails on non-Arrow file URL', t => { - loadArrow('https://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); \ No newline at end of file diff --git a/test/format/load-url-test.js b/test/format/load-url-test.js deleted file mode 100644 index 51a3bb7a..00000000 --- a/test/format/load-url-test.js +++ /dev/null @@ -1,57 +0,0 @@ -import tape from 'tape'; -import { load, loadArrow, loadCSV, loadJSON } from '../../src/format/load-url'; - -// add global fetch to emulate DOM environment -global.fetch = require('node-fetch'); - -tape('load loads from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/airports.csv'; - const dt = await load(url); - t.deepEqual([dt.numRows(), dt.numCols()], [3376, 7], 'load table'); - t.end(); -}); - -tape('loadCSV loads CSV files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/airports.csv'; - const dt = await loadCSV(url); - t.deepEqual([dt.numRows(), dt.numCols()], [3376, 7], 'load csv table'); - t.end(); -}); - -tape('loadJSON loads JSON files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/budgets.json'; - const rt = await loadJSON(url); - t.deepEqual([rt.numRows(), rt.numCols()], [230, 3], 'load json rows'); - t.end(); -}); - -tape('loadArrow loads Arrow files from a URL', async t => { - const url = 'https://vega.github.io/vega-datasets/data/flights-200k.arrow'; - const dt = await loadArrow(url); - t.deepEqual([dt.numRows(), dt.numCols()], [231083, 3], 'load arrow table'); - t.end(); -}); - -tape('load fails on non-existent path', t => { - load('https://foo.bar.baz/does.not.exist') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('load fails on invalid protocol', t => { - load('htsp://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadJSON fails on non-JSON file URL', t => { - loadJSON('https://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); - -tape('loadArrow fails on non-Arrow file URL', t => { - loadArrow('https://vega.github.io/vega-datasets/data/airports.csv') - .then(() => { t.fail('did not fail'); t.end(); }) - .catch(() => { t.pass('failed appropriately'); t.end(); }); -}); \ No newline at end of file diff --git a/test/format/to-arrow-test.js b/test/format/to-arrow-test.js deleted file mode 100644 index f9056a3c..00000000 --- a/test/format/to-arrow-test.js +++ /dev/null @@ -1,231 +0,0 @@ -import tape from 'tape'; -import { readFileSync } from 'fs'; -import { Int8Vector, Table, Type } from 'apache-arrow'; -import fromArrow from '../../src/format/from-arrow'; -import fromCSV from '../../src/format/from-csv'; -import fromJSON from '../../src/format/from-json'; -import toArrow from '../../src/format/to-arrow'; -import { table } from '../../src/table'; - -function date(year, month=0, date=1, hours=0, minutes=0, seconds=0, ms=0) { - return new Date(year, month, date, hours, minutes, seconds, ms); -} - -function utc(year, month=0, date=1, hours=0, minutes=0, seconds=0, ms=0) { - return new Date(Date.UTC(year, month, date, hours, minutes, seconds, ms)); -} - -function isArrayType(value) { - return Array.isArray(value) - || (value && value.map === Int8Array.prototype.map); -} - -function compareTables(aqt, art) { - const err = aqt.columnNames() - .map(name => compareColumns(name, aqt, art)) - .filter(a => a.length); - return err.length; -} - -function compareColumns(name, aqt, art) { - const normalize = v => v === undefined ? null : v instanceof Date ? +v : v; - const idx = aqt.indices(); - const aqc = aqt.column(name); - const arc = art.getColumn(name); - const err = []; - for (let i = 0; i < idx.length; ++i) { - let v1 = normalize(aqc.get(idx[i])); - let v2 = normalize(arc.get(i)); - if (isArrayType(v1)) { - v1 = v1.join(); - v2 = [...v2].join(); - } else if (typeof v1 === 'object') { - v1 = JSON.stringify(v1); - v2 = JSON.stringify(v2); - } - if (v1 !== v2) { - err.push({ name, index: i, v1, v2 }); - } - } - return err; -} - -tape('toArrow produces Arrow data for an input table', t => { - const dt = table({ - i: [1, 2, 3, undefined, 4, 5], - f: Float32Array.from([1.2, 2.3, 3.0, 3.4, null, 4.5]), - n: [4.5, 4.4, 3.4, 3.0, 2.3, 1.2], - b: [true, true, false, true, null, false], - s: ['foo', null, 'bar', 'baz', 'baz', 'bar'], - d: [date(2000,0,1), date(2000,1,2), null, date(2010,6,9), date(2018,0,1), date(2020,10,3)], - u: [utc(2000,0,1), utc(2000,1,2), null, utc(2010,6,9), utc(2018,0,1), utc(2020,10,3)], - e: [null, null, null, null, null, null], - v: Int8Vector.from([10, 9, 8, 7, 6, 5]), - a: [[1, null, 3], [4, 5], null, [6, 7], [8, 9], []], - l: [[1], [2], [3], [4], [5], [6]], - o: [1, 2, 3, null, 5, 6].map(v => v ? { key: v } : null) - }); - - const at = dt.toArrow(); - - t.equal( - compareTables(dt, at), 0, - 'arquero and arrow tables match' - ); - - t.equal( - compareTables(dt, toArrow(dt.objects())), 0, - 'object array and arrow tables match' - ); - - const buffer = at.serialize(); - const bt = Table.from(buffer); - - t.equal( - compareTables(dt, bt), 0, - 'arquero and serialized arrow tables match' - ); - - t.equal( - compareTables(fromArrow(bt), at), 0, - 'serialized arquero and arrow tables match' - ); - - t.end(); -}); - -tape('toArrow produces Arrow data for an input CSV', async t => { - const dt = fromCSV(readFileSync('test/format/data/beers.csv', 'utf8')); - const st = dt.derive({ name: d => d.name + '' }); - const at = dt.toArrow(); - - t.equal( - compareTables(st, at), 0, - 'arquero and arrow tables match' - ); - - t.equal( - compareTables(st, toArrow(st.objects())), 0, - 'object array and arrow tables match' - ); - - const buffer = at.serialize(); - - t.equal( - compareTables(st, Table.from(buffer)), 0, - 'arquero and serialized arrow tables match' - ); - - t.equal( - compareTables(fromArrow(Table.from(buffer)), at), 0, - 'serialized arquero and arrow tables match' - ); - - t.end(); -}); - -tape('toArrow handles ambiguously typed data', async t => { - const at = table({ x: [1, 2, 3, 'foo'] }).toArrow(); - t.deepEqual( - [...at.getColumn('x')], - ['1', '2', '3', 'foo'], - 'fallback to string type if a string is observed' - ); - - t.throws( - () => table({ x: [1, 2, 3, true] }).toArrow(), - 'fail on mixed types' - ); - - t.end(); -}); - -tape('toArrow result produces serialized arrow data', t => { - const dt = fromCSV(readFileSync('test/format/data/beers.csv', 'utf8')) - .derive({ name: d => d.name + '' }); - - const json = dt.toJSON(); - const jt = fromJSON(json); - - const bytes = dt.toArrow().serialize(); - const bt = fromArrow(Table.from(bytes)); - - t.deepEqual( - [bt.toJSON(), jt.toJSON()], - [json, json], - 'arrow and json round trips match' - ); - - t.end(); -}); - -tape('toArrow respects columns option', t => { - const dt = table({ - w: ['a', 'b', 'a'], - x: [1, 2, 3], - y: [1.6181, 2.7182, 3.1415], - z: [true, true, false] - }); - - const at = dt.toArrow({ columns: ['w', 'y'] }); - - t.deepEqual( - at.schema.fields.map(f => f.name), - ['w', 'y'], - 'column subset' - ); - - t.end(); -}); - -tape('toArrow respects limit and offset options', t => { - const dt = table({ - w: ['a', 'b', 'a'], - x: [1, 2, 3], - y: [1.6181, 2.7182, 3.1415], - z: [true, true, false] - }); - - t.equal( - JSON.stringify([...dt.toArrow({ limit: 2 })]), - '[{"w":"a","x":1,"y":1.6181,"z":true},{"w":"b","x":2,"y":2.7182,"z":true}]', - 'limit' - ); - t.equal( - JSON.stringify([...dt.toArrow({ offset: 1 })]), - '[{"w":"b","x":2,"y":2.7182,"z":true},{"w":"a","x":3,"y":3.1415,"z":false}]', - 'offset' - ); - t.equal( - JSON.stringify([...dt.toArrow({ offset: 1, limit: 1 })]), - '[{"w":"b","x":2,"y":2.7182,"z":true}]', - 'limit and offset' - ); - - t.end(); -}); - -tape('toArrow respects limit and types option', t => { - const dt = table({ - w: ['a', 'b', 'a'], - x: [1, 2, 3], - y: [1.6181, 2.7182, 3.1415], - z: [true, true, false] - }); - - const at = dt.toArrow({ - types: { w: Type.Utf8, x: Type.Int32, y: Type.Float32 } - }); - - const types = ['w', 'x', 'y', 'z'].map(name => at.getColumn(name).type); - - t.deepEqual( - types.map(t => t.typeId), - [Type.Utf8, Type.Int, Type.Float, Type.Bool], - 'type ids match' - ); - t.equal(types[1].bitWidth, 32, 'int32'); - t.equal(types[2].precision, 1, 'float32'); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/to-csv-test.js b/test/format/to-csv-test.js deleted file mode 100644 index 115683f6..00000000 --- a/test/format/to-csv-test.js +++ /dev/null @@ -1,74 +0,0 @@ -import tape from 'tape'; -import BitSet from '../../src/table/bit-set'; -import ColumnTable from '../../src/table/column-table'; -import toCSV from '../../src/format/to-csv'; - -function data() { - return { - str: ['a', 'b', 'c'], - int: [1, 2, 3], - num: [12.3, 45.6, 78.9], - bool: [true, null, false], - date: [new Date('2010-01-01'), new Date('2015-04-05'), new Date('2020-02-29')] - }; -} - -const text = [ - 'str,int,num,bool,date', - 'a,1,12.3,true,2010-01-01', - 'b,2,45.6,,2015-04-05', - 'c,3,78.9,false,2020-02-29' -]; - -const tabText = text.map(t => t.split(',').join('\t')); - -tape('toCSV formats delimited text', t => { - const dt = new ColumnTable(data()); - t.equal(toCSV(dt), text.join('\n'), 'csv text'); - t.equal( - toCSV(dt, { limit: 2, columns: ['str', 'int'] }), - text.slice(0, 3) - .map(s => s.split(',').slice(0, 2).join(',')) - .join('\n'), - 'csv text with limit' - ); - t.end(); -}); - -tape('toCSV formats delimited text with delimiter option', t => { - const dt = new ColumnTable(data()); - t.equal( - toCSV(dt, { delimiter: '\t' }), - tabText.join('\n'), - 'csv text with delimiter' - ); - t.equal( - toCSV(dt, { limit: 2, delimiter: '\t', columns: ['str', 'int'] }), - text.slice(0, 3) - .map(s => s.split(',').slice(0, 2).join('\t')) - .join('\n'), - 'csv text with delimiter and limit' - ); - t.end(); -}); - -tape('toCSV formats delimited text for filtered table', t => { - const bs = new BitSet(3).not(); bs.clear(1); - const dt = new ColumnTable(data(), null, bs); - t.equal( - toCSV(dt), - [ ...text.slice(0, 2), ...text.slice(3) ].join('\n'), - 'csv text with limit' - ); - t.end(); -}); - -tape('toCSV formats delimited text with format option', t => { - const dt = new ColumnTable(data()); - t.equal( - toCSV(dt, { limit: 2, columns: ['str'], format: { str: d => d + '!' } }), - ['str', 'a!', 'b!'].join('\n'), - 'csv text with custom format' - ); - t.end(); -}); \ No newline at end of file diff --git a/test/format/to-html-test.js b/test/format/to-html-test.js deleted file mode 100644 index 765d641c..00000000 --- a/test/format/to-html-test.js +++ /dev/null @@ -1,135 +0,0 @@ -import tape from 'tape'; -import ColumnTable from '../../src/table/column-table'; -import toHTML from '../../src/format/to-html'; - -tape('toHTML formats html table text', t => { - const l = 'style="text-align: left;"'; - const r = 'style="text-align: right;"'; - const html = (u, v) => [ - '', - ``, - '', - ``, - ``, - ``, - ``, - ``, - '
uv
a1
a2
b3
a4
b5
' - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.equal(toHTML(dt), html(l, r).join(''), 'html text'); - - t.equal( - toHTML(dt, { limit: 3 }), - html(l, r).slice(0, 6).join('') + '', - 'html text with limit' - ); - - t.end(); -}); - -tape('toHTML formats html table text with format option', t => { - const l = 'style="text-align: left;"'; - const r = 'style="text-align: right;"'; - const html = (u, v) => [ - '', - ``, - '', - ``, - ``, - ``, - ``, - ``, - '
uv
aa10
aa20
bb30
aa40
bb50
' - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.equal( - toHTML(dt, { - format: { - u: d => d + d, - v: d => d * 10 - } - }), - html(l, r).join(''), - 'html text with custom format' - ); - - t.end(); -}); - -tape('toHTML formats html table text with style option', t => { - const la = 'text-align: left;'; - const ra = 'text-align: right;'; - const cb = 'color: black;'; - const l = `style="${la} ${cb}"`; - const r = `style="${ra} ${cb}"`; - const html = (u, v) => [ - '', - '', - ``, - '', - ``, - ``, - ``, - ``, - ``, - '
uv
a1
a2
b3
a4
b5
' - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.equal( - toHTML(dt, { - style: { - tr: (col, idx, row) => `row(${idx},${row})`, - td: 'color: black;' - } - }), - html(l, r).join(''), - 'html text with custom style' - ); - - t.end(); -}); - -tape('toHTML formats html table text with null option', t => { - const a = 'style="text-align: right;"'; - const html = (a) => [ - '', - ``, - '', - ``, - ``, - ``, - ``, - '
u
a
0
null
undefined
' - ]; - - const dt = new ColumnTable({ u: ['a', 0, null, undefined] }); - - t.equal( - toHTML(dt, { - null: v => `${v}` - }), - html(a).join(''), - 'html text with custom null format' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/format/to-json-test.js b/test/format/to-json-test.js deleted file mode 100644 index ab157767..00000000 --- a/test/format/to-json-test.js +++ /dev/null @@ -1,80 +0,0 @@ -import tape from 'tape'; -import ColumnTable from '../../src/table/column-table'; -import toJSON from '../../src/format/to-json'; - -function data() { - return { - str: ['a', 'b', 'c'], - int: [1, 2, 3], - num: [12.3, 45.6, 78.9], - bool: [true, null, false], - date: [new Date('2010-01-01'), new Date('2015-04-05'), new Date('2020-02-29')] - }; -} - -function cols() { - return Object.keys(data()); -} - -const text = '{' - + '"str":["a","b","c"],' - + '"int":[1,2,3],' - + '"num":[12.3,45.6,78.9],' - + '"bool":[true,null,false],' - + '"date":["2010-01-01","2015-04-05","2020-02-29"]' - + '}'; - -function schema(names, text) { - return '{"schema":{"fields":' - + JSON.stringify(names.map(name => ({ name }))) - + '},"data":' + text + '}'; -} - -tape('toJSON formats JSON text with schema', t => { - const dt = new ColumnTable(data()); - t.equal(toJSON(dt), schema(cols(), text), 'json text'); - const names = ['str', 'int']; - t.equal( - toJSON(dt, { limit: 2, columns: names }), - schema(names, '{"str":["a","b"],"int":[1,2]}'), - 'json text with limit' - ); - t.end(); -}); - -tape('toJSON formats JSON text with format option with schema', t => { - const dt = new ColumnTable(data()); - const names = ['str']; - t.equal( - toJSON(dt, { limit: 2, columns: names, format: { str: d => d + '!' } }), - schema(names, '{"str":["a!","b!"]}'), - 'json text with custom format' - ); - t.end(); -}); - -tape('toJSON formats JSON text without schema', t => { - const dt = new ColumnTable(data()); - t.equal(toJSON(dt, { schema: false }), text, 'json text'); - t.equal( - toJSON(dt, { limit: 2, columns: ['str', 'int'], schema: false }), - '{"str":["a","b"],"int":[1,2]}', - 'json text with limit' - ); - t.end(); -}); - -tape('toJSON formats JSON text with format option without schema', t => { - const dt = new ColumnTable(data()); - t.equal( - toJSON(dt, { - schema: false, - limit: 2, - columns: ['str'], - format: { str: d => d + '!' } - }), - '{"str":["a!","b!"]}', - 'json text with custom format' - ); - t.end(); -}); \ No newline at end of file diff --git a/test/format/to-markdown-test.js b/test/format/to-markdown-test.js deleted file mode 100644 index eebc5eed..00000000 --- a/test/format/to-markdown-test.js +++ /dev/null @@ -1,62 +0,0 @@ -import tape from 'tape'; -import ColumnTable from '../../src/table/column-table'; -import toMarkdown from '../../src/format/to-markdown'; - -tape('toMarkdown formats markdown table text', t => { - const md = [ - '|u|v|\n', - '|:-|-:|\n', - '|a|1|\n', - '|a|2|\n', - '|b|3|\n', - '|a|4|\n', - '|b|5|\n' - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.equal(toMarkdown(dt), md.join(''), 'markdown text'); - - t.equal( - toMarkdown(dt, { limit: 3 }), - md.slice(0, 5).join(''), - 'markdown text with limit' - ); - - t.end(); -}); - -tape('toMarkdown formats markdown table text with format option', t => { - const md = [ - '|u|v|\n', - '|:-|-:|\n', - '|aa|10|\n', - '|aa|20|\n', - '|bb|30|\n', - '|aa|40|\n', - '|bb|50|\n' - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.equal( - toMarkdown(dt, { - format: { - u: d => d + d, - v: d => d * 10 - } - }), - md.join(''), - 'markdown text with custom format' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/groupby-equal.js b/test/groupby-equal.js deleted file mode 100644 index 2f5c0057..00000000 --- a/test/groupby-equal.js +++ /dev/null @@ -1,14 +0,0 @@ -export default function(t, table1, table2, msg) { - const extract = g => ({ - keys: g.keys, - names: g.names, - rows: g.rows, - size: g.size - }); - - t.deepEqual( - extract(table1.groups()), - extract(table2.groups()), - msg - ); -} \ No newline at end of file diff --git a/test/helpers/agg-test.js b/test/helpers/agg-test.js deleted file mode 100644 index d2114bd1..00000000 --- a/test/helpers/agg-test.js +++ /dev/null @@ -1,18 +0,0 @@ -import tape from 'tape'; -import { agg, op, table } from '../../src'; - -tape('agg computes aggregate values', t => { - const dt = table({ a: [1, 2, 3, 4] }); - - t.deepEqual( - { - sum: agg(dt, op.sum('a')), - max: agg(dt, op.max('a')), - ext: agg(dt, d => [op.min(d.a), op.max(d.a)]) - }, - { sum: 10, max: 4, ext: [1, 4] }, - 'agg helper' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/helpers/escape-test.js b/test/helpers/escape-test.js deleted file mode 100644 index 5bf17bbf..00000000 --- a/test/helpers/escape-test.js +++ /dev/null @@ -1,109 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { escape, op, query, table } from '../../src'; - -tape('derive supports escaped functions', t => { - const dt = table({ a: [1, 2], b: [3, 4] }); - const sq = x => x * x; - const off = 1; - - tableEqual(t, - dt.derive({ z: escape(d => sq(d.a) + off) }), - { a: [1, 2], b: [3, 4], z: [2, 5] }, - 'derive data with escape' - ); - - tableEqual(t, - dt.derive({ z: escape(d => d.a * -d.b + off) }), - { a: [1, 2], b: [3, 4], z: [-2, -7] }, - 'derive data with escape, two columns' - ); - - tableEqual(t, - dt.params({ foo: 2 }) - .derive({ z: escape((d, $) => sq(d.a) + off + op.abs($.foo)) }), - { a: [1, 2], b: [3, 4], z: [4, 7] }, - 'derive data with escape, op, and params' - ); - - tableEqual(t, - dt.derive({ z: escape(2) }), - { a: [1, 2], b: [3, 4], z: [2, 2] }, - 'derive data with escaped literal value' - ); - - t.end(); -}); - -tape('filter supports escaped functions', t => { - const thresh = 5; - tableEqual(t, - table({ a: [1, 4, 9], b: [1, 2, 3] }).filter(escape(d => d.a < thresh)), - { a: [1, 4], b: [1, 2] }, - 'filter data with escape' - ); - - t.end(); -}); - -tape('spread supports escaped functions', t => { - const pair = d => [d.v, d.v * d.v + 1]; - - tableEqual(t, - table({ v: [3, 2, 1] }).spread({ v: escape(pair) }, { as: ['a', 'b'] }), - { a: [3, 2, 1], b: [10, 5, 2] }, - 'spread data with escape' - ); - - t.end(); -}); - -tape('groupby supports escaped functions', t => { - tableEqual(t, - table({ v: [3, 2, 1] }).groupby({ g: escape(d => -d.v) }).count(), - { g: [-3, -2, -1], count: [1, 1, 1] }, - 'groupby data with escape' - ); - - t.end(); -}); - -tape('orderby supports escaped functions', t => { - tableEqual(t, - table({ v: [1, 2, 3] }).orderby(escape(d => -d.v)), - { v: [3, 2, 1] }, - 'orderby data with escape' - ); - - t.end(); -}); - -tape('aggregate verbs throw for escaped functions', t => { - t.throws( - () => table({ v: [1, 2, 3] }).rollup({ v: escape(d => -d.v) }), - 'rollup throws on escaped function' - ); - - t.throws( - () => table({ g: [1, 2], a: [3, 4] }).pivot('g', { v: escape(d => -d.a) }), - 'pivot throws on escaped function' - ); - - t.end(); -}); - -tape('query serialization throws for escaped functions', t => { - const sq = d => d.a * d.a; - - t.throws( - () => query().derive({ z: escape(sq) }).toObject(), - 'query toObject throws on escaped function' - ); - - t.throws( - () => query().derive({ z: escape(sq) }).toAST(), - 'query toAST throws on escape function' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/helpers/names-test.js b/test/helpers/names-test.js deleted file mode 100644 index a5efc645..00000000 --- a/test/helpers/names-test.js +++ /dev/null @@ -1,27 +0,0 @@ -import tape from 'tape'; -import { names, table } from '../../src'; - -tape('names produces a rename map', t => { - const dt = table({ x: [1], y: [2], z: [3] }); - const entries = [ ['x', 'a'], ['y', 'b'], ['z', 'c'] ]; - - t.deepEqual( - [ - names('a', 'b', 'c')(dt), - names(['a', 'b', 'c'])(dt), - names(['a', 'b'], 'c')(dt), - names('a', 'b')(dt), - names('a', 'b', 'c', 'd')(dt) - ], - [ - new Map(entries), - new Map(entries), - new Map(entries), - new Map(entries.slice(0, 2)), - new Map(entries) - ], - 'names helper' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/op/array-test.js b/test/op/array-test.js deleted file mode 100644 index 7adf4ab2..00000000 --- a/test/op/array-test.js +++ /dev/null @@ -1,196 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.compact compacts an array', t => { - t.deepEqual( - [ - op.compact(Float64Array.of(1, NaN, 2)), - op.compact([ 1, 2, 3 ]), - op.compact([ 1, null, 2, undefined, NaN, 3 ]), - op.compact(null), - op.compact(undefined), - op.compact(NaN) - ], - [ - Float64Array.of(1, 2), - [ 1, 2, 3 ], - [ 1, 2, 3 ], - null, - undefined, - NaN - ], - 'compact' - ); - t.end(); -}); - -tape('op.concat concats an array', t => { - t.deepEqual( - [ - op.concat(), - op.concat([ 1, 2 ], [ 3, 4 ], [ 5 ]), - op.concat(1, 2, [ 3 ]) - ], - [ - [], - [ 1, 2, 3, 4, 5 ], - [ 1, 2, 3 ] - ], - 'concat' - ); - t.end(); -}); - -tape('op.includes checks if a sequence contains a value', t => { - t.deepEqual( - [ - op.includes([1, 2], 1), - op.includes([1, 2], 1, 1), - op.includes('12', '1'), - op.includes('12', '1', 1), - op.includes(null, 1), - op.includes(undefined, 1), - op.includes(NaN, 1) - ], - [ - true, - false, - true, - false, - false, - false, - false - ], - 'includes' - ); - t.end(); -}); - -tape('op.indexof finds first index of a value', t => { - t.deepEqual( - [ - op.indexof([2, 1, 1], 1), - op.indexof([2, 1, 1], 3), - op.indexof('211', '1'), - op.indexof('211', '3'), - op.indexof(null, 1), - op.indexof(undefined, 1), - op.indexof(NaN, 1) - ], - [ 1, -1, 1, -1, -1, -1, -1 ], - 'indexof' - ); - t.end(); -}); - -tape('op.join maps an array to a string', t => { - t.deepEqual( - [ - op.join([2, 1, 1]), - op.join([2, 1, 1], ' '), - op.join('211', ' '), - op.join(null), - op.join(undefined), - op.join(NaN) - ], - [ '2,1,1', '2 1 1', undefined, undefined, undefined, undefined], - 'join' - ); - t.end(); -}); - -tape('op.lastindexof finds last index of a value', t => { - t.deepEqual( - [ - op.lastindexof([2, 1, 1], 1), - op.lastindexof([2, 1, 1], 3), - op.lastindexof('211', '1'), - op.lastindexof('211', '3'), - op.lastindexof(null, 1), - op.lastindexof(undefined, 1), - op.lastindexof(NaN, 1) - ], - [ 2, -1, 2, -1, -1, -1, -1 ], - 'lastindexof' - ); - t.end(); -}); - -tape('op.length returns the length of a sequence', t => { - t.deepEqual( - [ - op.length([]), - op.length(''), - op.length([2, 1, 1]), - op.length('211'), - op.length(null), - op.length(undefined), - op.length(NaN) - ], - [ 0, 0, 3, 3, 0, 0, 0 ], - 'length' - ); - t.end(); -}); - -tape('op.pluck retrieves a property from each array element', t => { - t.deepEqual( - [ - op.pluck([], 'x'), - op.pluck([{ x: 1 }, { x: 2 }, {}, 'foo'], 'x'), - op.pluck('foo', 'x'), - op.pluck(null, 'x'), - op.pluck(undefined, 'x'), - op.pluck(NaN, 'x') - ], - [ - [], [1, 2, undefined, undefined], - undefined, undefined, undefined, undefined - ], - 'pluck' - ); - t.end(); -}); - -tape('op.reverse reverses a sequence', t => { - t.deepEqual( - [ - op.reverse([]), - op.reverse(''), - op.reverse([2, 1, 1]), - op.reverse('211'), - op.reverse(null), - op.reverse(undefined), - op.reverse(NaN) - ], - [ - [], '', [1, 1, 2], '112', - undefined, undefined, undefined - ], - 'reverse' - ); - t.end(); -}); - -tape('op.slice extracts a subsequence', t => { - t.deepEqual( - [ - op.slice([2, 1, 3]), - op.slice([2, 1, 3], 1), - op.slice([2, 1, 3], 1, -1), - op.slice('213'), - op.slice('213', 1), - op.slice('213', 1, -1), - op.slice(null), - op.slice(undefined), - op.slice(NaN) - ], - [ - [2, 1, 3], [1, 3], [1], - '213', '13', '1', - undefined, undefined, undefined - ], - 'slice' - ); - t.end(); -}); diff --git a/test/op/date-test.js b/test/op/date-test.js deleted file mode 100644 index 046ef462..00000000 --- a/test/op/date-test.js +++ /dev/null @@ -1,42 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.dayofyear returns the day of the year', t => { - t.deepEqual([ - op.dayofyear(op.datetime(2000, 0, 1)), - op.dayofyear(op.datetime(2000, 0, 2)), - op.dayofyear(+op.datetime(2000, 11, 30)), - op.dayofyear(+op.datetime(2000, 11, 31)) - ], [1, 2, 365, 366], 'dayofyear'); - t.end(); -}); - -tape('op.week returns the week of the year', t => { - t.deepEqual([ - op.week(op.datetime(2000, 0, 1)), - op.week(op.datetime(2000, 0, 2)), - op.week(+op.datetime(2000, 11, 30)), - op.week(+op.datetime(2000, 11, 31)) - ], [0, 1, 52, 53], 'week'); - t.end(); -}); - -tape('op.utcdayofyear returns the UTC day of the year', t => { - t.deepEqual([ - op.utcdayofyear(op.utcdatetime(2000, 0, 1)), - op.utcdayofyear(op.utcdatetime(2000, 0, 2)), - op.utcdayofyear(+op.utcdatetime(2000, 11, 30)), - op.utcdayofyear(+op.utcdatetime(2000, 11, 31)) - ], [1, 2, 365, 366], 'utcdayofyear'); - t.end(); -}); - -tape('op.utcweek returns the UTC week of the year', t => { - t.deepEqual([ - op.utcweek(op.utcdatetime(2000, 0, 1)), - op.utcweek(op.utcdatetime(2000, 0, 2)), - op.utcweek(+op.utcdatetime(2000, 11, 30)), - op.utcweek(+op.utcdatetime(2000, 11, 31)) - ], [0, 1, 52, 53], 'week'); - t.end(); -}); \ No newline at end of file diff --git a/test/op/json-test.js b/test/op/json-test.js deleted file mode 100644 index eea7e917..00000000 --- a/test/op/json-test.js +++ /dev/null @@ -1,29 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.parse_json parses json strings', t => { - t.deepEqual([ - op.parse_json('1'), - op.parse_json('[3,2,1.2]'), - op.parse_json('{"foo":true,"bar":"bop","baz":null}') - ], [ - 1, - [3, 2, 1.2], - {foo: true, bar: 'bop', baz: null} - ], 'parse_json'); - t.end(); -}); - -tape('op.to_json generates json strings', t => { - t.deepEqual([ - op.to_json(1), - op.to_json([3, 2, 1.2]), - op.to_json({foo: true, bar: 'bop', baz: null, buz: undefined}) - ], [ - '1', - '[3,2,1.2]', - '{"foo":true,"bar":"bop","baz":null}' - - ], 'to_json'); - t.end(); -}); \ No newline at end of file diff --git a/test/op/math-test.js b/test/op/math-test.js deleted file mode 100644 index 2c8cbaed..00000000 --- a/test/op/math-test.js +++ /dev/null @@ -1,18 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.greatest returns the greatest element', t => { - t.equal(op.greatest(1, 2, 3), 3, 'greatest'); - t.equal(op.greatest(1, null, 3), 3, 'greatest with null'); - t.equal(op.greatest(1, undefined, 3), NaN, 'greatest with undefined'); - t.equal(op.greatest(1, NaN, 3), NaN, 'greatest with NaN'); - t.end(); -}); - -tape('op.least returns the least element', t => { - t.equal(op.least(1, 2, 3), 1, 'least'); - t.equal(op.least(1, null, 3), 0, 'least with null'); - t.equal(op.least(1, undefined, 3), NaN, 'least with undefined'); - t.equal(op.least(1, NaN, 3), NaN, 'least with NaN'); - t.end(); -}); \ No newline at end of file diff --git a/test/op/object-test.js b/test/op/object-test.js deleted file mode 100644 index 28dcec55..00000000 --- a/test/op/object-test.js +++ /dev/null @@ -1,90 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.has checks if a object/map/set has a key', t => { - t.deepEqual( - [ - op.has({ a: 1}, 'a'), - op.has({ a: 1}, 'b'), - op.has(new Map([['a', 1]]), 'a'), - op.has(new Map([['a', 1]]), 'b'), - op.has(new Set(['a']), 'a'), - op.has(new Set(['a']), 'b'), - op.has(null, 'a'), - op.has(undefined, 'a'), - op.has(NaN, 'a') - ], - [ - true, false, - true, false, - true, false, - false, - false, - false - ], - 'has' - ); - t.end(); -}); - -tape('op.keys returns object/map keys', t => { - t.deepEqual( - [ - op.keys({ a: 1}), - op.keys(new Map([['a', 1]])), - op.keys(null), - op.keys(undefined), - op.keys(NaN) - ], - [ ['a'], ['a'], [], [], [] ], - 'keys' - ); - t.end(); -}); - -tape('op.values returns object/map/set values', t => { - t.deepEqual( - [ - op.values({ a: 1}), - op.values(new Map([['a', 1]])), - op.values(new Set(['a'])), - op.values(null), - op.values(undefined), - op.values(NaN) - ], - [ [1], [1], ['a'], [], [], [] ], - 'values' - ); - t.end(); -}); - -tape('op.entries returns object/map/set entries', t => { - t.deepEqual( - [ - op.entries({ a: 1}), - op.entries(new Map([['a', 1]])), - op.entries(new Set(['a'])), - op.entries(null), - op.entries(undefined), - op.entries(NaN) - ], - [ [['a', 1]], [['a', 1]], [['a', 'a']], [], [], [] ], - 'entries' - ); - t.end(); -}); - -tape('op.object constructs an object from iterable entries', t => { - t.deepEqual( - [ - op.object([['a', 1]]), - op.object(new Map([['b', 2]])), - op.object(null), - op.object(undefined), - op.object(NaN) - ], - [ {a: 1}, {b: 2}, undefined, undefined, undefined ], - 'object' - ); - t.end(); -}); \ No newline at end of file diff --git a/test/op/op-test.js b/test/op/op-test.js deleted file mode 100644 index ac3bb56d..00000000 --- a/test/op/op-test.js +++ /dev/null @@ -1,46 +0,0 @@ -import tape from 'tape'; -import { aggregateFunctions, functions, windowFunctions } from '../../src/op'; -import op from '../../src/op/op-api'; -import has from '../../src/util/has'; - -tape('op includes all aggregate functions', t => { - let pass = true; - for (const name in aggregateFunctions) { - if (op[name] == null) { - pass = false; - t.fail(`missing aggregate function: ${name}`); - } - } - t.ok(pass, 'has aggregate functions'); - t.end(); -}); - -tape('op includes all window functions', t => { - let pass = true; - for (const name in windowFunctions) { - if (op[name] == null) { - pass = false; - t.fail(`missing window function: ${name}`); - } - } - t.ok(pass, 'has window functions'); - t.end(); -}); - -tape('op functions do not have name collision', t => { - const overlap = []; - - for (const name in aggregateFunctions) { - if (has(functions, name) || has(windowFunctions, name)) { - overlap.push(name); - } - } - for (const name in windowFunctions) { - if (has(functions, name)) { - overlap.push(name); - } - } - - t.deepEqual(overlap, [], 'no name collisons'); - t.end(); -}); \ No newline at end of file diff --git a/test/op/row-object-test.js b/test/op/row-object-test.js deleted file mode 100644 index 1c307b37..00000000 --- a/test/op/row-object-test.js +++ /dev/null @@ -1,44 +0,0 @@ -import tape from 'tape'; -import { op, table } from '../../src'; - -tape('op.row_object generates objects with row data', t => { - const dt = table({ a: [1, 2], b: [3, 4] }); - - t.deepEqual( - dt.derive({ row: op.row_object() }).array('row'), - dt.objects(), - 'row objects, outside function context' - ); - - t.deepEqual( - dt.derive({ row: () => op.row_object() }).array('row'), - dt.objects(), - 'row objects, inside function context' - ); - - t.deepEqual( - dt.derive({ row: op.row_object('a') }).array('row'), - dt.objects({ columns: 'a' }), - 'row objects, column names outside function context' - ); - - t.deepEqual( - dt.derive({ row: () => op.row_object('a' + '') }).array('row'), - dt.objects({ columns: 'a' }), - 'row objects, column names inside function context' - ); - - t.deepEqual( - dt.derive({ row: op.row_object(0) }).array('row'), - dt.objects({ columns: 'a' }), - 'row objects, column indices outside function context' - ); - - t.deepEqual( - dt.derive({ row: () => op.row_object(0 + 0) }).array('row'), - dt.objects({ columns: 'a' }), - 'row objects, column indices inside function context' - ); - - t.end(); -}); diff --git a/test/op/string-test.js b/test/op/string-test.js deleted file mode 100644 index d1c9b8f6..00000000 --- a/test/op/string-test.js +++ /dev/null @@ -1,236 +0,0 @@ -import tape from 'tape'; -import { op } from '../../src'; - -tape('op.parse_date parses date values', t => { - t.deepEqual( - [ - op.parse_date('2001-01-01'), - op.parse_date(null), - op.parse_date(undefined) - ], - [ new Date(Date.UTC(2001, 0, 1)), null, undefined ], - 'parse_date' - ); - t.end(); -}); - -tape('op.parse_float parses float values', t => { - t.deepEqual( - [ - op.parse_float('1.2'), - op.parse_float(null), - op.parse_float(undefined) - ], - [ 1.2, null, undefined ], - 'parse_float' - ); - t.end(); -}); - -tape('op.parse_int parses integer values', t => { - t.deepEqual( - [ - op.parse_int('1'), - op.parse_int('F', 16), - op.parse_int(null), - op.parse_int(undefined) - ], - [ 1, 15, null, undefined ], - 'parse_int' - ); - t.end(); -}); - -tape('op.endswith tests if a string ends with a substring', t => { - t.deepEqual( - [ - op.endswith('123', '3'), - op.endswith('123', '1'), - op.endswith('123', '3', 2), - op.endswith(null, '1'), - op.endswith(undefined, '1') - ], - [ true, false, false, false, false ], - 'endswith' - ); - t.end(); -}); - -tape('op.match returns pattern matches', t => { - t.deepEqual( - [ - op.match('foo', /bar/), - op.match('1 2 3 4', /\d+/).slice(), - op.match('1 2 3 4', /\d+/g), - op.match('1 2 3 4', /\d+ (\d+)/, 1), - op.match('1 2 3 4', /(?\d+)/, 'digit'), - op.match('1 2 3 4', /\d+/, 'digit'), - op.match(null, /\d+/), - op.match(undefined, /\d+/) - ], - [ - null, ['1'], ['1', '2', '3', '4'], '2', '1', null, - null, undefined - ], - 'match' - ); - t.end(); -}); - -tape('op.normalize normalizes strings', t => { - t.deepEqual( - [ - op.normalize('abc'), - op.normalize('\u006E\u0303'), - op.normalize(null), - op.normalize(undefined) - ], - [ 'abc', '\u00F1', null, undefined ], - 'normalize' - ); - t.end(); -}); - -tape('op.padend pads the end of strings', t => { - t.deepEqual( - [ - op.padend('abc', 4), - op.padend('abc', 4, ' '), - op.padend('abc', 5, '#'), - op.padend(null), - op.padend(undefined) - ], - [ 'abc ', 'abc ', 'abc##', null, undefined ], - 'padend' - ); - t.end(); -}); - -tape('op.padstart pads the start of strings', t => { - t.deepEqual( - [ - op.padstart('abc', 4), - op.padstart('abc', 4, ' '), - op.padstart('abc', 5, '#'), - op.padstart(null), - op.padstart(undefined) - ], - [ ' abc', ' abc', '##abc', null, undefined ], - 'padstart' - ); - t.end(); -}); - -tape('op.upper maps a string to upper-case', t => { - t.deepEqual( - [ - op.upper('abc'), - op.upper(null), - op.upper(undefined) - ], - [ 'ABC', null, undefined ], - 'upper' - ); - t.end(); -}); - -tape('op.lower maps a string to lower-case', t => { - t.deepEqual( - [ - op.lower('ABC'), - op.lower(null), - op.lower(undefined) - ], - [ 'abc', null, undefined ], - 'lower' - ); - t.end(); -}); - -tape('op.repeat repeats a string', t => { - t.deepEqual( - [ - op.repeat('a', 3), - op.repeat(null, 2), - op.repeat(undefined, 2) - ], - [ 'aaa', null, undefined ], - 'repeat' - ); - t.end(); -}); - -tape('op.replace replaces a pattern within a string', t => { - t.deepEqual( - [ - op.replace('aba', 'a', 'c'), - op.replace('aba', /a/, 'c'), - op.replace('aba', /a/g, 'c'), - op.replace(null, 'a', 'c'), - op.replace(undefined, 'a', 'c') - ], - [ 'cba', 'cba', 'cbc', null, undefined ], - 'replace' - ); - t.end(); -}); - -tape('op.substring extracts a substring', t => { - t.deepEqual( - [ - op.substring('aba', 0, 1), - op.substring('aba', 0, 2), - op.substring('aba', 1, 3), - op.substring(null, 0, 1), - op.substring(undefined, 0, 1) - ], - [ 'a', 'ab', 'ba', null, undefined ], - 'substring' - ); - t.end(); -}); - -tape('op.split splits a string on a delimter pattern', t => { - t.deepEqual( - [ - op.split('aba', ''), - op.split('a,b,a', ','), - op.split('a,b,a', /,/), - op.split(null, ','), - op.split(undefined, ',') - ], - [ ['a', 'b', 'a'], ['a', 'b', 'a'], ['a', 'b', 'a'], [], [] ], - 'split' - ); - t.end(); -}); - - -tape('op.startswith tests if a starts ends with a substring', t => { - t.deepEqual( - [ - op.startswith('123', '3'), - op.startswith('123', '1'), - op.startswith('123', '1', 2), - op.startswith(null, '1'), - op.startswith(undefined, '1') - ], - [ false, true, false, false, false ], - 'startswith' - ); - t.end(); -}); - -tape('op.trim trims whitespace from a string', t => { - t.deepEqual( - [ - op.trim('1'), - op.trim(' 1 '), - op.trim(null), - op.trim(undefined) - ], - [ '1', '1', null, undefined ], - 'trim' - ); - t.end(); -}); diff --git a/test/query/query-test.js b/test/query/query-test.js deleted file mode 100644 index 3c9bfdd2..00000000 --- a/test/query/query-test.js +++ /dev/null @@ -1,1217 +0,0 @@ -import tape from 'tape'; -import groupbyEqual from '../groupby-equal'; -import tableEqual from '../table-equal'; -import Query, { query } from '../../src/query/query'; -import { Verbs } from '../../src/query/verb'; -import isFunction from '../../src/util/is-function'; -import { all, desc, not, op, range, rolling, seed, table } from '../../src'; -import { field, func } from './util'; - -const { - count, dedupe, derive, filter, groupby, orderby, - reify, rollup, select, sample, ungroup, unorder, - relocate, rename, impute, fold, pivot, spread, unroll, - cross, join, semijoin, antijoin, - concat, union, except, intersect -} = Verbs; - -tape('Query builds single-table queries', t => { - const q = query() - .derive({ bar: d => d.foo + 1 }) - .rollup({ count: op.count(), sum: op.sum('bar') }) - .orderby('foo', desc('bar'), d => d.baz, desc(d => d.bop)) - .groupby('foo', { baz: d => d.baz, bop: d => d.bop }); - - t.deepEqual(q.toObject(), { - verbs: [ - { - verb: 'derive', - values: { bar: func('d => d.foo + 1') }, - options: undefined - }, - { - verb: 'rollup', - values: { - count: func('d => op.count()'), - sum: func('d => op.sum(d["bar"])') - } - }, - { - verb: 'orderby', - keys: [ - field('foo'), - field('bar', { desc: true }), - func('d => d.baz'), - func('d => d.bop', { desc: true }) - ] - }, - { - verb: 'groupby', - keys: [ - 'foo', - { - baz: func('d => d.baz'), - bop: func('d => d.bop') - } - ] - } - ] - }, 'serialized query from builder'); - - t.end(); -}); - -tape('Query supports multi-table verbs', t => { - const q = query() - .concat('concat_table') - .join('join_table'); - - t.deepEqual(q.toObject(), { - verbs: [ - { - verb: 'concat', - tables: ['concat_table'] - }, - { - verb: 'join', - table: 'join_table', - on: undefined, - values: undefined, - options: undefined - } - ] - }, 'serialized query from builder'); - - t.end(); -}); - -tape('Query supports multi-table queries', t => { - const qc = query('concat_table') - .select(not('foo')); - - const qj = query('join_table') - .select(not('bar')); - - const q = query() - .concat(qc) - .join(qj); - - t.deepEqual(q.toObject(), { - verbs: [ - { - verb: 'concat', - tables: [ qc.toObject() ] - }, - { - verb: 'join', - table: qj.toObject(), - on: undefined, - values: undefined, - options: undefined - } - ] - }, 'serialized query from builder'); - - t.end(); -}); - -tape('Query supports all defined verbs', t => { - const verbs = Object.keys(Verbs); - const q = query(); - t.equal( - verbs.filter(v => isFunction(q[v])).length, - verbs.length, - 'query builder supports all verbs' - ); - t.end(); -}); - -tape('Query serializes to objects', t => { - const q = new Query([ - derive({ bar: d => d.foo + 1 }), - rollup({ - count: op.count(), - sum: op.sum('bar') - }), - orderby(['foo', desc('bar'), d => d.baz, desc(d => d.bop)]), - groupby(['foo', { baz: d => d.baz, bop: d => d.bop }]) - ]); - - t.deepEqual(q.toObject(), { - verbs: [ - { - verb: 'derive', - values: { bar: func('d => d.foo + 1') }, - options: undefined - }, - { - verb: 'rollup', - values: { - count: func('d => op.count()'), - sum: func('d => op.sum(d["bar"])') - } - }, - { - verb: 'orderby', - keys: [ - field('foo'), - field('bar', { desc: true }), - func('d => d.baz'), - func('d => d.bop', { desc: true }) - ] - }, - { - verb: 'groupby', - keys: [ - 'foo', - { - baz: func('d => d.baz'), - bop: func('d => d.bop') - } - ] - } - ] - }, 'serialized query'); - t.end(); -}); - -tape('Query evaluates unmodified inputs', t => { - const q = new Query([ - derive({ bar: (d, $) => d.foo + $.offset }), - rollup({ count: op.count(), sum: op.sum('bar') }) - ], { offset: 1}); - - const dt = table({ foo: [0, 1, 2, 3] }); - const dr = q.evaluate(dt); - - tableEqual(t, dr, { count: [4], sum: [10] }, 'query data'); - t.end(); -}); - -tape('Query evaluates serialized inputs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([ - derive({ baz: (d, $) => d.foo + $.offset }), - orderby(['bar', 0]), - select([not('bar')]) - ], { offset: 1 }).toObject() - ).evaluate(dt), - { foo: [ 2, 3, 0, 1 ], baz: [ 3, 4, 1, 2 ] }, - 'serialized query data' - ); - - tableEqual( - t, - Query.from( - new Query([ - derive({ bar: (d, $) => d.foo + $.offset }), - rollup({ count: op.count(), sum: op.sum('bar') }) - ], { offset: 1 }).toObject() - ).evaluate(dt), - { count: [4], sum: [10] }, - 'serialized query data' - ); - - t.end(); -}); - -tape('Query evaluates count verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([count()]).toObject() - ).evaluate(dt), - { count: [4] }, - 'count query result' - ); - - tableEqual( - t, - Query.from( - new Query([count({ as: 'cnt' })]).toObject() - ).evaluate(dt), - { cnt: [4] }, - 'count query result, with options' - ); - - t.end(); -}); - -tape('Query evaluates dedupe verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([dedupe([])]).toObject() - ).evaluate(dt), - { foo: [0, 1, 2, 3], bar: [1, 1, 0, 0] }, - 'dedupe query result' - ); - - tableEqual( - t, - Query.from( - new Query([dedupe(['bar'])]).toObject() - ).evaluate(dt), - { foo: [0, 2], bar: [1, 0] }, - 'dedupe query result, key' - ); - - tableEqual( - t, - Query.from( - new Query([dedupe([not('foo')])]).toObject() - ).evaluate(dt), - { foo: [0, 2], bar: [1, 0] }, - 'dedupe query result, key selection' - ); - - t.end(); -}); - -tape('Query evaluates derive verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - const verb = derive( - { - baz: d => d.foo + 1 - op.mean(d.foo), - bop: 'd => 2 * (d.foo - op.mean(d.foo))', - sum: rolling(d => op.sum(d.foo)), - win: rolling(d => op.product(d.foo), [0, 1]) - }, - { - before: 'bar' - } - ); - - tableEqual( - t, - Query.from( - new Query([verb]).toObject() - ).evaluate(dt), - { - foo: [0, 1, 2, 3], - baz: [-0.5, 0.5, 1.5, 2.5], - bop: [-3, -1, 1, 3], - sum: [0, 1, 3, 6], - win: [0, 2, 6, 3], - bar: [1, 1, 0, 0] - }, - 'derive query result' - ); - - t.end(); -}); - -tape('Query evaluates filter verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - const verb = filter(d => d.bar > 0); - - tableEqual( - t, - Query.from( - new Query([verb]).toObject() - ).evaluate(dt), - { - foo: [0, 1], - bar: [1, 1] - }, - 'filter query result' - ); - - t.end(); -}); - -tape('Query evaluates groupby verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - groupbyEqual( - t, - Query.from( - new Query([groupby(['bar'])]).toObject() - ).evaluate(dt), - dt.groupby('bar'), - 'groupby query result' - ); - - groupbyEqual( - t, - Query.from( - new Query([groupby([{bar: d => d.bar}])]).toObject() - ).evaluate(dt), - dt.groupby('bar'), - 'groupby query result, table expression' - ); - - groupbyEqual( - t, - Query.from( - new Query([groupby([not('foo')])]).toObject() - ).evaluate(dt), - dt.groupby('bar'), - 'groupby query result, selection' - ); - - t.end(); -}); - -tape('Query evaluates orderby verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([orderby(['bar', 'foo'])]).toObject() - ).evaluate(dt), - { - foo: [2, 3, 0, 1], - bar: [0, 0, 1, 1] - }, - 'orderby query result' - ); - - tableEqual( - t, - Query.from( - new Query([orderby([ - d => d.bar, - d => d.foo - ])]).toObject() - ).evaluate(dt), - { - foo: [2, 3, 0, 1], - bar: [0, 0, 1, 1] - }, - 'orderby query result' - ); - - tableEqual( - t, - Query.from( - new Query([orderby([desc('bar'), desc('foo')])]).toObject() - ).evaluate(dt), - { - foo: [1, 0, 3, 2], - bar: [1, 1, 0, 0] - }, - 'orderby query result, desc' - ); - - t.end(); -}); - -tape('Query evaluates reify verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }).filter(d => d.foo < 1); - - tableEqual( - t, - Query.from( - new Query([ reify() ]).toObject() - ).evaluate(dt), - { foo: [0], bar: [1] }, - 'reify query result' - ); - - t.end(); -}); - -tape('Query evaluates relocate verbs', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - tableEqual( - t, - Query.from( - new Query([ - relocate('b', { after: 'b' }) - ]).toObject() - ).evaluate(dt), - { a, c, d, b }, - 'relocate query result' - ); - - tableEqual( - t, - Query.from( - new Query([ - relocate(not('b', 'd'), { before: range(0, 1) }) - ]).toObject() - ).evaluate(dt), - { a, c, b, d }, - 'relocate query result' - ); - - t.end(); -}); - -tape('Query evaluates rename verbs', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - tableEqual( - t, - Query.from( - new Query([ - rename({ d: 'w', a: 'z' }) - ]).toObject() - ).evaluate(dt), - { z: a, b, c, w: d }, - 'rename query result' - ); - - t.end(); -}); - -tape('Query evaluates rollup verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([rollup({ - count: op.count(), - sum: op.sum('foo'), - sump1: d => 1 + op.sum(d.foo + d.bar), - avgt2: 'd => 2 * op.mean(op.abs(d.foo))' - })]).toObject() - ).evaluate(dt), - { count: [4], sum: [6], sump1: [9], avgt2: [3] }, - 'rollup query result' - ); - - t.end(); -}); - -tape('Query evaluates sample verbs', t => { - seed(12345); - - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([sample(2)]).toObject() - ).evaluate(dt), - { foo: [ 3, 1 ], bar: [ 0, 1 ] }, - 'sample query result' - ); - - tableEqual( - t, - Query.from( - new Query([sample(2, { replace: true })]).toObject() - ).evaluate(dt), - { foo: [ 3, 0 ], bar: [ 0, 1 ] }, - 'sample query result, replace' - ); - - tableEqual( - t, - Query.from( - new Query([sample(2, { weight: 'foo' })]).toObject() - ).evaluate(dt), - { foo: [ 2, 3 ], bar: [ 0, 0 ] }, - 'sample query result, weight column name' - ); - - tableEqual( - t, - Query.from( - new Query([sample(2, { weight: d => d.foo })]).toObject() - ).evaluate(dt), - { foo: [ 3, 2 ], bar: [ 0, 0 ] }, - 'sample query result, weight table expression' - ); - - seed(null); - t.end(); -}); - -tape('Query evaluates select verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([select(['bar'])]).toObject() - ).evaluate(dt), - { bar: [1, 1, 0, 0] }, - 'select query result, column name' - ); - - tableEqual( - t, - Query.from( - new Query([select([all()])]).toObject() - ).evaluate(dt), - { foo: [0, 1, 2, 3], bar: [1, 1, 0, 0] }, - 'select query result, all' - ); - - tableEqual( - t, - Query.from( - new Query([select([not('foo')])]).toObject() - ).evaluate(dt), - { bar: [1, 1, 0, 0] }, - 'select query result, not' - ); - - tableEqual( - t, - Query.from( - new Query([select([range(1, 1)])]).toObject() - ).evaluate(dt), - { bar: [1, 1, 0, 0] }, - 'select query result, range' - ); - - t.end(); -}); - -tape('Query evaluates ungroup verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }).groupby('bar'); - - const qt = Query - .from( new Query([ ungroup() ]).toObject() ) - .evaluate(dt); - - t.equal(qt.isGrouped(), false, 'table is not grouped'); - t.end(); -}); - -tape('Query evaluates unorder verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }).orderby('foo'); - - const qt = Query - .from( new Query([ unorder() ]).toObject() ) - .evaluate(dt); - - t.equal(qt.isOrdered(), false, 'table is not ordered'); - t.end(); -}); - -tape('Query evaluates impute verbs', t => { - const dt = table({ - x: [1, 2], - y: [3, 4], - z: [1, 1] - }); - - const imputed = { - x: [1, 2, 1, 2], - y: [3, 4, 4, 3], - z: [1, 1, 0, 0] - }; - - const verb = impute( - { z: () => 0 }, - { expand: ['x', 'y'] } - ); - - tableEqual( - t, - Query.from( - new Query([verb]).toObject() - ).evaluate(dt), - imputed, - 'impute query result' - ); - - t.end(); -}); - -tape('Query evaluates fold verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - const folded = { - key: [ 'foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar' ], - value: [ 0, 1, 1, 1, 2, 0, 3, 0 ] - }; - - tableEqual( - t, - Query.from( - new Query([fold(['foo', 'bar'])]).toObject() - ).evaluate(dt), - folded, - 'fold query result, column names' - ); - - tableEqual( - t, - Query.from( - new Query([fold([all()])]).toObject() - ).evaluate(dt), - folded, - 'fold query result, all' - ); - - tableEqual( - t, - Query.from( - new Query([fold([{ foo: d => d.foo }])]).toObject() - ).evaluate(dt), - { - bar: [ 1, 1, 0, 0 ], - key: [ 'foo', 'foo', 'foo', 'foo' ], - value: [ 0, 1, 2, 3 ] - }, - 'fold query result, table expression' - ); - - t.end(); -}); - -tape('Query evaluates pivot verbs', t => { - const dt = table({ - foo: [0, 1, 2, 3], - bar: [1, 1, 0, 0] - }); - - tableEqual( - t, - Query.from( - new Query([pivot(['bar'], ['foo'])]).toObject() - ).evaluate(dt), - { '0': [2], '1': [0] }, - 'pivot query result, column names' - ); - - tableEqual( - t, - Query.from( - new Query([pivot( - [{ bar: d => d.bar }], - [{ foo: op.sum('foo') }] - )]).toObject() - ).evaluate(dt), - { '0': [5], '1': [1] }, - 'pivot query result, table expressions' - ); - - t.end(); -}); - -tape('Query evaluates spread verbs', t => { - const dt = table({ - list: [[1, 2, 3]] - }); - - tableEqual( - t, - Query.from( - new Query([spread(['list'])]).toObject() - ).evaluate(dt), - { - 'list_1': [1], - 'list_2': [2], - 'list_3': [3] - }, - 'spread query result, column names' - ); - - tableEqual( - t, - Query.from( - new Query([spread(['list'], { drop: false })]).toObject() - ).evaluate(dt), - { - 'list': [[1, 2, 3]], - 'list_1': [1], - 'list_2': [2], - 'list_3': [3] - }, - 'spread query result, column names' - ); - - tableEqual( - t, - Query.from( - new Query([spread([{ list: d => d.list }])]).toObject() - ).evaluate(dt), - { - // 'list': [[1, 2, 3]], - 'list_1': [1], - 'list_2': [2], - 'list_3': [3] - }, - 'spread query result, table expression' - ); - - tableEqual( - t, - Query.from( - new Query([spread(['list'], { limit: 2 })]).toObject() - ).evaluate(dt), - { - // 'list': [[1, 2, 3]], - 'list_1': [1], - 'list_2': [2] - }, - 'spread query result, limit' - ); - - t.end(); -}); - -tape('Query evaluates unroll verbs', t => { - const dt = table({ - list: [[1, 2, 3]] - }); - - tableEqual( - t, - Query.from( - new Query([unroll(['list'])]).toObject() - ).evaluate(dt), - { 'list': [1, 2, 3] }, - 'unroll query result, column names' - ); - - tableEqual( - t, - Query.from( - new Query([unroll([{ list: d => d.list }])]).toObject() - ).evaluate(dt), - { 'list': [1, 2, 3] }, - 'unroll query result, table expression' - ); - - tableEqual( - t, - Query.from( - new Query([unroll(['list'], { limit: 2 })]).toObject() - ).evaluate(dt), - { 'list': [1, 2] }, - 'unroll query result, limit' - ); - - t.end(); -}); - -tape('Query evaluates cross verbs', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - u: ['C'], - v: [3] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ - cross('other') - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], u: ['C', 'C'], v: [3, 3] }, - 'cross query result' - ); - - tableEqual( - t, - Query.from( - new Query([ - cross('other', ['y', 'v']) - ]).toObject() - ).evaluate(lt, catalog), - { y: [1, 2], v: [3, 3] }, - 'cross query result, column name values' - ); - - tableEqual( - t, - Query.from( - new Query([ - cross('other', [ - { y: d => d.y }, - { v: d => d.v } - ]) - ]).toObject() - ).evaluate(lt, catalog), - { y: [1, 2], v: [3, 3] }, - 'cross query result, table expression values' - ); - - tableEqual( - t, - Query.from( - new Query([ - cross('other', { - y: a => a.y, - v: (a, b) => b.v - }) - ]).toObject() - ).evaluate(lt, catalog), - { y: [1, 2], v: [3, 3] }, - 'cross query result, two-table expression values' - ); - - t.end(); -}); - -tape('Query evaluates join verbs', t => { - const lt = table({ - x: ['A', 'B', 'C'], - y: [1, 2, 3] - }); - - const rt = table({ - u: ['A', 'B', 'D'], - v: [4, 5, 6] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u']) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], u: ['A', 'B'], v: [4, 5] }, - 'join query result, column name keys' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', (a, b) => op.equal(a.x, b.u)) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], u: ['A', 'B'], v: [4, 5] }, - 'join query result, predicate expression' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u'], [['x', 'y'], 'v']) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], v: [4, 5] }, - 'join query result, column name values' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u'], [all(), not('u')]) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], v: [4, 5] }, - 'join query result, selection values' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u'], [ - { x: d => d.x, y: d => d.y }, - { v: d => d.v } - ]) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], v: [4, 5] }, - 'join query result, table expression values' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u'], { - x: a => a.x, - y: a => a.y, - v: (a, b) => b.v - }) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2], v: [4, 5] }, - 'join query result, two-table expression values' - ); - - tableEqual( - t, - Query.from( - new Query([ - join('other', ['x', 'u'], [['x', 'y'], ['u', 'v']], - { left: true, right: true}) - ]).toObject() - ).evaluate(lt, catalog), - { - x: [ 'A', 'B', 'C', undefined ], - y: [ 1, 2, 3, undefined ], - u: [ 'A', 'B', undefined, 'D' ], - v: [ 4, 5, undefined, 6 ] - }, - 'join query result, full join' - ); - - t.end(); -}); - -tape('Query evaluates semijoin verbs', t => { - const lt = table({ - x: ['A', 'B', 'C'], - y: [1, 2, 3] - }); - - const rt = table({ - u: ['A', 'B', 'D'], - v: [4, 5, 6] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ - semijoin('other', ['x', 'u']) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2] }, - 'semijoin query result, column name keys' - ); - - tableEqual( - t, - Query.from( - new Query([ - semijoin('other', (a, b) => op.equal(a.x, b.u)) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B'], y: [1, 2] }, - 'semijoin query result, predicate expression' - ); - - t.end(); -}); - -tape('Query evaluates antijoin verbs', t => { - const lt = table({ - x: ['A', 'B', 'C'], - y: [1, 2, 3] - }); - - const rt = table({ - u: ['A', 'B', 'D'], - v: [4, 5, 6] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ - antijoin('other', ['x', 'u']) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['C'], y: [3] }, - 'antijoin query result, column name keys' - ); - - tableEqual( - t, - Query.from( - new Query([ - antijoin('other', (a, b) => op.equal(a.x, b.u)) - ]).toObject() - ).evaluate(lt, catalog), - { x: ['C'], y: [3] }, - 'antijoin query result, predicate expression' - ); - - t.end(); -}); - -tape('Query evaluates concat verbs', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - x: ['B', 'C'], - y: [2, 3] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ concat(['other']) ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B', 'B', 'C'], y: [1, 2, 2, 3] }, - 'concat query result' - ); - - t.end(); -}); - -tape('Query evaluates concat verbs with subqueries', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - a: ['B', 'C'], - b: [2, 3] - }); - - const catalog = name => name === 'other' ? rt : null; - - const sub = query('other') - .select({ a: 'x', b: 'y' }); - - tableEqual( - t, - Query.from( - new Query([ concat([sub]) ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B', 'B', 'C'], y: [1, 2, 2, 3] }, - 'concat query result' - ); - - t.end(); -}); - -tape('Query evaluates union verbs', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - x: ['B', 'C'], - y: [2, 3] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ union(['other']) ]).toObject() - ).evaluate(lt, catalog), - { x: ['A', 'B', 'C'], y: [1, 2, 3] }, - 'union query result' - ); - - t.end(); -}); - -tape('Query evaluates except verbs', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - x: ['B', 'C'], - y: [2, 3] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ except(['other']) ]).toObject() - ).evaluate(lt, catalog), - { x: ['A'], y: [1] }, - 'except query result' - ); - - t.end(); -}); - -tape('Query evaluates intersect verbs', t => { - const lt = table({ - x: ['A', 'B'], - y: [1, 2] - }); - - const rt = table({ - x: ['B', 'C'], - y: [2, 3] - }); - - const catalog = name => name === 'other' ? rt : null; - - tableEqual( - t, - Query.from( - new Query([ intersect(['other']) ]).toObject() - ).evaluate(lt, catalog), - { x: ['B'], y: [2] }, - 'intersect query result' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/query/util.js b/test/query/util.js deleted file mode 100644 index c7e3e4c1..00000000 --- a/test/query/util.js +++ /dev/null @@ -1,7 +0,0 @@ -export const field = (expr, props) => ({ - expr, field: true, ...props -}); - -export const func = (expr, props) => ({ - expr, func: true, ...props -}); \ No newline at end of file diff --git a/test/query/verb-test.js b/test/query/verb-test.js deleted file mode 100644 index e45f68e7..00000000 --- a/test/query/verb-test.js +++ /dev/null @@ -1,612 +0,0 @@ -import tape from 'tape'; -import { query } from '../../src/query/query'; -import { Verb, Verbs } from '../../src/query/verb'; -import { - all, bin, desc, endswith, matches, not, op, range, rolling, startswith -} from '../../src'; -import { field, func } from './util'; - -const { - count, dedupe, derive, filter, groupby, orderby, - reify, rollup, sample, select, ungroup, unorder, - relocate, rename, impute, pivot, unroll, join, concat -} = Verbs; - -function test(t, verb, expect, msg) { - const object = verb.toObject(); - t.deepEqual(object, expect, msg); - const rt = Verb.from(object).toObject(); - t.deepEqual(rt, expect, msg + ' round-trip'); -} - -tape('count verb serializes to object', t => { - test(t, - count(), - { - verb: 'count', - options: undefined - }, - 'serialized count, no options' - ); - - test(t, - count({ as: 'cnt' }), - { - verb: 'count', - options: { as: 'cnt' } - }, - 'serialized count, with options' - ); - - t.end(); -}); - -tape('dedupe verb serializes to object', t => { - test(t, - dedupe(), - { - verb: 'dedupe', - keys: [] - }, - 'serialized dedupe, no keys' - ); - - test(t, - dedupe(['id', d => d.foo]), - { - verb: 'dedupe', - keys: [ - 'id', - func('d => d.foo') - ] - }, - 'serialized dedupe, with keys' - ); - - t.end(); -}); - -tape('derive verb serializes to object', t => { - const verb = derive( - { - foo: 'd.bar * 5', - bar: d => d.foo + 1, - baz: rolling(d => op.mean(d.foo), [-3, 3]) - }, - { - before: 'bop' - } - ); - - test(t, - verb, - { - verb: 'derive', - values: { - foo: 'd.bar * 5', - bar: func('d => d.foo + 1'), - baz: func( - 'd => op.mean(d.foo)', - { window: { frame: [ -3, 3 ], peers: false } } - ) - }, - options: { - before: 'bop' - } - }, - 'serialized derive verb' - ); - - t.end(); -}); - -tape('filter verb serializes to object', t => { - test(t, - filter(d => d.foo > 2), - { - verb: 'filter', - criteria: func('d => d.foo > 2') - }, - 'serialized filter verb' - ); - - t.end(); -}); - -tape('groupby verb serializes to object', t => { - const verb = groupby([ - 'foo', - { baz: d => d.baz, bop: d => d.bop } - ]); - - test(t, - verb, - { - verb: 'groupby', - keys: [ - 'foo', - { - baz: func('d => d.baz'), - bop: func('d => d.bop') - } - ] - }, - 'serialized groupby verb' - ); - - const binVerb = groupby([{ bin0: bin('foo') }]); - - test(t, - binVerb, - { - verb: 'groupby', - keys: [ - { - bin0: 'd => op.bin(d["foo"], ...op.bins(d["foo"]), 0)' - } - ] - }, - 'serialized groupby verb, with binning' - ); - - t.end(); -}); - -tape('orderby verb serializes to object', t => { - const verb = orderby([ - 1, - 'foo', - desc('bar'), - d => d.baz, - desc(d => d.bop) - ]); - - test(t, - verb, - { - verb: 'orderby', - keys: [ - 1, - field('foo'), - field('bar', { desc: true }), - func('d => d.baz'), - func('d => d.bop', { desc: true }) - ] - }, - 'serialized orderby verb' - ); - - t.end(); -}); - -tape('reify verb serializes to AST', t => { - const verb = reify(); - - test(t, - verb, - { verb: 'reify' }, - 'serialized reify verb' - ); - - t.end(); -}); - -tape('relocate verb serializes to object', t => { - test(t, - relocate(['foo', 'bar'], { before: 'baz' }), - { - verb: 'relocate', - columns: ['foo', 'bar'], - options: { before: 'baz' } - }, - 'serialized relocate verb' - ); - - test(t, - relocate(not('foo'), { after: range('a', 'b') }), - { - verb: 'relocate', - columns: { not: ['foo'] }, - options: { after: { range: ['a', 'b'] } } - }, - 'serialized relocate verb' - ); - - t.end(); -}); - -tape('rename verb serializes to object', t => { - test(t, - rename([{ foo: 'bar' }]), - { - verb: 'rename', - columns: [{ foo: 'bar' }] - }, - 'serialized rename verb' - ); - - test(t, - rename([{ foo: 'bar' }, { baz: 'bop' }]), - { - verb: 'rename', - columns: [{ foo: 'bar' }, { baz: 'bop' }] - }, - 'serialized rename verb' - ); - - t.end(); -}); - -tape('rollup verb serializes to object', t => { - const verb = rollup({ - count: op.count(), - sum: op.sum('bar'), - mean: d => op.mean(d.foo) - }); - - test(t, - verb, - { - verb: 'rollup', - values: { - count: func('d => op.count()'), - sum: func('d => op.sum(d["bar"])'), - mean: func('d => op.mean(d.foo)') - } - }, - 'serialized rollup verb' - ); - - t.end(); -}); - -tape('sample verb serializes to object', t => { - test(t, - sample(2, { replace: true }), - { - verb: 'sample', - size: 2, - options: { replace: true } - }, - 'serialized sample verb' - ); - - test(t, - sample(() => op.count()), - { - verb: 'sample', - size: { expr: '() => op.count()', func: true }, - options: undefined - }, - 'serialized sample verb, size function' - ); - - test(t, - sample('() => op.count()'), - { - verb: 'sample', - size: '() => op.count()', - options: undefined - }, - 'serialized sample verb, size function as string' - ); - - test(t, - sample(2, { weight: 'foo' }), - { - verb: 'sample', - size: 2, - options: { weight: 'foo' } - }, - 'serialized sample verb, weight column name' - ); - - test(t, - sample(2, { weight: d => 2 * d.foo }), - { - verb: 'sample', - size: 2, - options: { weight: { expr: 'd => 2 * d.foo', func: true } } - }, - 'serialized sample verb, weight table expression' - ); - - t.end(); -}); - -tape('select verb serializes to object', t => { - const verb = select([ - 'foo', - 'bar', - { bop: 'boo', baz: 'bao' }, - all(), - range(0, 1), - range('a', 'b'), - not('foo', 'bar', range(0, 1), range('a', 'b')), - matches('foo.bar'), - matches(/a|b/i), - startswith('foo.'), - endswith('.baz') - ]); - - test(t, - verb, - { - verb: 'select', - columns: [ - 'foo', - 'bar', - { bop: 'boo', baz: 'bao' }, - { all: [] }, - { range: [0, 1] }, - { range: ['a', 'b'] }, - { - not: [ - 'foo', - 'bar', - { range: [0, 1] }, - { range: ['a', 'b'] } - ] - }, - { matches: ['foo\\.bar', ''] }, - { matches: ['a|b', 'i'] }, - { matches: ['^foo\\.', ''] }, - { matches: ['\\.baz$', ''] } - ] - }, - 'serialized select verb' - ); - - t.end(); -}); - -tape('ungroup verb serializes to object', t => { - test(t, - ungroup(), - { verb: 'ungroup' }, - 'serialized ungroup verb' - ); - - t.end(); -}); - -tape('unorder verb serializes to object', t => { - test(t, - unorder(), - { verb: 'unorder' }, - 'serialized unorder verb' - ); - - t.end(); -}); - -tape('impute verb serializes to object', t => { - const verb = impute( - { v: () => 0 }, - { expand: 'x' } - ); - - test(t, - verb, - { - verb: 'impute', - values: { - v: func('() => 0') - }, - options: { - expand: 'x' - } - }, - 'serialized impute verb' - ); - - t.end(); -}); - -tape('pivot verb serializes to object', t => { - const verb = pivot( - ['key'], - ['value', { sum: d => op.sum(d.foo), prod: op.product('bar') }], - { sort: false } - ); - - test(t, - verb, - { - verb: 'pivot', - keys: ['key'], - values: [ - 'value', - { - sum: func('d => op.sum(d.foo)'), - prod: func('d => op.product(d["bar"])') - } - ], - options: { sort: false } - }, - 'serialized pivot verb' - ); - - t.end(); -}); - -tape('unroll verb serializes to object', t => { - test(t, - unroll(['foo', 1]), - { - verb: 'unroll', - values: [ 'foo', 1 ], - options: undefined - }, - 'serialized unroll verb' - ); - - test(t, - unroll({ - foo: d => d.foo, - bar: d => op.split(d.bar, ' ') - }), - { - verb: 'unroll', - values: { - foo: { expr: 'd => d.foo', func: true }, - bar: { expr: 'd => op.split(d.bar, \' \')', func: true } - }, - options: undefined - }, - 'serialized unroll verb, values object' - ); - - test(t, - unroll(['foo'], { index: true }), - { - verb: 'unroll', - values: [ 'foo' ], - options: { index: true } - }, - 'serialized unroll verb, index boolean' - ); - - test(t, - unroll(['foo'], { index: 'idxnum' }), - { - verb: 'unroll', - values: [ 'foo' ], - options: { index: 'idxnum' } - }, - 'serialized unroll verb, index string' - ); - - test(t, - unroll(['foo'], { drop: [ 'bar' ] }), - { - verb: 'unroll', - values: [ 'foo' ], - options: { drop: [ 'bar' ] } - }, - 'serialized unroll verb, drop column name' - ); - - test(t, - unroll(['foo'], { drop: d => d.bar }), - { - verb: 'unroll', - values: [ 'foo' ], - options: { drop: { expr: 'd => d.bar', func: true } } - }, - 'serialized unroll verb, drop table expression' - ); - - t.end(); -}); - -tape('join verb serializes to object', t => { - const verbSel = join( - 'tableRef', - ['keyL', 'keyR'], - [all(), not('keyR')], - { suffix: ['_L', '_R'] } - ); - - test(t, - verbSel, - { - verb: 'join', - table: 'tableRef', - on: [ - [field('keyL')], - [field('keyR')] - ], - values: [ - [ { all: [] } ], - [ { not: ['keyR'] } ] - ], - options: { suffix: ['_L', '_R'] } - }, - 'serialized join verb, column selections' - ); - - const verbCols = join( - 'tableRef', - [ - [d => d.keyL], - [d => d.keyR] - ], - [ - ['keyL', 'valL', { foo: d => 1 + d.valL }], - ['valR', { bar: d => 2 * d.valR }] - ], - { suffix: ['_L', '_R'] } - ); - - test(t, - verbCols, - { - verb: 'join', - table: 'tableRef', - on: [ - [ func('d => d.keyL') ], - [ func('d => d.keyR') ] - ], - values: [ - ['keyL', 'valL', { foo: func('d => 1 + d.valL') } ], - ['valR', { bar: func('d => 2 * d.valR') }] - ], - options: { suffix: ['_L', '_R'] } - }, - 'serialized join verb, column lists' - ); - - const verbExpr = join( - 'tableRef', - (a, b) => op.equal(a.keyL, b.keyR), - { - key: a => a.keyL, - foo: a => a.foo, - bar: (a, b) => b.bar - } - ); - - test(t, - verbExpr, - { - verb: 'join', - table: 'tableRef', - on: func('(a, b) => op.equal(a.keyL, b.keyR)'), - values: { - key: func('a => a.keyL'), - foo: func('a => a.foo'), - bar: func('(a, b) => b.bar') - }, - options: undefined - }, - 'serialized join verb, table expressions' - ); - - t.end(); -}); - -tape('concat verb serializes to object', t => { - test(t, - concat(['foo', 'bar']), - { - verb: 'concat', - tables: ['foo', 'bar'] - }, - 'serialized concat verb' - ); - - const ct1 = query('foo').select(not('bar')); - const ct2 = query('bar').select(not('foo')); - - test(t, - concat([ct1, ct2]), - { - verb: 'concat', - tables: [ ct1.toObject(), ct2.toObject() ] - }, - 'serialized concat verb, with subqueries' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/query/verb-to-ast-test.js b/test/query/verb-to-ast-test.js deleted file mode 100644 index 7365a460..00000000 --- a/test/query/verb-to-ast-test.js +++ /dev/null @@ -1,880 +0,0 @@ -import tape from 'tape'; -import { query } from '../../src/query/query'; -import { Verbs } from '../../src/query/verb'; -import { - all, bin, desc, endswith, matches, not, op, range, rolling, startswith -} from '../../src'; - -const { - count, dedupe, derive, filter, groupby, orderby, - reify, rollup, sample, select, ungroup, unorder, - relocate, rename, impute, pivot, unroll, join, concat -} = Verbs; - -function toAST(verb) { - return JSON.parse(JSON.stringify(verb.toAST())); -} - -tape('count verb serializes to AST', t => { - t.deepEqual( - toAST(count()), - { type: 'Verb', verb: 'count' }, - 'ast count, no options' - ); - - t.deepEqual( - toAST(count({ as: 'cnt' })), - { - type: 'Verb', - verb: 'count', - options: { as: 'cnt' } - }, - 'ast count, with options' - ); - - t.end(); -}); - -tape('dedupe verb serializes to AST', t => { - t.deepEqual( - toAST(dedupe()), - { - type: 'Verb', - verb: 'dedupe', - keys: [] - }, - 'ast dedupe, no keys' - ); - - t.deepEqual( - toAST(dedupe(['id', d => d.foo, d => Math.abs(d.bar)])), - { - type: 'Verb', - verb: 'dedupe', - keys: [ - { type: 'Column', name: 'id' }, - { type: 'Column', name: 'foo' }, - { - type: 'CallExpression', - callee: { type: 'Function', name: 'abs' }, - arguments: [ { type: 'Column', name: 'bar' } ] - } - ] - }, - 'ast dedupe, with keys' - ); - t.end(); -}); - -tape('derive verb serializes to AST', t => { - const verb = derive( - { - col: d => d.foo, - foo: 'd.bar * 5', - bar: d => d.foo + 1, - baz: rolling(d => op.mean(d.foo), [-3, 3]) - }, - { - before: 'bop' - } - ); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'derive', - values: [ - { type: 'Column', name: 'foo', as: 'col' }, - { - type: 'BinaryExpression', - left: { type: 'Column', name: 'bar' }, - operator: '*', - right: { type: 'Literal', value: 5, raw: '5' }, - as: 'foo' - }, - { - type: 'BinaryExpression', - left: { type: 'Column', name: 'foo' }, - operator: '+', - right: { type: 'Literal', value: 1, raw: '1' }, - as: 'bar' - }, - { - type: 'Window', - frame: [ -3, 3 ], - peers: false, - expr: { - type: 'CallExpression', - callee: { type: 'Function', name: 'mean' }, - arguments: [ { type: 'Column', name: 'foo' } ] - }, - as: 'baz' - } - ], - options: { - before: [ - { type: 'Column', name: 'bop' } - ] - } - }, - 'ast derive verb' - ); - - t.end(); -}); - -tape('filter verb serializes to AST', t => { - const ast = { - type: 'Verb', - verb: 'filter', - criteria: { - type: 'BinaryExpression', - left: { type: 'Column', name: 'foo' }, - operator: '>', - right: { type: 'Literal', value: 2, raw: '2' } - } - }; - - t.deepEqual( - toAST(filter(d => d.foo > 2)), - ast, - 'ast filter verb' - ); - - t.deepEqual( - toAST(filter('d.foo > 2')), - ast, - 'ast filter verb, expr string' - ); - - t.end(); -}); - -tape('groupby verb serializes to AST', t => { - t.deepEqual( - toAST(groupby([ - 'foo', - 1, - { baz: d => d.baz, bop: d => d.bop, bar: d => Math.abs(d.bar) } - ])), - { - type: 'Verb', - verb: 'groupby', - keys: [ - { type: 'Column', name: 'foo' }, - { type: 'Column', index: 1 }, - { type: 'Column', name: 'baz', as: 'baz' }, - { type: 'Column', name: 'bop', as: 'bop' }, - { - type: 'CallExpression', - callee: { type: 'Function', name: 'abs' }, - arguments: [ { type: 'Column', name: 'bar' } ], - as: 'bar' - } - ] - }, - 'ast groupby verb' - ); - - t.deepEqual( - toAST(groupby([{ bin0: bin('foo') }])), - { - type: 'Verb', - verb: 'groupby', - keys: [ - { - as: 'bin0', - type: 'CallExpression', - callee: { type: 'Function', name: 'bin' }, - arguments: [ - { type: 'Column', name: 'foo' }, - { - type: 'SpreadElement', - argument: { - type: 'CallExpression', - callee: { type: 'Function', name: 'bins' }, - arguments: [{ type: 'Column', name: 'foo' }] - } - }, - { type: 'Literal', value: 0, raw: '0' } - ] - } - ] - }, - 'ast groupby verb, with binning' - ); - - t.end(); -}); - -tape('orderby verb serializes to AST', t => { - const verb = orderby([ - 1, - 'foo', - desc('bar'), - d => d.baz, - desc(d => d.bop) - ]); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'orderby', - keys: [ - { type: 'Column', index: 1 }, - { type: 'Column', name: 'foo' }, - { type: 'Descending', expr: { type: 'Column', name: 'bar' } }, - { type: 'Column', name: 'baz' }, - { type: 'Descending', expr: { type: 'Column', name: 'bop' } } - ] - }, - 'ast orderby verb' - ); - - t.end(); -}); - -tape('reify verb serializes to AST', t => { - const verb = reify(); - - t.deepEqual( - toAST(verb), - { type: 'Verb', verb: 'reify' }, - 'ast reify verb' - ); - - t.end(); -}); - -tape('relocate verb serializes to AST', t => { - t.deepEqual( - toAST(relocate(['foo', 'bar'], { before: 'baz' })), - { - type: 'Verb', - verb: 'relocate', - columns: [ - { type: 'Column', name: 'foo' }, - { type: 'Column', name: 'bar' } - ], - options: { - before: [ { type: 'Column', name: 'baz' } ] - } - }, - 'ast relocate verb' - ); - - t.deepEqual( - toAST(relocate(not('foo'), { after: range('a', 'b') })), - { - type: 'Verb', - verb: 'relocate', - columns: [ - { - type: 'Selection', - operator: 'not', - arguments: [ { type: 'Column', name: 'foo' } ] - } - ], - options: { - after: [ - { - type: 'Selection', - operator: 'range', - arguments: [ - { type: 'Column', name: 'a' }, - { type: 'Column', name: 'b' } - ] - } - ] - } - }, - 'ast relocate verb' - ); - - t.end(); -}); - -tape('rename verb serializes to AST', t => { - t.deepEqual( - toAST(rename([{ foo: 'bar' }])), - { - type: 'Verb', - verb: 'rename', - columns: [ - { type: 'Column', name: 'foo', as: 'bar' } - ] - }, - 'ast rename verb' - ); - - t.deepEqual( - toAST(rename([{ foo: 'bar' }, { baz: 'bop' }])), - { - type: 'Verb', - verb: 'rename', - columns: [ - { type: 'Column', name: 'foo', as: 'bar' }, - { type: 'Column', name: 'baz', as: 'bop' } - ] - }, - 'ast rename verb' - ); - - t.end(); -}); - -tape('rollup verb serializes to AST', t => { - const verb = rollup({ - count: op.count(), - sum: op.sum('bar'), - mean: d => op.mean(d.foo) - }); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'rollup', - values: [ - { - as: 'count', - type: 'CallExpression', - callee: { type: 'Function', name: 'count' }, - arguments: [] - }, - { - as: 'sum', - type: 'CallExpression', - callee: { type: 'Function', name: 'sum' }, - arguments: [{ type: 'Column', name: 'bar' } ] - }, - { - as: 'mean', - type: 'CallExpression', - callee: { type: 'Function', name: 'mean' }, - arguments: [ { type: 'Column', name: 'foo' } ] - } - ] - }, - 'ast rollup verb' - ); - - t.end(); -}); - -tape('sample verb serializes to AST', t => { - t.deepEqual( - toAST(sample(2, { replace: true })), - { - type: 'Verb', - verb: 'sample', - size: 2, - options: { replace: true } - }, - 'ast sample verb' - ); - - t.deepEqual( - toAST(sample(() => op.count())), - { - type: 'Verb', - verb: 'sample', - size: { - type: 'CallExpression', - callee: { type: 'Function', name: 'count' }, - arguments: [] - } - }, - 'ast sample verb, size function' - ); - - t.deepEqual( - toAST(sample('() => op.count()')), - { - type: 'Verb', - verb: 'sample', - size: { - type: 'CallExpression', - callee: { type: 'Function', name: 'count' }, - arguments: [] - } - }, - 'ast sample verb, size function as string' - ); - - t.deepEqual( - toAST(sample(2, { weight: 'foo' })), - { - type: 'Verb', - verb: 'sample', - size: 2, - options: { weight: { type: 'Column', name: 'foo' } } - }, - 'ast sample verb, weight column name' - ); - - t.deepEqual( - toAST(sample(2, { weight: d => 2 * d.foo })), - { - type: 'Verb', - verb: 'sample', - size: 2, - options: { - weight: { - type: 'BinaryExpression', - left: { type: 'Literal', value: 2, raw: '2' }, - operator: '*', - right: { type: 'Column', name: 'foo' } - } - } - }, - 'ast sample verb, weight table expression' - ); - - t.end(); -}); - -tape('select verb serializes to AST', t => { - const verb = select([ - 'foo', - 'bar', - { bop: 'boo', baz: 'bao' }, - all(), - range(0, 1), - range('a', 'b'), - not('foo', 'bar', range(0, 1), range('a', 'b')), - matches('foo.bar'), - matches(/a|b/i), - startswith('foo.'), - endswith('.baz') - ]); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'select', - columns: [ - { type: 'Column', name: 'foo' }, - { type: 'Column', name: 'bar' }, - { type: 'Column', name: 'bop', as: 'boo' }, - { type: 'Column', name: 'baz', as: 'bao' }, - { type: 'Selection', operator: 'all' }, - { - type: 'Selection', - operator: 'range', - arguments: [ - { type: 'Column', index: 0 }, - { type: 'Column', index: 1 } - ] - }, - { - type: 'Selection', - operator: 'range', - arguments: [ - { type: 'Column', name: 'a' }, - { type: 'Column', name: 'b' } - ] - }, - { - type: 'Selection', - operator: 'not', - arguments: [ - { type: 'Column', name: 'foo' }, - { type: 'Column', name: 'bar' }, - { - type: 'Selection', - operator: 'range', - arguments: [ - { type: 'Column', index: 0 }, - { type: 'Column', index: 1 } - ] - }, - { - type: 'Selection', - operator: 'range', - arguments: [ - { type: 'Column', name: 'a' }, - { type: 'Column', name: 'b' } - ] - } - ] - }, - { - type: 'Selection', - operator: 'matches', - arguments: [ 'foo\\.bar', '' ] - }, - { - type: 'Selection', - operator: 'matches', - arguments: [ 'a|b', 'i' ] - }, - { - type: 'Selection', - operator: 'matches', - arguments: [ '^foo\\.', '' ] - }, - { - type: 'Selection', - operator: 'matches', - arguments: [ '\\.baz$', '' ] - } - ] - }, - 'ast select verb' - ); - - t.end(); -}); - -tape('ungroup verb serializes to AST', t => { - const verb = ungroup(); - - t.deepEqual( - toAST(verb), - { type: 'Verb', verb: 'ungroup' }, - 'ast ungroup verb' - ); - - t.end(); -}); - -tape('unorder verb serializes to AST', t => { - const verb = unorder(); - - t.deepEqual( - toAST(verb), - { type: 'Verb', verb: 'unorder' }, - 'ast unorder verb' - ); - - t.end(); -}); - -tape('pivot verb serializes to AST', t => { - const verb = pivot( - ['key'], - ['value', { sum: d => op.sum(d.foo), prod: op.product('bar') }], - { sort: false } - ); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'pivot', - keys: [ { type: 'Column', name: 'key' } ], - values: [ - { type: 'Column', name: 'value' }, - { - as: 'sum', - type: 'CallExpression', - callee: { type: 'Function', name: 'sum' }, - arguments: [ { type: 'Column', name: 'foo' } ] - }, - { - as: 'prod', - type: 'CallExpression', - callee: { type: 'Function', name: 'product' }, - arguments: [ { type: 'Column', name: 'bar' } ] - } - ], - options: { sort: false } - }, - 'ast pivot verb' - ); - - t.end(); -}); - -tape('impute verb serializes to AST', t => { - const verb = impute( - { v: () => 0 }, - { expand: 'x' } - ); - - t.deepEqual( - toAST(verb), - { - type: 'Verb', - verb: 'impute', - values: [ { as: 'v', type: 'Literal', raw: '0', value: 0 } ], - options: { expand: [ { type: 'Column', name: 'x' } ] } - }, - 'ast impute verb' - ); - - t.end(); -}); - -tape('unroll verb serializes to AST', t => { - t.deepEqual( - toAST(unroll(['foo', 1])), - { - type: 'Verb', - verb: 'unroll', - values: [ - { type: 'Column', name: 'foo' }, - { type: 'Column', index: 1 } - ] - }, - 'ast unroll verb' - ); - - t.deepEqual( - toAST(unroll({ - foo: d => d.foo, - bar: d => op.split(d.bar, ' ') - })), - { - type: 'Verb', - verb: 'unroll', - values: [ - { type: 'Column', name: 'foo', as: 'foo' }, - { - as: 'bar', - type: 'CallExpression', - callee: { type: 'Function', name: 'split' }, - arguments: [ - { type: 'Column', name: 'bar' }, - { type: 'Literal', value: ' ', raw: '\' \'' } - ] - } - ] - }, - 'ast unroll verb, values object' - ); - - t.deepEqual( - toAST(unroll(['foo'], { index: true })), - { - type: 'Verb', - verb: 'unroll', - values: [ { type: 'Column', name: 'foo' } ], - options: { index: true } - }, - 'ast unroll verb, index boolean' - ); - - t.deepEqual( - toAST(unroll(['foo'], { index: 'idxnum' })), - { - type: 'Verb', - verb: 'unroll', - values: [ { type: 'Column', name: 'foo' } ], - options: { index: 'idxnum' } - }, - 'ast unroll verb, index string' - ); - - t.deepEqual( - toAST(unroll(['foo'], { drop: [ 'bar' ] })), - { - type: 'Verb', - verb: 'unroll', - values: [ { type: 'Column', name: 'foo' } ], - options: { - drop: [ { type: 'Column', name: 'bar' } ] - } - }, - 'ast unroll verb, drop column name' - ); - - t.deepEqual( - toAST(unroll(['foo'], { drop: d => d.bar })), - { - type: 'Verb', - verb: 'unroll', - values: [ { type: 'Column', name: 'foo' } ], - options: { - drop: [ { type: 'Column', name: 'bar' } ] - } - }, - 'ast unroll verb, drop table expression' - ); - - t.end(); -}); - -tape('join verb serializes to AST', t => { - const verbSel = join( - 'tableRef', - ['keyL', 'keyR'], - [all(), not('keyR')], - { suffix: ['_L', '_R'] } - ); - - t.deepEqual( - toAST(verbSel), - { - type: 'Verb', - verb: 'join', - table: 'tableRef', - on: [ - [ { type: 'Column', name: 'keyL' } ], - [ { type: 'Column', name: 'keyR' } ] - ], - values: [ - [ { type: 'Selection', operator: 'all' } ], - [ { - type: 'Selection', - operator: 'not', - arguments: [ { type: 'Column', name: 'keyR' } ] - } ] - ], - options: { suffix: ['_L', '_R'] } - }, - 'ast join verb, column selections' - ); - - const verbCols = join( - 'tableRef', - [ - [d => d.keyL], - [d => d.keyR] - ], - [ - ['keyL', 'valL', { foo: d => 1 + d.valL }], - ['valR', { bar: d => 2 * d.valR }] - ], - { suffix: ['_L', '_R'] } - ); - - t.deepEqual( - toAST(verbCols), - { - type: 'Verb', - verb: 'join', - table: 'tableRef', - on: [ - [ { type: 'Column', name: 'keyL' } ], - [ { type: 'Column', name: 'keyR' } ] - ], - values: [ - [ - { type: 'Column', name: 'keyL' }, - { type: 'Column', name: 'valL' }, - { - as: 'foo', - type: 'BinaryExpression', - left: { type: 'Literal', 'value': 1, 'raw': '1' }, - operator: '+', - right: { type: 'Column', name: 'valL' } - } - ], - [ - { type: 'Column', name: 'valR' }, - { - as: 'bar', - type: 'BinaryExpression', - left: { type: 'Literal', 'value': 2, 'raw': '2' }, - operator: '*', - right: { type: 'Column', name: 'valR' } - } - ] - ], - options: { suffix: ['_L', '_R'] } - }, - 'ast join verb, column lists' - ); - - const verbExpr = join( - 'tableRef', - (a, b) => op.equal(a.keyL, b.keyR), - { - key: a => a.keyL, - foo: a => a.foo, - bar: (a, b) => b.bar - } - ); - - t.deepEqual( - toAST(verbExpr), - { - type: 'Verb', - verb: 'join', - table: 'tableRef', - on: { - type: 'CallExpression', - callee: { type: 'Function', name: 'equal' }, - arguments: [ - { type: 'Column', table: 1, name: 'keyL' }, - { type: 'Column', table: 2, name: 'keyR' } - ] - }, - values: [ - { type: 'Column', table: 1, name: 'keyL', as: 'key' }, - { type: 'Column', table: 1, name: 'foo', as: 'foo' }, - { type: 'Column', table: 2, name: 'bar', as: 'bar' } - ] - }, - 'ast join verb, table expressions' - ); - - t.end(); -}); - -tape('concat verb serializes to AST', t => { - t.deepEqual( - toAST(concat(['foo', 'bar'])), - { - type: 'Verb', - verb: 'concat', - tables: ['foo', 'bar'] - }, - 'ast concat verb' - ); - - const ct1 = query('foo').select(not('bar')); - const ct2 = query('bar').select(not('foo')); - - t.deepEqual( - toAST(concat([ct1, ct2])), - { - type: 'Verb', - verb: 'concat', - tables: [ - { - type: 'Query', - verbs: [ - { - type: 'Verb', - verb: 'select', - columns: [ - { - type: 'Selection', - operator: 'not', - arguments: [ { type: 'Column', name: 'bar' } ] - } - ] - } - ], - table: 'foo' - }, - { - type: 'Query', - verbs: [ - { - type: 'Verb', - verb: 'select', - columns: [ - { - type: 'Selection', - operator: 'not', - arguments: [ { type: 'Column', name: 'foo' } ] - } - ] - } - ], - table: 'bar' - } - ] - }, - 'ast concat verb, with subqueries' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/register-test.js b/test/register-test.js deleted file mode 100644 index e718fa96..00000000 --- a/test/register-test.js +++ /dev/null @@ -1,236 +0,0 @@ -import tape from 'tape'; -import tableEqual from './table-equal'; -import { aggregateFunctions, functions, windowFunctions } from '../src/op'; -import { ExprObject } from '../src/query/constants'; -import { - addAggregateFunction, - addFunction, - addPackage, - addTableMethod, - addVerb, - addWindowFunction -} from '../src/register'; -import { op, query, table } from '../src'; - -tape('addFunction registers new function', t => { - const SECRET = 0xDEADBEEF; - function secret() { return 0xDEADBEEF; } - - addFunction(secret); - addFunction('sssh', secret); - t.equal(functions.secret(), SECRET, 'add implicitly named function'); - t.equal(functions.sssh(), SECRET, 'add explicitly named function'); - - t.throws( - () => addFunction(() => 'foo'), - 'do not accept anonymous functions' - ); - - t.throws( - () => addFunction('abs', val => val < 0 ? -val : val), - 'do not overwrite existing functions' - ); - - const abs = op.abs; - t.doesNotThrow( - () => { - addFunction('abs', val => val < 0 ? -val : val, { override: true }); - addFunction('abs', abs, { override: true }); - }, - 'support override option' - ); - - t.end(); -}); - -tape('addAggregateFunction registers new aggregate function', t => { - const create = () => ({ - init: s => (s.altsign = -1, s.altsum = 0), - add: (s, v) => s.altsum += (s.altsign *= -1) * v, - rem: () => {}, - value: s => s.altsum - }); - - addAggregateFunction('altsum', { create, param: [1, 0] }); - t.deepEqual( - aggregateFunctions.altsum, - { create, param: [1, 0] }, - 'register aggregate function' - ); - t.equal( - table({ x: [1, 2, 3, 4, 5]}).rollup({ a: d => op.altsum(d.x) }).get('a', 0), - 3, 'evaluate aggregate function' - ); - - t.throws( - () => addAggregateFunction('mean', { create }), - 'do not overwrite existing function' - ); - - t.end(); -}); - -tape('addWindowFunction registers new window function', t => { - const create = (offset) => ({ - init: () => {}, - value: (w, f) => w.value(w.index, f) - w.index + (offset || 0) - }); - - addWindowFunction('vmi', { create, param: [1, 1] }); - t.deepEqual( - windowFunctions.vmi, - { create, param: [1, 1] }, - 'register window function' - ); - tableEqual(t, - table({ x: [1, 2, 3, 4, 5] }).derive({ a: d => op.vmi(d.x, 1) }).select('a'), - { a: [2, 2, 2, 2, 2] }, - 'evaluate window function' - ); - - t.throws( - () => addWindowFunction('rank', { create }), - 'do not overwrite existing function' - ); - - t.end(); -}); - -tape('addTableMethod registers a new table method', t => { - const dim1 = (t, ...args) => [t.numRows(), t.numCols(), ...args]; - const dim2 = (t) => [t.numRows(), t.numCols()]; - - addTableMethod('dims', dim1); - - t.deepEqual( - table({ a: [1, 2, 3], b: [4, 5, 6] }).dims('a', 'b'), - [3, 2, 'a', 'b'], - 'register table method' - ); - - t.throws( - () => addTableMethod('_foo', dim1), - 'do not allow names that start with underscore' - ); - - t.throws( - () => addTableMethod('toCSV', dim1, { override: true }), - 'do not override reserved names' - ); - - t.doesNotThrow( - () => addTableMethod('dims', dim1), - 'allow reassignment of existing value' - ); - - t.throws( - () => addTableMethod('dims', dim2), - 'do not override without option' - ); - - t.doesNotThrow( - () => addTableMethod('dims', dim2, { override: true }), - 'allow override with option' - ); - - t.deepEqual( - table({ a: [1, 2, 3], b: [4, 5, 6] }).dims('a', 'b'), - [3, 2], - 'register overridden table method' - ); - - t.end(); -}); - -tape('addVerb registers a new verb', t => { - const rup = (t, exprs) => t.rollup(exprs); - - addVerb('rup', rup, [ - { name: 'exprs', type: ExprObject } - ]); - - tableEqual(t, - table({ a: [1, 2, 3], b: [4, 5, 6] }).rup({ sum: op.sum('a') }), - { sum: [ 6 ] }, - 'register verb with table' - ); - - t.deepEqual( - query().rup({ sum: op.sum('a') }).toObject(), - { - verbs: [ - { - verb: 'rup', - exprs: { sum: { expr: 'd => op.sum(d["a"])', func: true } } - } - ] - }, - 'register verb with query' - ); - - t.end(); -}); - -tape('addPackage registers an extension package', t => { - const pkg = { - functions: { - secret_p: () => 0xDEADBEEF - }, - aggregateFunctions: { - altsum_p: { - create: () => ({ - init: s => (s.altsign = -1, s.altsum = 0), - add: (s, v) => s.altsum += (s.altsign *= -1) * v, - rem: () => {}, - value: s => s.altsum - }), - param: [1, 0] - } - }, - windowFunctions: { - vmi_p: { - create: (offset) => ({ - init: () => {}, - value: (w, f) => w.value(w.index, f) - w.index + (offset || 0) - }), - param: [1, 1] - } - }, - tableMethods: { - dims_p: t => [t.numRows(), t.numCols()] - }, - verbs: { - rup_p: { - method: (t, exprs) => t.rollup(exprs), - params: [ { name: 'exprs', type: ExprObject } ] - } - } - }; - - addPackage(pkg); - - t.equal(functions.secret_p, pkg.functions.secret_p, 'functions'); - t.equal(aggregateFunctions.altsum_p, pkg.aggregateFunctions.altsum_p, 'aggregate functions'); - t.equal(windowFunctions.vmi_p, pkg.windowFunctions.vmi_p, 'window functions'); - t.equal(table().dims_p.fn, pkg.tableMethods.dims_p, 'table methods'); - t.equal(table().rup_p.fn, pkg.verbs.rup_p.method, 'verbs'); - - t.doesNotThrow( - () => addPackage(pkg), - 'allow reassignment of existing value' - ); - - t.throws( - () => addPackage({ functions: { secret_p: () => 1 } }), - 'do not override without option' - ); - - const secret_p = () => 42; - addPackage({ functions: { secret_p } }, { override: true }); - t.equal( - functions.secret_p, secret_p, - 'allow override with option' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/table-equal.js b/test/table-equal.js deleted file mode 100644 index 553238b0..00000000 --- a/test/table-equal.js +++ /dev/null @@ -1,29 +0,0 @@ -import isArray from '../src/util/is-array'; -import isDate from '../src/util/is-date'; -import isObject from '../src/util/is-object'; -import isRegExp from '../src/util/is-regexp'; -import isTypedArray from '../src/util/is-typed-array'; - -export default function(t, table, data, message) { - table = table.reify(); - const tableData = {}; - for (const name of table.columnNames()) { - tableData[name] = Array.from(table.column(name), arrayMap); - } - t.deepEqual(tableData, data, message); -} - -function arrayMap(value) { - return isTypedArray(value) ? Array.from(value) - : isArray(value) ? value.map(arrayMap) - : isObject(value) && !isDate(value) && !isRegExp(value) ? objectMap(value) - : value; -} - -function objectMap(value) { - const obj = {}; - for (const name in value) { - obj[name] = arrayMap(value[name]); - } - return obj; -} \ No newline at end of file diff --git a/test/table/bitset-test.js b/test/table/bitset-test.js deleted file mode 100644 index 98019fa8..00000000 --- a/test/table/bitset-test.js +++ /dev/null @@ -1,103 +0,0 @@ -import tape from 'tape'; -import BitSet from '../../src/table/bit-set'; - -tape('BitSet manages a set of bits', t => { - const buckets = 5; - const size = 32 * buckets; - const bs = new BitSet(32 * buckets); - - // size - t.equal(bs.length, size, 'correct size'); - - // empty initial state - let tally = 0; - for (let i = 0; i < size; ++i) { - if (bs.get(i)) ++tally; - } - t.equal(tally, 0, 'bitset is clear'); - t.equal(bs.count(), 0, 'count = 0'); - - // set bits - let set = []; - const idx = [0, 33, 66, 99, 132]; - idx.forEach(i => bs.set(i)); - for (let i = 0; i < size; ++i) { - if (bs.get(i)) set.push(i); - } - t.deepEqual(set, idx, 'bitset has set bits'); - set = []; - for (let i = 0, b = bs.next(0); i < buckets; ++i, b = bs.next(b + 1)) { - set.push(b); - } - t.deepEqual(set, idx, 'bitset iterates set bits'); - t.equal(bs.count(), buckets, `count = ${buckets}`); - - // clear bits - for (let i = 0; i < buckets; ++i) { - bs.clear(33 * i); - } - tally = 0; - for (let i = 0; i < size; ++i) { - if (bs.get(i)) ++tally; - } - t.equal(tally, 0, 'bitset is clear'); - t.equal(bs.count(), 0, 'count = 0'); - - t.end(); -}); - -tape('BitSet ands with another BitSet', t => { - const ai = [1, 5, 9, 32, 34, 56, 62]; - const bi = [1, 4, 9, 33, 34, 55, 68]; - const a = new BitSet(69); - const b = new BitSet(69); - ai.forEach(i => a.set(i)); - bi.forEach(i => b.set(i)); - - const ab = a.and(b); - const ba = b.and(a); - - t.equal(ab.length, Math.min(a.length, b.length), 'correct size'); - t.equal(ab.length, ba.length, 'matching size'); - - const idx = [1, 9, 34].reduce((m, i) => (m[i] = 1, m), {}); - const flags = ['', '', '']; - for (let i = 0; i < ab.length; ++i) { - flags[0] += idx[i] ? 1 : 0; - flags[1] += ab.get(i) ? 1 : 0; - flags[2] += ba.get(i) ? 1 : 0; - } - - t.equal(flags[0], flags[1], 'bitset ab values'); - t.equal(flags[0], flags[2], 'bitset ba values'); - - t.end(); -}); - -tape('BitSet ors with another BitSet', t => { - const ai = [1, 5, 9, 32, 34, 56, 62]; - const bi = [1, 4, 9, 33, 34, 55, 68]; - const a = new BitSet(69); - const b = new BitSet(69); - ai.forEach(i => a.set(i)); - bi.forEach(i => b.set(i)); - - const ab = a.or(b); - const ba = b.or(a); - - t.equal(ab.length, Math.max(a.length, b.length), 'correct size'); - t.equal(ab.length, ba.length, 'matching size'); - - const idx = ai.concat(bi).reduce((m, i) => (m[i] = 1, m), {}); - const flags = ['', '', '']; - for (let i = 0; i < ab.length; ++i) { - flags[0] += idx[i] ? 1 : 0; - flags[1] += ab.get(i) ? 1 : 0; - flags[2] += ba.get(i) ? 1 : 0; - } - - t.equal(flags[0], flags[1], 'bitset ab values'); - t.equal(flags[0], flags[2], 'bitset ba values'); - - t.end(); -}); diff --git a/test/table/column-table-test.js b/test/table/column-table-test.js deleted file mode 100644 index d10cf0ea..00000000 --- a/test/table/column-table-test.js +++ /dev/null @@ -1,544 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { not } from '../../src/helpers/selection'; -import BitSet from '../../src/table/bit-set'; -import ColumnTable from '../../src/table/column-table'; - -tape('ColumnTable supports varied column types', t => { - const data = { - int: Uint32Array.of(1, 2, 3, 4, 5), - num: Float64Array.of(1.2, 2.3, 3.4, 4.5, 5.6), - str: ['a1', 'b2', 'c3', 'd4', 'e5'], - chr: 'abcde', - obj: [{a:1}, {b:2}, {c:3}, {d:4}, {e:5}] - }; - - const ref = { - int: [1, 2, 3, 4, 5], - num: [1.2, 2.3, 3.4, 4.5, 5.6], - str: ['a1', 'b2', 'c3', 'd4', 'e5'], - chr: ['a', 'b', 'c', 'd', 'e'], - obj: [{a:1}, {b:2}, {c:3}, {d:4}, {e:5}] - }; - - const ct = new ColumnTable(data); - - t.equal(ct.numRows(), 5, 'num rows'); - t.equal(ct.numCols(), 5, 'num cols'); - - const rows = [0, 1, 2, 3, 4]; - const get = { - int: rows.map(row => ct.get('int', row)), - num: rows.map(row => ct.get('num', row)), - str: rows.map(row => ct.get('str', row)), - chr: rows.map(row => ct.get('chr', row)), - obj: rows.map(row => ct.get('obj', row)) - }; - t.deepEqual(get, ref, 'extracted get values match'); - - const getters = ['int', 'num', 'str', 'chr', 'obj'].map(name => ct.getter(name)); - const getter = { - int: rows.map(row => getters[0](row)), - num: rows.map(row => getters[1](row)), - str: rows.map(row => getters[2](row)), - chr: rows.map(row => getters[3](row)), - obj: rows.map(row => getters[4](row)) - }; - t.deepEqual(getter, ref, 'extracted getter values match'); - - const arrays = ['int', 'num', 'str', 'chr', 'obj'].map(name => ct.columnArray(name)); - const array = { - int: rows.map(row => arrays[0][row]), - num: rows.map(row => arrays[1][row]), - str: rows.map(row => arrays[2][row]), - chr: rows.map(row => arrays[3][row]), - obj: rows.map(row => arrays[4][row]) - }; - t.deepEqual(array, ref, 'extracted columnArray values match'); - - const scanned = { - int: [], - num: [], - str: [], - chr: [], - obj: [] - }; - ct.scan((row, data) => { - for (const col in data) { - scanned[col].push(data[col].get(row)); - } - }); - t.deepEqual(scanned, ref, 'scanned values match'); - - t.end(); -}); - -tape('ColumnTable scan supports filtering and ordering', t => { - const table = new ColumnTable({ - a: ['a', 'a', 'a', 'b', 'b'], - b: [2, 1, 4, 5, 3] - }); - - let idx = []; - table.scan(row => idx.push(row), true); - t.deepEqual(idx, [0, 1, 2, 3, 4], 'standard scan'); - - const filter = new BitSet(5); - [1, 2, 4].forEach(i => filter.set(i)); - const ft = table.create({ filter }); - idx = []; - ft.scan(row => idx.push(row), true); - t.deepEqual(idx, [1, 2, 4], 'filtered scan'); - - const order = (u, v, { b }) => b.get(u) - b.get(v); - const ot = table.create({ order }); - t.ok(ot.isOrdered(), 'is ordered'); - idx = []; - ot.scan(row => idx.push(row), true); - t.deepEqual(idx, [1, 0, 4, 2, 3], 'ordered scan'); - - idx = []; - ot.scan(row => idx.push(row)); - t.deepEqual(idx, [0, 1, 2, 3, 4], 'no-order scan'); - - t.end(); -}); - -tape('ColumnTable scan supports early termination', t => { - const table = new ColumnTable({ - a: ['a', 'a', 'a', 'b', 'b'], - b: [2, 1, 4, 5, 3] - }); - - let count; - const visitor = (row, data, stop) => { if (++count > 1) stop(); }; - - count = 0; - table.scan(visitor, true); - t.equal(count, 2, 'standard scan'); - - count = 0; - const filter = new BitSet(5); - [1, 2, 4].forEach(i => filter.set(i)); - table.create({ filter }).scan(visitor, true); - t.equal(count, 2, 'filtered scan'); - - count = 0; - const order = (u, v, { b }) => b.get(u) - b.get(v); - table.create({ order }).scan(visitor, true); - t.equal(count, 2, 'ordered scan'); - - t.end(); -}); - -tape('ColumnTable memoizes indices', t => { - const ut = new ColumnTable({ v: [1, 3, 2] }); - const ui = ut.indices(false); - t.equal(ut.indices(), ui, 'memoize unordered'); - - const ot = ut.orderby('v'); - const of = ot.indices(false); - const oi = ot.indices(); - t.notEqual(of, oi, 'respect order flag'); - t.equal(ot.indices(), oi, 'memoize ordered'); - t.deepEqual(Array.from(oi), [0, 2, 1], 'indices ordered'); - - t.end(); -}); - -tape('ColumnTable supports column values output', t => { - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .filter(d => d.v > 1) - .orderby('v'); - - t.deepEqual( - Array.from(dt.values('u')), - ['a', 'b', 'a', 'b'], - 'column values, strings' - ); - - t.deepEqual( - Array.from(dt.values('v')), - [2, 3, 4, 5], - 'column values, numbers' - ); - - t.deepEqual( - Int32Array.from(dt.values('v')), - Int32Array.of(2, 3, 4, 5), - 'column values, typed array' - ); - - t.end(); -}); - -tape('ColumnTable supports column array output', t => { - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .filter(d => d.v > 1) - .orderby('v'); - - t.deepEqual( - dt.array('u'), - ['a', 'b', 'a', 'b'], - 'column array, strings' - ); - - t.deepEqual( - dt.array('v'), - [2, 3, 4, 5], - 'column array, numbers' - ); - - t.deepEqual( - dt.array('v', Int32Array), - Int32Array.of(2, 3, 4, 5), - 'column array, typed array' - ); - - t.end(); -}); - -tape('ColumnTable supports object output', t => { - const output = [ - { u: 'a', v: 1 }, - { u: 'a', v: 2 }, - { u: 'b', v: 3 }, - { u: 'a', v: 4 }, - { u: 'b', v: 5 } - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.deepEqual(dt.objects(), output, 'object data'); - - t.deepEqual( - dt.objects({ limit: 3 }), - output.slice(0, 3), - 'object data with limit' - ); - - t.deepEqual( - dt.objects({ columns: not('v') }), - output.map(d => ({ u: d.u })), - 'object data with column selection' - ); - - t.deepEqual( - dt.objects({ columns: { u: 'a', v: 'b'} }), - output.map(d => ({ a: d.u, b: d.v })), - 'object data with renaming column selection' - ); - - t.deepEqual( - dt.object(), - output[0], - 'single object, implicit row' - ); - - t.deepEqual( - dt.object(0), - output[0], - 'single object, explicit row' - ); - - t.deepEqual( - dt.object(1), - output[1], - 'single object, explicit row' - ); - - t.end(); -}); - -tape('ColumnTable supports grouped object output', t => { - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.deepEqual( - dt.groupby('u').objects({ grouped: 'object' }), - { - a: [ - { u: 'a', v: 1 }, - { u: 'a', v: 2 }, - { u: 'a', v: 4 } - ], - b: [ - { u: 'b', v: 3 }, - { u: 'b', v: 5 } - ] - }, - 'grouped object output' - ); - - t.deepEqual( - dt.groupby('u').objects({ grouped: 'entries' }), - [ - ['a',[ - { u: 'a', v: 1 }, - { u: 'a', v: 2 }, - { u: 'a', v: 4 } - ]], - ['b',[ - { u: 'b', v: 3 }, - { u: 'b', v: 5 } - ]] - ], - 'grouped entries output' - ); - - t.deepEqual( - dt.groupby('u').objects({ grouped: 'map' }), - new Map([ - ['a',[ - { u: 'a', v: 1 }, - { u: 'a', v: 2 }, - { u: 'a', v: 4 } - ]], - ['b',[ - { u: 'b', v: 3 }, - { u: 'b', v: 5 } - ]] - ]), - 'grouped map output' - ); - - t.deepEqual( - dt.groupby('u').objects({ grouped: true }), - new Map([ - ['a',[ - { u: 'a', v: 1 }, - { u: 'a', v: 2 }, - { u: 'a', v: 4 } - ]], - ['b',[ - { u: 'b', v: 3 }, - { u: 'b', v: 5 } - ]] - ]), - 'grouped map output, using true' - ); - - t.deepEqual( - dt.filter(d => d.v < 4).groupby('u').objects({ grouped: 'object' }), - { - a: [ - { u: 'a', v: 1 }, - { u: 'a', v: 2 } - ], - b: [ - { u: 'b', v: 3 } - ] - }, - 'grouped object output, with filter' - ); - - t.deepEqual( - dt.groupby('u').objects({ limit: 3, grouped: 'object' }), - { - a: [ - { u: 'a', v: 1 }, - { u: 'a', v: 2 } - ], - b: [ - { u: 'b', v: 3 } - ] - }, - 'grouped object output, with limit' - ); - - t.deepEqual( - dt.groupby('u').objects({ offset: 2, grouped: 'object' }), - { - a: [ - { u: 'a', v: 4 } - ], - b: [ - { u: 'b', v: 3 }, - { u: 'b', v: 5 } - ] - }, - 'grouped object output, with offset' - ); - - const dt2 = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - w: ['y', 'x', 'y', 'z', 'x'], - v: [2, 1, 4, 5, 3] - }) - .orderby('v'); - - t.deepEqual( - dt2.groupby(['u', 'w']).objects({ grouped: 'object' }), - { - a: { - x: [{ u: 'a', w: 'x', v: 1 }], - y: [{ u: 'a', w: 'y', v: 2 },{ u: 'a', w: 'y', v: 4 }] - }, - b: { - x: [{ u: 'b', w: 'x', v: 3 }], - z: [{ u: 'b', w: 'z', v: 5 }] - } - }, - 'grouped nested object output' - ); - - t.deepEqual( - dt2.groupby(['u', 'w']).objects({ grouped: 'entries' }), - [ - ['a', [ - ['y', [{ u: 'a', w: 'y', v: 2 }, { u: 'a', w: 'y', v: 4 }]], - ['x', [{ u: 'a', w: 'x', v: 1 }]] - ]], - ['b', [ - ['z', [{ u: 'b', w: 'z', v: 5 }]], - ['x', [{ u: 'b', w: 'x', v: 3 }]] - ]] - ], - 'grouped nested entries output' - ); - - t.deepEqual( - dt2.groupby(['u', 'w']).objects({ grouped: 'map' }), - new Map([ - ['a', new Map([ - ['x', [{ u: 'a', w: 'x', v: 1 }]], - ['y', [{ u: 'a', w: 'y', v: 2 },{ u: 'a', w: 'y', v: 4 }]] - ])], - ['b', new Map([ - ['x', [{ u: 'b', w: 'x', v: 3 }]], - ['z', [{ u: 'b', w: 'z', v: 5 }]] - ])] - ]), - 'grouped nested map output' - ); - - t.end(); -}); - -tape('ColumnTable supports iterator output', t => { - const output = [ - { u: 'a', v: 2 }, - { u: 'a', v: 1 }, - { u: 'a', v: 4 }, - { u: 'b', v: 5 }, - { u: 'b', v: 3 } - ]; - - const dt = new ColumnTable({ - u: ['a', 'a', 'a', 'b', 'b'], - v: [2, 1, 4, 5, 3] - }); - - t.deepEqual([...dt], output, 'iterator data'); - t.deepEqual( - [...dt.orderby('v')], - output.slice().sort((a, b) => a.v - b.v), - 'iterator data orderby' - ); - - t.end(); -}); - -tape('ColumnTable toString shows table state', t => { - const dt = new ColumnTable({ - a: ['a', 'a', 'a', 'b', 'b'], - b: [2, 1, 4, 5, 3] - }); - t.equal( - dt.toString(), - '[object Table: 2 cols x 5 rows]', - 'table toString' - ); - - const filter = new BitSet(5); - [1, 2, 4].forEach(i => filter.set(i)); - t.equal( - dt.create({ filter }).toString(), - '[object Table: 2 cols x 3 rows (5 backing)]', - 'filtered table toString' - ); - - const groups = { names: ['a'], get: [row => dt.get('a', row)], size: 2 }; - t.equal( - dt.create({ groups }).toString(), - '[object Table: 2 cols x 5 rows, 2 groups]', - 'grouped table toString' - ); - - const order = (u, v, { b }) => b[u] - b[v]; - t.equal( - dt.create({ order }).toString(), - '[object Table: 2 cols x 5 rows, ordered]', - 'ordered table toString' - ); - - t.equal( - dt.create({ filter, order, groups }).toString(), - '[object Table: 2 cols x 3 rows (5 backing), 2 groups, ordered]', - 'filtered, grouped, ordered table toString' - ); - - t.end(); -}); - -tape('ColumnTable assign merges tables', t => { - const t1 = new ColumnTable({ a: [1], b: [2], c: [3] }); - const t2 = new ColumnTable({ b: [-2], d: [4] }); - const t3 = new ColumnTable({ a: [-1], e: [5] }); - const dt = t1.assign(t2, t3); - - tableEqual(t, dt, { - a: [-1], b: [-2], c: [3], d: [4], e: [5] - }, 'assigned data'); - - t.deepEqual( - dt.columnNames(), - ['a', 'b', 'c', 'd', 'e'], - 'assigned names' - ); - - t.throws( - () => t1.assign(new ColumnTable({ c: [1, 2, 3] })), - 'throws on mismatched row counts' - ); - - tableEqual(t, t1.assign({ b: [-2], d: [4] }), { - a: [1], b: [-2], c: [3], d: [4] - }, 'assigned data from object'); - - t.throws( - () => t1.assign({ c: [1, 2, 3] }), - 'throws on mismatched row counts from object' - ); - - t.end(); -}); - -tape('ColumnTable transform applies transformations', t => { - const dt = new ColumnTable({ a: [1, 2], b: [2, 3], c: [3, 4] }); - - tableEqual(t, - dt.transform( - t => t.filter(d => d.c > 3), - t => t.select('a', 'b'), - t => t.reify() - ), - { a: [2], b: [3] }, - 'transform pipeline' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/table/columns-from-test.js b/test/table/columns-from-test.js deleted file mode 100644 index bb877be9..00000000 --- a/test/table/columns-from-test.js +++ /dev/null @@ -1,130 +0,0 @@ -import tape from 'tape'; -import columnsFrom from '../../src/table/columns-from'; - -tape('columnsFrom supports array input', t => { - t.deepEqual( - columnsFrom([]), - { }, - 'from empty array, names implicit' - ); - - t.deepEqual( - columnsFrom([], ['a', 'b']), - { a: [], b: [] }, - 'from empty array, names explicit' - ); - - t.deepEqual( - columnsFrom([ {a: 1, b: 2}, {a: 3, b: 4} ]), - { a: [1, 3], b: [2, 4] }, - 'from array, names implicit' - ); - - t.deepEqual( - columnsFrom([ {a: 1, b: 2}, {a: 3, b: 4} ], ['a', 'b']), - { a: [1, 3], b: [2, 4] }, - 'from array, names explicit' - ); - - t.deepEqual( - columnsFrom([ {a: 1, b: 2}, {a: 3, b: 4} ], ['b']), - { b: [2, 4] }, - 'from array, names partial' - ); - - t.end(); -}); - -tape('columnsFrom supports iterable input', t => { - const data = [ {a: 1, b: 2}, {a: 3, b: 4} ]; - const iterable = { - [Symbol.iterator]: () => data.values() - }; - - t.deepEqual( - columnsFrom(iterable), - { a: [1, 3], b: [2, 4] }, - 'from iterable, names implicit' - ); - - t.deepEqual( - columnsFrom(iterable, ['a', 'b']), - { a: [1, 3], b: [2, 4] }, - 'from iterable, names explicit' - ); - - t.deepEqual( - columnsFrom(iterable, ['b']), - { b: [2, 4] }, - 'from iterable, names partial' - ); - - t.end(); -}); - -tape('columnsFrom supports object input', t => { - t.deepEqual( - columnsFrom({}), - { key: [], value: [] }, - 'from empty object, names implicit' - ); - - t.deepEqual( - columnsFrom([], ['k', 'v']), - { k: [], v: [] }, - 'from empty object, names explicit' - ); - - t.deepEqual( - columnsFrom({ a: 1, b: 2, c: 3 }), - { key: ['a', 'b', 'c'], value: [1, 2, 3] }, - 'from object, names implicit' - ); - - t.deepEqual( - columnsFrom({ a: 1, b: 2, c: 3 }, ['k', 'v']), - { k: ['a', 'b', 'c'], v: [1, 2, 3] }, - 'from object, names explicit' - ); - - t.deepEqual( - columnsFrom({ a: 1, b: 2, c: 3 }, [null, 'v']), - { v: [1, 2, 3] }, - 'from object, names partial' - ); - - t.end(); -}); - -tape('columnsFrom supports map input', t => { - const map = new Map([ ['a', 1], ['b', 2], ['c', 3] ]); - - t.deepEqual( - columnsFrom(map), - { key: ['a', 'b', 'c'], value: [1, 2, 3] }, - 'from map, names implicit' - ); - - t.deepEqual( - columnsFrom(map, ['k', 'v']), - { k: ['a', 'b', 'c'], v: [1, 2, 3] }, - 'from map, names explicit' - ); - - t.deepEqual( - columnsFrom(map, [null, 'v']), - { v: [1, 2, 3] }, - 'from map, names partial' - ); - - t.end(); -}); - -tape('columnsFrom throws on unsupported type', t => { - t.throws(() => columnsFrom(true), 'no boolean'); - t.throws(() => columnsFrom(new Date()), 'no date'); - t.throws(() => columnsFrom(12.3), 'no number'); - t.throws(() => columnsFrom(/bop/), 'no regexp'); - t.throws(() => columnsFrom('foo'), 'no string'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/concat-test.js b/test/verbs/concat-test.js deleted file mode 100644 index e5627e4f..00000000 --- a/test/verbs/concat-test.js +++ /dev/null @@ -1,42 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -tape('concat combines tables', t => { - const t1 = table({ a: [1, 2], b: [3, 4] }); - const t2 = table({ a: [3, 4], c: [5, 6] }); - const dt = t1.concat(t2); - - t.equal(dt.numRows(), 4, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 3, 4], - b: [3, 4, undefined, undefined] - }, 'concat data'); - - t.end(); -}); - -tape('concat combines multiple tables', t => { - const t1 = table({ a: [1, 2], b: [3, 4] }); - const t2 = table({ a: [3, 4], c: [5, 6] }); - const t3 = table({ a: [5, 6], b: [7, 8] }); - - const dt = t1.concat(t2, t3); - t.equal(dt.numRows(), 6, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 3, 4, 5, 6], - b: [3, 4, undefined, undefined, 7, 8] - }, 'concat data'); - - const at = t1.concat([t2, t3]); - t.equal(at.numRows(), 6, 'num rows'); - t.equal(at.numCols(), 2, 'num cols'); - tableEqual(t, at, { - a: [1, 2, 3, 4, 5, 6], - b: [3, 4, undefined, undefined, 7, 8] - }, 'concat data'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/dedupe-test.js b/test/verbs/dedupe-test.js deleted file mode 100644 index fb989404..00000000 --- a/test/verbs/dedupe-test.js +++ /dev/null @@ -1,31 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -tape('dedupe de-duplicates table', t => { - const dt = table({ a: [1, 2, 1, 2, 1], b: [3, 4, 3, 4, 5] }) - .dedupe(); - - t.equal(dt.numRows(), 3, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 1], - b: [3, 4, 5] - }, 'dedupe data'); - t.equal(dt.isGrouped(), false, 'dedupe not grouped'); - t.end(); -}); - -tape('dedupe de-duplicates table based on keys', t => { - const dt = table({ a: [1, 2, 1, 2, 1], b: [3, 4, 3, 4, 5] }) - .dedupe('a'); - - t.equal(dt.numRows(), 2, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2], - b: [3, 4] - }, 'dedupe data'); - t.equal(dt.isGrouped(), false, 'dedupe not grouped'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/derive-test.js b/test/verbs/derive-test.js deleted file mode 100644 index ffe53e08..00000000 --- a/test/verbs/derive-test.js +++ /dev/null @@ -1,359 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, rolling, table } from '../../src'; -const { abs, lag, mean, median, rank, stdev } = op; - -tape('derive creates new columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const dt = table(data).derive({ c: d => d.a + d.b }); - t.equal(dt.numRows(), 4, 'num rows'); - t.equal(dt.numCols(), 3, 'num cols'); - tableEqual(t, dt, { ...data, c: [3, 7, 11, 15] }, 'derive data'); - t.end(); -}); - -tape('derive overwrites existing columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - tableEqual(t, - table(data).derive({ a: d => d.a + d.b }), - { ...data, a: [3, 7, 11, 15] }, - 'derive data' - ); - t.end(); -}); - -tape('derive drops existing columns with option', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - tableEqual(t, - table(data).derive({ z: d => d.a + d.b }, { drop: true }), - { z: [3, 7, 11, 15] }, - 'derive data' - ); - t.end(); -}); - -tape('derive can relocate new columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const t1 = table(data).derive({ z: d => d.a + d.b }, { before: 'a' }); - - tableEqual(t, - t1, - { z: [3, 7, 11, 15], ...data }, - 'derive data, with before' - ); - - t.deepEqual( - t1.columnNames(), - ['z', 'a', 'b'], - 'derive data columns, with before' - ); - - const t2 = table(data).derive({ z: d => d.a + d.b }, { after: 'a' }); - - tableEqual(t, - t2, - { a: data.a, z: [3, 7, 11, 15], b: data.b }, - 'derive data, with after' - ); - - t.deepEqual( - t2.columnNames(), - ['a', 'z', 'b'], - 'derive data columns, with after' - ); - - const t3 = table(data).derive({ a: d => -d.a, z: d => d.a + d.b }, { after: 'b' }); - - tableEqual(t, - t3, - { a: [-1, -3, -5, -7], b: data.b, z: [3, 7, 11, 15] }, - 'derive data, with after and overwrite' - ); - - t.deepEqual( - t3.columnNames(), - ['a', 'b', 'z'], - 'derive data columns, with after and overwrite' - ); - - t.end(); -}); - -tape('derive supports aggregate and window operators', t => { - const n = 10; - const k = Array(n); - const a = Array(n); - const b = Array(n); - - for (let i = 0; i < n; ++i) { - k[i] = i % 3; - a[i] = i; - b[i] = i + 1; - } - - const td = table({ k, a, b }) - .groupby('k') - .orderby('a') - .derive({ - rank: () => rank(), - diff: ({ a, b }) => a - lag(b, 1, 0), - roll: rolling(d => mean(d.a), [-2, 0]) - }); - tableEqual(t, td.select('rank', 'diff', 'roll'), { - rank: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4], - diff: [0, 1, 2, 2, 2, 2, 2, 2, 2, 2], - roll: [0, 1, 2, 1.5, 2.5, 3.5, 3, 4, 5, 6] - }, 'derive window queries'); - - const tz = td - .ungroup() - .derive({ - z: ({ a }) => abs(a - mean(a)) / stdev(a) - }); - tableEqual(t, tz.select('z'), { - z: [ - 1.4863010829205867, - 1.1560119533826787, - 0.8257228238447705, - 0.49543369430686224, - 0.1651445647689541, - 0.1651445647689541, - 0.49543369430686224, - 0.8257228238447705, - 1.1560119533826787, - 1.4863010829205867 - ] - }, 'z-score'); - - const tm = tz - .derive({ dev: d => abs(d.a - median(d.a)) }) - .rollup({ mad: d => median(d.dev) }); - tableEqual(t, tm.select('mad'), { - mad: [ 2.5 ] - }, 'mad'); - - t.end(); -}); - -tape('derive supports parameters', t => { - const output = { - n: [1, 2, 3, 4], - p: [NaN, 1, 1, 1 ] - }; - - const dt = table({ n: [1, 2, 3, 4] }).params({lag: 1}); - - tableEqual(t, - dt.derive({p: (d, $) => d.n * $.lag - op.lag(d.n, 1)}), - output, - 'parameter in main scope' - ); - - tableEqual(t, - dt.derive({p: (d, $) => d.n - op.lag(d.n, $.lag)}), - output, - 'parameter in operator input scope' - ); - - tableEqual(t, - dt.derive({p: 'd.n * $.lag - op.lag(d.n, 1)'}), - output, - 'default parameter in main scope' - ); - - tableEqual(t, - dt.derive({p: 'd.n * lag - op.lag(d.n, 1)'}), - output, - 'direct parameter in main scope' - ); - - tableEqual(t, - dt.derive({p: 'd.n - op.lag(d.n, $.lag)'}), - output, - 'default parameter in operator input scope' - ); - - tableEqual(t, - dt.derive({p: 'd.n - op.lag(d.n, lag)'}), - output, - 'direct parameter in operator input scope' - ); - - t.end(); -}); - -tape('derive supports differing window frames', t => { - const dt = table({ x: [1, 2, 3, 4, 5, 6] }) - .derive({ - cs0: rolling(d => op.sum(d.x)), - cs4: rolling(d => op.sum(d.x), [-4, 0]), - cs2: rolling(d => op.sum(d.x), [-2, 0]) - }); - - tableEqual(t, dt, - { - x: [1, 2, 3, 4, 5, 6], - cs0: [1, 3, 6, 10, 15, 21], - cs4: [1, 3, 6, 10, 15, 20], - cs2: [1, 3, 6, 9, 12, 15] - }, - 'derive data' - ); - - t.end(); -}); - -tape('derive supports streaming value windows', t => { - const dt = table({ val: [1, 2, 3, 4, 5] }) - .orderby('val') - .derive({ - sum: rolling(op.sum('val'), [-2, 0]), - index: () => op.row_number() - 1 - }) - .derive({ - frame: rolling(op.array_agg('index'), [-2, 0]) - }); - - tableEqual(t, dt, - { - val: [1, 2, 3, 4, 5], - sum: [1, 3, 6, 9, 12], - index: [0, 1, 2, 3, 4], - frame: [ [0], [0,1], [0,1,2], [1,2,3], [2,3,4] ] - }, - 'derive data' - ); - t.end(); -}); - -tape('derive supports bigint values', t => { - const data = { - v: [1n, 2n, 3n, 4n, 5n] - }; - - function roll(obj) { - for (const key in obj) { - obj[key] = rolling(obj[key], [-1, 1]); - } - return obj; - } - - const dt = table(data) - .derive(roll({ - v: d => 2n ** d.v, - sum: op.sum('v'), - prod: op.product('v'), - min: op.min('v'), - max: op.max('v'), - med: op.median('v'), - vals: op.array_agg('v'), - uniq: op.array_agg_distinct('v') - })); - - t.deepEqual( - dt.objects(), - [ - { v: 2n, sum: 3n, prod: 2n, min: 1n, max: 2n, med: 1n, vals: [ 1n, 2n ], uniq: [ 1n, 2n ] }, - { v: 4n, sum: 6n, prod: 6n, min: 1n, max: 3n, med: 2n, vals: [ 1n, 2n, 3n ], uniq: [ 1n, 2n, 3n ] }, - { v: 8n, sum: 9n, prod: 24n, min: 2n, max: 4n, med: 3n, vals: [ 2n, 3n, 4n ], uniq: [ 2n, 3n, 4n ] }, - { v: 16n, sum: 12n, prod: 60n, min: 3n, max: 5n, med: 4n, vals: [ 3n, 4n, 5n ], uniq: [ 3n, 4n, 5n ] }, - { v: 32n, sum: 9n, prod: 20n, min: 4n, max: 5n, med: 4n, vals: [ 4n, 5n ], uniq: [ 4n, 5n ] } - ], - 'derive data' - ); - - t.end(); -}); - -tape('derive aggregates support ordered tables', t => { - const rt = table({ v: [3, 1, 4, 2] }) - .orderby('v') - .derive({ a: op.array_agg('v') }); - - tableEqual(t, rt, { - v: [1, 2, 3, 4], - a: [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] - }, 'derive data'); - t.end(); -}); - -tape('derive supports recode function', t => { - const dt = table({ x: ['foo', 'bar', 'baz'] }); - - tableEqual(t, - dt.derive({ x: d => op.recode(d.x, {foo: 'farp', bar: 'borp'}, 'other') }), - { x: ['farp', 'borp', 'other'] }, - 'derive data, recode inline map Object' - ); - - const map = { - foo: 'farp', - bar: 'borp' - }; - - tableEqual(t, - dt.params({ map }) - .derive({ x: (d, $) => op.recode(d.x, $.map) }), - { x: ['farp', 'borp', 'baz'] }, - 'derive data, recode param map Object' - ); - - const map2 = new Map([['foo', 'farp'], ['bar', 'borp']]); - - tableEqual(t, - dt.params({ map2 }) - .derive({ x: (d, $) => op.recode(d.x, $.map2) }), - { x: ['farp', 'borp', 'baz'] }, - 'derive data, recode param map Map' - ); - - t.end(); -}); - -tape('derive supports fill window functions', t => { - const t1 = table({ x: ['a', null, undefined, 'b', NaN, null, 'c'] }); - - tableEqual(t, - t1.derive({ x: op.fill_down('x') }), - { x: ['a', 'a', 'a', 'b', 'b', 'b', 'c'] }, - 'derive data, fill_down' - ); - - tableEqual(t, - t1.derive({ x: op.fill_up('x') }), - { x: ['a', 'b', 'b', 'b', 'c', 'c', 'c'] }, - 'derive data, fill_up' - ); - - const t2 = table({ x: [null, 'a', null] }); - - tableEqual(t, - t2.derive({ x: op.fill_down('x', '?') }), - { x: ['?', 'a', 'a'] }, - 'derive data, fill_down with default' - ); - - tableEqual(t, - t2.derive({ x: op.fill_up('x', '?') }), - { x: ['a', 'a', '?'] }, - 'derive data, fill_up with default' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/filter-test.js b/test/verbs/filter-test.js deleted file mode 100644 index 34aa6b39..00000000 --- a/test/verbs/filter-test.js +++ /dev/null @@ -1,55 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, table } from '../../src'; - -tape('filter filters a table', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).filter(d => 1 < d.a && d.a < 7).reify(); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - tableEqual(t, ft, { a: [3, 5], b: [4, 6] }, 'filtered data'); - t.end(); -}); - -tape('filter filters a filtered table', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols) - .filter(d => 1 < d.a) - .filter(d => d.a < 7) - .reify(); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - tableEqual(t, ft, { a: [3, 5], b: [4, 6] }, 'filter data'); - t.end(); -}); - -tape('filter supports value functions', t => { - const cols = { a: ['aa', 'ab', 'ba', 'bb'], b: [2, 4, 6, 8] }; - const ft = table(cols).filter(d => op.startswith(d.a, 'a')); - tableEqual(t, ft, { a: ['aa', 'ab'], b: [2, 4] }, 'filter data'); - t.end(); -}); - -tape('filter supports aggregate functions', t => { - const cols = { a: [1, 3, 5, 7], b: [2, 4, 6, 8] }; - const ft = table(cols).filter(({ a }) => a < op.median(a)); - tableEqual(t, ft, { a: [1, 3], b: [2, 4] }, 'filter data'); - t.end(); -}); - -tape('filter supports window functions', t => { - const cols = { a: [1, 3, 5, 7], b: [2, 4, 6, 8]}; - const ft = table(cols).filter(({ a }) => op.lag(a) > 2); - tableEqual(t, ft, { a: [5, 7], b: [6, 8] }, 'filter data'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/fold-test.js b/test/verbs/fold-test.js deleted file mode 100644 index dc4187a7..00000000 --- a/test/verbs/fold-test.js +++ /dev/null @@ -1,37 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { not, table } from '../../src'; - -function data() { - return { - k: ['a', 'b', 'b'], - x: [1, 2, 3], - y: [9, 8, 7] - }; -} - -function output(key = 'key', value = 'value') { - return { - k: ['a', 'a', 'b', 'b', 'b', 'b'], - [key]: ['x', 'y', 'x', 'y', 'x', 'y'], - [value]: [1, 9, 2, 8, 3, 7] - }; -} - -tape('fold generates key-value pair columns', t => { - const ut = table(data()).fold(['x', 'y']); - tableEqual(t, ut, output(), 'fold data'); - t.end(); -}); - -tape('fold accepts select statements', t => { - const ut = table(data()).fold(not('k')); - tableEqual(t, ut, output(), 'fold selected data'); - t.end(); -}); - -tape('fold accepts named output columns', t => { - const ut = table(data()).fold(['x', 'y'], { as: ['u', 'v'] }); - tableEqual(t, ut, output('u', 'v'), 'fold as data'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/groupby-test.js b/test/verbs/groupby-test.js deleted file mode 100644 index 9d806f87..00000000 --- a/test/verbs/groupby-test.js +++ /dev/null @@ -1,173 +0,0 @@ -import tape from 'tape'; -import fromArrow from '../../src/format/from-arrow'; -import { desc, op, table } from '../../src'; - -tape('groupby computes groups based on field names', t => { - const data = { - k: 'aabb'.split(''), - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const gt = table(data).groupby('k'); - - t.equal(gt.numRows(), 4, 'num rows'); - t.equal(gt.numCols(), 3, 'num cols'); - t.equal(gt.isGrouped(), true, 'is grouped'); - - const { keys, names, rows, size } = gt.groups(); - t.deepEqual( - { keys, names, rows, size }, - { - keys: Uint32Array.from([0, 0, 1, 1]), - names: ['k'], - rows: [0, 2], - size: 2 - }, - 'group data' - ); - t.end(); -}); - -tape('groupby computes groups based on a function', t => { - const data = { - k: 'aabb'.split(''), - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const gt = table(data).groupby({ key: d => d.k }); - - t.equal(gt.numRows(), 4, 'num rows'); - t.equal(gt.numCols(), 3, 'num cols'); - t.equal(gt.isGrouped(), true, 'is grouped'); - - const { keys, names, rows, size } = gt.groups(); - t.deepEqual( - { keys, names, rows, size }, - { - keys: Uint32Array.from([0, 0, 1, 1]), - names: ['key'], - rows: [0, 2], - size: 2 - }, - 'group data' - ); - t.end(); -}); - -tape('groupby supports aggregate functions', t => { - const data = { a: [1, 3, 5, 7] }; - const gt = table(data).groupby({ res: d => op.abs(d.a - op.mean(d.a)) }); - - const { keys, names, rows, size } = gt.groups(); - t.deepEqual( - { keys, names, rows, size }, - { - keys: Uint32Array.from([0, 1, 1, 0]), - names: ['res'], - rows: [0, 1], - size: 2 - }, - 'group data' - ); - t.end(); -}); - -tape('groupby supports grouped aggregate functions', t => { - const data = { k: [0, 0, 1, 1], a: [1, 3, 5, 7] }; - const gt = table(data) - .groupby('k') - .groupby({ res: d => d.a - op.mean(d.a) }); - - const { keys, names, rows, size } = gt.groups(); - t.deepEqual( - { keys, names, rows, size }, - { - keys: Uint32Array.from([0, 1, 0, 1]), - names: ['res'], - rows: [0, 1], - size: 2 - }, - 'group data' - ); - t.end(); -}); - -tape('groupby throws on window functions', t => { - const data = { a: [1, 3, 5, 7] }; - t.throws(() => table(data).groupby({ res: d => op.lag(d.a) }), 'no window'); - t.end(); -}); - -tape('groupby persists after filter', t => { - const dt = table({ a: [1, 3, 5, 7] }) - .groupby('a') - .filter(d => d.a > 1); - - t.ok(dt.isGrouped(), 'is grouped'); - - const { rows, get } = dt.groups(); - t.deepEqual( - rows.map(r => get[0](r)), - [3, 5, 7], - 'retrieves correct group values' - ); - - t.end(); -}); - -tape('groupby persists after select', t => { - const dt = table({ a: [1, 3, 5, 7], b: [2, 4, 6, 8] }) - .groupby('a') - .select('b'); - - t.ok(dt.isGrouped(), 'is grouped'); - - const { rows, get } = dt.groups(); - t.deepEqual( - rows.map(r => get[0](r)), - [1, 3, 5, 7], - 'retrieves correct group values' - ); - - t.end(); -}); - -tape('groupby persists after reify', t => { - const dt = table({ a: [1, 3, 5, 7], b: [2, 4, 6, 8] }) - .groupby('a') - .orderby(desc('b')) - .filter(d => d.a > 1) - .select('b') - .reify(); - - t.ok(dt.isGrouped(), 'is grouped'); - - const { rows, get } = dt.groups(); - t.deepEqual( - rows.map(r => get[0](r)), - [3, 5, 7], - 'retrieves correct group values' - ); - - t.end(); -}); - -tape('groupby optimizes Arrow dictionary columns', t => { - const dt = fromArrow( - table({ - d: ['a', 'a', 'b', 'b'], - v: [1, 2, 3, 4] - }).toArrow() - ); - - const gt = dt.groupby('d'); - t.equal( - gt.groups().keys, - dt.column('d').groups().keys, - 'groupby reuses internal dictionary keys' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/impute-test.js b/test/verbs/impute-test.js deleted file mode 100644 index ab0734bb..00000000 --- a/test/verbs/impute-test.js +++ /dev/null @@ -1,183 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, table } from '../../src'; - -const na = undefined; - -tape('impute imputes values for an ungrouped table', t => { - const dt = table({ x: [1, null, NaN, undefined, 3] }); - - tableEqual(t, - dt.impute({ x: () => 2 }), - { x: [1, 2, 2, 2, 3] }, - 'impute data, constant' - ); - - tableEqual(t, - dt.impute({ x: op.mean('x') }), - { x: [1, 2, 2, 2, 3] }, - 'impute data, mean' - ); - - t.end(); -}); - -tape('impute imputes values for a grouped table', t => { - const dt = table({ - k: [0, 0, 0, 0, 1, 1, 1, 1], - x: [1, null, NaN, 3, 3, null, undefined, 5] - }).groupby('k'); - - const t1 = dt.impute({ x: () => 2 }); - tableEqual(t, t1, { - k: [0, 0, 0, 0, 1, 1, 1, 1], - x: [1, 2, 2, 3, 3, 2, 2, 5] - }, 'impute data, constant'); - - t.equal(dt.groups(), t1.groups(), 'groups'); - - const t2 = dt.impute({ x: op.mean('x') }); - tableEqual(t, t2, { - k: [0, 0, 0, 0, 1, 1, 1, 1], - x: [1, 2, 2, 3, 3, 4, 4, 5] - }, 'impute data, mean'); - - t.equal(dt.groups(), t2.groups(), 'groups'); - - t.end(); -}); - -tape('impute imputes expanded rows for an ungrouped table', t => { - const dt = table({ - x: ['a', 'b', 'c'], - y: [1, 2, 3], - z: ['x', 'x', 'x'] - }) - .impute(null, { expand: ['x', 'y'] }) - .orderby('x', 'y') - .reify(); - - tableEqual(t, dt, { - x: ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], - y: [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ], - z: ['x', na, na, na, 'x', na, na, na, 'x'] - }, 'impute data'); - - t.equal(dt.groups(), null, 'no groups'); - - t.end(); -}); - -tape('impute imputes expanded rows for a grouped table', t => { - const dt = table({ - x: ['a', 'a', 'b', 'c'], - y: [1, 1, 2, 3], - z: ['x', 'x', 'y', 'z'], - v: [0, 9, 8, 7] - }) - .groupby('x', 'y') - .impute(null, { expand: 'z' }) - .orderby('x', 'y', 'z') - .reify(); - - tableEqual(t, dt, { - x: ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], - y: [ 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 ], - z: ['x', 'x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'], - v: [ 0, 9, na, na, na, 8, na, na, na, 7 ] - }, 'impute data'); - - t.deepEqual( - Array.from(dt.groups().keys), - [0, 0, 0, 0, 1, 1, 1, 2, 2, 2], - 'group keys' - ); - - t.end(); -}); - -tape('impute imputes values and rows for an ungrouped table', t => { - const imp = 'imp'; - const dt = table({ - x: ['a', 'b', 'c'], - y: [1, 2, 3], - z: ['x', 'x', 'x'] - }) - .impute({ z: () => 'imp' }, { expand: ['x', 'y'] }) - .orderby('x', 'y') - .reify(); - - tableEqual(t, dt, { - x: ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], - y: [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ], - z: ['x', imp, imp, imp, 'x', imp, imp, imp, 'x'] - }, 'impute data'); - - t.equal(dt.groups(), null, 'no groups'); - - t.end(); -}); - -tape('impute imputes expanded rows for a grouped table', t => { - const dt = table({ - x: ['a', 'a', 'b', 'c'], - y: [1, 1, 2, 3], - z: ['x', 'x', 'y', 'z'], - v: [0, 9, 8, 7] - }) - .groupby('x', 'y') - .impute({ v: op.max('v') }, { expand: 'z' }) - .orderby('x', 'y', 'z') - .reify(); - - tableEqual(t, dt, { - x: ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], - y: [ 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 ], - z: ['x', 'x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'], - v: [ 0, 9, 9, 9, 8, 8, 8, 7, 7, 7 ] - }, 'impute data'); - - t.deepEqual( - Array.from(dt.groups().keys), - [0, 0, 0, 0, 1, 1, 1, 2, 2, 2], - 'group keys' - ); - - t.end(); -}); - -tape('impute imputes expanded rows given fixed values', t => { - const dt = table({ - x: ['a', 'a', 'b', 'c'], - y: [1, 1, 2, 3], - z: ['x', 'x', 'y', 'z'], - v: [0, 9, 8, 7] - }) - .groupby('x', 'y') - .impute({ v: op.max('v') }, { expand: { z: ['x', 'y', 'z'] } }) - .orderby('x', 'y', 'z') - .reify(); - - tableEqual(t, dt, { - x: ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'], - y: [ 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 ], - z: ['x', 'x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'], - v: [ 0, 9, 9, 9, 8, 8, 8, 7, 7, 7 ] - }, 'impute data'); - - t.deepEqual( - Array.from(dt.groups().keys), - [0, 0, 0, 0, 1, 1, 1, 2, 2, 2], - 'group keys' - ); - - t.end(); -}); - -tape('impute throws on non-existent values column', t => { - const dt = table({ x: [1, null, NaN, undefined, 3] }); - - t.throws(() => dt.impute({ z: () => 1 })); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/join-filter-test.js b/test/verbs/join-filter-test.js deleted file mode 100644 index f8103466..00000000 --- a/test/verbs/join-filter-test.js +++ /dev/null @@ -1,159 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -function joinTables() { - return [ - table({ - k: ['a', 'b', 'b', 'c'], - x: [1, 2, 3, 4], - y: [9, 8, 7, 6] - }), - table({ - u: ['b', 'a', 'b', 'd'], - v: [5, 4, 6, 0] - }) - ]; -} - -tape('semijoin uses natural join criteria', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 0]}); - const tr = table({ k: [1, 2], b: [5, 6]}); - - const tj = tl.semijoin(tr); - - tableEqual(t, tj, { - k: [ 1, 2 ], - a: [ 3, 4 ] - }, 'natural semijoin data'); - - t.end(); -}); - -tape('semijoin filters left table to matching rows', t => { - const [tl, tr] = joinTables(); - const output = { - k: [ 'a', 'b', 'b' ], - x: [ 1, 2, 3 ], - y: [ 9, 8, 7 ] - }; - - tableEqual(t, - tl.semijoin(tr, ['k', 'u']), - output, - 'semijoin data, with keys' - ); - - tableEqual(t, - tl.semijoin(tr, (a, b) => a.k === b.u), - output, - 'semijoin data, with predicate' - ); - - t.end(); -}); - -tape('antijoin uses natural join criteria', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 0]}); - const tr = table({ k: [1, 2], b: [5, 6]}); - - const tj = tl.antijoin(tr); - - tableEqual(t, tj, { - k: [ 3 ], - a: [ 0 ] - }, 'natural antijoin data'); - - t.end(); -}); - -tape('antijoin filters left table to non-matching rows', t => { - const [tl, tr] = joinTables(); - const output = { - k: [ 'c' ], - x: [ 4 ], - y: [ 6 ] - }; - - tableEqual(t, - tl.antijoin(tr, ['k', 'u']), - output, - 'antijoin data, with keys' - ); - - tableEqual(t, - tl.antijoin(tr, (a, b) => a.k === b.u), - output, - 'antijoin data, with predicate' - ); - - t.end(); -}); - -tape('except returns table given empty input', t => { - const data = { k: [1, 2, 3], a: [3, 4, 0] }; - const tl = table(data); - tableEqual(t, tl.except([]), data, 'except data'); - t.end(); -}); - -tape('except removes intersecting rows', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 0]}); - const tr = table({ k: [1, 2], a: [3, 4]}); - - tableEqual(t, tl.except(tr), { - k: [ 3 ], - a: [ 0 ] - }, 'except data'); - - t.end(); -}); - -tape('except removes intersecting rows for multiple tables', t => { - const t0 = table({ k: [1, 2, 3], a: [3, 4, 0] }); - const t1 = table({ k: [1], a: [3]}); - const t2 = table({ k: [2], a: [4]}); - - tableEqual(t, t0.except(t1, t2), { - k: [ 3 ], - a: [ 0 ] - }, 'except data'); - - t.end(); -}); - -tape('intersect returns empty table given empty input', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 0] }); - - tableEqual(t, tl.intersect([]), { - k: [ ], - a: [ ] - }, 'intersect data'); - - t.end(); -}); - -tape('intersect removes non-intersecting rows', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 0] }); - const tr = table({ k: [1, 2], a: [3, 4]}); - - tableEqual(t, tl.intersect(tr), { - k: [ 1, 2 ], - a: [ 3, 4 ] - }, 'intersect data'); - - t.end(); -}); - -tape('intersect removes non-intersecting rows for multiple tables', t => { - const t0 = table({ k: [1, 2, 3], a: [3, 4, 0] }); - const t1 = table({ k: [1], a: [3]}); - const t2 = table({ k: [2], a: [4]}); - - tableEqual(t, t0.intersect(t1, t2), { - k: [ ], - a: [ ] - }, 'intersect data'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/join-test.js b/test/verbs/join-test.js deleted file mode 100644 index c8d7109b..00000000 --- a/test/verbs/join-test.js +++ /dev/null @@ -1,473 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { all, not, op, table } from '../../src'; - -function joinTables() { - return [ - table({ - k: ['a', 'b', 'b', 'c'], - x: [1, 2, 3, 4], - y: [9, 8, 7, 6] - }), - table({ - u: ['b', 'a', 'b', 'd'], - v: [5, 4, 6, 0] - }) - ]; -} - -tape('cross computes Cartesian product', t => { - const [tl, tr] = joinTables(); - - const tj = tl.cross(tr); - - t.equal(tj.numRows(), tl.numRows() * tr.numRows()); - tableEqual(t, tj, { - k: [ 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c' ], - x: [ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4 ], - y: [ 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6 ], - u: [ 'b', 'a', 'b', 'd', 'b', 'a', 'b', 'd', 'b', 'a', 'b', 'd', 'b', 'a', 'b', 'd' ], - v: [ 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0 ] - }, 'cross data'); - - t.end(); -}); - -tape('cross computes Cartesian product with column selection', t => { - const [tl, tr] = joinTables(); - - const tj = tl.cross(tr, [not('y'), not('u')]); - - t.equal(tj.numRows(), tl.numRows() * tr.numRows()); - tableEqual(t, tj, { - k: [ 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c' ], - x: [ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4 ], - v: [ 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0 ] - }, 'selected cross data'); - - t.end(); -}); - -tape('cross computes Cartesian product with column renaming', t => { - const [tl, tr] = joinTables(); - - const tj = tl.cross(tr, [ - {j: d => d.k, z: d => d.x}, - {w: d => d.v} - ]); - - t.equal(tj.numRows(), tl.numRows() * tr.numRows()); - tableEqual(t, tj, { - j: [ 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c' ], - z: [ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4 ], - w: [ 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0, 5, 4, 6, 0 ] - }, 'selected cross data'); - - t.end(); -}); - -tape('join performs natural join', t => { - const tl = table({ k: [1, 2, 3], a: [3, 4, 1]}); - const t1 = table({ k: [1, 2, 4], b: [5, 6, 2]}); - const t2 = table({ u: [1, 2], v: [5, 6]}); - - tableEqual(t, tl.join(t1), { - k: [ 1, 2 ], - a: [ 3, 4 ], - b: [ 5, 6 ] - }, 'natural join data, common columns'); - - tableEqual(t, tl.join_left(t1), { - k: [ 1, 2, 3 ], - a: [ 3, 4, 1 ], - b: [ 5, 6, undefined ] - }, 'natural left join data, common columns'); - - tableEqual(t, tl.join_right(t1), { - k: [ 1, 2, 4 ], - a: [ 3, 4, undefined ], - b: [ 5, 6, 2 ] - }, 'natural right join data, common columns'); - - tableEqual(t, tl.join_full(t1), { - k: [ 1, 2, 3, 4 ], - a: [ 3, 4, 1, undefined ], - b: [ 5, 6, undefined, 2 ] - }, 'natural full join data, common columns'); - - t.throws( - () =>tl.join(t2), - 'natural join throws, no common columns' - ); - - t.end(); -}); - -tape('join handles filtered tables', t => { - const tl = table({ - key: [1, 2, 3, 4], - value1: [1, 2, 3, 4] - }).filter(d => d.key < 3); - - const tr = table({ - key: [1, 2, 5], - value2: [1, 2, 5] - }); - - tableEqual(t, tl.join_left(tr), { - key: [ 1, 2 ], - value1: [ 1, 2 ], - value2: [ 1, 2 ] - }, 'natural left join on filtered data'); - - tableEqual(t, tl.join_right(tr), { - key: [ 1, 2, 5 ], - value1: [ 1, 2, undefined ], - value2: [ 1, 2, 5 ] - }, 'natural right join on filtered data'); - - const dt = table({ - year: [2017, 2017, 2017, 2018, 2018, 2018], - month: ['01', '02', 'YR', '01', '02', 'YR'], - count: [6074, 7135, 220582, 5761, 6764, 222153] - }); - - const jt = dt - .filter(d => d.month === 'YR') - .select('year', {count: 'total'}) - .join(dt.filter(d => d.month !== 'YR')); - - tableEqual(t, jt, { - total: [ 220582, 220582, 222153, 222153 ], - year: [ 2017, 2017, 2018, 2018 ], - month: [ '01', '02', '01', '02' ], - count: [ 6074, 7135, 5761, 6764 ] - }, 'join of two filtered tables'); - - t.end(); -}); - -tape('join performs inner join with predicate', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join(tr, (a, b) => a.k === b.u, { - k: d => d.k, - x: d => d.x, - y: d => d.y, - u: (a, b) => b.u, - v: (a, b) => b.v, - z: (a, b) => a.x + b.v - }); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b' ], - x: [ 1, 2, 2, 3, 3 ], - y: [ 9, 8, 8, 7, 7 ], - u: [ 'a', 'b', 'b', 'b', 'b' ], - v: [ 4, 5, 6, 5, 6 ], - z: [ 5, 7, 8, 8, 9 ] - }, 'inner join data'); - - t.end(); -}); - -tape('join_left performs left outer join with predicate', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_left(tr, (a, b) => a.k === b.u, { - k: d => d.k, - x: d => d.x, - y: d => d.y, - u: (a, b) => b.u, - v: (a, b) => b.v, - z: (a, b) => a.x + b.v - }); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b', 'c' ], - x: [ 1, 2, 2, 3, 3, 4 ], - y: [ 9, 8, 8, 7, 7, 6 ], - u: [ 'a', 'b', 'b', 'b', 'b', undefined ], - v: [ 4, 5, 6, 5, 6, undefined ], - z: [ 5, 7, 8, 8, 9, NaN ] - }, 'left join data'); - - t.end(); -}); - -tape('join_right performs right outer join with predicate', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_right(tr, (a, b) => a.k === b.u, { - k: d => d.k, - x: d => d.x, - y: d => d.y, - u: (a, b) => b.u, - v: (a, b) => b.v, - z: (a, b) => a.x + b.v - }); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b', undefined ], - x: [ 1, 2, 2, 3, 3, undefined ], - y: [ 9, 8, 8, 7, 7, undefined ], - u: [ 'a', 'b', 'b', 'b', 'b', 'd' ], - v: [ 4, 5, 6, 5, 6, 0 ], - z: [ 5, 7, 8, 8, 9, NaN ] - }, 'right join data'); - - t.end(); -}); - -tape('join_full performs full outer join with predicate', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_full(tr, (a, b) => a.k === b.u, { - k: d => d.k, - x: d => d.x, - y: d => d.y, - u: (a, b) => b.u, - v: (a, b) => b.v, - z: (a, b) => a.x + b.v - }); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b', 'c', undefined ], - x: [ 1, 2, 2, 3, 3, 4, undefined ], - y: [ 9, 8, 8, 7, 7, 6, undefined ], - u: [ 'a', 'b', 'b', 'b', 'b', undefined, 'd' ], - v: [ 4, 5, 6, 5, 6, undefined, 0 ], - z: [ 5, 7, 8, 8, 9, NaN, NaN ] - }, 'full join data'); - - t.end(); -}); - -tape('join performs inner join with keys', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join(tr, ['k', 'u'], [all(), not('u')]); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b' ], - x: [ 1, 2, 2, 3, 3 ], - y: [ 9, 8, 8, 7, 7 ], - v: [ 4, 5, 6, 5, 6 ] - }, 'inner join data'); - - t.end(); -}); - -tape('join_left performs left outer join with keys', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_left(tr, ['k', 'u'], [all(), not('u')]); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b', 'c' ], - x: [ 1, 2, 2, 3, 3, 4 ], - y: [ 9, 8, 8, 7, 7, 6 ], - v: [ 4, 5, 6, 5, 6, undefined ] - }, 'left join data'); - - t.end(); -}); - -tape('join_right performs right outer join with keys', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_right(tr, ['k', 'u'], [not('k'), all()]); - - tableEqual(t, tj, { - x: [ 1, 2, 2, 3, 3, undefined ], - y: [ 9, 8, 8, 7, 7, undefined ], - u: [ 'a', 'b', 'b', 'b', 'b', 'd' ], - v: [ 4, 5, 6, 5, 6, 0 ] - }, 'right join data'); - - t.end(); -}); - -tape('join_full performs full outer join with keys', t => { - const [tl, tr] = joinTables(); - - const tj = tl.join_full(tr, ['k', 'u'], [all(), all()]); - - tableEqual(t, tj, { - k: [ 'a', 'b', 'b', 'b', 'b', 'c', undefined ], - x: [ 1, 2, 2, 3, 3, 4, undefined ], - y: [ 9, 8, 8, 7, 7, 6, undefined ], - u: [ 'a', 'b', 'b', 'b', 'b', undefined, 'd' ], - v: [ 4, 5, 6, 5, 6, undefined, 0 ] - }, 'full join data'); - - t.end(); -}); - -tape('join handles column name collisions', t => { - const [tl] = joinTables(); - const tr = table({ k: ['a', 'b'], x: [9, 8] }); - - const tj_inner = tl.join(tr, 'k'); - tableEqual(t, tj_inner, { - k: [ 'a', 'b', 'b' ], - x_1: [ 1, 2, 3 ], - y: [ 9, 8, 7 ], - x_2: [ 9, 8, 8 ] - }, 'name collision inner join data'); - - const tj_full = tl.join_full(tr, 'k'); - tableEqual(t, tj_full, { - k: [ 'a', 'b', 'b', 'c' ], - x_1: [ 1, 2, 3, 4 ], - y: [ 9, 8, 7, 6 ], - x_2: [ 9, 8, 8, undefined ] - }, 'name collision full join data'); - - const tj1 = tl.join(tr, ['k', 'k'], [all(), all()]); - tableEqual(t, tj1, { - k_1: [ 'a', 'b', 'b' ], - x_1: [ 1, 2, 3 ], - y: [ 9, 8, 7 ], - k_2: [ 'a', 'b', 'b' ], - x_2: [ 9, 8, 8 ] - }, 'name collision join data'); - - const tj2 = tl.join(tr, ['k', 'k'], [ - all(), - all(), - { y: (a, b) => a.x + b.x } - ]); - tableEqual(t, tj2, { - k_1: [ 'a', 'b', 'b' ], - x_1: [ 1, 2, 3 ], - k_2: [ 'a', 'b', 'b' ], - x_2: [ 9, 8, 8 ], - y: [ 10, 10, 11 ] - }, 'name override join data'); - - t.end(); -}); - -tape('join does not treat null values as equal', t => { - const tl = table({ u: ['a', null, undefined, NaN], a: [1, 2, 3, 4] }); - const tr = table({ v: [null, undefined, NaN, 'a'], b: [9, 8, 7, 6] }); - - const tj1 = tl.join(tr, ['u', 'v'], [all(), all()]); - - tableEqual(t, tj1, { - u: [ 'a' ], - v: [ 'a' ], - a: [ 1 ], - b: [ 6 ] - }, 'null join data with keys'); - - const tj2 = tl.join(tr, (a, b) => op.equal(a.u, b.v), [all(), all()]); - - tableEqual(t, tj2, { - u: [ 'a' ], - v: [ 'a' ], - a: [ 1 ], - b: [ 6 ] - }, 'null join data with equal predicate'); - - t.end(); -}); - -tape('join supports date-valued keys', t => { - const d1 = new Date(2000, 0, 1); - const d2 = new Date(2012, 1, 3); - const tl = table({ u: [d1, d2, null], a: [9, 8, 7] }); - const tr = table({ v: [new Date(+d1), +d2], b: [5, 4] }); - - const tj1 = tl.join(tr, ['u', 'v'], [all(), not('v')]); - - tableEqual(t, tj1, { - u: [d1, d2], - a: [9, 8], - b: [5, 4] - }, 'hash join data with date keys'); - - const tj2 = tl.join(tr, (a, b) => op.equal(a.u, b.v), [all(), not('v')]); - - tableEqual(t, tj2, { - u: [d1, d2], - a: [9, 8], - b: [5, 4] - }, 'loop join data with date keys'); - - t.end(); -}); - -tape('join supports regexp-valued keys', t => { - const tl = table({ u: [/foo/g, /bar.*/i, null], a: [9, 8, 7] }); - const tr = table({ v: [/foo/g, /bar.*/i], b: [5, 4] }); - - const tj1 = tl.join(tr, ['u', 'v'], [all(), not('v')]); - - tableEqual(t, tj1, { - u: [/foo/g, /bar.*/i], - a: [9, 8], - b: [5, 4] - }, 'hash join data with regexp keys'); - - const tj2 = tl.join(tr, (a, b) => op.equal(a.u, b.v), [all(), not('v')]); - - tableEqual(t, tj2, { - u: [/foo/g, /bar.*/i], - a: [9, 8], - b: [5, 4] - }, 'loop join data with regexp keys'); - - t.end(); -}); - -tape('join supports array-valued keys', t => { - const tl = table({ u: [[1, 2], [3, 4], null], a: [9, 8, 7] }); - const tr = table({ v: [[1, 2], [3, 4]], b: [5, 4] }); - - const tj1 = tl.join(tr, ['u', 'v']); - - tableEqual(t, tj1, { - u: [[1, 2], [3, 4]], - a: [9, 8], - v: [[1, 2], [3, 4]], - b: [5, 4] - }, 'hash join data with array keys'); - - const tj2 = tl.join(tr, (a, b) => op.equal(a.u, b.v)); - - tableEqual(t, tj2, { - u: [[1, 2], [3, 4]], - a: [9, 8], - v: [[1, 2], [3, 4]], - b: [5, 4] - }, 'loop join data with array keys'); - - t.end(); -}); - -tape('join supports object-valued keys', t => { - const tl = table({ u: [{k: 1, l: [2]}, {k: 2}, null], a: [9, 8, 7] }); - const tr = table({ v: [{k: 1, l: [2]}, {k: 2}], b: [5, 4] }); - - const tj1 = tl.join(tr, ['u', 'v']); - - tableEqual(t, tj1, { - u: [{k: 1, l: [2]}, {k: 2}], - a: [9, 8], - v: [{k: 1, l: [2]}, {k: 2}], - b: [5, 4] - }, 'hash join data with object keys'); - - const tj2 = tl.join(tr, (a, b) => op.equal(a.u, b.v)); - - tableEqual(t, tj2, { - u: [{k: 1, l: [2]}, {k: 2}], - a: [9, 8], - v: [{k: 1, l: [2]}, {k: 2}], - b: [5, 4] - }, 'loop join data with object keys'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/lookup-test.js b/test/verbs/lookup-test.js deleted file mode 100644 index ef022b1b..00000000 --- a/test/verbs/lookup-test.js +++ /dev/null @@ -1,54 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, table } from '../../src'; - -tape('lookup retrieves values from lookup table with string values', t => { - const right = table({ - key: [1, 2, 3], - u: ['a', 'b', 'c'], - v: [5, 3, 1] - }); - - const left = table({ - id: [1, 2, 3, 4, 1] - }); - - const lt = left.lookup(right, ['id', 'key'], ['u', 'v']); - - t.equal(lt.numRows(), 5, 'num rows'); - t.equal(lt.numCols(), 3, 'num cols'); - - tableEqual(t, lt, { - id: [1, 2, 3, 4, 1], - u: ['a', 'b', 'c', undefined, 'a'], - v: [5, 3, 1, undefined, 5] - }, 'lookup data'); - t.end(); -}); - -tape('lookup retrieves values from lookup table with function values', t => { - const right = table({ - key: [1, 2, 3], - u: ['a', 'b', 'c'], - v: [5, 3, 1] - }); - - const left = table({ - id: [1, 2, 3, 4, 1] - }); - - const lt = left.lookup(right, ['id', 'key'], { - u: d => d.u, - v: d => d.v - op.mean(d.v) - }); - - t.equal(lt.numRows(), 5, 'num rows'); - t.equal(lt.numCols(), 3, 'num cols'); - - tableEqual(t, lt, { - id: [1, 2, 3, 4, 1], - u: ['a', 'b', 'c', undefined, 'a'], - v: [2, 0, -2, undefined, 2] - }, 'lookup data'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/orderby-test.js b/test/verbs/orderby-test.js deleted file mode 100644 index 4fcf8fa1..00000000 --- a/test/verbs/orderby-test.js +++ /dev/null @@ -1,50 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { desc, op, table } from '../../src'; - -tape('orderby orders a table', t => { - const data = { - a: [2, 2, 3, 3, 1, 1], - b: [1, 2, 1, 2, 1, 2] - }; - - const ordered = { - a: [1, 1, 2, 2, 3, 3], - b: [2, 1, 2, 1, 2, 1] - }; - - const dt = table(data).orderby('a', desc('b')); - - const rows = []; - dt.scan(row => rows.push(row), true); - t.deepEqual(rows, [5, 4, 1, 0, 3, 2], 'orderby scan'); - - tableEqual(t, dt, ordered, 'orderby data'); - - t.end(); -}); - -tape('orderby supports aggregate functions', t => { - const data = { - a: [1, 2, 2, 3, 4, 5], - b: [9, 8, 7, 6, 5, 4] - }; - - const dt = table(data) - .groupby('a') - .orderby(d => op.mean(d.b)) - .reify(); - - tableEqual(t, dt, { - a: [5, 4, 3, 2, 2, 1], - b: [4, 5, 6, 8, 7, 9] - }, 'orderby data'); - - t.end(); -}); - -tape('orderby throws on window functions', t => { - const data = { a: [1, 3, 5, 7] }; - t.throws(() => table(data).orderby({ res: d => op.lag(d.a) }), 'no window'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/pivot-test.js b/test/verbs/pivot-test.js deleted file mode 100644 index d89c99a3..00000000 --- a/test/verbs/pivot-test.js +++ /dev/null @@ -1,130 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, table } from '../../src'; - -tape('pivot generates cross-tabulation for single value', t => { - const data = { - k: ['a', 'b', 'c'], - x: [1, 2, 3] - }; - - const ut = table(data).pivot('k', 'x'); - tableEqual(t, ut, { - a: [ 1 ], b: [ 2 ], c: [ 3 ] - }, 'pivot data'); - t.end(); -}); - -tape('pivot generates cross-tabulation for single value as function', t => { - const data = { - k: ['a', 'b', 'c'], - x: [1, 2, 3] - }; - - const ut = table(data).pivot('k', { x: d => op.any(d.x) }); - tableEqual(t, ut, { - a: [ 1 ], b: [ 2 ], c: [ 3 ] - }, 'pivot data'); - t.end(); -}); - -tape('pivot generates cross-tabulation with groupby', t => { - const data = { - g: [0, 0, 1, 1], - k: ['a', 'b', 'a', 'b'], - x: [1, 2, 3, 4] - }; - - const ut = table(data).groupby('g').pivot('k', 'x'); - tableEqual(t, ut, { - g: [ 0, 1 ], a: [ 1, 3 ], b: [ 2, 4 ] - }, 'pivot data'); - t.end(); -}); - -tape('pivot generates cross-tabulation for multiple values', t => { - const data = { - k: ['a', 'b', 'b', 'c'], - x: [+1, +2, +2, +3], - y: [-9, -2, +2, -7] - }; - - const ut = table(data).pivot('k', { - x: d => op.sum(d.x), - y: d => op.product(op.abs(d.y)) - }); - - tableEqual(t, ut, { - x_a: [ 1 ], - x_b: [ 4 ], - x_c: [ 3 ], - y_a: [ 9 ], - y_b: [ 4 ], - y_c: [ 7 ] - }, 'pivot data'); - - t.end(); -}); - -tape('pivot respects input options', t => { - const data = { - k: ['a', 'b', 'c'], - j: ['d', 'e', 'f'], - x: [1, 2, 3], - y: [9, 8, 7] - }; - - const ut = table(data).pivot(['k', 'j'], ['x', 'y'], { - keySeparator: '/', - valueSeparator: ':', - limit: 2 - }); - - tableEqual(t, ut, { - 'x:a/d': [ 1 ], - 'x:b/e': [ 2 ], - 'y:a/d': [ 9 ], - 'y:b/e': [ 8 ] - }, 'pivot data'); - - t.end(); -}); - -tape('pivot correctly orders integer column names', t => { - const data = { - g: ['a', 'a', 'a', 'b', 'b', 'b'], - k: [2002, 2001, 2000, 2002, 2001, 2000], - x: [1, 2, 3, 4, 5, 6] - }; - - const ut = table(data) - .groupby('g') - .pivot('k', 'x', { sort: false }); - - tableEqual(t, ut, { - 'g': ['a', 'b'], '2002': [ 1, 4 ], '2001': [ 2, 5 ], '2000': [ 3, 6 ] - }, 'pivot data'); - - t.deepEqual(ut.columnNames(), ['g', '2002', '2001', '2000']); - t.end(); -}); - -tape('pivot handles filtered and ordered table', t => { - const dt = table({ - country: ['France', 'France', 'France', 'Germany', 'Germany', 'Germany', 'Japan', 'Japan', 'Japan'], - year: [2017, 2018, 2019, 2017, 2018, 2019, 2017, 2018, 2019], - expenditure: ['NA', 51410, 52229, 45340, 46512, 51190, 46542, 46618, 46562] - }) - .filter(d => d.year > 2017) - .orderby('country') - .groupby('country') - .pivot('year', 'expenditure'); - - tableEqual(t, dt, { - country: ['France', 'Germany', 'Japan'], - 2018: [51410, 46512, 46618], - 2019: [52229,51190,46562] - }, 'pivot data'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/reduce-test.js b/test/verbs/reduce-test.js deleted file mode 100644 index 470c536d..00000000 --- a/test/verbs/reduce-test.js +++ /dev/null @@ -1,41 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import countPattern from '../../src/engine/reduce/count-pattern'; -import { table } from '../../src'; - -tape('reduce produces multiple aggregates', t => { - const data = { - text: ['foo bar', 'foo', 'bar baz', 'baz'] - }; - - const dt = table(data).reduce(countPattern('text')); - - t.equal(dt.numRows(), 3, 'num rows'); - t.equal(dt.numCols(), 2, 'num columns'); - tableEqual(t, dt, { - word: ['foo', 'bar', 'baz'], - count: [2, 2, 2] - }, 'reduce data'); - t.end(); -}); - -tape('reduce produces grouped multiple aggregates', t => { - const data = { - key: ['a', 'a', 'b', 'b'], - text: ['foo bar', 'foo', 'bar baz', 'baz bop'] - }; - - const dt = table(data) - .groupby('key') - .reduce(countPattern('text')); - - t.equal(dt.numRows(), 5, 'num rows'); - t.equal(dt.numCols(), 3, 'num columns'); - tableEqual(t, dt, { - key: ['a', 'a', 'b', 'b', 'b'], - word: ['foo', 'bar', 'bar', 'baz', 'bop'], - count: [2, 1, 1, 2, 1] - }, 'reduce data'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/reify-test.js b/test/verbs/reify-test.js deleted file mode 100644 index bea6401c..00000000 --- a/test/verbs/reify-test.js +++ /dev/null @@ -1,50 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src/table'; -import { fromArrow, toArrow } from '../../src'; - -tape('reify materializes filtered and ordered tables', t => { - const dt = table({ - a: [5, 4, 3, 2, 1], - b: [1, 1, 0, 0, 1] - }); - - const rt = dt.filter(d => d.b) - .orderby('a') - .reify(); - - tableEqual(t, rt, - { a: [1, 4, 5], b: [1, 1, 1] }, - 'reify data' - ); - - t.end(); -}); - -tape('reify preserves binary data', t => { - const data = [ - { a: 1.0, b: 'a', c: [1], d: new Date(2000, 0, 1, 1) }, - { a: 1.3, b: 'b', c: [2], d: new Date(2001, 1, 1, 2) }, - { a: 1.5, b: 'c', c: [3], d: new Date(2002, 2, 1, 3) }, - { a: 1.7, b: 'd', c: [4], d: new Date(2003, 3, 1, 4) } - ]; - - const dt = fromArrow(toArrow(data)); - const rt = dt.filter(d => d.b !== 'c').reify(); - - tableEqual(t, rt, - { - a: [1.0, 1.3, 1.7], - b: ['a', 'b', 'd'], - c: [[1], [2], [4]], - d: [ - new Date(2000, 0, 1, 1), - new Date(2001, 1, 1, 2), - new Date(2003, 3, 1, 4) - ] - }, - 'reify data' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/relocate-test.js b/test/verbs/relocate-test.js deleted file mode 100644 index 2509dba9..00000000 --- a/test/verbs/relocate-test.js +++ /dev/null @@ -1,91 +0,0 @@ -import tape from 'tape'; -import { not, range, table } from '../../src'; - -tape('relocate repositions columns', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - t.deepEqual( - dt.relocate('a', { before: 'd' }).columnNames(), - ['b', 'c', 'a', 'd'], - 'relocate data, before' - ); - - t.deepEqual( - dt.relocate(not('b', 'd'), { before: 'b' }).columnNames(), - ['a', 'c', 'b', 'd'], - 'relocate data, before' - ); - - t.deepEqual( - dt.relocate(not('b', 'd'), { after: 'd' }).columnNames(), - ['b', 'd', 'a', 'c'], - 'relocate data, after' - ); - - t.deepEqual( - dt.relocate(not('b', 'd'), { before: 'c' }).columnNames(), - ['b', 'a', 'c', 'd'], - 'relocate data, before self' - ); - - t.deepEqual( - dt.relocate(not('b', 'd'), { after: 'a' }).columnNames(), - ['a', 'c', 'b', 'd'], - 'relocate data, after self' - ); - - t.end(); -}); - -tape('relocate repositions columns using multi-column anchor', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - t.deepEqual( - dt.relocate([1, 3], { before: range(2, 3) }).columnNames(), - ['a', 'b', 'd', 'c'], - 'relocate data, before range' - ); - - t.deepEqual( - dt.relocate([1, 3], { after: range(2, 3) }).columnNames(), - ['a', 'c', 'b', 'd'], - 'relocate data, after range' - ); - - t.end(); -}); - -tape('relocate repositions and renames columns', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - t.deepEqual( - dt.relocate({ a: 'e', c: 'f' }, { before: { b: '?' } }).columnNames(), - ['e', 'f', 'b', 'd'], - 'relocate data, before plus rename' - ); - - t.deepEqual( - dt.relocate({ a: 'e', c: 'f' }, { after: { b: '?' } }).columnNames(), - ['b', 'e', 'f', 'd'], - 'relocate data, after plus rename' - ); - - t.end(); -}); - -tape('relocate throws errors for invalid options', t => { - const a = [1], b = [2], c = [3], d = [4]; - const dt = table({ a, b, c, d }); - - t.throws(() => dt.relocate(not('b', 'd')), 'missing options'); - t.throws(() => dt.relocate(not('b', 'd'), {}), 'empty options'); - t.throws( - () => dt.relocate(not('b', 'd'), { before: 'b', after: 'b' }), - 'over-specified options' - ); - - t.end(); -}); diff --git a/test/verbs/rename-test.js b/test/verbs/rename-test.js deleted file mode 100644 index 05151fe2..00000000 --- a/test/verbs/rename-test.js +++ /dev/null @@ -1,49 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -tape('rename renames columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - tableEqual(t, - table(data).rename({ a: 'z'}), - { z: data.a, b: data.b, c: data.c }, - 'renamed data, single column' - ); - - tableEqual(t, - table(data).rename({ a: 'z', b: 'y' }), - { z: data.a, y: data.b, c: data.c }, - 'renamed data, multiple columns' - ); - - t.deepEqual( - table(data).rename({ a: 'z', c: 'x' }).columnNames(), - ['z', 'b', 'x'], - 'renamed data, preserves order' - ); - - tableEqual(t, - table(data).rename('a', 'b'), - data, - 'renamed data, no rename' - ); - - tableEqual(t, - table(data).rename(), - data, - 'renamed data, no arguments' - ); - - tableEqual(t, - table(data).rename({ a: 'z'}, { c: 'x' }), - { z: data.a, b: data.b, x: data.c }, - 'renamed data, multiple arguments' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/rollup-test.js b/test/verbs/rollup-test.js deleted file mode 100644 index cc49c89b..00000000 --- a/test/verbs/rollup-test.js +++ /dev/null @@ -1,289 +0,0 @@ -/* eslint-disable no-undef */ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { bin, op, table } from '../../src'; - -tape('rollup produces flat aggregates', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const rt = table(data).rollup({ sum: d => op.sum(d.a + d.b) }); - - t.equal(rt.numRows(), 1, 'num rows'); - t.equal(rt.numCols(), 1, 'num cols'); - tableEqual(t, rt, { sum: [36] }, 'rollup data'); - t.end(); -}); - -tape('rollup produces grouped aggregates', t => { - const data = { - k: ['a', 'a', 'b', 'b'], - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const rt = table(data) - .groupby({ key: d => d.k }) - .rollup({ sum: d => op.sum(d.a + d.b) }); - - t.equal(rt.numRows(), 2, 'num rows'); - t.equal(rt.numCols(), 2, 'num cols'); - tableEqual(t, rt, { - key: ['a', 'b'], - sum: [10, 26] - }, 'rollup data'); - t.end(); -}); - -tape('rollup handles empty tables', t => { - [[], [null, null], [undefined, undefined], [NaN, NaN]].forEach(v => { - const rt = table({ v }).rollup({ - sum: op.sum('v'), - prod: op.product('v'), - mode: op.mode('v'), - med: op.median('v'), - min: op.min('v'), - max: op.min('v'), - sd: op.stdev('v') - }); - - tableEqual(t, rt, { - sum: [undefined], - prod: [undefined], - mode: [undefined], - med: [undefined], - min: [undefined], - max: [undefined], - sd: [undefined] - }, 'rollup data, ' + (v.length ? v[0] : 'empty')); - }); - - t.end(); -}); - -tape('rollup handles empty input', t => { - const dt = table({ x: ['a', 'b', 'c'] }); - - tableEqual(t, dt.rollup(), { }, 'rollup data, no groups'); - - tableEqual(t, - dt.groupby('x').rollup(), - { x: ['a', 'b', 'c'] }, - 'rollup data, groups' - ); - - t.end(); -}); - -tape('rollup supports bigint values', t => { - const data = { - v: [1n, 2n, 3n, 4n, 5n] - }; - - const dt = table(data) - .rollup({ - any: op.any('v'), - dist: op.distinct('v'), - cnt: op.count('v'), - val: op.valid('v'), - inv: op.invalid('v'), - sum: op.sum('v'), - prod: op.product('v'), - min: op.min('v'), - max: op.max('v'), - med: op.median('v'), - vals: op.array_agg('v'), - uniq: op.array_agg_distinct('v') - }); - - t.deepEqual( - dt.objects()[0], - { - any: 1n, - dist: 5, - cnt: 5, - val: 5, - inv: 0, - sum: 15n, - prod: 120n, - min: 1n, - max: 5n, - med: 3n, - vals: [1n, 2n, 3n, 4n, 5n], - uniq: [1n, 2n, 3n, 4n, 5n] - }, - 'rollup data' - ); - t.end(); -}); - -tape('rollup supports ordered tables', t => { - const rt = table({ v: [3, 1, 4, 2] }) - .orderby('v') - .rollup({ v: op.array_agg('v') }); - - tableEqual(t, rt, { v: [ [1, 2, 3, 4] ] }, 'rollup data'); - t.end(); -}); - -tape('rollup supports object_agg functions', t => { - const data = { - g: [0, 0, 1, 1, 1], - k: ['a', 'b', 'a', 'b', 'a'], - v: [1, 2, 3, 4, 5] - }; - - const dt = table(data).groupby('g'); - - t.deepEqual( - dt.rollup({ o: op.object_agg('k', 'v') }) - .columnArray('o'), - [ - { a: 1, b: 2 }, - { a: 5, b: 4 } - ], - 'rollup data - object_agg' - ); - - t.deepEqual( - dt.rollup({ o: op.entries_agg('k', 'v') }) - .columnArray('o'), - [ - [['a', 1], ['b', 2]], - [['a', 3], ['b', 4], ['a', 5]] - ], - 'rollup data - entries_agg' - ); - - t.deepEqual( - dt.rollup({ o: op.map_agg('k', 'v') }) - .columnArray('o'), - [ - new Map([['a', 1], ['b', 2]]), - new Map([['a', 5], ['b', 4]]) - ], - 'rollup data - map_agg' - ); - - t.end(); -}); - -tape('rollup supports histogram', t => { - const data = { x: [1, 1, 3, 4, 5, 6, 7, 8, 9, 10] }; - const result = { - b0: [1, 3, 4, 5, 6, 7, 8, 9, 10], - b1: [1.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5], - count: [2, 1, 1, 1, 1, 1, 1, 1, 1] - }; - - const dt = table(data) - .groupby({ - b0: d => bin(d.x, ...bins(d.x, 20)), - b1: d => bin(d.x, ...bins(d.x, 20), 1) - }) - .count(); - tableEqual(t, dt, result, 'histogram'); - - const ht = table(data) - .groupby({ - b0: bin('x', { maxbins: 20 }), - b1: bin('x', { maxbins: 20, offset: 1 }) - }) - .count(); - tableEqual(t, ht, result, 'histogram from bin helper, maxbins'); - - const st = table(data) - .groupby({ - b0: bin('x', { step: 0.5 }), - b1: bin('x', { step: 0.5, offset: 1 }) - }) - .count(); - tableEqual(t, st, result, 'histogram from bin helper, step'); - - t.end(); -}); - -tape('rollup supports dot product', t => { - const data = { x: [1, 2, 3], y: [4, 5, 6] }; - const dt = table(data).rollup({ dot: d => sum(d.x * d.y) }); - tableEqual(t, dt, { dot: [32] }, 'dot product'); - t.end(); -}); - -tape('rollup supports geometric mean', t => { - const data = { x: [1, 2, 3, 4, 5, 6] }; - const dt = table(data).rollup({ gm: d => exp(mean(log(d.x))) }); - const gm = [ Math.pow(1 * 2 * 3 * 4 * 5 * 6, 1/6) ]; - tableEqual(t, dt, { gm }, 'geometric mean'); - t.end(); -}); - -tape('rollup supports harmonic mean', t => { - const data = { x: [1, 2, 3, 4, 5, 6] }; - const dt = table(data).rollup({ hm: d => 1 / mean(1 / d.x) }); - const hm = [ 6 / (1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6) ]; - tableEqual(t, dt, { hm }, 'harmonic mean'); - t.end(); -}); - -tape('rollup supports median skew', t => { - const data = { x: [1, 2, 3, 4, 5, 1000] }; - const dt = table(data).rollup({ - ms: ({ x }) => stdev(x) ? (mean(x) - median(x)) / stdev(x) : 0 - }); - tableEqual(t, dt, { ms: [ 0.4070174034861516 ] }, 'median skew'); - t.end(); -}); - -tape('rollup supports vector norm', t => { - const data = { x: [1, 2, 3, 4, 5] }; - const dt = table(data).rollup({ vn: d => sqrt(sum(d.x * d.x)) }); - const vn = [ Math.sqrt(1 + 4 + 9 + 16 + 25) ]; - tableEqual(t, dt, { vn }, 'vector norm'); - t.end(); -}); - -tape('rollup supports cohens d', t => { - const data = { - a: [3, 4, 5, 6, 7], - b: [1, 2, 3, 4, 5] - }; - const dt = table(data).rollup({ - cd: ({ a, b }) => { - const va = (valid(a) - 1) * variance(a); - const vb = (valid(b) - 1) * variance(b); - const sd = sqrt((va + vb) / (valid(a) + valid(b) - 2)); - return sd ? (mean(a) - mean(b)) / sd : 0; - } - }); - tableEqual(t, dt, { cd: [ 1.2649110640673518 ] }, 'cohens d'); - t.end(); -}); - -tape('rollup supports entropy', t => { - const data = { x: [1, 1, 1, 2, 2, 3] }; - const dt = table(data) - .groupby('x') - .count({ as: 'num' }) - .derive({ p: d => d.num / sum(d.num) }) - .rollup({ h: d => -sum(d.p ? d.p * log2(d.p) : 0) }); - tableEqual(t, dt, { h: [ 1.4591479170272448 ] }, 'entropy'); - t.end(); -}); - -tape('rollup supports spearman rank correlation', t => { - const data = { - a: [1, 2, 2, 3, 4, 5], - b: [9, 8, 8, 7, 6, 5] - }; - - const dt = table(data) - .orderby('a').derive({ ra: () => avg_rank() }) - .orderby('b').derive({ rb: () => avg_rank() }) - .rollup({ rho: d => corr(d.ra, d.rb) }); - - tableEqual(t, dt, { rho: [ -1 ]}, 'spearman rank correlation'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/sample-test.js b/test/verbs/sample-test.js deleted file mode 100644 index e7ce1652..00000000 --- a/test/verbs/sample-test.js +++ /dev/null @@ -1,157 +0,0 @@ -import tape from 'tape'; -import { frac, table } from '../../src'; - -function check(t, table, replace, prefix = '') { - prefix = `${prefix}sample ${replace ? 'replace ' : ''}rows`; - const vals = []; - const cnts = {}; - table.scan((row, data) => { - const val = data.a.get(row); - vals.push(val); - cnts[val] = (cnts[val] || 0) + 1; - }); - - t.ok( - vals.every(v => v === 1 || v === 3 || v === 5 || v === 7), - `${prefix} valid` - ); - - const test = replace - ? Object.values(cnts).some(c => c > 1) - : Object.values(cnts).every(c => c === 1); - - t.ok(test, `${prefix} count`); -} - -tape('sample draws a sample without replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(2); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, false); - t.end(); -}); - -tape('sample draws a maximal sample without replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(10); - - t.equal(ft.numRows(), 4, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, false); - t.end(); -}); - -tape('sample draws a sample with replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(10, { replace: true }); - - t.equal(ft.numRows(), 10, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, true); - t.end(); -}); - -tape('sample draws a column-weighted sample without replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(2, { weight: 'a' }); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, false, 'weighted '); - t.end(); -}); - -tape('sample draws an expression-weighted sample without replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(2, { weight: d => d.a }); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, false, 'expr weighted '); - t.end(); -}); - -tape('sample draws a weighted sample with replacement', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(10, { weight: 'a', replace: true }); - - t.equal(ft.numRows(), 10, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, true, 'weighted '); - t.end(); -}); - -tape('sample tables support downstream transforms', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const dt = table(cols) - .sample(10, { weight: 'a', replace: true }) - .filter(d => d.a > 1) - .groupby(['a', 'b']) - .count(); - - t.equal(dt.numCols(), 3, 'num cols'); - t.end(); -}); - -tape('sample supports dynamic sample size', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8] - }; - - const ft = table(cols).sample(frac(0.5)); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - check(t, ft, false); - t.end(); -}); - -tape('sample supports stratified sample', t => { - const cols = { - a: [1, 3, 5, 7], - b: [2, 2, 4, 4] - }; - - const ft = table(cols).groupby('b').sample(1); - - t.equal(ft.numRows(), 2, 'num rows'); - t.equal(ft.numCols(), 2, 'num cols'); - t.deepEqual( - ft.column('b').data.sort((a, b) => a - b), - [2, 4], - 'stratify keys' - ); - check(t, ft, false); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/select-test.js b/test/verbs/select-test.js deleted file mode 100644 index 26a10c77..00000000 --- a/test/verbs/select-test.js +++ /dev/null @@ -1,214 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { - all, desc, endswith, matches, not, range, startswith, table -} from '../../src'; - -tape('select selects a subset of columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - const st = table(data).select('a', 'c'); - - t.equal(st.numRows(), 4, 'num rows'); - t.equal(st.numCols(), 2, 'num cols'); - tableEqual(t, st, { a: data.a, c: data.c }, 'selected data'); - t.end(); -}); - -tape('select handles columns with numeric names', t => { - const data = { - country: [0], - '1999': [1], - '2000': [2] - }; - - const dt = table(data, ['country', '1999', '2000']); - t.deepEqual( - dt.columnNames(), - ['country', '1999', '2000'] - ); - - t.deepEqual( - dt.select('1999', 'country', '2000').columnNames(), - ['1999', 'country', '2000'] - ); - - t.end(); -}); - -tape('select renames columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - const st = table(data).select({ b: 'foo', c: 'bar', a: 'baz' }); - - t.deepEqual(st.columnNames(), ['foo', 'bar', 'baz'], 'renamed columns'); - tableEqual(t, st, { - foo: data.b, bar: data.c, baz: data.a - }, 'selected data'); - t.end(); -}); - -tape('select uses last instance of repeated columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - const st = table(data).select(all(), { a: 'x', c: 'y' }, { c: 'z' }); - - t.deepEqual(st.columnNames(), ['x', 'b', 'z'], 'renamed columns'); - tableEqual(t, st, { - x: data.a, b: data.b, z: data.c - }, 'selected data'); - t.end(); -}); - -tape('select reorders columns', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - const dt = table(data); - const st = dt.select(dt.columnNames().reverse()); - - t.deepEqual(st.columnNames(), ['c', 'b', 'a'], 'reordered names'); - t.deepEqual( - [st.columnIndex('a'), st.columnIndex('b'), st.columnIndex('c')], - [2, 1, 0], - 'reordered indices' - ); - tableEqual(t, st, data, 'selected data'); - t.end(); -}); - -tape('select accepts selection helpers', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abcd'.split('') - }; - - t.deepEqual( - table(data).select(not('a', 'c')).columnNames(), - ['b'], - 'select not name' - ); - - t.deepEqual( - table(data).select(not(1)).columnNames(), - ['a', 'c'], - 'select not index' - ); - - t.deepEqual( - table(data).select(range('b', 'c')).columnNames(), - ['b', 'c'], - 'select range name' - ); - - t.deepEqual( - table(data).select(range('c', 'b')).columnNames(), - ['b', 'c'], - 'select reversed range name' - ); - - t.deepEqual( - table(data).select(range(0, 1)).columnNames(), - ['a', 'b'], - 'select range index' - ); - - t.deepEqual( - table(data).select(range(1, 0)).columnNames(), - ['a', 'b'], - 'select reversed range index' - ); - - t.deepEqual( - table(data).select(not(range(0, 1))).columnNames(), - ['c'], - 'select not range' - ); - - t.deepEqual( - table(data).select(matches('b')).columnNames(), - ['b'], - 'select match string' - ); - - t.deepEqual( - table(data).select(matches(/A|c/i)).columnNames(), - ['a', 'c'], - 'select match regexp' - ); - - const data2 = { - 'foo.bar': [], - 'foo.baz': [], - 'bop.bar': [], - 'bop.baz': [] - }; - - t.deepEqual( - table(data2).select(startswith('foo.')).columnNames(), - ['foo.bar', 'foo.baz'], - 'select startswith' - ); - - t.deepEqual( - table(data2).select(endswith('.baz')).columnNames(), - ['foo.baz', 'bop.baz'], - 'select startswith' - ); - - t.end(); -}); - -tape('select does not conflict with groupby', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 4, 6, 8], - c: 'abbb'.split('') - }; - - const st = table(data).groupby('c').select('a', 'b', {'c': 'd'}); - - t.deepEqual( - st.columnNames(), - ['a', 'b', 'd'], - 'renamed columns' - ); - - tableEqual(t, st.count({ as: 'n' }), { - c: ['a', 'b'], n: [1, 3] - }, 'groupby not conflicted'); - - t.end(); -}); - -tape('select does not conflict with orderby', t => { - const data = { - a: [1, 3, 5, 7], - b: [2, 6, 8, 4], - c: 'abcd'.split('') - }; - - const st = table(data).orderby(desc('b')).select('c').reify(); - - tableEqual(t, st, { - c: ['c', 'b', 'd', 'a'] - }, 'orderby not conflicted'); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/slice-test.js b/test/verbs/slice-test.js deleted file mode 100644 index 8a63cc47..00000000 --- a/test/verbs/slice-test.js +++ /dev/null @@ -1,88 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -tape('slice slices a table', t => { - const dt = table({ v: [1, 2, 3, 4] }); - - tableEqual(t, - dt.slice(), - { v: [1, 2, 3, 4] }, - 'sliced data, all' - ); - - tableEqual(t, - dt.slice(2), - { v: [3, 4] }, - 'sliced data, start' - ); - - tableEqual(t, - dt.slice(1, 3), - { v: [2, 3] }, - 'sliced data, start and end' - ); - - tableEqual(t, - dt.slice(1, -1), - { v: [2, 3] }, - 'sliced data, start and negative end' - ); - - tableEqual(t, - dt.slice(-3, -1), - { v: [2, 3] }, - 'sliced data, negative start and end' - ); - - tableEqual(t, - dt.slice(-1000, -900), - { v: [] }, - 'sliced data, extreme negative start and end' - ); - - t.end(); -}); - -tape('slice slices a grouped table', t => { - const dt = table({ v: [1, 2, 3, 4, 5, 6, 7] }) - .groupby({ k: d => d.v % 2 }); - - tableEqual(t, - dt.slice(), - { v: [1, 2, 3, 4, 5, 6, 7] }, - 'sliced data, all' - ); - - tableEqual(t, - dt.slice(2), - { v: [5, 6, 7] }, - 'sliced data, start' - ); - - tableEqual(t, - dt.slice(1, 3), - { v: [3, 4, 5, 6] }, - 'sliced data, start and end' - ); - - tableEqual(t, - dt.slice(1, -1), - { v: [3, 4, 5] }, - 'sliced data, start and negative end' - ); - - tableEqual(t, - dt.slice(-3, -1), - { v: [2, 3, 4, 5] }, - 'sliced data, negative start and end' - ); - - tableEqual(t, - dt.slice(-1000, -900), - { v: [] }, - 'sliced data, extreme negative start and end' - ); - - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/spread-test.js b/test/verbs/spread-test.js deleted file mode 100644 index a1a3e5e8..00000000 --- a/test/verbs/spread-test.js +++ /dev/null @@ -1,130 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { op, table } from '../../src'; - -tape('spread produces multiple columns from arrays', t => { - const data = { - text: ['foo bar bop', 'foo', 'bar baz', 'baz bop'] - }; - - const dt = table(data).spread( - { split: d => op.split(d.text, ' ') }, - { limit: 2 } - ); - - tableEqual(t, dt, { - ...data, - split_1: [ 'foo', 'foo', 'bar', 'baz' ], - split_2: [ 'bar', undefined, 'baz', 'bop' ] - }, 'spread data'); - t.end(); -}); - -tape('spread supports column name argument', t => { - const data = { - list: [['foo', 'bar', 'bop'], ['foo'], ['bar', 'baz'], ['baz', 'bop']] - }; - - const dt = table(data).spread('list', { drop: false, limit: 2 }); - - tableEqual(t, dt, { - ...data, - list_1: [ 'foo', 'foo', 'bar', 'baz' ], - list_2: [ 'bar', undefined, 'baz', 'bop' ] - }, 'spread data'); - t.end(); -}); - -tape('spread supports column index argument', t => { - const data = { - list: [['foo', 'bar', 'bop'], ['foo'], ['bar', 'baz'], ['baz', 'bop']] - }; - - const dt = table(data).spread(0, { limit: 2 }); - - tableEqual(t, dt, { - list_1: [ 'foo', 'foo', 'bar', 'baz' ], - list_2: [ 'bar', undefined, 'baz', 'bop' ] - }, 'spread data'); - t.end(); -}); - -tape('spread supports multiple input columns', t => { - const data = { - a: [['foo', 'bar', 'bop'], ['foo'], ['bar', 'baz'], ['baz', 'bop']], - b: [['baz', 'bop'], ['bar', 'baz'], ['foo'], ['foo', 'bar', 'bop']] - }; - - const dt = table(data).spread(['a', 'b'], { limit: 2 }); - - tableEqual(t, dt, { - a_1: [ 'foo', 'foo', 'bar', 'baz' ], - a_2: [ 'bar', undefined, 'baz', 'bop' ], - b_1: [ 'baz', 'bar', 'foo', 'foo' ], - b_2: [ 'bop', 'baz', undefined, 'bar' ] - }, 'spread data'); - t.end(); -}); - -tape('spread supports as option with single column input', t => { - const data = { - list: [['foo', 'bar', 'bop'], ['foo'], ['bar', 'baz'], ['baz', 'bop']] - }; - - const dt = table(data).spread('list', { as: ['bip', 'bop'] }); - - tableEqual(t, dt, { - bip: [ 'foo', 'foo', 'bar', 'baz' ], - bop: [ 'bar', undefined, 'baz', 'bop' ] - }, 'spread data with as'); - t.end(); -}); - -tape('spread ignores as option with multi column input', t => { - const data = { - key: ['a', 'b', 'c', 'd'], - a: [['foo', 'bar', 'bop'], ['foo'], ['bar', 'baz'], ['baz', 'bop']], - b: [['baz', 'bop'], ['bar', 'baz'], ['foo'], ['foo', 'bar', 'bop']] - }; - - const dt = table(data).spread(['a', 'b'], { limit: 2, as: ['bip', 'bop'] }); - - tableEqual(t, dt, { - key: ['a', 'b', 'c', 'd'], - a_1: [ 'foo', 'foo', 'bar', 'baz' ], - a_2: [ 'bar', undefined, 'baz', 'bop' ], - b_1: [ 'baz', 'bar', 'foo', 'foo' ], - b_2: [ 'bop', 'baz', undefined, 'bar' ] - }, 'spread data with as'); - t.end(); -}); - -tape('spread handles arrays of varying length', t => { - const data1 = { - u: [ - ['A', 'B', 'C'], - ['D', 'E'] - ] - }; - const data2 = { - u: data1.u.slice().reverse() - }; - const obj = [ - { u_1: 'A', u_2: 'B', u_3: 'C' }, - { u_1: 'D', u_2: 'E', u_3: undefined } - ]; - - t.deepEqual( - table(data1).spread('u').objects(), - obj, - 'spread data, larger first' - ); - - t.deepEqual( - table(data2).spread('u').objects(), - obj.slice().reverse(), - 'spread data, smaller first' - ); - - t.end(); -}); diff --git a/test/verbs/union-test.js b/test/verbs/union-test.js deleted file mode 100644 index 892daabf..00000000 --- a/test/verbs/union-test.js +++ /dev/null @@ -1,65 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { table } from '../../src'; - -tape('union combines tables', t => { - const t1 = table({ a: [1, 2], b: [3, 4] }); - const t2 = table({ a: [3, 4], c: [5, 6] }); - const dt = t1.union(t2); - - t.equal(dt.numRows(), 4, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 3, 4], - b: [3, 4, undefined, undefined] - }, 'union data'); - t.end(); -}); - -tape('union combines multiple tables', t => { - const t1 = table({ a: [1, 2], b: [3, 4] }); - const t2 = table({ a: [3, 4], c: [5, 6] }); - const t3 = table({ a: [5, 6], b: [7, 8] }); - - const dt = t1.union(t2, t3); - t.equal(dt.numRows(), 6, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 3, 4, 5, 6], - b: [3, 4, undefined, undefined, 7, 8] - }, 'union data'); - - const at = t1.union([t2, t3]); - t.equal(at.numRows(), 6, 'num rows'); - t.equal(at.numCols(), 2, 'num cols'); - tableEqual(t, at, { - a: [1, 2, 3, 4, 5, 6], - b: [3, 4, undefined, undefined, 7, 8] - }, 'union data'); - - t.end(); -}); - -tape('union deduplicates combined data', t => { - const t1 = table({ a: [1, 2], b: [3, 4] }); - const t2 = table({ a: [1, 2], b: [3, 6] }); - const dt = t1.union(t2); - - t.equal(dt.numRows(), 3, 'num rows'); - t.equal(dt.numCols(), 2, 'num cols'); - tableEqual(t, dt, { - a: [1, 2, 2], - b: [3, 4, 6] - }, 'union data'); - - const t3 = table({ a: [1, 1], b: [5, 5] }); - const ut = t1.union(t3); - - t.equal(ut.numRows(), 3, 'num rows'); - t.equal(ut.numCols(), 2, 'num cols'); - tableEqual(t, ut, { - a: [1, 2, 1], - b: [3, 4, 5] - }, 'union data'); - t.end(); -}); \ No newline at end of file diff --git a/test/verbs/unroll-test.js b/test/verbs/unroll-test.js deleted file mode 100644 index 8837fe5d..00000000 --- a/test/verbs/unroll-test.js +++ /dev/null @@ -1,180 +0,0 @@ -import tape from 'tape'; -import tableEqual from '../table-equal'; -import { not, op, table } from '../../src'; - -tape('unroll generates rows for array values', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [1, 2, 3]] - }; - - const ut = table(data).unroll('x', { limit: 2 }); - - tableEqual(t, ut, { - k: ['a', 'a', 'b', 'b'], - x: [1, 2, 1, 2] - }, 'unroll data'); - t.end(); -}); - -tape('unroll generates rows for array values with index', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [1, 2, 3]] - }; - - const ut = table(data).unroll('x', { limit: 2, index: true }); - - tableEqual(t, ut, { - k: ['a', 'a', 'b', 'b'], - x: [1, 2, 1, 2], - index: [0, 1, 0, 1] - }, 'unroll data with index'); - t.end(); -}); - -tape('unroll generates rows for array values with named index', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [1, 2, 3]] - }; - - const ut = table(data).unroll('x', { limit: 2, index: 'arridx' }); - - tableEqual(t, ut, { - k: ['a', 'a', 'b', 'b'], - x: [1, 2, 1, 2], - arridx: [0, 1, 0, 1] - }, 'unroll data with index'); - t.end(); -}); - -tape('unroll generates rows for parallel array values', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [4, 5, 6]], - y: [[9, 8, 7], [9, 8]] - }; - - const ut = table(data).unroll(['x', 'y']); - - tableEqual(t, ut, { - k: ['a', 'a', 'a', 'b', 'b', 'b'], - x: [1, 2, 3, 4, 5, 6], - y: [9, 8, 7, 9, 8, undefined] - }, 'unroll data'); - t.end(); -}); - -tape('unroll generates rows for parallel array values with index', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [4, 5, 6]], - y: [[9, 8, 7], [9, 8]] - }; - - const ut = table(data).unroll(['x', 'y'], { index: true }); - - tableEqual(t, ut, { - k: ['a', 'a', 'a', 'b', 'b', 'b'], - x: [1, 2, 3, 4, 5, 6], - y: [9, 8, 7, 9, 8, undefined], - index: [0, 1, 2, 0, 1, 2] - }, 'unroll data with index'); - t.end(); -}); - -tape('unroll generates rows for parallel array values with named index', t => { - const data = { - k: ['a', 'b'], - x: [[1, 2, 3], [4, 5, 6]], - y: [[9, 8, 7], [9, 8]] - }; - - const ut = table(data).unroll(['x', 'y'], { index: 'arridx' }); - - tableEqual(t, ut, { - k: ['a', 'a', 'a', 'b', 'b', 'b'], - x: [1, 2, 3, 4, 5, 6], - y: [9, 8, 7, 9, 8, undefined], - arridx: [0, 1, 2, 0, 1, 2] - }, 'unroll data with index'); - t.end(); -}); - -tape('unroll generates rows for derived array', t => { - const data = { - k: ['a', 'b'], - x: ['foo bar', 'baz bop bop'] - }; - - const ut = table(data).unroll({ t: d => op.split(d.x, ' ') }); - - tableEqual(t, ut, { - k: ['a', 'a', 'b', 'b', 'b'], - x: ['foo bar', 'foo bar', 'baz bop bop', 'baz bop bop', 'baz bop bop'], - t: ['foo', 'bar', 'baz', 'bop', 'bop'] - }, 'unroll data'); - t.end(); -}); - -tape('unroll can invert a rollup', t => { - const data = { - k: ['a', 'a', 'b', 'b'], - x: [1, 2, 3, 4] - }; - - const ut = table(data) - .groupby('k') - .rollup({ x: d => op.array_agg(d.x) }) - .unroll('x'); - - tableEqual(t, ut, data, 'unroll rollup data'); - t.end(); -}); - -tape('unroll preserves column order', t => { - const ut = table({ - x: [[1, 2, 3, 4, 5]], - v: [0] - }) - .unroll('x'); - - tableEqual(t, ut, { - x: [1, 2, 3, 4, 5], - v: [0, 0, 0, 0, 0] - }, 'unroll data'); - - t.end(); -}); - -tape('unroll can drop columns', t => { - const dt = table({ - x: [[1, 2, 3, 4, 5]], - u: [0], - v: [1] - }); - - tableEqual(t, dt.unroll('x', { drop: 'x' }), { - u: [0, 0, 0, 0, 0], - v: [1, 1, 1, 1, 1] - }, 'unroll drop-1 data'); - - tableEqual(t, dt.unroll('x', { drop: ['u', 'x'] }), { - v: [1, 1, 1, 1, 1] - }, 'unroll drop-2 array data'); - - tableEqual(t, dt.unroll('x', { drop: [0, 1] }), { - v: [1, 1, 1, 1, 1] - }, 'unroll drop-2 index data'); - - tableEqual(t, dt.unroll('x', { drop: { u: 1, x: 1 } }), { - v: [1, 1, 1, 1, 1] - }, 'unroll drop-2 object data'); - - tableEqual(t, dt.unroll('x', { drop: not('v') }), { - v: [1, 1, 1, 1, 1] - }, 'unroll drop-not data'); - - t.end(); -}); \ No newline at end of file From 0b384e6e831531c1ab49c6ca93f971c51f814752 Mon Sep 17 00:00:00 2001 From: cristianvogel Date: Fri, 11 Feb 2022 12:27:46 +0100 Subject: [PATCH 3/3] removed bigint check --- src/arrow/encode/profiler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arrow/encode/profiler.js b/src/arrow/encode/profiler.js index 0a96a00d..1bd9c5b3 100644 --- a/src/arrow/encode/profiler.js +++ b/src/arrow/encode/profiler.js @@ -101,7 +101,7 @@ function infer(p) { return Type.Float64; } else if (p.bigints === valid) { - const v = -p.min > p.max ? -p.min - 1n : p.max; + const v = -p.min > p.max ? -p.min - 1 : p.max; return p.min < 0 ? v < 2 ** 63 ? Type.Int64 : error(`BigInt exceeds 64 bits: ${v}`) @@ -135,4 +135,4 @@ function infer(p) { else { error('Type inference failure'); } -} \ No newline at end of file +}