NumPy dtype: Inspect, Choose and Convert Data Types

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 codeMeaning
bool / ?Boolean values
int8, int16, int32, int64 / i1, i2, i4, i8Signed integers with explicit widths
uint8, uint16, uint32, uint64 / u1, u2, u4, u8Unsigned integers
float16, float32, float64 / f2, f4, f8Floating-point values
complex64, complex128 / c8, c16Complex numbers; total bytes include real and imaginary parts
U / S or aFixed-width Unicode characters / bytes
M / mdatetime64 / timedelta64; specify a unit, for example datetime64[D]
O / VPython objects / raw void data (also used for structured records)
b / BSigned / unsigned one-byte integer codes
int_, intp, longdoubleWidths or precision depend on the platform; inspect dtype and itemsize

Inspect an Array dtype

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: b

Set dtype When Creating an Array

Use 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 == 8

Expected output

[1 2 3 4] int32 4
[1. 2. 3. 4.] float64 8

dtype Codes, Kinds and Byte Order

Width 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: 4

Convert Numbers to Boolean Values

Zero 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]

Convert Numbers to Unicode Text

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]

astype Returns a Converted Array

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] int32

Float to Integer Truncates toward Zero

Integer 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]

Check Integer Bounds before Narrowing

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]

Safe Casting Checks Types, Not Actual Values

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]

Reject Missing, Fractional and Out-of-Range Values

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]

Fixed-Width Unicode Can Truncate Text

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']

Structured Student Records Need dtype in array Creation

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']

Use Unicode Fields for Text Names

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]

Compare Array Storage with nbytes

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: 4000

Widening Cannot Restore Lost Float Precision

float32 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]) == 16777216

Expected output

Original integer: 16777217
Stored float32 as integer: 16777216
Widened as integer: 16777216

Missing Values Need a Deliberate Representation

Plain 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() == 2

Expected output

Report: [12. nan  5.]
Known subtotal: 17
Known count: 2

Practical Import: Keep Rejected Sales Quantities

These 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')]

Exercise: Validate Unsigned Stock Counts

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.

Exercise Solution

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')]

Practice in Google Colab

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.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer