NumPy - Insert Elements (np.insert)

import numpy as np

Key Insights

  • np.insert() adds elements at specified positions along any axis without modifying the original array, returning a new array with O(n) complexity due to element shifting
  • The function handles multi-dimensional arrays with axis-specific insertion, broadcasting scalar values across dimensions, and accepting array-like objects for bulk insertions
  • Understanding insertion behavior across axes and memory implications is critical for performance optimization in data manipulation pipelines

Understanding np.insert() Fundamentals

np.insert() inserts values before specified indices along a given axis. Unlike Python lists, NumPy arrays have fixed sizes, so insertion creates a new array rather than modifying in place.

import numpy as np

# Basic 1D insertion
arr = np.array([1, 2, 3, 4, 5])
result = np.insert(arr, 2, 99)
print(result)  # [1 2 99 3 4 5]

# Original array unchanged
print(arr)  # [1 2 3 4 5]

# Multiple insertions at same index
result = np.insert(arr, 2, [99, 100, 101])
print(result)  # [1 2 99 100 101 3 4 5]

The signature is np.insert(arr, obj, values, axis=None). The obj parameter specifies insertion indices, values contains elements to insert, and axis determines the dimension for insertion.

Insertion at Multiple Indices

You can insert different values at multiple positions simultaneously by passing arrays to both obj and values parameters.

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

# Insert at multiple indices
indices = [1, 3, 4]
values = [15, 35, 45]
result = np.insert(arr, indices, values)
print(result)  # [10 15 20 30 35 40 45 50]

# When indices repeat, insertions happen sequentially
result = np.insert(arr, [2, 2, 2], [25, 26, 27])
print(result)  # [10 20 25 26 27 30 40 50]

The insertion order matters when indices repeat. Elements are inserted left-to-right, with each insertion affecting subsequent index positions.

Working with Multi-Dimensional Arrays

The axis parameter controls which dimension receives the insertion. Without specifying axis, the array flattens before insertion.

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

# No axis specified - flattens array
result = np.insert(arr_2d, 5, 99)
print(result)  # [1 2 3 4 5 99 6 7 8 9]

# Insert along axis 0 (rows)
result = np.insert(arr_2d, 1, [10, 11, 12], axis=0)
print(result)
# [[ 1  2  3]
#  [10 11 12]
#  [ 4  5  6]
#  [ 7  8  9]]

# Insert along axis 1 (columns)
result = np.insert(arr_2d, 2, [30, 60, 90], axis=1)
print(result)
# [[ 1  2 30  3]
#  [ 4  5 60  6]
#  [ 7  8 90  9]]

When inserting along an axis, the values array must broadcast correctly against the remaining dimensions.

Broadcasting and Scalar Insertion

NumPy broadcasts scalar values across the appropriate dimension when inserting into multi-dimensional arrays.

arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])

# Scalar broadcasts across columns
result = np.insert(arr_2d, 1, 99, axis=0)
print(result)
# [[ 1  2  3]
#  [99 99 99]
#  [ 4  5  6]]

# Insert multiple rows with broadcasting
result = np.insert(arr_2d, [0, 2], 0, axis=0)
print(result)
# [[0 0 0]
#  [1 2 3]
#  [4 5 6]
#  [0 0 0]]

# Broadcasting with 2D values
arr_3d = np.ones((2, 3, 4))
new_slice = np.full((2, 4), 99)
result = np.insert(arr_3d, 1, new_slice, axis=1)
print(result.shape)  # (2, 4, 4)

Practical Data Manipulation Patterns

Here are common scenarios where np.insert() proves valuable in data processing workflows.

# Adding padding to signal data
signal = np.array([0.5, 0.8, 1.2, 0.9, 0.6])
padded = np.insert(signal, [0, len(signal)], [0, 0])
print(padded)  # [0.  0.5 0.8 1.2 0.9 0.6 0. ]

# Inserting computed values
data = np.array([100, 150, 200, 250])
averages = (data[:-1] + data[1:]) / 2
indices = np.arange(1, len(data))
interpolated = np.insert(data, indices, averages)
print(interpolated)  # [100.  125.  150.  175.  200.  225.  250.]

# Adding metadata columns to structured data
measurements = np.array([[1.2, 3.4],
                         [2.3, 4.5],
                         [3.4, 5.6]])
timestamps = np.array([[0], [1], [2]])
with_metadata = np.insert(measurements, 0, timestamps.flatten(), axis=1)
print(with_metadata)
# [[0.  1.2 3.4]
#  [1.  2.3 4.5]
#  [2.  3.4 5.6]]

Performance Considerations

np.insert() has O(n) time complexity because it must allocate new memory and copy elements. For repeated insertions, consider alternatives.

import time

# Poor performance: repeated insertions
def slow_build():
    arr = np.array([1])
    for i in range(1000):
        arr = np.insert(arr, len(arr), i)
    return arr

# Better: pre-allocate and assign
def fast_build():
    arr = np.empty(1001, dtype=int)
    arr[0] = 1
    for i in range(1000):
        arr[i + 1] = i
    return arr

# Best: use list then convert
def fastest_build():
    lst = [1] + list(range(1000))
    return np.array(lst)

# Timing comparison
start = time.time()
slow_build()
print(f"Repeated insert: {time.time() - start:.4f}s")

start = time.time()
fast_build()
print(f"Pre-allocate: {time.time() - start:.4f}s")

start = time.time()
fastest_build()
print(f"List conversion: {time.time() - start:.4f}s")

For batch operations, np.concatenate() or np.hstack()/np.vstack() often perform better than multiple np.insert() calls.

Edge Cases and Validation

Handle boundary conditions and invalid inputs appropriately in production code.

arr = np.array([1, 2, 3, 4, 5])

# Negative indices work like Python lists
result = np.insert(arr, -1, 99)
print(result)  # [1 2 3 4 99 5]

# Out of bounds indices append to end
result = np.insert(arr, 100, 99)
print(result)  # [1 2 3 4 5 99]

# Empty values array
result = np.insert(arr, 2, [])
print(result)  # [1 2 3 4 5] - no change

# Mismatched dimensions raise errors
arr_2d = np.array([[1, 2], [3, 4]])
try:
    result = np.insert(arr_2d, 1, [5, 6, 7], axis=0)
except ValueError as e:
    print(f"Error: {e}")

# Type coercion happens automatically
arr_int = np.array([1, 2, 3])
result = np.insert(arr_int, 1, 2.5)
print(result.dtype)  # int64
print(result)  # [1 2 3] - 2.5 truncated to 2

Integration with Real-World Applications

Time series data manipulation demonstrates practical np.insert() usage patterns.

# Financial data with missing timestamps
timestamps = np.array([0, 1, 3, 4, 7])  # Missing 2, 5, 6
prices = np.array([100.0, 102.5, 98.3, 101.2, 105.8])

# Find gaps and insert NaN placeholders
full_timeline = np.arange(timestamps[-1] + 1)
missing_indices = np.setdiff1d(full_timeline, timestamps)

# Calculate insertion positions
insert_positions = np.searchsorted(timestamps, missing_indices)
filled_prices = np.insert(prices, insert_positions, np.nan)

print("Timestamps:", full_timeline)
print("Prices:", filled_prices)
# [100.   102.5  nan   98.3  101.2  nan   nan  105.8]

# Image processing: add border
image = np.random.randint(0, 255, (4, 4), dtype=np.uint8)
border_value = 0
bordered = np.insert(image, [0, image.shape[0]], border_value, axis=0)
bordered = np.insert(bordered, [0, bordered.shape[1]], border_value, axis=1)
print(f"Original shape: {image.shape}, Bordered: {bordered.shape}")

Understanding np.insert() mechanics enables efficient array manipulation while avoiding common pitfalls around memory allocation and performance degradation in tight loops.

Liked this? There's more.

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