A NumPy dtype describes the type and storage of each array element. Inspect it before calculating totals, importing text or reducing memory use. Choosing a smaller type can lose values, precision or characters; validate the data before conversion.
| Type or code | Meaning |
|---|---|
bool / ? | Boolean values |
int8, int16, int32, int64 / i1, i2, i4, i8 | Signed integers with explicit widths |
uint8, uint16, uint32, uint64 / u1, u2, u4, u8 | Unsigned integers |
float16, float32, float64 / f2, f4, f8 | Floating-point values |
complex64, complex128 / c8, c16 | Complex numbers; total bytes include real and imaginary parts |
U / S or a | Fixed-width Unicode characters / bytes |
M / m | datetime64 / timedelta64; specify a unit, for example datetime64[D] |
O / V | Python objects / raw void data (also used for structured records) |
b / B | Signed / unsigned one-byte integer codes |
int_, intp, longdouble | Widths or precision depend on the platform; inspect dtype and itemsize |
Every ordinary NumPy array has one dtype describing its elements. Inspect array.dtype as an attribute; call np.dtype() to construct a dtype descriptor. Inferred integer width can depend on the platform.
import numpy as np
for values in [['a', 'b', 'c'], [1, 2, 3], [True, False, True]]:
a = np.array(values)
print(a, "dtype:", a.dtype, "kind:", a.dtype.kind)
assert np.array(['a', 'b', 'c']).dtype == np.dtype('U1')Expected output
['a' 'b' 'c'] dtype: <U1 kind: U
[1 2 3] dtype: int64 kind: i
[ True False True] dtype: bool kind: bUse an explicit width when storage or a file format requires it. int32 uses four bytes per element; float64 uses eight. The dtype describes storage, while shape describes dimensions.
a = np.array([1, 2, 3, 4], dtype=np.int32)
b = np.array([1, 2, 3, 4], dtype=np.float64)
print(a, a.dtype, a.itemsize)
print(b, b.dtype, b.itemsize)
assert a.itemsize == 4 and b.itemsize == 8Expected output
[1 2 3 4] int32 4
[1. 2. 3. 4.] float64 8Width codes such as i4, u4 and f8 specify bytes. Unicode U2 specifies characters. A kind such as u identifies a family; use a full descriptor such as u4 to construct an unsigned dtype. The prefix > means big endian; native byte order depends on the machine.
for spec in ['i4', 'u4', 'f8', 'c16', 'U2', 'S2', '>i4']:
d = np.dtype(spec)
print(spec, "kind:", d.kind, "bytes:", d.itemsize)
assert np.dtype('U2').itemsize == 8
assert np.dtype('>i4').byteorder == '>' Expected output
i4 kind: i bytes: 4
u4 kind: u bytes: 4
f8 kind: f bytes: 8
c16 kind: c bytes: 16
U2 kind: U bytes: 8
S2 kind: S bytes: 2
>i4 kind: i bytes: 4Zero becomes False and nonzero numbers become True. This is truth-value conversion, not a check that the source contains only zero and one. The ? dtype code means Boolean.
a = np.array([1, 2, 3, 4], dtype='?')
b = np.array([0, -2, 3], dtype=bool)
print(a)
print(b)
np.testing.assert_array_equal(b, [False, True, True])Expected output
[ True True True True]
[False True True]A Unicode array stores text, so arithmetic requires a deliberate conversion back to a numeric type. Preserve identifiers with leading zeros as strings.
a = np.array([1, 2, 3, 4], dtype='U')
ids = np.array(['001', '002', '010'])
print(a, a.dtype)
print("Text identifiers:", ids)
print("Converted identifiers:", ids.astype(np.int32))
assert ids[0] == '001' Expected output
['1' '2' '3' '4'] <U1
Text identifiers: ['001' '002' '010']
Converted identifiers: [ 1 2 10]Call astype() to convert values. By default it creates a new array and leaves the source unchanged. Boolean values convert to zero and one.
a = np.array([True, False, True])
b = a.astype(np.int32)
print("Source:", a, a.dtype)
print("Converted:", b, b.dtype)
assert a.dtype == np.dtype(bool)
np.testing.assert_array_equal(b, [1, 0, 1])Expected output
Source: [ True False True] bool
Converted: [1 0 1] int32Integer conversion does not round to the nearest whole number. Decide whether truncation or rounding fits the data before converting. Check finite values and the destination range first.
a = np.array([1.4, 3.0, 4.9, 9.0, -4.9])
b = a.astype(np.int8)
print("Truncated:", b)
print("Rounded first:", np.rint(a).astype(np.int8))
np.testing.assert_array_equal(b, [1, 3, 4, 9, -4])Expected output
Truncated: [ 1 3 4 9 -4]
Rounded first: [ 1 3 5 9 -5]A smaller integer dtype saves space but has a finite range. Use np.iinfo() to inspect it. Do not rely on an unsafe cast to report an out-of-range value.
a = np.array([-129, -128, 0, 127, 128], dtype=np.int64)
limits = np.iinfo(np.int8)
valid = (a >= limits.min) & (a <= limits.max)
print("int8 range:", limits.min, limits.max)
print("Rejected:", a[~valid])
print("Accepted:", a[valid].astype(np.int8))
np.testing.assert_array_equal(a[~valid], [-129, 128])Expected output
int8 range: -128 127
Rejected: [-129 128]
Accepted: [-128 0 127]The default astype casting rule permits lossy conversions. casting="safe" checks whether the source dtype can be represented safely by the target dtype for all its possible values. Even small int64 values therefore cannot safely narrow to int8 under this rule.
a = np.array([1, 2, 3], dtype=np.int64)
try:
a.astype(np.int8, casting='safe')
except TypeError:
print("Safe casting rejects int64 to int8.")
else:
raise AssertionError("Expected a TypeError")
print("Safe widening:", np.array([1, 2], dtype=np.int8).astype(np.int64, casting='safe'))Expected output
Safe casting rejects int64 to int8.
Safe widening: [1 2]For a whole-number field, require finite values, no fractional part and destination bounds. Keep rejected values for review instead of silently changing their meaning. Use Boolean selection to select accepted records.
a = np.array([12.0, -5.0, 3.5, np.nan, np.inf, 40000.0])
limits = np.iinfo(np.int16)
valid = np.isfinite(a) & (a == np.trunc(a)) & (a >= limits.min) & (a <= limits.max)
print("Accepted:", a[valid].astype(np.int16))
print("Review:", a[~valid])
np.testing.assert_array_equal(a[valid].astype(np.int16), [12, -5])Expected output
Accepted: [12 -5]
Review: [3.5e+00 nan inf 4.0e+04]Widening U1 to U2 reserves more room; it does not add characters. Narrowing to U2 discards characters beyond the first two. Assigning longer text to an existing fixed-width array can also truncate it.
a = np.array(['a', 'b', 'c'])
wide = a.astype('U2')
long_names = np.array(['a12', 'b123', 'c1234'])
short = long_names.astype('U2')
print("Widened:", wide, wide.dtype)
print("Narrowed:", short)
wide[0] = 'Alex'
print("After assignment:", wide)
assert wide[0] == 'Al'
np.testing.assert_array_equal(short, ['a1', 'b1', 'c1'])Expected output
Widened: ['a' 'b' 'c'] <U2
Narrowed: ['a1' 'b1' 'c1']
After assignment: ['Al' 'b' 'c']A structured array keeps named fields with different types in each record. Pass the descriptor to np.array(); defining it alone does not apply it. S20 stores up to 20 bytes per name. This small example uses ASCII names. See the student dataset for further practice.
student = np.dtype([('id', 'i4'), ('name', 'S20')])
records = np.array([(1, 'Alex'), (2, 'Rabi')], dtype=student)
print("Records:", records)
print("IDs:", records['id'])
print("Names as bytes:", records['name'])
print("Decoded:", np.char.decode(records['name'], 'utf-8'))
assert records.shape == (2,)
assert records['id'].dtype == np.dtype('i4')Expected output
Records: [(1, b'Alex') (2, b'Rabi')]
IDs: [1 2]
Names as bytes: [b'Alex' b'Rabi']
Decoded: ['Alex' 'Rabi']A U20 field holds up to 20 Unicode characters. A plain array of mixed numeric and text values instead finds one common dtype, often converting numbers to text. Choose structured fields when each column needs its own type.
plain = np.array([(1, 'Alex'), (2, 'Rabi')])
students = np.array([(1, 'Alex'), (2, 'Rabi')], dtype=[('id', 'i4'), ('name', 'U20')])
print("Plain array shape and kind:", plain.shape, plain.dtype.kind)
print("Structured names:", students['name'])
print("Structured IDs:", students['id'])
assert plain.dtype.kind == 'U'
assert students['name'][0] == 'Alex' Expected output
Plain array shape and kind: (2, 2) U
Structured names: ['Alex' 'Rabi']
Structured IDs: [1 2]For these ordinary numeric arrays, nbytes equals element count times itemsize. A smaller dtype reduces array storage, but conversions can temporarily require both arrays in memory. Object-array nbytes counts its pointer buffer, not all referenced Python objects.
a = np.arange(1000, dtype=np.int64)
b = a.astype(np.int32)
print("int64 bytes:", a.nbytes)
print("int32 bytes:", b.nbytes)
assert a.nbytes == 8000 and b.nbytes == 4000
np.testing.assert_array_equal(a, b)Expected output
int64 bytes: 8000
int32 bytes: 4000float32 cannot represent every large integer exactly. Once a value has been rounded during storage, converting to float64 does not recover the original number. Choose precision before loading values, and use an appropriate tolerance for floating-point calculations.
original = np.array([16777217], dtype=np.int64)
small = original.astype(np.float32)
restored = small.astype(np.float64)
print("Original integer:", original[0])
print("Stored float32 as integer:", int(small[0]))
print("Widened as integer:", int(restored[0]))
assert int(restored[0]) == 16777216Expected output
Original integer: 16777217
Stored float32 as integer: 16777216
Widened as integer: 16777216Plain integer arrays do not represent NaN. A floating array can hold NaN; an integer array can retain a separate validity mask. Do not treat a placeholder zero as an observed zero. Read NaN and data types and sum and missing-value counts.
values = np.array([12, 0, 5], dtype=np.int16)
valid = np.array([True, False, True])
report = np.where(valid, values.astype(np.float64), np.nan)
print("Report:", report)
print("Known subtotal:", values[valid].sum(dtype=np.int64))
print("Known count:", valid.sum())
assert np.isnan(report[1])
assert valid.sum() == 2Expected output
Report: [12. nan 5.]
Known subtotal: 17
Known count: 2These synthetic CSV-style strings represent whole-unit quantities. Parse with Python integers, validate the destination bounds, then create the NumPy array. Preserve row positions and rejected text so a later report cannot hide missing or invalid records. This example allows signed int16 quantities; business rules may impose stricter limits.
raw = ['12', '-5', '3.5', '', '40000', '007']
limits = np.iinfo(np.int16)
accepted, positions, rejected = [], [], []
for row, text in enumerate(raw):
try:
value = int(text)
except ValueError:
rejected.append((row, text, 'not a whole number'))
continue
if not limits.min <= value <= limits.max:
rejected.append((row, text, 'outside int16 range'))
continue
accepted.append(value)
positions.append(row)
quantities = np.array(accepted, dtype=np.int16)
print("Accepted rows:", positions)
print("Quantities:", quantities)
print("Review:", rejected)
assert positions == [0, 1, 5]
np.testing.assert_array_equal(quantities, [12, -5, 7])Expected output
Accepted rows: [0, 1, 5]
Quantities: [12 -5 7]
Review: [(2, '3.5', 'not a whole number'), (3, '', 'not a whole number'), (4, '40000', 'outside int16 range')]Given the text values ["0", "255", "256", "-1", "12", "bad"], retain whole numbers that fit uint8. Report accepted positions and rejected values. Predict accepted positions 0, 1 and 4. Do not cast the entire source before validation.
Unsigned uint8 supports values from zero through 255. Validate Python integers before creating the array, so negative and oversized values cannot wrap.
raw = ['0', '255', '256', '-1', '12', 'bad']
limits = np.iinfo(np.uint8)
accepted, positions, rejected = [], [], []
for row, text in enumerate(raw):
try:
value = int(text)
except ValueError:
rejected.append((row, text))
continue
if limits.min <= value <= limits.max:
positions.append(row)
accepted.append(value)
else:
rejected.append((row, text))
stock = np.array(accepted, dtype=np.uint8)
print("Accepted positions:", positions)
print("Stock:", stock)
print("Rejected:", rejected)
assert positions == [0, 1, 4]
np.testing.assert_array_equal(stock, [0, 255, 12])Expected output
Accepted positions: [0, 1, 4]
Stock: [ 0 255 12]
Rejected: [(2, '256'), (3, '-1'), (5, 'bad')]Open in Google Colab View on GitHub
All sample data is embedded. Run the examples and practise validating quantities before converting them. Save a copy in Drive to keep your changes.
Continue with NumPy tutorials, axis reduction and array totals. References: NumPy dtype documentation and astype documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.