@hyperplanes/rutils v0.3.1
rutils
A library of javascript functions that implement set and sequence-based operations in a style similar to R. The library provides analogs of seq
, union
,unique
, intersect
, setdiff
, and identical
. It also includes a cartesian
method for which there is no analog in base R.
Getting Started
This library is written in modern javascript modules, using webpack and babel to transpile to a more browser-compatible code bundle. If you are not developing with javascript modules using the ES6 import
syntax, it is recommended you use the transpiled bundle instead of the original source code.
Running transpiled code in a webpage
The transpiled release code is in dist\rutils.bundle.js
. To include this in a web app, copy the bundle to your webserver and include it in a script tag:
<script src="rutils.bundle.js"></script>
Then you can access the methods through the exported rutils
object:
var unique=rutils.unique;
var x=[1,1,2,2,3,3];
var unique_x=unique(x); //[1,2,3];
Importing the original methods in a javascript module
The original source files are in src
and all of the library methods are exported by src/index.js
. You can copy these files to your project and import them in your javascript module:
import {unique} from 'src/index.js';
let x=[1,1,2,2,3,3];
let unique_x=unique(x); //[1,2,3];
Usage
seq
- generate sequences of dates or numbers
seq
takes an object as it's argument, with the optional properties from
,to
,length_out
,by
, and along_with
. The last one, along_with
is just used to set length_out
equal to the length of an existing array. At most one of from
,to
,by
, and length_out
or along_with
can be left empty and seq
will impute it from the other parameters. If a single integer N
is passed to seq
instead of an object, it will generate an array of integers starting at 1 and counting up by 1 to N
.
Generating a sequence of even numbers starting at 2 and ending at 10:
import {seq} from 'src/index.js';
let x=seq({from:2,to:10,by:2}); //[2,4,6,8,10]
Counting the number of quarters since Q1 2010:
import {seq} from 'src/index.js';
let x=seq({from:new Date(2010,0,1), to:new Date(),by:"3 months"}).length;
Counting backwards:
import {seq} from 'src/index.js';
let x=seq({from:10,to:2,by:-2}); //[10,8,6,4,2]
What was the date 100 days ago?
import {seq} from 'src/index.js';
let x=seq({to:new Date(),by:"days", length_out:100})[0];
cumsum
- cumulative sum over an array
Cumsum takes an array of numbers and returns an array of the cumulative sum
import {seq} from 'src/index.js';
import {cumsum} from 'src/index.js';
let x=cumsum(seq(3)); //[1,3,6]
cartesian
- cartesian product of two or more arrays
cartesian can take any number of arrays as arguments, and will return an array with the cartesian product of the arguments
import {cartesian} from 'src/index.js';
let x=[1,2]
let y=['a','b'];
let result=cartesian(x,y);
// result:
// [
// [1,'a'],
// [1,'b'],
// [2,'a'],
// [2,'b']
// ];
cartesian
can handle both primitive types like numbers and strings as well as objects like dates and object literals as elements. Note, however, that it will flatten arrays of arrays, which might not be the behavior you're expecting:
let A=[[1,2],[3,4]];
let B=[
['a','b'],
['c','d']
];
let result=cartesian(A,B);
// result:
// [
// [1,2,'a','b'],
// [1,2,'c','d'],
// [3,4,'a','b'],
// [3,4,'c','d']
// ]
identical
- deep equivalence of two objects
identical(x,y)
returns true if the object x
is deep-equal to the object y
, and false otherwise. Primitive types (string, number, null, undefined, bigint, symbol, boolean) are compared by strict equality (===
), dates are compared by valueOf
, while all other objects are compared by comparing keys and values, using recursion for nested objects.
let x={
prop1:1,
prop2:{
nested1:'foo',
nested2:'bar',
nestedArray:[1,2,3]
}
}
let y={
prop1:1,
prop2:{
nested1:'foo',
nested2:'bar',
nestedArray:[1,2,3]
}
}
let result=identical(x,y); //true
let result2=identical(NaN,NaN); //also true
One exception to the above rule is that NaN
is considered identical to itself, which differs from native javascript where NaN
is the only value that is not considered equal to itself. This is because rutils intends to treat NaN
as the javascript analog to R's NA
.
intersect
- set of objects contained in both of two arrays
intersect(A,B)
returns the elements of an array A
that are identical to an element in array B
, as determined by the itentical
method above. Note that the returned objects are the exact objects that were in A
and not clones of them.
let x={
foo:'bar'
};
let A=[1,2,x];
let B=[3,2,x,5];
let result=intersect(A,B);//returns [2,x];
let isSameObject=result[1]===x; //returns true, both references point to the same object
setdiff
- difference between two arrays
setdiff(A,B)
returns the distinct elements of array A
that are not identical to any element in array B
, as determined by the identical
method above. Works the same as intersect
in that returned objects are literally elements of A
and not clones of them.
let A=[1,2,3];
let B=[3,2,4];
let result=setdiff(A,B); //returns [2,3]
union
- set of objects in either of two arrays
union(A,B)
returns a new array containing all of the unique elements of A
followed by all the unique elements of B
. If any two elements are identical, as determined by the identical
method above, only the first instance will be returned in the resulting array.
let x={
foo:'bar'
};
let A=[1,2,x];
let B=[3,2,x,5];
let result=union(A,B);//returns [1,2,x,3,5]
unique
- get the first instance of each object in an array
unique(A)
returns the first occurance of each object in array A
, using the identical
method above to compare objects.
let A=[1,2,3,2,4];
let result=unique(A); //returns [1,2,3,4]
max
- get the maximum value in an array
max(A)
returns the maximum value across its arguments. max
differs from native javascript Math.max
in that it accepts both arrays and objects as arguments and if the maximal value is a date, it will return the original date object and not an integer value.
let result1=max([1,2,3],[6,4],5); //returns 6
let result2=max([1,2,3]); //returns 3
let result3=max(1,2,3); //returns 3
let a=new Date(2019,1,1);
let b=new Date(2019,0,1);
let c=new Date(2019,2,1);
let result4=max(a,b,c);
console.log(result4); //prints Fri Mar 01 2019 00:00:00 GMT-0500 (Eastern Standard Time)
let result5=c===result4; //returns true because max returns the original object
min
- get the minimum value in an array
min(A)
returns the minimum value across its arguments. Works the same as max
above.
diff
- get lagged differences of an array
diff(A,{lag:n,differences:k})
returns an array of the k
th differences in array A
lagged by n
places. The lag
and differences
parameters are optional, with default value of 1. They must be positive integers. diff
also works on date objects but unlike R, will consistently return a result in miliseconds.
let A=[1,2,3,2,4];
let result1=diff(A); //returns [1,1,-1,2]
let result2=diff(A,{differences:2}); //returns [0,-2,3]
let result3=diff(A,{lag:2}); //returns [2,0,1]
let result4=diff(A,{lag:2,differences:2}); //returns [-1]
let B=[new Date(2019,1,1),new Date(2019,0,1),new Date(2019,2,1)];
let result5=diff(B);// returns [-2678400000, 5097600000]
Running the tests
This assumes you have cloned the rutils
directory and have Node.js installed. In the command prompt run npm install
in th rutils directory to install the required node modules. It may help to also run npm install -g webpack webpack-cli
to globally install the command-line tools for webpack.
To build the distribution bundle, run the command
npm run build
To run the unit tests, run
npm start
to launch webpack's dev server. Then open a web browser to http://localhost:8080/test/
(or whatever URL domain webpack tells you). The tests will only run in a browser, such as Chrome, that accepts modular script tags like this:
<script type="module" src="intersectTests.js"></script>
Note, although this uses QUnit for the unit tests, the command qunit
won't run the tests because Node.js is not yet compatible with the official ES6 module syntax. Also note that Chrome will refuse to load modular scripts if you open the html file directly in the browser rather than using the dev server.
Every exported function has its own file in the src\js
directory, and each file has a corresponding test/[name]Tests.js
file in the test
directory, with the exception of the sequence related source files which are all tested together in tests/seqTests.js
:
"standardize_date_units.js"
"calculate_date_by.js"
"DateMethods.js"
"seq.js"
"seq_Date.js"
"seq_date_primitive.js"
"seq_num.js"
"seq_num_primitive.js"
The unit tests aim for 100 percent code coverage. The file tests/index.html
contains script tags for all of the unit test files and therefore will run them all when opened in the browser. Additional html files in test
will run specific subsets of the unit tests.
License
This project is licensed under the MIT License - see the LICENSE.md file for details