NumPy dtypes
In one line: the element type of an array — it fixes memory use, precision, overflow behaviour and speed, and NumPy will never change it for you silently.
The families
| Family | Examples | Notes |
|---|---|---|
| Signed int | int8 int16 int32 int64 |
Wraps silently on overflow |
| Unsigned int | uint8 uint16 uint32 uint64 |
Mixing with signed promotes to float |
| Float | float16 float32 float64 |
float64 is the default |
| Complex | complex64 complex128 |
Rare in bio |
| Bool | bool_ |
One byte per element, not one bit |
| Fixed string | <U10, S10 |
Unicode / bytes, fixed width — truncates! |
| Object | object |
Python objects in a box; kills all performance |
| Datetime | datetime64[D], timedelta64[ns] |
Unit is part of the dtype |
| Structured | see Structured Arrays | Named fields, C-struct-like |
a = np.array([1, 2, 3]) # int64 on Linux/macOS
b = np.array([1.0, 2.0]) # float64
c = np.array([1, 2], dtype=np.float32)
a.astype(np.float32) # returns a new array (copy)
Precision, memory and the real tradeoff
| dtype | Bytes | Range / precision |
|---|---|---|
int8 |
1 | −128 … 127 |
int16 |
2 | ±32,767 |
int32 |
4 | ±2.1 × 10⁹ |
int64 |
8 | ±9.2 × 10¹⁸ |
float16 |
2 | ~3 decimal digits |
float32 |
4 | ~7 decimal digits |
float64 |
8 | ~16 decimal digits |
A 20,000 × 500 matrix: 80 MB as float64, 40 MB as float32. For expression counts, coverage, or quality scores, float32 is plenty — 7 significant digits far exceeds the measurement precision of any assay. For accumulating sums over millions of elements, keep float64, because rounding error accumulates.
counts.astype(np.float32) # halve the memory
counts.sum(axis=0, dtype=np.float64) # but accumulate in float64
That dtype= argument on reductions is underused and exactly right: low-precision storage, high-precision accumulation.
Overflow is silent and it will get you
np.array([127], dtype=np.int8) + 1 # array([-128]) ← no error
np.array([2**31 - 1], dtype=np.int32) * 2 # negative ← no error
Genomic coordinates on chromosome 1 (~249 Mb) fit in int32, but a whole-genome cumulative offset (~3.1 Gb) does not. Coverage totals across a large cohort do not. Use int64 for anything positional or cumulative.
Python's own ints are arbitrary-precision, so this is a NumPy-specific hazard that people migrating from pure Python do not expect.
NEP 50: what changed in NumPy 2
Scalar promotion rules changed. Under NumPy 2:
np.float32(1.0) + 1.0 # float32 (NumPy 1.x gave float64)
np.uint8(200) + 100 # OverflowError-ish behaviour differs
The rule is now: a Python scalar adopts the array's dtype rather than promoting it. This is more predictable, but it means numerical results can differ between NumPy 1 and 2 in low-precision code. If you have old float32 pipelines, re-verify their output. See Versions and Compatibility.
The fixed-string trap
a = np.array(["chr1", "chr2"]) # dtype '<U4'
a[0] = "chromosome_1" # silently truncated to 'chro'
The width is baked in at creation. This is one of the reasons genomic labels belong in Pandas (which now has a proper str dtype) rather than NumPy string arrays.
The object dtype is a warning sign
np.array([1, "a", None]) # dtype=object
An object array is a Python list wearing an ndarray costume: every operation goes through the interpreter, so you lose all of NumPy's speed and most of its functions. If you land in object dtype, you almost certainly want a DataFrame.
Checking and converting
np.issubdtype(a.dtype, np.integer)
np.issubdtype(a.dtype, np.floating)
a.astype(np.int32, casting="safe") # raises rather than silently truncating
np.can_cast(np.int64, np.int32) # False
casting="safe" is a good habit in pipeline code — it turns a silent data corruption into an exception.
Common mistakes
- Integer division surprises.
np.array([5]) / 2gives2.5(float), but// 2gives2and stays integer. In-placearr /= 2on an int array raises. np.nanin an integer array. Impossible — NaN is a float concept. The array is silently promoted to float, or the assignment fails. See Missing Data in NumPy.- Assuming
intmeansint64everywhere. On Windows, the default integer was historicallyint32. Be explicit in cross-platform code. float16for real work. It has about 3 decimal digits. Fine for storage of already-noisy data, dangerous for computation.- Forgetting
astypereturns a copy.a.astype(np.float32)does not modifya.
See also
ndarray · Missing Data in NumPy · Structured Arrays · pandas dtypes · Memory Layout and Strides · Versions and Compatibility
Test cases · 2
| # | via | input | expected stdout |
|---|---|---|---|
| 1 | stdin | 30.7 20.2 41.9 | [30, 20, 41] dtype=int16 |
| 2 | stdin | 12.0 0.5 60.9 33.3 | [12, 0, 60, 33] dtype=int16 |
Hints · 2
01Hint
np.array(values, dtype=np.int16) truncates toward zero.
02Hint
arr.astype(np.float32) returns a new array; it does not modify in place.