NumSharp's ndarray is NDArray!
NumPy's central type is numpy.ndarray. NumSharp's is NDArray. If you know one, you know the other same concept, same memory model, same semantics, same operator behavior, ported to .NET idioms. This page is the quick tour: what NDArray is, how to make one, how to read and modify it, how it compares to numpy.ndarray, and where the two diverge because C# is not Python.
Anatomy
An NDArray is three things glued together:
NDArray user-facing handle (the type you work with)
Storage UnmanagedStorage: raw pointer to native memory
Shape dimensions, strides, offset, flags
TensorEngine dispatches operations (DefaultEngine by default)
- Storage holds the actual bytes in unmanaged memory (not GC-allocated). This beat every managed alternative in benchmarking and is what makes SIMD and zero-copy interop practical.
- Shape is a
readonly structdescribing how the 1-D byte block is viewed as N-D. It knows dimensions, strides, offset, and precomputedArrayFlags(contiguous, broadcasted, writeable, owns-data). - TensorEngine is where
+,-,sum,matmul, etc. actually run. Different engines can plug in (GPU/SIMD/BLAS); the default is pure C# with IL-generated kernels.
You rarely touch Storage or TensorEngine directly NDArray exposes everything.
Creating an NDArray
The usual ways, with their numpy counterparts:
np.array(new[] {1, 2, 3}); // np.array([1, 2, 3])
np.array(new int[,] {{1, 2}, {3, 4}}); // np.array([[1, 2], [3, 4]])
np.zeros((3, 4)); // np.zeros((3, 4))
np.ones(5); // np.ones(5)
np.full((2, 2), 7); // np.full((2, 2), 7)
np.full(new Shape(2, 2), 7); // same thing, explicit Shape form
np.empty((3, 3)); // np.empty((3, 3))
np.eye(4); // np.eye(4)
np.identity(4); // np.identity(4)
np.arange(10); // np.arange(10)
np.arange(0, 1, 0.1); // np.arange(0, 1, 0.1)
np.linspace(0, 1, 11); // np.linspace(0, 1, 11)
np.random.rand(3, 4); // np.random.rand(3, 4)
np.random.randn(100); // np.random.randn(100)
Where
(3, 4)comes from. NumSharp'sShapestruct has implicit conversions fromint,long,int[],long[], and value tuples of 26 dimensions. So these four calls all produce the same (3, 4) array:np.zeros((3, 4)); // tuple Shape np.zeros(new[] {3, 4}); // int[] Shape np.zeros(new Shape(3, 4)); // explicit Shape np.zeros(new Shape(new[] {3L, 4L}));A bare
np.zeros(5)creates a 1-D length-5 array it hits theint shapeoverload, not a tuple.
Scalars (0-d arrays) flow in implicitly:
NDArray a = 42; // 0-d int32
NDArray b = 3.14; // 0-d double
NDArray c = Half.One; // 0-d float16
NDArray d = NDArray.Scalar(100.123m); // 0-d decimal
NDArray e = NDArray.Scalar<long>(1); // 0-d with explicit dtype
Implicit scalar NDArray exists for all 15 dtypes (bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, Half, float, double, decimal, Complex). Use NDArray.Scalar<T>(value) to force a specific dtype the C# literal wouldn't pick e.g. NDArray.Scalar<short>(1) instead of NDArray x = 1; (which would be int32).
See also: Dtypes for how to pick element types, Broadcasting for shape rules.
Wrapping Existing Buffers np.frombuffer
When you already have memory a byte[] read from a file, a network packet, a pointer from a native library, or even a typed T[] you want to reinterpret np.frombuffer wraps it as an NDArray without copying whenever possible. Same contract as NumPy's numpy.frombuffer.
// From a byte[] creates a view (pins the array)
byte[] buffer = File.ReadAllBytes("sensor_data.bin");
var readings = np.frombuffer(buffer, typeof(float));
// Skip a header
var data = np.frombuffer(buffer, typeof(float), offset: 16);
// Read only part of the buffer
var subset = np.frombuffer(buffer, typeof(float), count: 1000, offset: 16);
// Reinterpret a typed array as a different dtype (view)
int[] ints = { 1, 2, 3, 4 };
var bytes = np.frombuffer<int>(ints, typeof(byte)); // 16 bytes: [1,0,0,0, 2,0,0,0, ...]
// From .NET buffer types
var fromSegment = np.frombuffer(new ArraySegment<byte>(buffer, 0, 128), typeof(int));
var fromMemory = np.frombuffer((Memory<byte>)buffer, typeof(float));
// ReadOnlySpan<byte> always copies (spans can't be pinned)
ReadOnlySpan<byte> span = stackalloc byte[16];
var fromSpan = np.frombuffer(span, typeof(int));
// From native memory NumSharp takes ownership and frees on GC
IntPtr owned = Marshal.AllocHGlobal(1024);
var arr1 = np.frombuffer(owned, 1024, typeof(float),
dispose: () => Marshal.FreeHGlobal(owned));
// Or just borrow caller must keep it alive and free it later
IntPtr borrowed = NativeLib.GetData(out int size);
var arr2 = np.frombuffer(borrowed, size, typeof(float));
// ... use arr2 ...
NativeLib.FreeData(borrowed); // after arr2 is done
// Endianness via dtype strings (big-endian triggers a copy)
byte[] networkData = ReceivePacket();
var be = np.frombuffer(networkData, ">i4"); // big-endian int32 (copy)
var le = np.frombuffer(networkData, "<i4"); // little-endian int32 (view on x86/x64)
View or copy?
| Source | Behavior |
|---|---|
byte[], ArraySegment<byte>, array-backed Memory<byte> |
view (array is pinned) |
T[] via frombuffer<T>(T[], ) |
view (reinterpret bytes) |
IntPtr |
view (optionally with dispose callback for ownership transfer) |
ReadOnlySpan<byte> |
copy (spans can't be pinned) |
Memory<byte> not backed by an array |
copy |
| Big-endian dtype string on a little-endian CPU | copy (must swap bytes) |
Key rules (same as NumPy)
offsetis in bytes,countis in elements. Afloatbuffer withoffset: 4, count: 10reads 40 bytes starting at byte 4.- Buffer length (minus offset) must be a multiple of the element size, or NumSharp throws.
- Views couple lifetimes. If you return an NDArray wrapping a local
byte[], the array can be GC'd out from under the view. Either.copy()before returning, or allocate through NumSharp (np.zeros,np.empty). - Native memory without
disposeis borrowed the caller must keep the memory alive and free it after all viewing NDArrays are gone.
See the Buffering & Memory page for the full story: memory architecture, ownership patterns (ArrayPool, COM, P/Invoke), endianness, and troubleshooting.
Core Properties
| Property | Type | NumPy equivalent | Description |
|---|---|---|---|
shape |
long[] |
ndarray.shape |
Dimensions |
ndim |
int |
ndarray.ndim |
Number of dimensions |
size |
long |
ndarray.size |
Total element count |
dtype |
Type |
ndarray.dtype |
C# element type |
typecode |
NPTypeCode |
Compact enum form of dtype | |
strides |
long[] |
ndarray.strides |
Byte stride per dimension |
T |
NDArray |
ndarray.T |
Transpose (view) |
flat |
NDArray |
ndarray.flat |
1-D iterator view |
Shape |
Shape |
Full shape object (dimensions + strides + flags) | |
@base |
NDArray? |
ndarray.base |
Owner array if this is a view, else null |
var a = np.arange(12).reshape(3, 4); // arange defaults to int64 (NumPy 2.x)
a.shape; // [3, 4]
a.ndim; // 2
a.size; // 12
a.dtype; // typeof(long)
a.typecode; // NPTypeCode.Int64
a.T.shape; // [4, 3]
a.@base; // the 1-D arange buffer reshape returns a view (like NumPy)
var b = a["1:, :2"];
b.@base; // wraps a's Storage (b is a view)
Indexing & Slicing
Python's slice notation is accepted as a string:
var a = np.arange(20).reshape(4, 5);
a[0]; // first row reduces dim, returns (5,)
a[-1]; // last row
a[1, 2]; // single element at row 1, col 2
a["1:3"]; // rows 1-2 keeps dim, returns (2, 5)
a["1:3, :2"]; // rows 1-2, first two cols (2, 2)
a["::2"]; // every other row
a["::-1"]; // reversed first axis
a["..., -1"]; // ellipsis + last column
Boolean and fancy indexing work like NumPy:
var arr = np.array(new[] {10, 20, 30, 40, 50});
var mask = arr > 20; // NDArray<bool>
arr[mask]; // [30, 40, 50]
var idx = np.array(new[] {0, 2, 4});
arr[idx]; // [10, 30, 50] fancy indexing
Assignment follows the same rules:
a[1, 2] = 99; // scalar write
a[0] = np.zeros(5); // row write (assign a full row)
a[a > 10] = -1; // masked write
View / copy summary for indexing:
- Plain slices (
a["1:3"],a[0],a[..., -1]): writeable view shares memory with the parent.- Fancy indexing (
a[indexArray]): writeable copy independent memory (matches NumPy).- Boolean masking (
a[mask]): writeable copy independent memory (matches NumPy). Writing into the returned array is allowed but does not touch the parent; to modify the parent usea[mask] = value, which goes through the setter.
Views vs Copies Most Important Rule
Slicing returns a view, not a copy. The view shares memory with the parent. This matches NumPy and is the source of most "why did my array change?" questions.
var a = np.arange(10);
var v = a["2:5"]; // view shares memory with a
v[0] = 999; // mutates a[2] as well!
a[2]; // 999
var c = a["2:5"].copy(); // explicit copy independent memory
c[0] = 0;
a[2]; // still 999
Detect views with arr.@base is not null (use the C# is pattern, not != null NDArray's ==/!= operators are element-wise and return an array, not a bool). Force a copy with .copy() or np.copy(arr).
Broadcasted arrays are a special case: they're views with stride=0 dimensions, and they're read-only (Shape.IsWriteable == false) to prevent cross-row corruption. See Broadcasting.
Operators
Every NumPy operator that C# can express is defined on NDArray with matching semantics.
Arithmetic
| NumPy | NumSharp | Broadcasts? |
|---|---|---|
a + b |
a + b |
yes |
a - b |
a - b |
yes |
a * b |
a * b |
yes |
a / b |
a / b |
yes returns float dtype for int inputs |
a % b |
a % b |
yes result sign follows divisor (Python/NumPy convention) |
-a |
-a |
|
+a |
+a |
returns a copy |
Each takes NDArray NDArray, NDArray object, and object NDArray so 10 - arr works just like arr - 10.
Bitwise & shift
| NumPy | NumSharp | Notes |
|---|---|---|
a & b |
a & b |
bool arrays: logical AND |
a \| b |
a \| b |
bool arrays: logical OR |
a ^ b |
a ^ b |
|
~a |
~a |
|
a << b |
a << b |
integer dtypes only |
a >> b |
a >> b |
integer dtypes only |
Comparison
| NumPy | NumSharp | Returns |
|---|---|---|
a == b |
a == b |
NDArray<bool> |
a != b |
a != b |
NDArray<bool> |
a < b |
a < b |
NDArray<bool> |
a <= b |
a <= b |
NDArray<bool> |
a > b |
a > b |
NDArray<bool> |
a >= b |
a >= b |
NDArray<bool> |
Comparisons with NaN return False (IEEE 754), just like NumPy.
Logical
| NumPy | NumSharp | Notes |
|---|---|---|
np.logical_not(a) |
!a |
NDArray<bool> only |
Operators NumPy has that C# doesn't
C# has no **, //, @ operators, and no __abs__/__divmod__ protocol. Use the functions:
| NumPy | NumSharp |
|---|---|
a ** b |
np.power(a, b) |
a // b |
np.floor_divide(a, b) |
a @ b |
np.matmul(a, b) or np.dot(a, b) |
abs(a) |
np.abs(a) |
divmod(a, b) |
(np.floor_divide(a, b), a % b) |
C# shift-operator quirk
C# requires the declaring type on the left of << / >>, so object << NDArray is a compile error. Use the named form:
object rhs = 2;
arr << 2; // OK int RHS
arr << rhs; // OK object RHS supported
2 << arr; // compile error
np.left_shift(2, arr); // use the function instead
Compound assignment
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= all work. But: C# synthesizes them as a = a op b they produce a new array and reassign the variable. They are not in-place like NumPy's compound operators. Other references to the original array do not see the change:
var x = np.array(new[] {1, 2, 3});
var alias = x;
x += 10; // x new array [11, 12, 13]
// alias // still [1, 2, 3] different from NumPy!
This is a C# language constraint compound operators on reference types cannot be defined independently of the binary operator not a NumSharp choice.
Dtype Conversion
Three ways to change an array's type:
var a = np.array(new[] {1, 2, 3});
// astype allocates a new array (default) or rewrites in place (copy: false)
var b = a.astype(np.float64);
var c = a.astype(NPTypeCode.Int64);
// explicit cast on 0-d arrays matches NumPy's int(arr), float(arr), complex(arr)
NDArray scalar = NDArray.Scalar(42); // 0-d
int i = (int)scalar; // 42
double d = (double)scalar; // 42.0
Half h = (Half)scalar; // (Half)42
Complex cx = (Complex)scalar; // 42 + 0i
Rules (match NumPy 2.x):
- 0-d required. Casting an N-d array to a scalar throws
ScalarConversionException. - Complex non-complex throws
TypeError(mirroring Python'sint(1+2j)error). Usenp.real(arr)first. - Numeric numeric follows NEP 50 promotion:
int32 + float64 float64,int32 * 1.0 float64, etc.
See Dtypes for the full type table and conversion rules.
Scalars (0-d Arrays)
A 0-d array has no dimensions ndim == 0, shape == [], size == 1. Create one with NDArray.Scalar<T>(value) or implicit scalar conversion:
var s1 = NDArray.Scalar(42); // explicit
NDArray s2 = 42; // implicit (same result)
s1.ndim; // 0
s1.size; // 1
(int)s1; // 42 explicit cast out
Integer indexing always reduces one dimension:
- 1-D
a[i]0-d NDArray (single element, still wrapped as an array matches NumPy 2.x) - 2-D
a[i]1-D NDArray (a row view) - 3-D
a[i]2-D NDArray (a slab view)
To unwrap a 0-d result to a raw C# scalar, cast: (int)a[i] or a.item<int>(i).
Reading & Writing Elements
Four ways to touch individual elements, picked based on how many indices you have and whether you already know the dtype:
var a = np.arange(12).reshape(3, 4); // int64 (NumPy 2.x default integer)
// 1. Indexer returns NDArray (0-d for a single element)
NDArray elem = a[1, 2];
long v = (long)elem; // explicit cast to scalar (converts)
// 2. .item<T>() direct scalar extraction (NumPy parity; converts if T differs)
long v2 = a.item<long>(6); // flat index 6 row 1, col 2
object box = a.item(6); // untyped form returns object (boxed long)
// 3. GetValue<T> N-D coordinates, typed. T must match the dtype exactly.
long v3 = a.GetValue<long>(1, 2);
// 4. GetAtIndex<T> flat index, typed, no Shape math (fastest). T must match the dtype.
long v4 = a.GetAtIndex<long>(6);
// Writes mirror the reads:
a[1, 2] = 99; // indexer assignment (converts to the array dtype)
a.SetValue(99L, 1, 2); // N-D coordinates (value must match the dtype)
a.SetAtIndex(99L, 6); // flat index
Rule of thumb: use .item<T>() when porting NumPy code, GetAtIndex<T> in a hot loop, and the indexer (a[i, j]) when you want NumPy-like ergonomics and don't mind the 0-d NDArray detour.
.item()without arguments works on any size-1 array (0-d, 1-element 1-d, 11 2-d) and throwsIncorrectSizeExceptionotherwise the NumPy 2.x replacement for the removednp.asscalar().
Iterating (foreach)
NDArray implements IEnumerable, so foreach works and it iterates along axis 0, matching NumPy:
var m = np.arange(6).reshape(2, 3);
foreach (NDArray row in m)
{
Console.WriteLine(row); // each `row` is shape (3,), a view of m
}
For a 1-D array, foreach yields individual elements (boxed). For higher-D arrays, each iteration yields a view of the subarray at that axis-0 index.
To iterate all elements flat, use .flat or index into .ravel():
foreach (var x in m.flat) { ... }
Common Patterns
Flatten to 1-D (view if possible)
a.ravel(); // view if contiguous, copy if not
a.flatten(); // always a copy
Reshape
a.reshape(3, 4); // explicit dims
a.reshape(-1); // auto-size one dim 1-D flatten
a.reshape(-1, 4); // infer first dim, second is 4
All three return a view when the source is contiguous and a copy otherwise.
Transpose / axis shuffle
a.T; // full transpose (view)
a.transpose(new[] {1, 0, 2}); // permute axes
np.swapaxes(a, 0, 1);
np.moveaxis(a, 0, -1);
Copy semantics at a glance
| Operation | Result |
|---|---|
a["1:3"] |
view |
a.T |
view |
a.reshape(...) |
view if possible, else copy |
a.ravel() |
view if contiguous, else copy |
a.flatten() |
always copy |
a.copy() |
always copy |
a + b |
always new array |
a[mask] with bool mask |
copy |
a[idx] with int indices |
copy |
Generic NDArray<T>
For type-safe element access, use NDArray<T>:
NDArray<double> a = np.zeros(10).MakeGeneric<double>();
double first = a[0]; // T, not NDArray
a[0] = 3.14;
Three ways to get a typed wrapper:
| Method | Allocates? | On dtype mismatch | When to use |
|---|---|---|---|
MakeGeneric<T>() |
never (same storage) | throws ArgumentException |
You already know the dtype matches |
AsGeneric<T>() |
never (same storage) | returns null (like C# as) |
Defensive typing check for null |
AsOrMakeGeneric<T>() |
only if dtype differs (then astype) |
converts via astype |
Accept any dtype, convert if needed |
NDArray<T> wraps the same storage; use the untyped NDArray when dtype is dynamic.
Saving, Loading, and Interop
NumSharp reads and writes NumPy's .npy / .npz formats and raw binary. The .npy/.npz stack is a port of NumPy 2.4.2's format code (NEP-01), so a file written by np.save is byte-for-byte identical to what NumPy itself writes data moves between Python and C# in both directions, losslessly. To wrap an existing in-memory byte buffer (file bytes, a network packet, a native pointer) see np.frombuffer above.
// .npy a single array (byte-identical to NumPy's np.save)
np.save("arr.npy", arr);
NDArray a = np.load_npy("arr.npy"); // typed load
// .npz many arrays in one archive
np.savez("bundle.npz", x, y); // positional "arr_0", "arr_1"
np.savez("bundle.npz", new Dictionary<string, NDArray> { ["w"] = w, ["b"] = b });
np.savez_compressed("bundle.npz", w, b); // same, Deflate-compressed
using NpzFile npz = np.load_npz("bundle.npz"); // lazy + cached; dispose it (holds the file handle)
NDArray w1 = npz["w"]; // "w.npy" also works as a key
NDArray w2 = npz.f.w; // dot access, like NumPy's npz.f
foreach (string name in npz.Files) { } // "w", "b" the ".npy" is stripped
// np.load dispatches on the file's magic bytes and returns `object`
object any = np.load("bundle.npz"); // NDArray for .npy, NpzFile for .npz
// Raw binary element bytes only, no header
arr.tofile("data.bin");
var raw = np.fromfile("data.bin", np.float64);
Format versions 1.0 / 2.0 / 3.0, C- and Fortran-order, and big-endian files all load; the writer emits the byte-exact, 64-byte-aligned, mmap-ready layout NumPy produces. np.load returns object because like NumPy it yields an array for a .npy and an archive for a .npz, decided by the file's contents rather than its name; prefer the typed np.load_npy / np.load_npz when you know the kind. allow_pickle defaults to false (NumPy's security default), so object-array files are rejected with a clear message instead of executed. See NumPy Compliance for the full dtype map and the handful of unsupported types.
Interop with standard .NET arrays:
var arr = np.array(new[,] {{1, 2}, {3, 4}});
// To multi-dim array (preserves shape). Note the method name is "Muli", not "Multi"
// a longstanding API typo preserved for backwards compatibility.
int[,] md = (int[,])arr.ToMuliDimArray<int>();
// To jagged array
int[][] jag = (int[][])arr.ToJaggedArray<int>();
// From .NET array back (np.array accepts any rank)
NDArray fromMd = np.array(md);
For unsafe interop with native code, use arr.Data<T>() (gets the ArraySlice<T> handle) or the underlying arr.Storage.Address pointer. Contiguous-only; check arr.Shape.IsContiguous first or copy with arr.copy().
Memory Layout
NumSharp is C-contiguous only row-major storage, like NumPy's default. The order parameter on reshape, ravel, flatten, and copy is accepted for API compatibility but ignored (there is no F-order path).
This means:
arr.shape = [3, 4]element[i, j]is at flat offseti * 4 + j.arr.stridesreports byte strides, not element strides.- For higher dimensions, the last axis varies fastest (element
[i, j, k]is ati * stride[0] + j * stride[1] + k * stride[2]bytes fromStorage.Address).
Views can be non-contiguous (sliced, transposed, broadcasted). Use arr.Shape.IsContiguous to detect; use arr.copy() to materialize contiguous memory when a kernel needs it.
When Two Arrays Are "The Same"
| Comparison | Returns | Meaning |
|---|---|---|
a == b |
NDArray<bool> |
element-wise equality (broadcasts) |
np.array_equal(a, b) |
bool |
same shape AND all elements equal |
np.allclose(a, b) |
bool |
same shape AND all elements within tolerance (good for floats) |
ReferenceEquals(a, b) |
bool |
same C# object (rarely what you want) |
a.@base is not null |
bool |
a is a view (shares memory with some owner); use is, not != null |
Caveat: NumSharp does not expose a direct "do these two arrays share memory?" check from user code.
a.@basereturns a fresh wrapper on every call and the underlyingStorageisprotected internal, so strict memory-identity testing is only available inside the assembly.
Troubleshooting
"My array changed when I modified a slice!"
That's views. a["1:3"] shares memory with a. Force a copy: a["1:3"].copy().
"NumSharpException: assignment destination is read-only"
You're writing to a broadcasted view (stride=0 dimension), which is read-only. Copy first: b.copy()["..."] = value.
"ScalarConversionException on (int)arr"
The array isn't 0-d. (int) casts only work on scalars. Use arr.GetAtIndex<int>(0) or index first: (int)arr[0].
"10 << arr doesn't compile"
C# requires the declaring type on the left of shift operators. Use np.left_shift(10, arr).
"a += 1 didn't update another reference"
C# compound assignment reassigns the variable; it doesn't mutate. See Compound assignment above. For in-place modification, write directly: a["..."] = a + 1 (or a[":"] = a + 1).
API Reference
Properties
| Member | Type | Description |
|---|---|---|
shape |
long[] |
Dimensions |
ndim |
int |
Rank |
size |
long |
Total elements |
dtype |
Type |
Element Type |
typecode |
NPTypeCode |
Element type enum |
strides |
long[] |
Byte strides |
T |
NDArray |
Transpose (view) |
flat |
NDArray |
1-D view |
Shape |
Shape |
Full shape struct |
@base |
NDArray? |
Owning array if view, else null |
Storage |
UnmanagedStorage |
Raw memory handle (internal) |
TensorEngine |
TensorEngine |
Operation dispatcher |
Instance Methods
| Method | Description |
|---|---|
astype(type, copy) |
Cast to different dtype (copy by default) |
copy() |
Deep copy |
Clone() |
Same as copy() (ICloneable) |
reshape(...) |
Reshape (view if possible) |
ravel() |
Flatten to 1-D (view if contiguous) |
flatten() |
Flatten to 1-D (always copy) |
transpose(...) |
Permute axes |
view(dtype) |
Reinterpret bytes as a different dtype (no copy) |
item() / item<T>() |
Extract size-1 array as scalar |
item(index) / item<T>(index) |
Extract element at flat index as scalar |
GetAtIndex<T>(i) |
Read element at flat index (typed, fastest) |
SetAtIndex<T>(value, i) |
Write element at flat index |
GetValue<T>(indices) |
Read at N-D coordinates |
SetValue<T>(value, indices) |
Write at N-D coordinates |
MakeGeneric<T>() |
Wrap as NDArray<T> (same storage); throws if dtype differs |
AsGeneric<T>() |
Wrap as NDArray<T>; returns null if dtype differs |
AsOrMakeGeneric<T>() |
Wrap as NDArray<T>; astype if dtype differs |
Data<T>() |
Get the underlying ArraySlice<T> handle |
ToMuliDimArray<T>() |
Copy to a rank-N .NET array |
ToJaggedArray<T>() |
Copy to a jagged .NET array |
tofile(path) |
Write raw bytes to file |
Operators
| Operator | Overloads |
|---|---|
+, -, *, /, % |
(NDArray, NDArray), (NDArray, object), (object, NDArray) |
unary -, unary + |
(NDArray) |
&, \|, ^ |
(NDArray, NDArray), (NDArray, object), (object, NDArray) |
~, ! |
(NDArray), (NDArray<bool>) |
<<, >> |
(NDArray, NDArray), (NDArray, object) RHS only |
==, !=, <, <=, >, >= |
(NDArray, NDArray), (NDArray, object), (object, NDArray) |
Conversions
| Direction | Kind | Notes |
|---|---|---|
scalar NDArray |
implicit | bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, Half, float, double, decimal, Complex |
NDArray scalar |
explicit | same 15 types + string 0-d required; complex non-complex throws TypeError |
Persistence & Buffers
| Call | Format | View / copy | Notes |
|---|---|---|---|
np.save(path, arr) |
.npy |
Byte-identical to NumPy's np.save; Stream / byte[] overloads |
|
np.savez(path, ) |
.npz |
Many arrays; positional (arr_0, arr_1, ) or IDictionary<string, NDArray> |
|
np.savez_compressed(path, ) |
.npz |
Same as savez, Deflate-compressed |
|
np.load(path) |
.npy / .npz |
Returns object (NDArray or NpzFile); Stream / byte[] overloads |
|
np.load_npy(path) |
.npy |
copy | Typed NDArray |
np.load_npz(path) |
.npz |
lazy | Typed NpzFile (IDisposable; .Files, ["w"] / .f.w access) |
arr.tofile(path) |
raw | Element bytes only, no header | |
np.fromfile(path, dtype) |
raw | copy | Pair with tofile |
np.frombuffer(byte[], ) |
in-memory | view (pins array) | Endian-prefix dtype strings trigger a copy |
np.frombuffer(ArraySegment<byte>, ) |
in-memory | view | Uses segment's offset |
np.frombuffer(Memory<byte>, ) |
in-memory | view if array-backed, else copy | |
np.frombuffer(ReadOnlySpan<byte>, ) |
in-memory | copy | Spans can't be pinned |
np.frombuffer(IntPtr, byteLength, , dispose) |
native | view (optional ownership) | Pass dispose to transfer ownership |
np.frombuffer<T>(T[], ) |
in-memory | view | Reinterpret typed array as different dtype |
See also: Dtypes, Broadcasting, Exceptions, NumPy Compliance.