ndarray vs ndarray-pack vs ndarray-ops vs ndarray-scratch
N-dimensional Array Manipulation in JavaScript Comparison
3 Years
ndarrayndarray-packndarray-opsndarray-scratch
What's N-dimensional Array Manipulation in JavaScript?

N-dimensional array manipulation libraries in JavaScript provide tools for creating, manipulating, and performing mathematical operations on multi-dimensional arrays (tensors). These libraries are essential for tasks in scientific computing, data analysis, and machine learning, where operations on high-dimensional data are common. They offer functionalities such as element-wise operations, linear algebra, and support for various array shapes and types, enabling efficient and expressive manipulation of complex data structures. ndarray is a foundational library for creating and manipulating n-dimensional arrays, while ndarray-ops adds a suite of mathematical operations for these arrays. ndarray-pack focuses on packing and unpacking data between different formats, and ndarray-scratch provides a temporary array allocation system to optimize memory usage during computations.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
ndarray591,243
1,234-226 years agoMIT
ndarray-pack372,739
10-19 years agoMIT
ndarray-ops340,796
67-611 years agoMIT
ndarray-scratch82,795
12-510 years agoMIT
Feature Comparison: ndarray vs ndarray-pack vs ndarray-ops vs ndarray-scratch

Core Functionality

  • ndarray:

    ndarray provides the core functionality for creating and manipulating n-dimensional arrays. It supports various array shapes, data types, and basic operations like indexing, slicing, and reshaping.

  • ndarray-pack:

    ndarray-pack focuses on packing and unpacking data in n-dimensional arrays. It provides utilities for converting arrays to and from binary formats, making it easier to handle data serialization and deserialization.

  • ndarray-ops:

    ndarray-ops extends the functionality of ndarray by adding a comprehensive set of mathematical operations, including element-wise operations, reductions, and linear algebra functions. It requires ndarray as a dependency.

  • ndarray-scratch:

    ndarray-scratch provides a system for allocating temporary arrays that can be reused during computations. This helps reduce memory allocation overhead and improve performance in scenarios where temporary arrays are frequently created and discarded.

Performance Optimization

  • ndarray:

    ndarray is designed to be lightweight and efficient, with minimal overhead for array manipulation. However, it does not include specialized optimizations for performance-critical applications.

  • ndarray-pack:

    ndarray-pack is focused on efficient data packing and unpacking, which can improve performance in applications that require frequent serialization and deserialization of array data. However, it does not provide general performance optimizations for array manipulation.

  • ndarray-ops:

    ndarray-ops is optimized for performance when performing mathematical operations on n-dimensional arrays. It is designed to work efficiently with the ndarray library, but the performance may vary depending on the complexity of the operations being performed.

  • ndarray-scratch:

    ndarray-scratch is specifically designed to optimize memory usage and reduce the overhead of frequent array allocations. By reusing temporary arrays, it helps improve performance in scenarios where memory allocation is a bottleneck.

Use Case Scenarios

  • ndarray:

    ndarray is suitable for a wide range of applications that require basic n-dimensional array manipulation, such as data processing, visualization, and simple scientific computing.

  • ndarray-pack:

    ndarray-pack is useful in applications that involve data serialization, such as sending array data over the network, saving to binary files, or interfacing with WebAssembly.

  • ndarray-ops:

    ndarray-ops is ideal for applications in scientific computing, machine learning, and data analysis that require advanced mathematical operations on n-dimensional arrays.

  • ndarray-scratch:

    ndarray-scratch is beneficial in performance-critical applications where minimizing memory allocation and garbage collection is important, such as real-time data processing or large-scale simulations.

Ease of Integration

  • ndarray:

    ndarray can be easily integrated into any JavaScript project, as it has no external dependencies and a simple API for array manipulation.

  • ndarray-pack:

    ndarray-pack can be integrated into projects that require data packing and unpacking functionality, especially those that work with binary data or need efficient serialization methods.

  • ndarray-ops:

    ndarray-ops integrates seamlessly with ndarray, making it easy to add mathematical capabilities to projects that already use the ndarray library.

  • ndarray-scratch:

    ndarray-scratch can be used alongside other ndarray libraries to optimize memory usage during computations, making it a valuable addition to performance-sensitive applications.

Example Code

  • ndarray:

    Creating and manipulating n-dimensional arrays with ndarray

    const ndarray = require('ndarray');
    const arr = ndarray(new Float32Array(12), [3, 4]); // Create a 3x4 array
    arr.set(1, 2, 42); // Set value at (1, 2)
    console.log(arr.get(1, 2)); // Get value at (1, 2)
    console.log(arr.shape); // Get array shape
    
  • ndarray-pack:

    Packing and unpacking data with ndarray-pack

    const ndarray = require('ndarray');
    const pack = require('ndarray-pack');
    const arr = ndarray(new Float32Array([1, 2, 3, 4]), [2, 2]);
    const packed = pack(arr); // Pack array data
    console.log(packed); // Packed binary data
    const unpacked = pack.unpack(packed, [2, 2]); // Unpack data
    console.log(unpacked.data); // Output: [1, 2, 3, 4]
    
  • ndarray-ops:

    Performing mathematical operations with ndarray-ops

    const ndarray = require('ndarray');
    const ops = require('ndarray-ops');
    const arr1 = ndarray(new Float32Array([1, 2, 3, 4]), [2, 2]);
    const arr2 = ndarray(new Float32Array([5, 6, 7, 8]), [2, 2]);
    ops.add(arr1, arr2, arr1); // Add arr2 to arr1
    console.log(arr1.data); // Output: [6, 8, 10, 12]
    
  • ndarray-scratch:

    Using temporary arrays with ndarray-scratch

    const ndarray = require('ndarray');
    const scratch = require('ndarray-scratch');
    const arr = ndarray(new Float32Array(10), [10]);
    const temp = scratch.alloc1d(10); // Allocate temporary array
    for (let i = 0; i < arr.shape[0]; i++) {
      temp.set(i, arr.get(i) * 2); // Use temporary array
    }
    scratch.free(temp); // Free temporary array
    
How to Choose: ndarray vs ndarray-pack vs ndarray-ops vs ndarray-scratch
  • ndarray:

    Choose ndarray if you need a lightweight and flexible library for creating and manipulating n-dimensional arrays without any dependencies. It is ideal for projects that require basic array manipulation and mathematical operations.

  • ndarray-pack:

    Choose ndarray-pack if your work involves packing and unpacking data between different formats, such as converting arrays to and from binary or other structured formats. This is useful for applications that require efficient data serialization and deserialization.

  • ndarray-ops:

    Choose ndarray-ops if you need to perform a wide range of mathematical operations on n-dimensional arrays. It is suitable for applications in scientific computing, data analysis, and machine learning where advanced mathematical functions are required.

  • ndarray-scratch:

    Choose ndarray-scratch if you want to optimize memory usage during computations by using temporary arrays. This package is beneficial in performance-critical applications where minimizing memory allocation and garbage collection is important.

README for ndarray

ndarray

Modular multidimensional arrays for JavaScript.

browser support

build status

stable

Browse a number of ndarray-compatible modules in the scijs documentation
Coming from MATLAB or numpy? See: scijs/ndarray for MATLAB users
Big list of ndarray modules

Introduction

ndarrays provide higher dimensional views of 1D arrays. 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])

//Now:
//
// mat = 1 0
//       0 1
//

Once you have an nd-array you can access elements using .set and .get. For example, here is an implementation of Conway's game of life using ndarrays:

function stepLife(next_state, cur_state) {

  //Get array shape
  var nx = cur_state.shape[0], 
      ny = cur_state.shape[1]

  //Loop over all cells
  for(var i=1; i<nx-1; ++i) {
    for(var j=1; j<ny-1; ++j) {

      //Count neighbors
      var n = 0
      for(var dx=-1; dx<=1; ++dx) {
        for(var dy=-1; dy<=1; ++dy) {
          if(dx === 0 && dy === 0) {
            continue
          }
          n += cur_state.get(i+dx, j+dy)
        }
      }
      
      //Update state according to rule
      if(n === 3 || n === 3 + cur_state.get(i,j)) {
        next_state.set(i,j,1)
      } else {
        next_state.set(i,j,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(new Float32Array(25), [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)
  }
}

//Now:
//    x = 0 0 0 0 0
//        0 1 1 1 0
//        0 1 1 1 0
//        0 1 1 1 0
//        0 0 0 0 0

ndarrays can be transposed, flipped, sheared and sliced in constant time per operation. They are useful for representing images, audio, volume graphics, matrices, strings and much more. They work both in node.js and with browserify.

Install

Install the library using npm:

npm install ndarray

You can also use ndarrays in a browser with any tool that follows the CommonJS/node module conventions. The most direct way to do this is to use browserify. If you want live-reloading for faster debugging, check out beefy.

API

Once you have ndarray installed, you can use it in your project as follows:

var ndarray = require("ndarray")

Constructor

ndarray(data[, shape, stride, offset])

The default module.exports method is the constructor for ndarrays. It creates an n-dimensional array view wrapping an underlying storage type

  • data is a 1D array storage. It is either an instance of Array, a typed array, or an object that implements get(), set(), .length
  • shape is the shape of the view (Default: data.length)
  • stride is the resulting stride of the new array. (Default: row major)
  • offset is the offset to start the view (Default: 0)

Returns an n-dimensional array view of the buffer

Members

The central concept in ndarray is the idea of a view. The way these work is very similar to SciPy's array slices. Views are affine projections to 1D storage types. To better understand what this means, let's first look at the properties of the view object. It has exactly 4 variables:

  • array.data - The underlying 1D storage for the multidimensional array
  • array.shape - The shape of the typed array
  • array.stride - The layout of the typed array in memory
  • array.offset - The starting offset of the array in memory

Keeping a separate stride means that we can use the same data structure to support both row major and column major storage

Element Access

To access elements of the array, you can use the set/get methods:

array.get(i,j,...)

Retrieves element i,j,... from the array. In psuedocode, this is implemented as follows:

function get(i,j,...) {
  return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ]
}

array.set(i,j,...,v)

Sets element i,j,... to v. Again, in psuedocode this works like this:

function set(i,j,...,v) {
  return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ] = v
}

array.index(i,j, ...)

Retrieves the index of the cell in the underlying ndarray. In JS,

function index(i,j, ...) {
  return this.offset + this.stride[0] * i + this.stride[1] * j + ...
}

Properties

The following properties are created using Object.defineProperty and do not take up any physical memory. They can be useful in calculations involving ndarrays

array.dtype

Returns a string representing the undelying data type of the ndarray. Excluding generic data stores these types are compatible with typedarray-pool. This is mapped according to the following rules:

Data type | String --------: | :----- Int8Array | "int8" Int16Array | "int16" Int32Array | "int32" Uint8Array | "uint8" Uint16Array | "uint16" Uint32Array | "uint32" BigInt64Array | "bigint64" BigUint64Array | "biguint64" Float32Array | "float32" Float64Array | "float64" Array | "array" Uint8ArrayClamped | "uint8_clamped" Buffer | "buffer" Other | "generic"

Generic arrays access elements of the underlying 1D store using get()/set() instead of array accessors.

array.size

Returns the size of the array in logical elements.

array.order

Returns the order of the stride of the array, sorted in ascending length. The first element is the first index of the shortest stride and the last is the index the longest stride.

array.dimension

Returns the dimension of the array.

Slicing

Given a view, we can change the indexing by shifting, truncating or permuting the strides. This lets us perform operations like array reversals or matrix transpose in constant time (well, technically O(shape.length), but since shape.length is typically less than 4, it might as well be). To make life simpler, the following interfaces are exposed:

array.lo(i,j,k,...)

This creates a shifted view of the array. Think of it as taking the upper left corner of the image and dragging it inward by an amount equal to (i,j,k...).

array.hi(i,j,k,...)

This does the dual of array.lo(). Instead of shifting from the top-left, it truncates from the bottom-right of the array, returning a smaller array object. Using hi and lo in combination lets you select ranges in the middle of an array.

Note: hi and lo do not commute. In general:

a.hi(3,3).lo(3,3)  !=  a.lo(3,3).hi(3,3)

array.step(i,j,k...)

Changes the stride length by rescaling. Negative indices flip axes. For example, here is how you create a reversed view of a 1D array:

var reversed = a.step(-1)

You can also change the step size to be greater than 1 if you like, letting you skip entries of a list. For example, here is how to split an array into even and odd components:

var evens = a.step(2)
var odds = a.lo(1).step(2)

array.transpose(p0, p1, ...)

Finally, for higher dimensional arrays you can transpose the indices without replicating the data. This has the effect of permuting the shape and stride values and placing the result in a new view of the same data. For example, in a 2D array you can calculate the matrix transpose by:

M.transpose(1, 0)

Or if you have a 3D volume image, you can shift the axes using more generic transformations:

volume.transpose(2, 0, 1)

array.pick(p0, p1, ...)

You can also pull out a subarray from an ndarray by fixing a particular axis. The way this works is you specify the direction you are picking by giving a list of values. For example, if you have an image stored as an nxmx3 array you can pull out the channel as follows:

var red   = image.pick(null, null, 0)
var green = image.pick(null, null, 1)
var blue  = image.pick(null, null, 2)

As the above example illustrates, passing a negative or non-numeric value to a coordinate in pick skips that index.

More information

For more discussion about ndarrays, here are some talks, tutorials and articles about them:

License

(c) 2013-2016 Mikola Lysenko. MIT License