Module ndarray::doc::ndarray_for_numpy_users[][src]

Expand description

ndarray for NumPy users.

This is an introductory guide to ndarray for people with experience using NumPy, although it may also be useful to others. For a more general introduction to ndarray’s array type ArrayBase, see the ArrayBase docs.

Contents

Similarities

ndarray’s array type (ArrayBase), is very similar to NumPy’s array type (numpy.ndarray):

  • Arrays have a single element type.
  • Arrays can have arbitrarily many dimensions.
  • Arrays can have arbitrary strides.
  • Indexing starts at zero, not one.
  • The default memory layout is row-major, and the default iterators follow row-major order (also called “logical order” in the documentation).
  • Arithmetic operators work elementwise. (For example, a * b performs elementwise multiplication, not matrix multiplication.)
  • Owned arrays are contiguous in memory.
  • Many operations, such as slicing, are very cheap because they can return a view of an array instead of copying the data.

NumPy has many features that ndarray doesn’t have yet, such as:

Some key differences

NumPy

ndarray

In NumPy, there is no distinction between owned arrays, views, and mutable views. There can be multiple arrays (instances of numpy.ndarray) that mutably reference the same data.

In ndarray, all arrays are instances of ArrayBase, but ArrayBase is generic over the ownership of the data. Array owns its data; ArrayView is a view; ArrayViewMut is a mutable view; CowArray either owns its data or is a view (with copy-on-write mutation of the view variant); and ArcArray has a reference-counted pointer to its data (with copy-on-write mutation). Arrays and views follow Rust’s aliasing rules.

In NumPy, all arrays are dynamic-dimensional.

In ndarray, you can create fixed-dimension arrays, such as Array2. This takes advantage of the type system to help you write correct code and also avoids small heap allocations for the shape and strides.

When slicing in NumPy, the indices are start, start + step, start + 2*step, … until reaching end (exclusive).

When slicing in ndarray, the axis is first sliced with start..end. Then if step is positive, the first index is the front of the slice; if step is negative, the first index is the back of the slice. This means that the behavior is the same as NumPy except when step < -1. See the docs for the s![] macro for more details.

The ndarray ecosystem

ndarray does not provide advanced linear algebra routines out of the box (e.g. SVD decomposition). Most of the routines that you can find in scipy.linalg/numpy.linalg are provided by another crate, ndarray-linalg.

The same holds for statistics: ndarray provides some basic functionalities (e.g. mean) but more advanced routines can be found in ndarray-stats.

If you are looking to generate random arrays instead, check out ndarray-rand.

It is also possible to serialize NumPy arrays in .npy/.npz format and deserialize them as ndarray arrays (and vice versa) using ndarray-npy.

Other Rust array/matrix crates

Of the array/matrix types in Rust crates, the ndarray array type is probably the most similar to NumPy’s arrays and is the most flexible. However, if your use-case is constrained to linear algebra on 1-D and 2-D vectors and matrices, it might be worth considering other crates:

  • nalgebra provides 1-D and 2-D column-major vector and matrix types for linear algebra. Vectors and matrices can have constant or dynamic shapes, and nalgebra uses the type system to provide compile-time checking of shapes, not just the number of dimensions. nalgebra provides convenient functionality for geometry (e.g. coordinate transformations) and linear algebra.
  • cgmath provides 1-D and 2-D column-major types of shape 4×4 or smaller. It’s primarily designed for computer graphics and provides convenient functionality for geometry (e.g. coordinate transformations). Similar to nalgebra, cgmath uses the type system to provide compile-time checking of shapes.
  • rulinalg provides 1-D and 2-D row-major vector and matrix types with dynamic shapes. Similar to ndarray, rulinalg provides compile-time checking of the number of dimensions, but not shapes. rulinalg provides pure-Rust implementations of linear algebra operations.
  • If there’s another crate that should be listed here, please let us know.

In contrast to these crates, ndarray provides an n-dimensional array type, so it’s not restricted to 1-D and 2-D vectors and matrices. Also, operators operate elementwise by default, so the multiplication operator * performs elementwise multiplication instead of matrix multiplication. (You have to specifically call .dot() if you want matrix multiplication.)

Rough ndarray–NumPy equivalents

These tables provide some rough equivalents of NumPy operations in ndarray. There are a variety of other methods that aren’t included in these tables, including shape-manipulation, array creation, and iteration routines.

It’s assumed that you’ve imported NumPy like this:

import numpy as np

and ndarray like this:

use ndarray::prelude::*;

Array creation

This table contains ways to create arrays from scratch. For creating arrays by operations on other arrays (e.g. arithmetic), see the other tables. Also see the ::from_vec(), ::from_iter(), ::default(), ::from_shape_fn(), and ::from_shape_vec_unchecked() methods.

NumPyndarrayNotes
np.array([[1.,2.,3.], [4.,5.,6.]])array![[1.,2.,3.], [4.,5.,6.]] or arr2(&[[1.,2.,3.], [4.,5.,6.]])2×3 floating-point array literal
np.arange(0., 10., 0.5) or np.r_[:10.:0.5]Array::range(0., 10., 0.5)create a 1-D array with values 0., 0.5, …, 9.5
np.linspace(0., 10., 11) or np.r_[:10.:11j]Array::linspace(0., 10., 11)create a 1-D array with 11 elements with values 0., …, 10.
np.logspace(2.0, 3.0, num=4, base=10.0)Array::logspace(10.0, 2.0, 3.0, 4)create a 1-D array with 4 elements with values 100., 215.4, 464.1, 1000.
np.geomspace(1., 1000., num=4)Array::geomspace(1e0, 1e3, 4)create a 1-D array with 4 elements with values 1., 10., 100., 1000.
np.ones((3, 4, 5))Array::ones((3, 4, 5))create a 3×4×5 array filled with ones (inferring the element type)
np.zeros((3, 4, 5))Array::zeros((3, 4, 5))create a 3×4×5 array filled with zeros (inferring the element type)
np.zeros((3, 4, 5), order='F')Array::zeros((3, 4, 5).f())create a 3×4×5 array with Fortran (column-major) memory layout filled with zeros (inferring the element type)
np.zeros_like(a, order='C')Array::zeros(a.raw_dim())create an array of zeros of the shape shape as a, with row-major memory layout (unlike NumPy, this infers the element type from context instead of duplicating a’s element type)
np.full((3, 4), 7.)Array::from_elem((3, 4), 7.)create a 3×4 array filled with the value 7.
np.eye(3)Array::eye(3)create a 3×3 identity matrix (inferring the element type)
np.diag(np.array([1, 2, 3]))Array2::from_diag(&arr1(&[1, 2, 3]))create a 3×3 matrix with [1, 2, 3] as diagonal and zeros elsewhere (inferring the element type)
np.array([1, 2, 3, 4]).reshape((2, 2))Array::from_shape_vec((2, 2), vec![1, 2, 3, 4])?create a 2×2 array from the elements in the list/Vec
np.array([1, 2, 3, 4]).reshape((2, 2), order='F')Array::from_shape_vec((2, 2).f(), vec![1, 2, 3, 4])?create a 2×2 array from the elements in the list/Vec using Fortran (column-major) order
np.randomSee the ndarray-rand crate.create arrays of random numbers

Note that the examples in the table rely on the compiler inferring the element type and dimensionality from context, which is usually sufficient. However, if the compiler cannot infer the types, you can specify them manually. These are examples of creating a 3-D Fortran-layout array of f64s:

// This is an example where the compiler can infer the element type
// because `f64::sin` can only be called on `f64` elements:
let arr1 = Array::zeros((3, 2, 4).f());
arr1.mapv(f64::sin);

// Specify just the element type and infer the dimensionality:
let arr2 = Array::<f64, _>::zeros((3, 2, 4).f());
let arr3: Array<f64, _> = Array::zeros((3, 2, 4).f());

// Specify both the element type and dimensionality:
let arr4 = Array3::<f64>::zeros((3, 2, 4).f());
let arr5: Array3<f64> = Array::zeros((3, 2, 4).f());
let arr6 = Array::<f64, Ix3>::zeros((3, 2, 4).f());
let arr7: Array<f64, Ix3> = Array::zeros((3, 2, 4).f());

Indexing and slicing

A few notes:

  • Indices start at 0. For example, “row 1” is the second row in the array.

  • Some methods have multiple variants in terms of ownership and mutability. Only the non-mutable methods that take the array by reference are listed in this table. For example, .slice() also has corresponding methods .slice_mut(), .slice_move(), and .slice_collapse().

  • The behavior of slicing is slightly different from NumPy for slices with step < -1. See the docs for the s![] macro for more details.

NumPyndarrayNotes
a[-1]a[a.len() - 1]access the last element in 1-D array a
a[1, 4]a[[1, 4]]access the element in row 1, column 4
a[1] or a[1, :, :]a.slice(s![1, .., ..]) or a.index_axis(Axis(0), 1)get a 2-D subview of a 3-D array at index 1 of axis 0
a[0:5] or a[:5] or a[0:5, :]a.slice(s![0..5, ..]) or a.slice(s![..5, ..]) or a.slice_axis(Axis(0), Slice::from(0..5))get the first 5 rows of a 2-D array
a[-5:] or a[-5:, :]a.slice(s![-5.., ..]) or a.slice_axis(Axis(0), Slice::from(-5..))get the last 5 rows of a 2-D array
a[:3, 4:9]a.slice(s![..3, 4..9])columns 4, 5, 6, 7, and 8 of the first 3 rows
a[1:4:2, ::-1]a.slice(s![1..4;2, ..;-1])rows 1 and 3 with the columns in reverse order

Shape and strides

Note that a.shape(), a.dim(), and a.raw_dim() all return the shape of the array, but as different types. a.shape() returns the shape as &[Ix], (where Ix is usize) which is useful for general operations on the shape. a.dim() returns the shape as D::Pattern, which is useful for pattern-matching shapes. a.raw_dim() returns the shape as D, which is useful for creating other arrays of the same shape.

NumPyndarrayNotes
np.ndim(a) or a.ndima.ndim()get the number of dimensions of array a
np.size(a) or a.sizea.len()get the number of elements in array a
np.shape(a) or a.shapea.shape() or a.dim()get the shape of array a
a.shape[axis]a.len_of(Axis(axis))get the length of an axis
a.stridesa.strides()get the strides of array a
np.size(a) == 0 or a.size == 0a.is_empty()check if the array has zero elements

Mathematics

Note that .mapv() has corresponding methods .map(), .mapv_into(), .map_inplace(), and .mapv_inplace(). Also look at .fold(), .for_each(), .fold_axis(), and .map_axis().

NumPy

ndarray

Notes

a.transpose() or a.T

a.t() or a.reversed_axes()

transpose of array a (view for .t() or by-move for .reversed_axes())

mat1.dot(mat2)

mat1.dot(&mat2)

2-D matrix multiply

mat.dot(vec)

mat.dot(&vec)

2-D matrix dot 1-D column vector

vec.dot(mat)

vec.dot(&mat)

1-D row vector dot 2-D matrix

vec1.dot(vec2)

vec1.dot(&vec2)

vector dot product

a * b, a + b, etc.

a * b, a + b, etc.

element-wise arithmetic operations

a**3

a.mapv(|a| a.powi(3))

element-wise power of 3

np.sqrt(a)

a.mapv(f64::sqrt)

element-wise square root for f64 array

(a>0.5)

a.mapv(|a| a > 0.5)

array of bools of same shape as a with true where a > 0.5 and false elsewhere

np.sum(a) or a.sum()

a.sum()

sum the elements in a

np.sum(a, axis=2) or a.sum(axis=2)

a.sum_axis(Axis(2))

sum the elements in a along axis 2

np.mean(a) or a.mean()

a.mean().unwrap()

calculate the mean of the elements in f64 array a

np.mean(a, axis=2) or a.mean(axis=2)

a.mean_axis(Axis(2))

calculate the mean of the elements in a along axis 2

np.allclose(a, b, atol=1e-8)

a.abs_diff_eq(&b, 1e-8)

check if the arrays’ elementwise differences are within an absolute tolerance (it requires the approx feature-flag)

np.diag(a)

a.diag()

view the diagonal of a

np.linalg

See ndarray-linalg

linear algebra (matrix inverse, solving, decompositions, etc.)

Array manipulation

NumPyndarrayNotes
a[:] = 3.a.fill(3.)set all array elements to the same scalar value
a[:] = ba.assign(&b)copy the data from array b into array a
np.concatenate((a,b), axis=1)concatenate![Axis(1), a, b] or concatenate(Axis(1), &[a.view(), b.view()])concatenate arrays a and b along axis 1
np.stack((a,b), axis=1)stack![Axis(1), a, b] or stack(Axis(1), vec![a.view(), b.view()])stack arrays a and b along axis 1
a[:,np.newaxis] or np.expand_dims(a, axis=1)a.slice(s![.., NewAxis]) or a.insert_axis(Axis(1))create an view of 1-D array a, inserting a new axis 1
a.transpose() or a.Ta.t() or a.reversed_axes()transpose of array a (view for .t() or by-move for .reversed_axes())
np.diag(a)a.diag()view the diagonal of a
a.flatten()use std::iter::FromIterator; Array::from_iter(a.iter().cloned())create a 1-D array by flattening a

Iteration

ndarray has lots of interesting iterators/producers that implement the NdProducer trait, which is a generalization of Iterator to multiple dimensions. This makes it possible to correctly and efficiently zip together slices/subviews of arrays in multiple dimensions with Zip or azip!(). The purpose of this is similar to np.nditer, but Zip is implemented and used somewhat differently.

This table lists some of the iterators/producers which have a direct equivalent in NumPy. For a more complete introduction to producers and iterators, see Loops, Producers, and Iterators. Note that there are also variants of these iterators (with a _mut suffix) that yield ArrayViewMut instead of ArrayView.

NumPyndarrayNotes
a.flata.iter()iterator over the array elements in logical order
np.ndenumerate(a)a.indexed_iter()flat iterator yielding the index along with each element reference
iter(a)a.outer_iter() or a.axis_iter(Axis(0))iterator over the first (outermost) axis, yielding each subview

Convenience methods for 2-D arrays

NumPyndarrayNotes
len(a) or a.shape[0]a.nrows()get the number of rows in a 2-D array
a.shape[1]a.ncols()get the number of columns in a 2-D array
a[1] or a[1,:]a.row(1) or a.row_mut(1)view (or mutable view) of row 1 in a 2-D array
a[:,4]a.column(4) or a.column_mut(4)view (or mutable view) of column 4 in a 2-D array
a.shape[0] == a.shape[1]a.is_square()check if the array is square

Modules

Example of rotation with Euler angles.

Example of translating rk_step function from SciPy.

Example of simple math operations on 2-D arrays.