For the complete documentation index, see llms.txt. This page is also available as Markdown.

Matrix Manipulation

Reshaping, transposing, permuting, concatenating, sorting, and other shape manipulation operations on INDArrays

Overview

Shape manipulation covers all operations that change how an INDArray is structured — its rank, its dimension sizes, the order of its axes, or how multiple arrays are combined into one. These operations are frequent in neural network code (batching, flattening activations, preparing inputs) and in general scientific computing.

Views vs. copies is the central concern. Many shape operations return a view — a new INDArray object backed by the same off-heap memory buffer as the original. Modifying a view modifies the original. Operations that return copies are explicitly labeled. When in doubt, call .dup() on the result to guarantee independence.

Reshape

Reshape gives an array a new shape without changing its element values or the order they appear in the underlying buffer. The product of all dimensions must remain identical.

Basic reshape

import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.api.ndarray.INDArray;

// 12-element source
INDArray src = Nd4j.arange(12);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

INDArray mat = src.reshape(3, 4);   // 3 rows x 4 cols
/*
[[         0,    1.0000,    2.0000,    3.0000],
 [    4.0000,    5.0000,    6.0000,    7.0000],
 [    8.0000,    9.0000,   10.0000,   11.0000]]
*/

INDArray cube = src.reshape(2, 3, 2);  // rank-3
/*
[[[         0,    1.0000],
  [    2.0000,    3.0000],
  [    4.0000,    5.0000]],

 [[    6.0000,    7.0000],
  [    8.0000,    9.0000],
  [   10.0000,   11.0000]]]
*/

Specifying memory order

reshape returns a view (shared data!)

reshape tries to return a view whenever the layout allows it. When ND4J can satisfy the new shape without rearranging bytes, the returned array shares the original buffer:

If the requested layout requires data movement (e.g., converting F-order to a C-order reshape), ND4J allocates a new buffer. Never assume reshape is always a view or always a copy. Call .dup() if you need an independent result:

Using -1 as a wildcard

Pass -1 for one dimension to let ND4J infer it:

Transpose

For a 2D matrix, transpose swaps rows and columns: element [i, j] moves to [j, i]. The diagonal is unchanged.

Out-of-place transpose (returns a view)

transpose() returns a view with reordered strides. Mutating t mutates mat:

For an independent transposed copy:

In-place transpose

transposei() modifies the array's strides in place, making it its own transpose. The underlying data buffer is not copied:

Use transposei() when you are done with the original shape and want to avoid even the lightweight view-object allocation of transpose().

Non-square matrices

Permute

For arrays with more than two dimensions, permute(int...) reorders axes in an arbitrary way. Pass the desired axis order as integers.

permute always returns a view — strides are reordered but the buffer is shared. For a copy with contiguous memory:

Transpose on a 2D array is equivalent to permute(1, 0).

Ravel and Flatten

Both operations reduce an array to one dimension. They differ in whether they share data.

ravel() — view when possible

Because ravel() may return a view, writing into the result can change mat:

Nd4j.toFlattened() — always a copy

When you need a guaranteed copy, or when you want to flatten multiple arrays into one in a specified order:

toFlattened always allocates a new buffer regardless of input layout.

Concatenation

Nd4j.concat — along any dimension

concat accepts a varargs list so you can combine more than two arrays:

Nd4j.vstack — vertical stack (along rows)

vstack is shorthand for concat(0, ...). All arrays must have the same number of columns.

Nd4j.hstack — horizontal stack (along columns)

hstack is shorthand for concat(1, ...). All arrays must have the same number of rows.

concat, vstack, and hstack always return copies — the new array has its own buffer.

Stack and Unstack

Where concat joins arrays along an existing dimension, stack creates a new dimension. This is useful for batching multiple samples into a single tensor.

Nd4j.stack

All inputs must have exactly the same shape. The result has rank one higher than the inputs.

Unstacking

To reverse the operation and split a batched tensor back into individual slices, use Nd4j.unstack:

The second argument is the axis along which to unstack; the third is the number of arrays to produce.

Squeeze and Unsqueeze

Squeeze — remove size-1 dimensions

Nd4j.squeeze(INDArray, int dimension) removes the specified dimension if its size is 1. This commonly arises after reductions that preserve dimensions.

Squeezing a dimension whose size is not 1 throws an exception.

Unsqueeze — add a size-1 dimension

reshape is the most direct way to insert a size-1 dimension:

The result of reshape may be a view, so the same shared-data caution applies.

Sort

Sort along a dimension

Nd4j.sort sorts elements along a specified axis in ascending or descending order. It returns a new sorted array.

The boolean third argument is true for ascending, false for descending. Nd4j.sort returns a copy.

sortRows and sortColumns

Nd4j.sortRows(INDArray, int column, boolean ascending) and Nd4j.sortColumns(INDArray, int row, boolean ascending) sort the rows (or columns) of a 2D array by the values in a specified column (or row):

Both methods return copies. Rows or columns are moved as units, preserving their internal structure.

Repeat

INDArray.repeat(int dimension, long... repeats) tiles an array's elements along the specified dimension.

repeat returns a copy.

Pad

Nd4j.pad(INDArray, int[][], PadMode) adds border elements around an array. This is used in convolution operations (zero-padding) and in batching sequences to a uniform length.

Available PadMode values: CONSTANT (fill with a fixed value), REFLECT (mirror without repeating the edge), SYMMETRIC (mirror repeating the edge), EDGE (replicate the edge value).

Nd4j.pad always returns a copy.

Swap Axes

swapAxes(int dim1, int dim2) exchanges two dimensions of an array. For a 2D matrix this is identical to transpose(). It is more useful for higher-rank tensors.

swapAxes returns a view with reordered strides, not a copy. The same shared-data caution applies.

Views vs. Copies Reference

Getting this right prevents subtle bugs. The table below summarises which operations return views and which always allocate new memory.

Operation
Returns

reshape(long...)

View when layout permits; copy otherwise

reshape(char, long...)

View when layout permits; copy otherwise

transpose()

View

transposei()

In-place (same object)

permute(int...)

View

swapAxes(int, int)

View

ravel()

View when array is C-contiguous; copy otherwise

Nd4j.toFlattened(char, INDArray...)

Always a copy

Nd4j.concat(int, INDArray...)

Always a copy

Nd4j.vstack(INDArray...)

Always a copy

Nd4j.hstack(INDArray...)

Always a copy

Nd4j.stack(int, INDArray...)

Always a copy

Nd4j.unstack(INDArray, int, int)

Always copies

Nd4j.squeeze(INDArray, int)

Copy

repeat(int, long...)

Always a copy

Nd4j.pad(INDArray, int[][], PadMode)

Always a copy

Nd4j.sort(INDArray, int, boolean)

Always a copy

Nd4j.sortRows / sortColumns

Always copies

When you are unsure, check by modifying the result and seeing whether the original changes. Alternatively, call .dup() on any operation result to guarantee an independent copy at the cost of a memory allocation.

Practical Example: Preparing a Batch

This example shows how shape manipulation operations compose in a typical deep learning workflow — loading individual samples and assembling them into a batched tensor.

Last updated

Was this helpful?