NumPy - Create Evenly Spaced Array (np.linspace)

import numpy as np

Key Insights

  • np.linspace() generates evenly spaced values over a specified interval, unlike np.arange() which uses step size and can produce unexpected results with floating-point arithmetic
  • The function calculates spacing automatically based on the number of points you need, making it ideal for mathematical operations, plotting, and numerical analysis
  • Understanding the endpoint, retstep, and dtype parameters unlocks advanced use cases like creating meshgrids, generating periodic signals, and optimizing memory usage

Basic Syntax and Core Behavior

np.linspace() creates an array of evenly spaced numbers between a start and stop value. The fundamental difference from np.arange() is that you specify how many points you want rather than the step size.

import numpy as np

# Basic usage: 5 evenly spaced values from 0 to 10
arr = np.linspace(0, 10, 5)
print(arr)
# Output: [ 0.   2.5  5.   7.5 10. ]

# The spacing is calculated as: (stop - start) / (num - 1)
# (10 - 0) / (5 - 1) = 2.5

The function signature is:

numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)

By default, np.linspace() includes the endpoint and generates 50 points. This behavior contrasts with range-based functions where the endpoint is typically excluded.

# Default 50 points
default_arr = np.linspace(0, 1)
print(f"Length: {len(default_arr)}")  # Length: 50
print(f"First: {default_arr[0]}, Last: {default_arr[-1]}")  # First: 0.0, Last: 1.0

Endpoint Parameter: Including or Excluding the Stop Value

The endpoint parameter controls whether the stop value is included in the output array. This is crucial for certain mathematical operations and periodic functions.

# With endpoint (default)
with_endpoint = np.linspace(0, 10, 5, endpoint=True)
print("With endpoint:", with_endpoint)
# Output: [ 0.   2.5  5.   7.5 10. ]

# Without endpoint
without_endpoint = np.linspace(0, 10, 5, endpoint=False)
print("Without endpoint:", without_endpoint)
# Output: [0. 2. 4. 6. 8.]

Excluding the endpoint is particularly useful when creating periodic signals or working with circular data:

# Creating angles for a full circle (2π radians)
# We want points that don't duplicate 0 and 2π
angles = np.linspace(0, 2*np.pi, 8, endpoint=False)
print("Angles (radians):", angles)
# Output: [0.    0.785 1.571 2.356 3.142 3.927 4.712 5.498]

# Calculate sine values
sine_values = np.sin(angles)
print("Sine values:", sine_values)

Retrieving Step Size with retstep

The retstep parameter returns a tuple containing both the array and the calculated step size. This is valuable when you need to know the exact spacing for further calculations.

# Get both array and step size
arr, step = np.linspace(0, 100, 11, retstep=True)
print("Array:", arr)
print(f"Step size: {step}")
# Step size: 10.0

# Practical use case: creating custom tick marks
x_values, spacing = np.linspace(0, 1, 6, retstep=True)
print(f"Plotting {len(x_values)} points with spacing {spacing:.3f}")
# Plotting 6 points with spacing 0.200

This feature eliminates manual calculation and ensures precision:

# Without retstep, you'd need to calculate manually
start, stop, num = 0, 100, 11
manual_step = (stop - start) / (num - 1)
print(f"Manual calculation: {manual_step}")  # 10.0

# retstep is cleaner and less error-prone
arr, step = np.linspace(start, stop, num, retstep=True)
print(f"Using retstep: {step}")  # 10.0

Data Type Control with dtype

The dtype parameter specifies the output array’s data type. By default, np.linspace() returns float64 arrays, but you can optimize memory usage or ensure compatibility with other operations.

# Default dtype (float64)
default = np.linspace(0, 10, 5)
print(f"Default dtype: {default.dtype}")  # float64

# Explicit float32 for memory efficiency
float32_arr = np.linspace(0, 10, 5, dtype=np.float32)
print(f"Float32 dtype: {float32_arr.dtype}")  # float32
print(f"Memory usage: {float32_arr.nbytes} bytes")  # 20 bytes vs 40 for float64

# Integer dtype (values are rounded)
int_arr = np.linspace(0, 10, 5, dtype=int)
print("Integer array:", int_arr)
# Output: [ 0  2  5  7 10]

Be cautious with integer dtypes as they truncate decimal values:

# Potential precision loss
precise = np.linspace(0, 10, 6)
print("Float:", precise)
# Output: [ 0.  2.  4.  6.  8. 10.]

integer = np.linspace(0, 10, 6, dtype=int)
print("Integer:", integer)
# Output: [ 0  2  4  6  8 10]
# Same result here, but not always guaranteed

Practical Applications: Plotting and Visualization

np.linspace() excels at generating x-coordinates for smooth function plots:

import matplotlib.pyplot as plt

# Create smooth curve with 100 points
x = np.linspace(-2*np.pi, 2*np.pi, 100)
y = np.sin(x) * np.exp(-x**2/10)

# Compare with fewer points
x_sparse = np.linspace(-2*np.pi, 2*np.pi, 20)
y_sparse = np.sin(x_sparse) * np.exp(-x_sparse**2/10)

plt.figure(figsize=(10, 4))
plt.plot(x, y, label='100 points (smooth)', linewidth=2)
plt.plot(x_sparse, y_sparse, 'o-', label='20 points (sparse)', alpha=0.7)
plt.legend()
plt.grid(True)
plt.title('Impact of Point Density on Curve Smoothness')

Creating logarithmic scales for specific ranges:

# Linear space in log domain
log_space = np.linspace(np.log10(1), np.log10(1000), 50)
actual_values = 10**log_space

print(f"First 5 values: {actual_values[:5]}")
# Creates logarithmically spaced values between 1 and 1000

Multidimensional Arrays and Meshgrids

Use np.linspace() with np.meshgrid() to create coordinate matrices for 2D/3D plotting:

# Create 2D grid
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)

# Calculate function values over the grid
Z = np.sin(np.sqrt(X**2 + Y**2))

print(f"Grid shape: {X.shape}")  # (50, 50)
print(f"Z shape: {Z.shape}")     # (50, 50)

# This creates a 50x50 grid for contour plots or 3D surfaces

Practical example for numerical integration:

# Trapezoidal integration
def integrate_function(func, start, stop, num_points):
    x, dx = np.linspace(start, stop, num_points, retstep=True)
    y = func(x)
    # Trapezoidal rule
    integral = np.trapz(y, dx=dx)
    return integral

# Integrate sin(x) from 0 to π (should be ~2)
result = integrate_function(np.sin, 0, np.pi, 1000)
print(f"Integral of sin(x) from 0 to π: {result:.6f}")
# Output: ~2.000000

Performance Considerations and Alternatives

np.linspace() is computationally efficient but understanding when to use alternatives matters:

import time

# np.linspace vs np.arange for large arrays
start_time = time.time()
linspace_arr = np.linspace(0, 1000000, 1000000)
linspace_time = time.time() - start_time

start_time = time.time()
arange_arr = np.arange(0, 1000000, 1)
arange_time = time.time() - start_time

print(f"linspace: {linspace_time:.4f}s")
print(f"arange: {arange_time:.4f}s")

For logarithmic spacing, use np.logspace() instead:

# Don't do this:
linear = np.linspace(0, 3, 50)
log_manual = 10**linear

# Do this instead:
log_proper = np.logspace(0, 3, 50)  # 10^0 to 10^3

print(np.allclose(log_manual, log_proper))  # True

Common Pitfalls and Solutions

Pitfall 1: Forgetting endpoint behavior

# Expecting 10 intervals but getting 11 points
arr = np.linspace(0, 10, 10)  # Creates 10 points, not 10 intervals
print(len(arr))  # 10
print(arr)  # Spacing is ~1.11, not 1.0

# Solution: Use num+1 for n intervals
arr_correct = np.linspace(0, 10, 11)  # 11 points = 10 intervals of 1.0

Pitfall 2: Float precision with large ranges

# Large range with many points
large_range = np.linspace(0, 1e10, 1000000)
step = large_range[1] - large_range[0]
print(f"Step size: {step}")  # May have precision issues

# Verify uniformity
differences = np.diff(large_range)
print(f"Step variation: {differences.std()}")  # Should be close to 0

The key advantage of np.linspace() is predictable output size and automatic spacing calculation, making it the go-to function for mathematical operations requiring precise point distributions across intervals.

Liked this? There's more.

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