NumPy - np.take() - Select Elements by Index

import numpy as np

Key Insights

  • np.take() extracts elements from an array using integer indices along a specified axis, offering more flexibility than basic indexing for non-contiguous selections
  • The function supports negative indices, wrapping with mode parameter (‘raise’, ‘wrap’, ‘clip’), and works efficiently with both 1D and multidimensional arrays
  • Unlike fancy indexing with brackets, np.take() provides explicit axis control and consistent behavior across array dimensions, making it ideal for programmatic element selection

Basic Usage and Syntax

np.take() selects elements from an array by their indices. The basic syntax is np.take(array, indices, axis=None). When axis=None, the array is flattened before indexing.

import numpy as np

# 1D array selection
arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4]
result = np.take(arr, indices)
print(result)  # [10 30 50]

# Negative indices work
result = np.take(arr, [-1, -2])
print(result)  # [50 40]

# Duplicate indices allowed
result = np.take(arr, [1, 1, 3, 1])
print(result)  # [20 20 40 20]

The function returns a new array containing the selected elements. Unlike slicing, which creates views, np.take() always returns a copy.

Working with Multidimensional Arrays

Specify the axis parameter to extract elements along a particular dimension. Without axis, the array flattens first.

# 2D array
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

# Flatten and take (axis=None, default)
result = np.take(matrix, [0, 4, 8])
print(result)  # [1 5 9] - diagonal elements

# Take along axis 0 (rows)
result = np.take(matrix, [0, 2], axis=0)
print(result)
# [[1 2 3]
#  [7 8 9]]

# Take along axis 1 (columns)
result = np.take(matrix, [0, 2], axis=1)
print(result)
# [[1 3]
#  [4 6]
#  [7 9]]

For 3D arrays, you can select along any of the three axes:

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

# Select specific depth slices (axis=0)
result = np.take(arr_3d, [1], axis=0)
print(result.shape)  # (1, 3, 4)

# Select specific rows (axis=1)
result = np.take(arr_3d, [0, 2], axis=1)
print(result.shape)  # (2, 2, 4)

# Select specific columns (axis=2)
result = np.take(arr_3d, [1, 3], axis=2)
print(result.shape)  # (2, 3, 2)

Index Boundary Handling with Mode Parameter

The mode parameter controls behavior when indices fall outside array bounds: ‘raise’ (default), ‘wrap’, and ‘clip’.

arr = np.array([10, 20, 30, 40, 50])

# mode='raise' - raises IndexError for out-of-bounds
try:
    result = np.take(arr, [0, 10], mode='raise')
except IndexError as e:
    print(f"Error: {e}")

# mode='wrap' - wraps around using modulo
result = np.take(arr, [0, 5, 6, -6], mode='wrap')
print(result)  # [10 10 20 50]
# Index 5 wraps to 0, index 6 to 1, index -6 to 4

# mode='clip' - clips to valid range [0, len-1]
result = np.take(arr, [0, 10, -10], mode='clip')
print(result)  # [10 50 10]
# Index 10 clips to 4, index -10 clips to 0

This is particularly useful when working with circular buffers or when you want graceful degradation instead of exceptions:

# Circular buffer simulation
buffer = np.array([1, 2, 3, 4, 5])
read_positions = np.array([0, 3, 7, 11])  # Some positions beyond buffer

# Wrap mode treats buffer as circular
values = np.take(buffer, read_positions, mode='wrap')
print(values)  # [1 4 3 2]

Performance Comparison with Alternative Methods

np.take() competes with fancy indexing and np.ix_(). Each has different performance characteristics.

import time

arr = np.arange(1000000)
indices = np.random.randint(0, 1000000, size=100000)

# Method 1: np.take()
start = time.perf_counter()
result1 = np.take(arr, indices)
time1 = time.perf_counter() - start

# Method 2: Fancy indexing
start = time.perf_counter()
result2 = arr[indices]
time2 = time.perf_counter() - start

# Method 3: List comprehension (slowest)
start = time.perf_counter()
result3 = np.array([arr[i] for i in indices])
time3 = time.perf_counter() - start

print(f"np.take(): {time1:.4f}s")
print(f"Fancy indexing: {time2:.4f}s")
print(f"List comp: {time3:.4f}s")
# Typical results:
# np.take(): 0.0012s
# Fancy indexing: 0.0011s
# List comp: 0.0450s

For multidimensional arrays with axis specification, np.take() provides clearer intent:

matrix = np.random.rand(1000, 1000)
col_indices = [0, 100, 500, 999]

# Clear and explicit
result1 = np.take(matrix, col_indices, axis=1)

# Less clear - requires understanding of advanced indexing
result2 = matrix[:, col_indices]

# Verify they're equivalent
print(np.array_equal(result1, result2))  # True

Practical Applications

Reordering Data Based on Sort Indices

# Sort one array and apply same order to related arrays
scores = np.array([85, 92, 78, 95, 88])
names = np.array(['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'])

# Get indices that would sort scores
sort_indices = np.argsort(scores)[::-1]  # Descending

sorted_scores = np.take(scores, sort_indices)
sorted_names = np.take(names, sort_indices)

for name, score in zip(sorted_names, sorted_scores):
    print(f"{name}: {score}")
# Diana: 95
# Bob: 92
# Eve: 88
# Alice: 85
# Charlie: 78

Sampling from Probability Distributions

# Weighted random sampling
items = np.array(['A', 'B', 'C', 'D', 'E'])
probabilities = np.array([0.1, 0.2, 0.3, 0.25, 0.15])

# Generate random indices based on probabilities
sample_size = 1000
random_indices = np.random.choice(len(items), size=sample_size, p=probabilities)

# Extract samples
samples = np.take(items, random_indices)

# Verify distribution
unique, counts = np.unique(samples, return_counts=True)
for item, count in zip(unique, counts):
    print(f"{item}: {count/sample_size:.2%}")

Extracting Diagonal and Anti-diagonal Elements

# Large matrix
size = 5
matrix = np.arange(size * size).reshape(size, size)

# Main diagonal using np.take with flattened indices
diag_indices = np.arange(size) * (size + 1)
diagonal = np.take(matrix, diag_indices)
print("Diagonal:", diagonal)  # [0 6 12 18 24]

# Anti-diagonal
anti_diag_indices = np.arange(size) * (size - 1) + (size - 1)
anti_diagonal = np.take(matrix, anti_diag_indices)
print("Anti-diagonal:", anti_diagonal)  # [4 8 12 16 20]

Time Series Windowing

# Extract sliding windows from time series
time_series = np.arange(100)
window_size = 5
stride = 2

# Create indices for each window
num_windows = (len(time_series) - window_size) // stride + 1
indices = np.arange(window_size)[None, :] + np.arange(num_windows)[:, None] * stride

# Extract all windows at once
windows = np.take(time_series, indices)
print(windows.shape)  # (48, 5)
print(windows[:3])
# [[0 1 2 3 4]
#  [2 3 4 5 6]
#  [4 5 6 7 8]]

Edge Cases and Gotchas

Empty index arrays return empty results with preserved shape:

arr = np.array([[1, 2], [3, 4]])
result = np.take(arr, [], axis=0)
print(result.shape)  # (0, 2)

Boolean arrays don’t work directly with np.take(). Convert them first:

arr = np.array([10, 20, 30, 40, 50])
mask = arr > 25

# Wrong - boolean indexing with brackets
result1 = arr[mask]  # [30 40 50]

# Convert boolean mask to indices for np.take()
indices = np.where(mask)[0]
result2 = np.take(arr, indices)  # [30 40 50]

np.take() always returns a copy, which matters for memory-intensive operations. Use views when possible if you don’t need to modify data.

Liked this? There's more.

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