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.
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.
API Reference Links
Last updated
Was this helpful?