NumPy - Create Array with Range (np.arange)

import numpy as np

Key Insights

  • np.arange() generates arrays with evenly spaced values using start, stop, and step parameters, offering more flexibility than Python’s range() for numerical computing
  • Unlike range(), np.arange() supports floating-point steps and returns NumPy arrays with vectorized operations, but requires careful handling of floating-point precision issues
  • For many use cases, np.linspace() provides better control when you need a specific number of elements rather than a specific step size

Basic Syntax and Parameters

np.arange() creates arrays with regularly incrementing values. The function accepts up to four parameters: start, stop, step, and dtype.

import numpy as np

# Single parameter: generates 0 to n-1
arr1 = np.arange(5)
print(arr1)  # [0 1 2 3 4]

# Two parameters: start and stop
arr2 = np.arange(2, 8)
print(arr2)  # [2 3 4 5 6 7]

# Three parameters: start, stop, step
arr3 = np.arange(0, 10, 2)
print(arr3)  # [0 2 4 6 8]

# Negative step for descending sequences
arr4 = np.arange(10, 0, -2)
print(arr4)  # [10  8  6  4  2]

The stop value is always exclusive. The array generation stops before reaching this value, consistent with Python’s indexing conventions.

Working with Floating-Point Steps

Unlike Python’s built-in range(), np.arange() handles floating-point numbers, making it valuable for scientific computing and data visualization.

import numpy as np

# Floating-point range
float_arr = np.arange(0.0, 2.0, 0.3)
print(float_arr)
# [0.  0.3 0.6 0.9 1.2 1.5 1.8]

# Small increments for smooth curves
x = np.arange(0, np.pi, 0.1)
y = np.sin(x)
print(f"Generated {len(x)} points for sine wave")

# Negative floating-point steps
desc_arr = np.arange(5.0, 0.0, -0.5)
print(desc_arr)
# [5.  4.5 4.  3.5 3.  2.5 2.  1.5 1.  0.5]

Floating-Point Precision Pitfalls

Floating-point arithmetic introduces precision issues that can cause unexpected results with np.arange().

import numpy as np

# Problematic: unclear number of elements
arr1 = np.arange(0.0, 1.0, 0.1)
print(len(arr1))  # Might be 10, might be 11 depending on rounding
print(arr1)
# [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]

# More problematic case
arr2 = np.arange(0.1, 1.0, 0.1)
print(len(arr2))
print(arr2[-1])  # Last element might exceed expectations

# Solution: Use np.linspace for predictable results
arr3 = np.linspace(0.0, 1.0, 11)  # Exactly 11 points including endpoints
print(len(arr3))  # Always 11
print(arr3)

# Alternative: Use integer arange then scale
arr4 = np.arange(0, 10) * 0.1
print(arr4)
# [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]

For critical applications requiring exact element counts, prefer np.linspace() or generate integer sequences then transform them.

Specifying Data Types

Control memory usage and precision by explicitly setting the array’s data type.

import numpy as np

# Default dtype (typically int64 or float64)
default_arr = np.arange(5)
print(default_arr.dtype)  # int64 (platform dependent)

# Explicit integer types
int32_arr = np.arange(0, 100, dtype=np.int32)
print(int32_arr.dtype)  # int32
print(f"Memory: {int32_arr.nbytes} bytes")

int8_arr = np.arange(0, 100, dtype=np.int8)
print(f"Memory: {int8_arr.nbytes} bytes")

# Float types
float32_arr = np.arange(0.0, 10.0, 0.5, dtype=np.float32)
print(float32_arr.dtype)  # float32

# Complex numbers
complex_arr = np.arange(5, dtype=complex)
print(complex_arr)
# [0.+0.j 1.+0.j 2.+0.j 3.+0.j 4.+0.j]

Reshaping and Multidimensional Arrays

Combine np.arange() with reshaping operations to create multidimensional arrays with sequential values.

import numpy as np

# Create 2D array
arr_2d = np.arange(12).reshape(3, 4)
print(arr_2d)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# Create 3D array
arr_3d = np.arange(24).reshape(2, 3, 4)
print(arr_3d.shape)  # (2, 3, 4)

# Using -1 for automatic dimension calculation
arr_auto = np.arange(20).reshape(4, -1)
print(arr_auto.shape)  # (4, 5)

# Create coordinate grids
x = np.arange(0, 5)
y = np.arange(0, 3)
xx, yy = np.meshgrid(x, y)
print("X coordinates:")
print(xx)
print("Y coordinates:")
print(yy)

Performance Comparisons

np.arange() significantly outperforms Python’s range() for numerical operations due to vectorization.

import numpy as np
import time

# Benchmark: np.arange vs range
n = 10_000_000

# Using np.arange
start = time.time()
arr_np = np.arange(n)
result_np = (arr_np ** 2).sum()
time_np = time.time() - start

# Using Python range
start = time.time()
arr_py = list(range(n))
result_py = sum(x ** 2 for x in arr_py)
time_py = time.time() - start

print(f"NumPy time: {time_np:.4f}s")
print(f"Python time: {time_py:.4f}s")
print(f"Speedup: {time_py/time_np:.2f}x")

# Memory efficiency
import sys
np_size = arr_np.nbytes
py_size = sys.getsizeof(arr_py)
print(f"NumPy memory: {np_size / 1e6:.2f} MB")
print(f"Python memory: {py_size / 1e6:.2f} MB")

Practical Applications

Real-world scenarios where np.arange() proves essential.

import numpy as np
import matplotlib.pyplot as plt

# Generate time series indices
sampling_rate = 1000  # Hz
duration = 2  # seconds
time_points = np.arange(0, duration, 1/sampling_rate)
signal = np.sin(2 * np.pi * 5 * time_points)  # 5 Hz sine wave

# Create histogram bins
data = np.random.randn(10000)
bins = np.arange(-4, 4, 0.2)
hist, _ = np.histogram(data, bins=bins)

# Index-based operations
indices = np.arange(100)
filtered_indices = indices[indices % 5 == 0]  # Every 5th index
print(filtered_indices[:10])  # [ 0  5 10 15 20 25 30 35 40 45]

# Creating lookup tables
angles = np.arange(0, 360, 1)  # Degrees
sin_lookup = np.sin(np.radians(angles))
cos_lookup = np.cos(np.radians(angles))

# Batch processing with offsets
batch_size = 32
total_samples = 1000
batch_starts = np.arange(0, total_samples, batch_size)
for start in batch_starts:
    end = min(start + batch_size, total_samples)
    # Process batch[start:end]
    pass

Alternative Functions

Understanding when to use alternatives to np.arange().

import numpy as np

# np.linspace: specify number of points instead of step
lin = np.linspace(0, 10, 50)  # Exactly 50 points from 0 to 10
print(f"linspace length: {len(lin)}")

# np.logspace: logarithmically spaced values
log = np.logspace(0, 3, 4)  # 10^0 to 10^3, 4 points
print(log)  # [   1.   10.  100. 1000.]

# np.geomspace: geometric progression
geom = np.geomspace(1, 1000, 4)
print(geom)  # [   1.   10.  100. 1000.]

# When to use each:
# - np.arange: Known step size, integer sequences
# - np.linspace: Known number of points, endpoints included
# - np.logspace: Logarithmic scales, frequency analysis
# - np.geomspace: Geometric growth, exponential data

# Comparison for same range
print("arange:", np.arange(0, 1.1, 0.25))
print("linspace:", np.linspace(0, 1, 5))
# Both produce similar results, but linspace includes endpoint

Choose np.arange() when you need specific step sizes and work primarily with integers. Switch to np.linspace() for floating-point ranges where the exact number of elements matters more than the step size.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.