Working with Arrays in Python
NumPy helps Python process numerical data efficiently. Python lists can hold mixed types, while a NumPy array normally stores values of one data type in a compact structure. For vectorized numerical operations over many values, this representation is often faster than an equivalent Python loop.
The core ndarray object represents an n-dimensional, fixed-size container whose elements share one data type. This design enables vectorized operations, where one expression describes work over an entire array.
NumPy arrays provide the following advantages for scientific programming:
- Efficient numerical loops implemented in compiled native code
- Vectorization allows operations on entire arrays without explicit loops
- Compact storage for homogeneous numerical values
- A large collection of ready-to-use mathematical operations
Vectorization means expressing one operation for an entire array. NumPy still works through the elements internally, but that loop runs in optimized compiled code.
# Element multiplication using Python list
x = [1, 2, 3, 4, 5]
y = [2, 3, 4, 5, 6]
z = []
for i in range(len(x)):
z.append(x[i] * y[i])
print("List result:", z) # Output: List result: [2, 6, 12, 20, 30]
# Element multiplication using NumPy
import numpy as np
x_np = np.array([1, 2, 3, 4, 5])
y_np = np.array([2, 3, 4, 5, 6])
z_np = x_np * y_np
print("NumPy result:", z_np) # Output: NumPy result: [ 2 6 12 20 30]Manual Array Creation
Use np.array() to convert an existing Python data structure into a NumPy array.
One-dimensional arrays are like a sequence of numbers in a single row. You can create them from Python lists using the np.array() function.
import numpy as np
# Create 1D array from list
a = np.array([0, 1, 2, 3])
print("1D Array:", a) # Output: 1D Array: [0 1 2 3]
print("Data type:", type(a)) # Output: Data type: <class 'numpy.ndarray'>
print("Shape:", a.shape) # Output: Shape: (4,)
print("Dimensions:", a.ndim) # Output: Dimensions: 1Two-dimensional arrays are like tables with rows and columns. You can create them from nested lists.
import numpy as np
# Create 2D array from nested list
a = np.array([[0, 1], [2, 3]])
print("2D Array:")
print(a)
# Output:
# [[0 1]
# [2 3]]
print("Shape:", a.shape) # Output: Shape: (2, 2)
print("Dimensions:", a.ndim) # Output: Dimensions: 2
print("Total elements:", a.size) # Output: Total elements: 4A three-dimensional array can be pictured as a stack of tables. Each layer contains rows and columns, and the additional axis selects a layer.
import numpy as np
# Create 3D array
a = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print("3D Array:")
print(a)
# Output:
# [[[ 1 2 3]
# [ 4 5 6]]
# [[ 7 8 9]
# [10 11 12]]]
print("Shape:", a.shape) # Output: Shape: (2, 2, 3)
print("Axis 0 (planes):", a.shape[0]) # Output: Axis 0 (planes): 2
print("Axis 1 (rows):", a.shape[1]) # Output: Axis 1 (rows): 2
print("Axis 2 (columns):", a.shape[2]) # Output: Axis 2 (columns): 3Multidimensional Structure
| Dimension | Shape | Structure | Example | Axis |
|---|---|---|---|---|
| One-dimensional | (4,) | Number sequence in single row | [0, 1, 2, 3] | Axis for element index |
| Two-dimensional | (2, 2) | Table with rows and columns | [[0, 1], [2, 3]] | Axis for rows, Axis for columns |
| Three-dimensional | (2, 2, 3) | Stack of tables (planes) | planes, each plane | Axis depth, Axis height, Axis width |
The higher the array dimension, the more complex the data structure, but the basic principle remains the same. Each axis represents one dimension of data organization.
NumPy can create arrays from various Python data structures, including lists, tuples, and mixtures of both.
import numpy as np
# From list
arr_from_list = np.array([0, 1, 2, 3])
print("From list:", arr_from_list) # Output: From list: [0 1 2 3]
# From tuple
arr_from_tuple = np.array((0, 1, 2, 3))
print("From tuple:", arr_from_tuple) # Output: From tuple: [0 1 2 3]
# From mixture (will be converted to compatible type)
arr_mixed = np.array([0, 1, 2.5, 3])
print("From mixture:", arr_mixed) # Output: From mixture: [0. 1. 2.5 3. ]
print("Automatic data type:", arr_mixed.dtype) # Output: Automatic data type: float64Array Creation Functions
Writing every element by hand only works for tiny examples. NumPy therefore ships dedicated creation functions that build an array from a rule instead of from a literal list, which keeps the code short and the shape explicit. The functions below fall into three groups: filled arrays with a constant value, arrays of random numbers, and arrays that follow a mathematical pattern.
Constant Value Functions
The np.ones() function creates an array filled with . It is useful when every element needs the same initial value.
import numpy as np
# 1D array with value 1
a = np.ones(3)
print("1D ones:", a) # Output: 1D ones: [1. 1. 1.]
# 2D array with value 1
a = np.ones((2, 3))
print("2D ones:")
print(a)
# Output:
# [[1. 1. 1.]
# [1. 1. 1.]]
# 3D array with value 1
a = np.ones((2, 2, 3))
print("3D ones shape:", a.shape) # Output: 3D ones shape: (2, 2, 3)
print("3D ones:")
print(a)
# Output:
# [[[1. 1. 1.]
# [1. 1. 1.]]
# [[1. 1. 1.]
# [1. 1. 1.]]]Besides np.ones(), there are other functions for creating arrays with special patterns:
import numpy as np
# Array with zero values
a = np.zeros((2, 3))
print("Zeros array:")
print(a)
# Output:
# [[0. 0. 0.]
# [0. 0. 0.]]
# Identity array (diagonal 1, others 0)
a = np.eye(3, 3)
print("Identity matrix:")
print(a)
# Output:
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# Diagonal array with specific values
a = np.diag((1, 2, 3))
print("Diagonal array:")
print(a)
# Output:
# [[1 0 0]
# [0 2 0]
# [0 0 3]]Random Functions
Use the random number generator np.random.default_rng() to create simulation data. The same seed reproduces a sequence with the same generator and NumPy version. The uniform values below lie in [0, 1): zero is included and one is excluded.
import numpy as np
# Set seed for consistent results
rng = np.random.default_rng(10)
# Array with uniform random values between 0 and 1
a = rng.random((2, 3))
print("Random uniform [0,1):")
print(a)
# Output:
# [[0.95600171 0.20768181 0.82844489]
# [0.14928212 0.51280462 0.1359196 ]]
# Array with normal distribution (mean=0, std=1)
a = rng.standard_normal(3)
print("Random normal:", a) # Output: Random normal: [0.84304257 0.85793655 0.47518364]
# Array with random integers in specific range
a = rng.integers(1, 10, size=(2, 3))
print("Random integers [1,10):")
print(a)
# Output:
# [[5 9 3]
# [8 1 4]]Mathematical Functions
The np.fromfunction function passes arrays of index coordinates to your function. Here, f(i, j) receives an array of row indices in i and an array of column indices in j. Adding i + j works element by element: position [1, 2], for example, receives 3.
import numpy as np
# Create array using function
def f(i, j):
return i + j
# 2x3 array with values based on function f(i,j) = i + j
a = np.fromfunction(f, (2, 3))
print("Array from function f(i,j) = i + j:")
print(a)
# Output:
# [[0. 1. 2.]
# [1. 2. 3.]]
# More complex function
def g(i, j):
return i * j + 1
b = np.fromfunction(g, (3, 3))
print("Array from function g(i,j) = i*j + 1:")
print(b)
# Output:
# [[1. 1. 1.]
# [1. 2. 3.]
# [1. 3. 5.]]The np.empty function allocates an array without initializing its element values. The existing memory contents are arbitrary, so every element must be assigned before it is read. Use it only when the array will be filled immediately.
import numpy as np
# Create empty array (undefined values)
a = np.empty((3, 2))
a.fill(0) # Fill every element before reading its value
print("Array after filling:")
print(a)
# Output:
# [[0. 0.]
# [0. 0.]
# [0. 0.]]
print("Shape:", a.shape) # Output: Shape: (3, 2)
print("Dtype:", a.dtype) # Output: Dtype: float64Arrays from Sequences
Some arrays are easier to describe as a sequence than as a rule for each element: every value between two limits, or a fixed number of evenly spaced points. np.arange and np.linspace build such arrays, and the difference between them is how the end of the sequence is specified.
NumPy Arange Function
The np.arange function resembles Python's range(), but returns an array and accepts floating-point arguments. For noninteger steps, floating-point rounding can make the final values less predictable, so np.linspace is often clearer when the number of points matters.
import numpy as np
# Array from 0 to 4
a = np.arange(5)
print("arange(5):", a) # Output: arange(5): [0 1 2 3 4]
# Array with start, stop, and step
a = np.arange(1.5, 3., 0.5)
print("arange(1.5, 3., 0.5):", a) # Output: arange(1.5, 3., 0.5): [1.5 2. 2.5]
# Array with step that produces decimal values
a = np.arange(1.5, 4.)
print("arange(1.5, 4.):", a) # Output: arange(1.5, 4.): [1.5 2.5 3.5]
# Parameter demonstration
print("\nParameter arange(start, end, step):")
print("- start, end, step can be float")
print("- end is excluded from result")
print("- step default is 1")
print("- start default is 0")NumPy Linspace Function
Unlike np.arange which uses fixed steps, np.linspace divides a range into a number of evenly spaced points.
import numpy as np
# Create 5 evenly spaced points between 0 and 10
a = np.linspace(0, 10, 5)
print("linspace(0, 10, 5):", a) # Output: linspace(0, 10, 5): [ 0. 2.5 5. 7.5 10. ]
# Create 11 evenly spaced points between -1 and 1
a = np.linspace(-1, 1, 11)
print("linspace(-1, 1, 11):", a)
# Output: linspace(-1, 1, 11): [-1. -0.8 -0.6 -0.4 -0.2 0. 0.2 0.4 0.6 0.8 1. ]
# Comparison linspace vs arange
print("\nDifference linspace vs arange:")
print("linspace: number of elements is given, spacing is calculated")
print("arange: step is given, number of elements follows")Comparing arange and linspace
Both functions build an array from a range, and the parameter you pass decides the difference. arange fixes the step between elements, while linspace fixes how many elements you want.
| Aspect | np.arange | np.linspace |
|---|---|---|
| Main parameters | start, stop, step | start, stop, num |
| Control | Distance between elements | Total number of elements |
| Endpoint | Excluded | Included (default) |
| Data type | Inferred from input | Uses a floating-point type by default. Set dtype to choose another type |
| Usage | Sequence with fixed distance | Even range division |
Basic Operations
Before reshaping an array, inspect its element count and shape. The array below has two rows and three columns, so its size is 6. Its explicit int64 dtype uses eight bytes per element.
import numpy as np
# Create example array
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64)
print("Array:")
print(a)
# Output:
# [[1 2 3]
# [4 5 6]]
print("Shape:", a.shape) # Output: Shape: (2, 3)
print("Size (total elements):", a.size) # Output: Size (total elements): 6
print("Ndim (dimensions):", a.ndim) # Output: Ndim (dimensions): 2
print("Dtype (data type):", a.dtype) # Output: Dtype (data type): int64
print("Itemsize (bytes per element):", a.itemsize) # Output: Itemsize (bytes per element): 8The element count must stay the same when you use reshape(): 3 * 4, 2 * 6, and 4 * 3 all equal 12. Passing -1 asks NumPy to infer one missing dimension. Reshaping may return a view or a copy, depending on the array layout.
import numpy as np
# 1D array with 12 elements
a = np.arange(12)
print("Original array:", a) # Output: Original array: [ 0 1 2 3 4 5 6 7 8 9 10 11]
# Reshape to 3x4
b = a.reshape(3, 4)
print("Reshape 3x4:")
print(b)
# Output:
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# Reshape to 2x6
c = a.reshape(2, 6)
print("Reshape 2x6:")
print(c)
# Output:
# [[ 0 1 2 3 4 5]
# [ 6 7 8 9 10 11]]
# Reshape with -1 (automatically calculate dimension)
d = a.reshape(4, -1)
print("Reshape 4x-1 (automatic):")
print(d)
# Output:
# [[ 0 1 2]
# [ 3 4 5]
# [ 6 7 8]
# [ 9 10 11]]