What is ndarray?
The ndarray npm package is a multidimensional array library for JavaScript. It provides efficient storage and manipulation of large datasets, similar to NumPy arrays in Python. It is particularly useful for scientific computing, image processing, and other applications that require handling of large, multi-dimensional datasets.
What are ndarray's main functionalities?
Creating an ndarray
This feature allows you to create a new ndarray. The first argument is the data, and the second argument is the shape of the array. In this example, a 2x2 array is created.
const ndarray = require('ndarray');
const array = ndarray([1, 2, 3, 4], [2, 2]);
console.log(array);
Accessing elements
This feature allows you to access elements in the ndarray using the `get` method. The indices are provided as arguments to the method.
const ndarray = require('ndarray');
const array = ndarray([1, 2, 3, 4], [2, 2]);
console.log(array.get(0, 1)); // Output: 2
Setting elements
This feature allows you to set elements in the ndarray using the `set` method. The indices and the new value are provided as arguments to the method.
const ndarray = require('ndarray');
const array = ndarray([1, 2, 3, 4], [2, 2]);
array.set(0, 1, 5);
console.log(array.get(0, 1)); // Output: 5
Slicing
This feature allows you to create a sub-array or slice of the original ndarray using the `hi` method. The arguments specify the end indices for each dimension.
const ndarray = require('ndarray');
const array = ndarray([1, 2, 3, 4, 5, 6], [2, 3]);
const slice = array.hi(1, 2);
console.log(slice);
Transpose
This feature allows you to transpose the ndarray, swapping its dimensions. The arguments specify the new order of the dimensions.
const ndarray = require('ndarray');
const array = ndarray([1, 2, 3, 4], [2, 2]);
const transposed = array.transpose(1, 0);
console.log(transposed);
Other packages similar to ndarray
numjs
Numjs is a library that provides similar functionality to ndarray, offering a NumPy-like API for JavaScript. It supports operations on multi-dimensional arrays and matrices, and is designed for scientific computing. Compared to ndarray, numjs provides a higher-level API that is more similar to NumPy, making it easier for users familiar with Python's NumPy library.
mathjs
Math.js is a comprehensive math library for JavaScript and Node.js. It includes support for complex numbers, matrices, and multi-dimensional arrays. While it offers a broader range of mathematical functions compared to ndarray, it is also more complex and may be overkill for users who only need basic multi-dimensional array manipulation.
scijs
SciJS is a collection of scientific computing libraries for JavaScript. It includes modules for linear algebra, statistics, and multi-dimensional array manipulation. SciJS is more modular compared to ndarray, allowing users to pick and choose the specific functionalities they need. However, it may require more effort to integrate the different modules compared to using a single library like ndarray.
ndarray
Multidimensional arrays for JavaScript.
Basic Usage
First, install the library using npm:
npm install ndarray
Then you can use it in your projects as follows:
var ndarray = require("ndarray")
To create an array full of zeros, you just call ndarray.zeros()
. For example, this makes a 128x128 array of floats:
var img = ndarray.zeros([128, 128], ndarray.FLOAT32)
You can also wrap existing typed arrays in ndarrays. For example, here is how you can turn a length 4 typed array into an nd-array:
var mat = ndarray(new Float64Array([1, 0, 0, 1]), [2,2])
Once you have an nd-array you can access elements using .set
and .get
. For example, here is some code to apply a box filter to an image using these routines:
var A = ndarray.zeros([128,128])
var B = ndarray.zeros([128,128])
for(var i=1; i<127; ++i) {
for(var j=1; j<127; ++j) {
var s = 0;
for(var dx=-1; dx<=1; ++dx) {
for(var dy=-1; dy<=1; ++dy) {
s += A.get(i+dx, j+dy)
}
}
B.set(i,j,s/9.0)
}
}
You can also pull out views of ndarrays without copying the underlying elements. Here is an example showing how to update part of a subarray:
var x = ndarray.zeros([5, 5])
var y = x.hi(4,4).lo(1,1)
for(var i=0; i<y.shape[0]; ++i) {
for(var j=0; j<y.shape[1]; ++j) {
y.set(i,j,1)
}
}
It is also possible to do bulk assignment to views of ndarrays:
var x = ndarray.zeros([5,5])
var y = ndarray.zeros([3,3])
for(var i=0; i<y.shape[0]; ++i) {
for(var j=0; j<y.shape[1]; ++j) {
y.set(i,j,1)
}
}
x.hi(3,3).assign(y)
x.lo(2,2).assign(y)
And you can also make copies of arrays:
var x = ndarray.zeros([1])
var z = x.clone()
z.set(0, 1)
Basic Functions
ndarray(typed_array[, shape, order])
Creates an n-dimensional array view wrapping the specified typed array.
typed_array
is a typed arrayshape
is the shape of the view (Default: [typed_array].length
)order
is an ordered list giving the order of the coordinates for the typed array. (Default: row major, or [shape.length-1, shape.length-2, ..., 1, 0]
)
Returns an n-dimensional array view of the buffer
ndarray.dtype(array)
Returns the data type of the underlying array. The result is one of the following strings: int8
, int16
, int32
, uint8
, uint16
, uint32
, float32
, float64
.
ndarray.zeros(shape[, dtype, order])
Creates an array filled with zeros.
shape
is the shape of the array to createdtype
is the datatype of the array to create. Must be one of the types specified in the above list.order
is the order of the components of the array.
Returns a view of a newly allocated array.
View Properties
data
The underlying typed array of the multidimensional array
shape
The shape of the multidimensional array
stride
The stride of the typed array
get(i,j,k,...)
Retrieves an element from the array
set(i,j,k,..., v)
Sets an element in the array to value v
clone([o0, o1, o2, ... ])
Makes a copy of the array with an optionally specified ordering
assign(other)
Copies the contents of other
into this view
lo(i,j,k,...)
Returns a new view of a shifted subarray
hi(i,j,k,...)
Returns a new view of a truncated subarray
FAQ
What are the goals of this library?
To expose a simple, low level interface for working with contiguous blocks of memory. The intended applications for this code are:
- WebGL interoperability
- Image processing
- Volume graphics
- Mesh processing
- Scientific computing (ie finite difference based PDE solvers)
This is not a linear algebra library, and does not implement things like component-wise arithmetic or tensor operations. (Though it should be possible to build such features on top of this library as separate module.) For now, the best option if you need those features would be to use numeric.js.
How does it work?
The central concept in ndarray is the idea of a view
. A view is basically an ArrayBufferView together with a shape and a stride. The shape
of an ndarray is basically its dimensions, while the stride
describes how it is arranged in memory. To compute an index in a view, you would use the following recipe:
this.data[i0 * this.stride[0] + i1 * this.stride[1] + i2 * this.stide[2] ....]
Where i0, i1, ...
is the index of the element we are accessing.
Note: You should not assume that this.stride[this.stride-length-1]=1
. In general, a view can be arranged in either C/row major order), FORTRAN/column major), or anything in between. Also, the contents of a view may not be packed tightly, as it could represent some view of a subarray.
Why use this library instead of manual management of flat typed arrays?
While you can recreate the functionality of this library using typed arrays and manual index arithmetic, in practice doing that is very tedious and error prone. It also means that you need to pass around extra semantic information, like the shape of the multidimensional array and it's striding. Using a view, you can get nearly the same performance as a flat typed array, while still maintaining all of the relevant semantic information.
Why use this library instead of numeric.js?
Numeric.js is a fantastic library, and has many useful features for numerical computing. If you are working with sparse linear systems, need to do quadratic programming or solve some other complicated problem it should be your go-to library. However, numeric.js uses arrays-of-native-arrays to encode multidimensional arrays. Doing this presents several problems:
- Native arrays are much slower than typed arrays. Proof
- Allocating an array of native-arrays induces an overhead of O(shape.length^2) extra independent JavaScript objects. Not only does this greatly increase the amount of memory they consume, but it also prevents them from scaling with block size (leading to cache performance problems).
- Slicing arrays-of-arrays is an O(n) operation, while resizing a view is only O(1) and can be done without allocating any intermediate objects.
- Arrays-of-arrays can not be directly uploaded to WebGL, and instead require a costly "unboxing" step to convert them into a typed array.
What optimizations does this library use?
The following optimizations are planned:
- Typed array back storage
- 0-allocation accessor interface
- In place slicing (ie
subarray()
like semantics) - Optimized classes for low dimensional views (shape.length <= 4)
- Cache oblivious view assignment and copying
Does this library do any error checking?
No. Don't write past the bounds of the array or you will crash/corrupt its contents.
Credits
(c) 2013 Mikola Lysenko. MIT License