pythonforbio.
[WASM idle]
NumPy12/18

NumPy dtypes

Starting sandbox…
Beginnerlesson2 tests

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]) / 2 gives 2.5 (float), but // 2 gives 2 and stays integer. In-place arr /= 2 on an int array raises.
  • np.nan in 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 int means int64 everywhere. On Windows, the default integer was historically int32. Be explicit in cross-platform code.
  • float16 for real work. It has about 3 decimal digits. Fine for storage of already-noisy data, dangerous for computation.
  • Forgetting astype returns a copy. a.astype(np.float32) does not modify a.

See also

ndarray · Missing Data in NumPy · Structured Arrays · pandas dtypes · Memory Layout and Strides · Versions and Compatibility

Test cases · 2

#viainputexpected stdout
1stdin30.7 20.2 41.9[30, 20, 41] dtype=int16
2stdin12.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.

starter

No output yet — run the code to populate this drawer.