2.1 Arrays, Shapes, Axes, and Data Types
Chapter 1 established a contract: every row and field must have a declared meaning, and every result must be reproducible. NumPy gives us a compact computational structure for carrying that contract into numerical work.
NumPy, short for Numerical Python, is a library built around the multidimensional array, or ndarray. An array is not merely a Python list written with square brackets. It has a fixed rectangular shape, numbered axes, and normally one shared data type. Those constraints let NumPy store values compactly and apply numerical operations efficiently.
Import NumPy using its conventional alias:
import numpy as npFrom a Python list to an ndarray
Suppose three rows represent Monday through Wednesday and four columns represent North, Central, South, and West delivery zones:
delivery_minutes = np.array([
[31, 38, 44, 36],
[29, 41, 47, 35],
[33, 39, 42, 37],
])The nested source resembles a list of lists, but the resulting object has array metadata:
print(delivery_minutes.ndim) # 2
print(delivery_minutes.shape) # (3, 4)
print(delivery_minutes.size) # 12
print(delivery_minutes.dtype) # commonly int64 on a 64-bit systemndim is the number of axes. shape gives the length of each axis in order. size is the total number of elements, so for a rectangular array it equals the product of the shape dimensions. dtype describes how every element is stored.
Shape is not decoration. It carries analytical meaning:
- Axis 0 has length 3 and represents dates.
- Axis 1 has length 4 and represents zones.
- Coordinate
[1, 2]means the value at the second date and third zone because Python indexes from zero.
If another analyst assumes rows are zones, every later aggregation will be labeled incorrectly even though the calculations run. Keep axis meaning near the array, in variable names, comments, or accompanying labels.
Construct arrays deliberately
np.array converts existing values. NumPy also provides constructors for common patterns:
zeros = np.zeros((3, 4), dtype=np.float64)
ones = np.ones((2, 3), dtype=np.int64)
placeholder = np.full((2, 2), fill_value=-1)
hours = np.arange(14, 22)
grid = np.arange(12).reshape(3, 4)np.arange(14, 22) follows Python’s half-open convention: it begins at 14 and stops before 22. reshape(3, 4) changes the coordinate system without changing the 12 values. The requested shape must contain the same number of elements; a 12-element array cannot become shape (5, 3) because that would require 15 values.
Avoid using an arbitrary placeholder as though it were real data. A value such as -1 is only safe when the schema states that negative delivery time is impossible and the code treats -1 explicitly as a sentinel. Later in this chapter, np.nan will represent missing floating-point observations more clearly.
One array, one dtype
Unlike a Python list, an ordinary NumPy array is homogeneous: its elements share one dtype. When inputs differ, NumPy finds a common representation through type promotion.
whole = np.array([31, 38, 44])
mixed_numeric = np.array([31, 38.5, 44])
mixed_text = np.array([31, "missing", 44])
print(whole.dtype) # integer dtype
print(mixed_numeric.dtype) # floating dtype
print(mixed_text.dtype) # Unicode string dtypeThe integer values in mixed_numeric can be represented as floating-point numbers, so the whole array is promoted. The word "missing" cannot be represented numerically, so the third array becomes text. Arithmetic such as .mean() is no longer meaningful until the text is cleaned and converted.
You can request a dtype explicitly:
precise = np.array([31, 38.5, 44], dtype=np.float64)
truncated = np.array([31, 38.5, 44], dtype=np.int32)The second conversion changes 38.5 to 38. NumPy followed the instruction; it did not know that the fraction mattered. Explicit conversion is therefore a data decision, not only a performance setting.
Choose dtype from meaning and range
Common dtype families include signed integers such as int32 and int64, unsigned integers, floating-point types such as float32 and float64, Boolean values, and fixed-width Unicode strings. The number often describes how many bits each element uses. More bits usually support a larger integer range or greater floating precision, at the cost of memory.
Do not choose the smallest dtype merely because a sample currently fits. A signed 8-bit integer can represent only values from through . Adding to a value near the boundary can overflow and wrap to an unexpected result. Likewise, float32 is useful for large arrays and many machine-learning workloads, but it preserves fewer significant digits than float64.
Inspect before converting:
print(values.min(), values.max())
converted = values.astype(np.float64)astype normally creates a converted array. Validate missing tokens, ranges, and fractional values before using it. A successful conversion only proves that the operation was possible; it does not prove that the result preserved the intended meaning.
The next section treats an array as a coordinate system. You will select individual values, rectangular windows, and records satisfying several conditions while preserving the distinction between positions and data meaning.