Shape, size and dtype
Every NumPy array has a shape (how many elements along each dimension) and a dtype (the one type all its elements share). Knowing both explains most of NumPy's behaviour.
Shape and dimensions
import numpy as np
scalar = np.array(7)
vector = np.array([1, 2, 3])
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
cube = np.zeros((2, 3, 4)) # 2 blocks of 3 rows by 4 columns
for name, arr in [("scalar", scalar), ("vector", vector), ("matrix", matrix), ("cube", cube)]:
print(f"{name:7} ndim={arr.ndim} shape={arr.shape} size={arr.size}")ndimis the number of dimensions, called axes.shapeis a tuple with the length along each axis:(2, 3)means 2 rows and 3 columns.sizeis the total number of elements, the product of the shape.len(arr)gives only the length of the first axis, which for the matrix is 2.
dtype
import numpy as np
print(np.array([1, 2, 3]).dtype) # a whole-number type, such as int64
print(np.array([1.5, 2, 3]).dtype) # float64: one float makes them all floats
print(np.array([True, False]).dtype) # bool
print(np.array(["cat", "horse"]).dtype) # <U5: text of up to 5 characters
print(np.array([1, 2.5, "3"])) # everything became textAn array holds one type. Mix types, and NumPy converts everything to the most general one, so a single float makes every element a float, and a single string makes every element a string. (The exact integer type can differ between systems; on most it's int64.)
Converting with astype
import numpy as np
prices = np.array([19.99, 5.49, 120.0])
print(prices.astype(int)) # [ 19 5 120]: the decimals are cut off, not rounded
print(np.round(prices).astype(int)) # round first if that's what you mean
text = np.array(["10", "20", "35"])
numbers = text.astype(int)
print(numbers.sum()) # 65
flags = np.array([0, 3, 0, 1])
print(flags.astype(bool)) # zero is False, anything else is Trueastype returns a new array of the requested type; the original is unchanged.
Fixed-size integers can overflow
import numpy as np
small = np.array([100, 120, 127], dtype=np.int8) # int8 holds -128 to 127
print(small + 10) # silently wraps around
print(np.iinfo(np.int8))
print(np.iinfo(np.int64).max)Unlike Python's own integers, which grow as large as needed, NumPy integers have a fixed size. Going past the limit wraps around without an error. The default 64-bit integers reach about 9.2 quintillion, so this mostly bites when you choose a small dtype to save memory.
Floating point isn't exact
import numpy as np
a = np.array([0.1, 0.2])
print(a.sum() == 0.3) # False!
print(a.sum())
print(np.isclose(a.sum(), 0.3)) # True: compare floats with a toleranceDecimal fractions like 0.1 can't be stored exactly in binary, so tiny errors creep in. Compare floats with np.isclose, never with ==.
Memory
import numpy as np
a = np.arange(1_000_000)
print(a.dtype, a.itemsize, "bytes each,", a.nbytes // 1_000_000, "MB in total")
b = a.astype(np.int32)
print(b.dtype, b.itemsize, "bytes each,", b.nbytes // 1_000_000, "MB in total")itemsize is the size of one element and nbytes the size of the whole array. A smaller dtype halves the memory, which matters for big datasets, provided the values fit.