A powerful yet light-weight interface to manage data. Designed to be used with florence.
In Node or browser applications, Datacontainer
can be installed from NPM (npm install @snlab/florence-datacontainer
). After installation, it can be imported into any project using ES6 import syntax.
import DataContainer from '@snlab/florence-datacontainer'
You can also load the library directly into an HTML page, which exports a DataContainer
global:
<script src="https://unpkg.com/@snlab/florence-datacontainer"></script>
Loading data to DataContainer
is done by passing a supported data structure as first argument to the DataContainer
constructor:
const dataContainer = new DataContainer(supportedDataStructure)
DataContainer
currently supports 3 data structures:
- Column-oriented data
- Row-oriented data
- GeoJSON
More structures might be supported in the future.
DataContainer
internally stores data in a column-oriented format.
This means that loading column-oriented data will be slightly faster than row-oriented data.
DataContainer
supports 6 data types. These data types correspond to native JS data types/structures (see table below).
Data type | JS equivalent | Loadable | Column name |
---|---|---|---|
quantitative | Number |
yes | NA |
categorical | String |
yes | NA |
temporal | Date |
yes | NA |
interval | Array of two Number s |
yes | NA |
geometry | GeoJSON geometry (except GeometryCollection ) |
only by loading GeoJSON | $geometry |
grouped | DataContainer |
no | $grouped |
Data of the types quantitative, categorical, temporal and interval are 'loadable', which means that they can be passed
in either column-oriented or row-oriented format to the DataContainer
constructor.
Geometry data can only be loaded from GeoJSON.
Geometry data can only exists in a column called $geometry
.
Grouped data is not loadable: it can only be generated by using .groupBy
or .bin
transformations (see Transformations).
A column of grouped data is just a column of other DataContainer
s.
Grouped data can only exist in a column called $grouped
.
// Column-oriented data
const columnOriented = new DataContainer({
fruit: ['apple', 'banana', 'coconut', 'durian'],
amount: [1, 2, 3, 4]
})
// Row-oriented data
const rowOriented = new DataContainer([
{ fruit: 'apple', amount: 1 },
{ fruit: 'banana', amount: 2 },
{ fruit: 'coconut', amount: 3 },
{ fruit: 'durian', amount: 4 }
])
const s = JSON.stringify
s(columnOriented.column('fruit')) === s(rowOriented.column('fruit')) // true
s(columnOriented.column('amount')) === s(rowOriented.column('amount')) // true
When loading GeoJSON FeatureCollections, the geometry data will end up in a
column called $geometry
:
const geojson = new DataContainer({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point', coordinates: [0, 0]
},
properties: {
fruit: 'apple', amount: 1
}
}
]
})
geojson.column('$geometry') // [{ type: 'Point', coordinates: [0, 0] }]
geojson.column('fruit') // ['apple']
The DataContainer
constructor takes a second argument, which is an optional object of options:
const dataContainer = new DataContainer(data, { validate: true })
Option name | Default value | Type |
---|---|---|
validate | true |
Boolean |
Setting the validate
option to false
will disable column validation. This can save a bit of time
when you are certain your data is completely valid (i.e. all columns have only one data type and contain
at least one valid that is not NaN
, undefined
, null
, or Infinity
) or don't care if it is.
More options might be added in the future.
The DataContainer
automatically generates a key for each row when data is loaded. These keys are preserved during transformations like arrange and filter to keep track of which row is which:
const dataContainer = new DataContainer({ a: [2, 4, 6, 8, 10, 12, 14] })
dataContainer.keys() // ['0', '1', '2', '3', '4', '5', '6']
const transformed = dataContainer.filter(row => row.a > 10)
transformed.keys() // ['5', '6']
Furthermore, retrieving, updating or deleting rows (see accessing data) can be done either by index or by key:
const dataContainer = new DataContainer({ a: [2, 4, 6, 8, 10, 12, 14] })
const transformed = dataContainer.filter(row => row.a > 10)
transformed.row({ index: 0 }) // { a: 12, $key: '5' }
transformed.row({ key: '5' }) // { a: 12, $key: '5' }
Automatically generated keys are always strings. Besides using automatically generated keys, it is also possible to use a column present in the data as custom key (see setKey). When using a custom key, it is recommended to use a column with primitive values, like quantitative
or categorical
columns, containing respectively Number
s and String
s. Other types and objects like dates are possible too, since the key functionality internally uses a Map. Beware, however, that a reference to the exact same (and not an equivalent) object must be used in this case to retrieve, update or delete rows.
# DataContainer.keys()
Returns an array of keys:
const dataContainer = new DataContainer({ a: [100, 200, 300], b: ['a', 'b', 'c'] })
dataContainer.keys() // ['0', '1', '2']
# DataContainer.setKey(columnName)
Sets a column as key:
const dataContainer = new DataContainer({ a: [100, 200, 300], b: ['a', 'b', 'c'] })
dataContainer.setKey('b')
dataContainer.keys() // ['a', 'b', 'c']
# DataContainer.resetKey()
Resets the current keys:
const dataContainer = new DataContainer({ a: [100, 200, 300], b: ['a', 'b', 'c'] })
dataContainer.setKey('b')
dataContainer.keys() // ['a', 'b', 'c']
dataContainer.resetKeys()
dataContainer.keys() ['0', '1', '2']
Also works to reset keys after a transformation:
const dataContainer = new DataContainer({ a: [2, 4, 6, 8, 10, 12, 14] })
const transformed = dataContainer.filter(row => row.a > 10)
transformed.keys() // ['5', '6']
transformed.resetKey()
transformed.keys() // ['0', '1']
# DataContainer.data()
Returns whatever data is currently loaded to the DataContainer
in a column-oriented format.
const dataContainer = new DataContainer([
{ fruit: 'apple', amount: 1 },
{ fruit: 'banana', amount: 2 }
])
dataContainer.data() // { fruit: ['apple', 'banana'], amount: [1, 2], $key: ['0', '1'] }
# DataContainer.row(accessorObject)
Returns an object representing a row. accessorObject
is either { index: <Number> }
or { key: <key value> }
.
const dataContainer = new DataContainer({ fruit: ['apple', 'banana'], amount: [1, 2] })
dataContainer.row({ index: 0 }) // { fruit: 'apple', amount: 1, $key: '0' }
# DataContainer.rows()
Returns an Array
of rows.
const dataContainer = new DataContainer({ fruit: ['apple', 'banana'], amount: [1, 2] })
dataContainer.rows()
/* [
* { fruit: 'apple', amount: 1, $key: '0' },
* { fruit: 'banana', amount: 2, $key: '1' },
* ]
*/
# DataContainer.column(columnName)
Returns a column as an Array
.
const dataContainer = new DataContainer({ fruit: ['apple', 'banana'], amount: [1, 2] })
dataContainer.column('fruit') // ['apple', 'banana']
dataContainer.keys() // ['0', '1']
# DataContainer.map(columnName, func)
Equivalent to .column(columnName).map(func)
# DataContainer.nrow()
Returns the number of rows.
# DataContainer.columnNames()
Returns the names of the currently loaded columns.
# DataContainer.domain(columnName)
Returns the domain of a column.
const dataContainer = new DataContainer({
fruit: ['apple', 'banana', 'apple', 'banana'],
quantity: [1, 2, 3, 4],
dayOfSale: [new Date(2019, 4, 3), new Date(2019, 4, 4), new Date(2019, 4, 5), new Date(2019, 4, 6)]
})
dataContainer.domain('fruit') // ['apple', 'banana']
dataContainer.domain('quantity') // [1, 4]
dataContainer.domain('dayOfSale') // [Date Fri May 03 2019 ..., Date Mon May 06 2019 ...]
For geometry data (.domain('$geometry')
), this will return the bounding box.
# DataContainer.min(columnName)
Equivalent to domain(columnName)[0]
. Only works for quantitative and interval columns.
# DataContainer.max(columnName)
Equivalent to domain(columnName)[1]
. Only works for quantitative and interval columns.
# DataContainer.bbox()
Equivalent to .domain('$geometry')
# DataContainer.type(columnName)
Returns the type of a column.
const dataContainer = new DataContainer({
fruit: ['apple', 'banana', 'apple', 'banana'],
quantity: [1, 2, 3, 4],
dayOfSale: [new Date(2019, 4, 3), new Date(2019, 4, 4), new Date(2019, 4, 5), new Date(2019, 4, 6)]
})
dataContainer.type('fruit') // categorical
dataContainer.type('quantity') // quantitative
dataContainer.type('dayOfScale') // temporal
Some convenience functions to check data during development.
# DataContainer.hasColumn(columnName)
Checks if the DataContainer
has a column.
# DataContainer.hasRow(accessorObject)
Checks if the DataContainer
has a row. See .row for an explanation of the accessorObject
.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4] })
dataContainer.hasColumn('a') // true
dataContainer.hasColumn('b') // false
# DataContainer.columnIsValid(columnName)
Check if a column is valid.
const dataContainer = new DataContainer({ a: [1, NaN, 3] })
.mutate({ b: () => NaN })
dataContainer.columnIsValid('a') // true
dataContainer.columnIsValid('b') // false
# DataContainer.validateColumn(columnName)
Similar to columnIsValid
, but throws an error if a column is invalid, instead of returning false
.
,
const dataContainer = new DataContainer({ a: [1, NaN, 3] })
.mutate({ b: () => NaN })
dataContainer.validateColumn('a') // nothing happens
dataContainer.validateColumn('b') // throws error
# DataContainer.validateAllColumns()
When data is first loaded to the DataContainer
, validateAllColumns
is ran by default.
To avoid wasting time on checks after every subsequent transformation, it is not ran after that.
If you want DataContainer
to throw an error if any data has somehow become invalid, you can call this method manually.
Invalid here means that they contain mixed types, or only have invalid data like NaN
.
DataContainer
's transformations are heavily inspired by R's dplyr
(part of the tidyverse). All transformations will return a new DataContainer
.
# DataContainer.select(selectInstructions)
select
returns a DataContainer
with only the columns specified in selectInstructions
. selectInstructions
can be a single String
column name, or an Array
of column names.
const dataContainer = new DataContainer({
fruit: ['apple', 'banana'],
quantity: [1, 2],
dayOfSale: [new Date(2019, 4, 3), new Date(2019, 4, 4)]
})
const withoutDayOfSale = dataContainer.select(['fruit', 'quantity'])
withoutDayOfSale.data() // { fruit: ['apple', 'banana'], quantity: [1, 2], $key: ['0', '1'] }
# DataContainer.rename(renameInstructions)
rename
is used to rename columns. renameInstructions
must be an object with current column names as keys, and desired new column names as values.
const dataContainer = new DataContainer({ f: ['apple', 'banana'], a: [1, 2] })
const renamed = dataContainer.rename({ f: 'fruit', a: 'amount' })
renamed.column('fruit') // ['apple', 'banana']
# DataContainer.filter(filterFunction)
filter
will throw away all rows that do not satisfy the condition expressed in the filterFunction
.
const dataContainer = new DataContainer({ fruit: ['apple', 'banana'], amount: [1, 2] })
dataContainer.filter(row => row.fruit !== 'banana').data() // { fruit: ['apple'], amount: [1], $key: ['0'] }
# DataContainer.dropNA(dropNAInstructions)
dropNA
is essentially a special case of filter
that disposes of invalid values like NaN
, null
or undefined
.
dropNAInstructions
can be
- nothing, in which case it will dispose of all rows in all columns that contain invalid values
- a
String
value with a column name. All rows that have invalid values in this column will be removed - an
Array
of column names (String
s). All rows that have invalid values in any of these columns will be removed
const dataContainer = new DataContainer(
{ a: [1, 2, undefined, 4], b: [5, null, 7, 8], c: [NaN, 10, 11, 12] }
)
dataContainer.dropNA().data() // { a: [4], b: [8], c: [12], $key: [3] }
dataContainer.dropNA(['a', 'b']).data() // { a: [1, 4], b: [5, 8], c: [NaN, 12], $key: ['0', '3'] }
# DataContainer.arrange(arrangeInstructions)
arrange
is used to sort data. arrangeInstructions
can be an
Object
, with exactly one key (the column by which to sort) and one value (how to sort it, either'ascending'
,'descending
' or a compareFunction)Array
, containingObject
s as described in the line above
const dataContainer = new DataContainer({
fruit: ['apple', 'banana', 'coconut' 'durian', 'coconut', 'banana'],
amount: [4, 3, 7, 2, 4, 5]
})
const arranged = dataContainer.arrange([ { fruit: 'descending' }, { amount: 'ascending' } ])
arranged.data()
/* {
* fruit: ['durian', 'coconut', 'coconut', 'banana', 'banana', 'apple']
* value: [2, 4, 7, 3, 5, 4],
* $key: ['3', '4', '2', '1', '5', '0']
* } */
# DataContainer.mutate(mutateInstructions)
mutate
can be used to generate a new column based on existing rows.
mutateInstructions
must be an object with new column names as keys, and functions showing how to calculate the new column as values.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4 ]})
const dataContainerWithASquared = dataContainer.mutate({ aSquared: row => row.a * row.a })
dataContainerWithASquared.column('aSquared') // [1, 4, 9, 16]
# DataContainer.transmute(mutateInstructions)
Same as mutate
, except that it removes all the old columns.
# DataContainer.groupBy(groupByInstructions)
Used to split up a DataContainer
in several DataContainer
s based on different categories.
groupByInstructions
can be a String
containing a single column name, or an Array
containing multiple column names.
const dataContainer = new DataContainer(
{ fruit: ['apple', 'banana', 'banana', 'apple'], amount: [10, 5, 13, 9] }
)
const grouped = dataContainer.groupBy('fruit')
grouped.column('fruit') // ['apple', 'banana']
grouped.column('$grouped') // [<DataContainer>, <DataContainer>]
grouped.map('$grouped', group => group.column('amount')) // [[10, 9], [5, 13]]
# DataContainer.bin(binInstructions)
Used to split up a DataContainer
in several DataContainers
based on classification of quantitative data.
const dataContainer = new DataContainer(
{ a: [1, 2, 3, 4, 5, 6, 7], b: [8, 9, 10, 11, 12, 13, 14] }
)
const binned = dataContainer.bin({ column: 'a', method: 'EqualInterval', numClasses: 3 })
binned.column('bins') // [[1, 3], [3, 5], [5, 7]]
binned.type('bins') // 'interval'
binned.row(1).$grouped.rows() // [{ a: 3, b: 10, $key: '2' }, { a: 4, b: 11, $key: '3' }]
Besides 'EqualInterval'
, other methods of classification are supported. Different methods might require different additional
keys to be passed to binInstructions
. See the table below for an overview.
Class. method | option name |
---|---|
'EqualInterval' |
numClasses |
'StandardDeviation' |
numClasses |
'Quantile' |
numClasses |
'Jenks' |
numClasses |
'CKMeans' |
numClasses |
'IntervalSize' |
binSize |
'Manual' |
manualClasses |
For 'Manual'
, manualClasses
is required and must be an Array of interval
s, which will become the bins.
The classification is performed internally by classify-series.
It is also possible to bin over multiple dimensions by providing an Array
of binInstructions
.
Instead of a single bins
column, this will create multiple columns called bins_<original column name>
:
const dataContainer = new DataContainer({
a: [1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5],
b: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 5, 5, 5, 5]
})
const binned = dataContainer.bin([
{ column: 'a', method: 'IntervalSize', binSize: 2 },
{ column: 'b', method: 'IntervalSize', binSize: 2 }
])
console.log(binned.column('bins_a')) // [[1, 3], [1, 3], [3, 5], [3, 5]]
console.log(binned.column('bins_b')) // [[1, 3], [3, 5], [1, 3], [3, 5]]
# DataContainer.summarise(summariseInstructions)
Used to summarise columns. You can also use summarize
if you prefer. summariseInstructions
must be an Object with new column
names as keys, and columnInstruction
Object
s as values. These columnInstruction
s must have the name of the column
that is to be summarised as key, and the summaryMethod
as value.
When applying summarise
to a grouped DataContainer
, the summaries of the groups will be taken.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4], b: ['a', 'b', 'a', 'b'] })
const grouped = dataContainer.groupBy('b')
dataContainer.summarise({ mean_a: { a: 'mean' }}).data() // { mean_a: [2.5], $key: ['0'] }
grouped.summarise({ mean_a: { a: 'mean' } }).data() // { b: ['a', 'b'], mean_a: [2, 3], $key: ['0', '1'] }
The following summaryMethod
s are available:
- count
- sum
- mean
- median
- mode
- min
- max
It also possible to create your own summaryMethod
by providing a function that receives the requested column as first argument,
and returns a value that's either quantitative, categorical, temporal or an interval.
# DataContainer.mutarise(mutariseInstructions)
mutarise
(or mutarize
) is similar to summarise
, but instead of collapsing the data to a single row, the summarised value
will be added as a new column.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4], b: ['a', 'b', 'a', 'b'] })
const mutarised = dataContainer.groupBy('b').mutarise({ mean_a: { a: 'mean' } })
mutarised.column('a') // [1, 2, 3, 4]
mutarised.column('mean_a') // [2, 3, 2, 3]
# DataContainer.transform(transformFunction)
Used for arbitrary transformations on the data. transformFunction
receives an Object
with all the columns currently loaded,
and should return another object with columns.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4] })
const transformed = dataContainer.transform(columns => {
const b = columns.a.map(a => a ** 2)
return { b }
})
transformed.column('b') // [1, 4, 9, 16]
# DataContainer.reproject(reprojectFunction)
Used to reproject data in the $geometry
column. Can only be used when a $geometry
column is present.
reprojectFunction
should be a function that accepts an Array
of two Number
s and returns an Array
of two Number
s.
Particularly convenient to use with proj4:
const reprojectFunction = proj4('EPSG:4326', 'EPSG:3857').forward
const dataContainer = new DataContainer(geojson).reproject(reprojectFunction)
# DataContainer.cumsum(cumsumInstructions, { asInterval: false })
Calculates the cumulative sum of one or more columns. cumsumInstructions
is an Object
with new column
names as keys, and the columns on which the cumulative sum should be based as values. Only quantitative
columns can be used.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4] })
dataContainer.cumsum({ cumsum_a: 'a' }).column('cumsum_a') // [1, 3, 6, 10]
If asInterval
is set to true
, intervals instead of integers will be returned (mimicking d3's
stack):
const dataContainer = new DataContainer({ a: [1, 2, 3, 4] })
.cumsum({ cumsum_a: 'a' }, { asInterval: true })
dataContainer.column('cumsum_a') // [[0, 1], [1, 3], [3, 6], [6, 10]]
# DataContainer.rowCumsum(cumsumInstructions, { asInterval: false })
Calculates the cumulative sum over all rows for the selected columns. cumsumInstructions
is an Array
with either:
- column names (
String
s) Object
s with only one key and one value, where the key is the new column name and the value the old.
In the case of 1., the old columns will be overwritten.
const dataContainer = new DataContainer({
a: [1, 2, 3, 4],
b: [1, 2, 3, 4],
c: [1, 2, 3, 4]
}).rowCumsum(['a', 'b', { rowCumsum_c: 'c' }])
dataContainer.column('a') // [1, 2, 3, 4]
dataContainer.column('b') // [2, 4, 6, 8]
dataContainer.column('rowCumsum_c') // [3, 6, 9, 12]
dataContainer.column('c') // [1, 2, 3, 4]
rowCumsum
's asInterval
functionality works similar to cumsum
's.
# DataContainer.pivotLonger(pivotInstructions)
Pivots 'wide' data to 'long' data- meaning that a set of selected columns and their values will be converted to two columns: one containing the selected columns' names, and one containing their values.
const dataContainer = new DataContainer({
col1: [1, 2],
col2: [10, 20],
col3: ['a', 'b'],
col4: ['aa', 'bb']
}).pivotLonger({
columns: ['col3', 'col4'],
namesTo: 'name',
valuesTo: 'value'
})
dataContainer.column('col1') // [1, 1, 2, 2]
dataContainer.column('name') // ['col3', 'col4', 'col3', 'col4']
dataContainer.column('value') // ['a', 'aa', 'b', 'bb']
# DataContainer.pivotWider(pivotInstructions)
The opposite of pivotLonger: pivots 'long' to 'wide' data, meaning that two columns will be converted into a larger set of columns. Missing values in the wider data will be filled with null
. This default behavior can be changed by providing a valuesFill
option in de pivotInstructions
object, besides the namesFrom
and valuesFrom
options.
const dataContainer = new DataContainer({
idCol: ['a', 'a', 'b', 'b', 'b', 'c', 'c'],
names: ['x', 'y', 'x', 'y', 'z', 'x', 'z'],
values: [1, 2, 10, 20, 30, 100, 300]
}).pivotWider({
namesFrom: 'names',
valuesFrom: 'values'
})
dataContainer.column('idCol') // ['a', 'b', 'c']
dataContainer.column('x') // [1, 10, 100]
dataContainer.column('y') // [2, 20, null]
All of these functions work in-place.
# DataContainer.addRow(row)
Adds a new row to the DataContainer
. row
must be an object with one key for every column.
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.addRow({ a: 4, b: 'd' })
dataContainer.column('b') // ['a', 'b', 'c', 'd']
# DataContainer.updateRow(accessorObject, row)
Updates an existing row. accessorObject
is either { index: <Number> }
or { key: <key value> }
(see keying).
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.updateRow({ index: 2 }, { a: 100 })
dataContainer.column('a') // [1, 2, 100]
Instead of using an Object
as the second argument, it is also possible to use a Function
. This function
will receive the existing row as first argument, and must return an Object
.
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.updateRow({ index: 2 }, row => ({ a: row.a + 100 }))
dataContainer.column('a') // [1, 2, 103]
# DataContainer.deleteRow(accessorObject)
Deletes an existing row.
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.deleteRow({ index: 2 })
dataContainer.column('a') // [1, 2]
All of these functions work in-place.
# DataContainer.addColumn(columnName, column)
Adds a new column. column
must be an Array
of the same length as the rest of the data.
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.addColumn('c', [4, 5, 6])
dataContainer.column('c') // [4, 5, 6]
# DataContainer.replaceColumn(columnName, column)
Equivalent to calling .deleteColumn
followed by addColumn
.
# DataContainer.deleteColumn(columnName)
Deletes an existing column.
const dataContainer = new DataContainer({ a: [1, 2, 3], b: ['a', 'b', 'c'] })
dataContainer.deleteColumn('b')
dataContainer.data() // { $key: ['0', '1', '2'], a: [1, 2, 3] }
# DataContainer.bounds(binInstructions)
Returns an array containing the boundaries of the classes found by the classification/binning algorithm. See bin for the structure of binInstructions
.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4, 5, 6, 7] })
dataContainer.bounds(
{ column: 'a', method: 'EqualInterval', numClasses: 3 }
) // [3, 5]
# DataContainer.fullBounds(binInstructions)
Similar to .bounds, but returns the minimum and maximum value as well:
const dataContainer = new DataContainer({ a: [1, 2, 3, 4, 5, 6, 7] })
dataContainer.fullBounds(
{ column: 'a', method: 'EqualInterval', numClasses: 3 }
) // [1, 3, 5, 7]
# DataContainer.boundRanges(binInstructions)
Similar to .fullBounds, but different format:
const dataContainer = new DataContainer({ a: [1, 2, 3, 4, 5, 6, 7] })
dataContainer.boundRanges(
{ column: 'a', method: 'EqualInterval', numClasses: 3 }
) // [[1, 3], [3, 5], [5, 7]]
# DataContainer.classify(binInstructions, range)
Returns a threshold scale based on the bounds determined by classification/binning algorithm, with any type of range
. range
must be an array with the same length as numClasses
in the binInstructions
.
const dataContainer = new DataContainer({ a: [1, 2, 3, 4, 5, 6, 7] })
const scale = dataContainer.classify(
{ column: 'a', method: 'EqualInterval', numClasses: 3 },
['red', 'blue', 'green']
)
scale(2) // 'red'
scale(4) // 'blue'
scale(6) // 'green'