NumPy - Copies & Views



In NumPy, when you perform operations on arrays, the result might be a copy of the original data or just a view of the original data. Understanding the difference between these two is important for efficient memory management and avoiding unintended side effects in your code.

Creating Copies in NumPy

We can create a copy of an array explicitly in NumPy using the copy() function. This function generates a new array and copies the data from the original array into this new array.

When you create a copy of an array in NumPy, the data is fully duplicated. This means that changes made to the copy do not affect the original array, and vice versa. Copies are useful when you need to work with a modified version of an array without altering the original data.

Example

In the following example, modifying copied_array does not affect original_array, demonstrating the independence of the two arrays −

import numpy as np

# Original array
original_array = np.array([1, 2, 3])

# Creating a copy
copied_array = original_array.copy()

# Modifying the copy
copied_array[0] = 10

print("Original Array:", original_array)  
print("Copied Array:", copied_array)     

Following is the output obtained −

Original Array: [1 2 3]
Copied Array: [10  2  3]

Shallow Copy Vs. Deep Copy

In the context of NumPy arrays, the difference between shallow and deep copies is important for understanding how data is handled when copied.

Shallow Copy

A shallow copy of an array creates a new array object, but it does not create copies of the elements contained within the original array if those elements themselves are arrays or other complex objects.

Instead, the new array still references the same elements as the original array. This means that changes to the contents of the elements will affect both the original and the copied array.

  • Array-Level Copy − In the case of NumPy arrays, a shallow copy means that while the top-level array object is duplicated, the underlying data buffer is not copied. The new array is simply a new view of the same data.
  • Usage − Shallow copies are useful when you need a new array object but want to avoid the overhead of duplicating large amounts of data.

Example

In this example, modifying shallow_copy also modifies original_array because they share the same underlying data −

import numpy as np

# Original array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Shallow copy
shallow_copy = original_array.view()

# Modify an element in the shallow copy
shallow_copy[0, 0] = 100

print("Original Array:")
print(original_array)

print("\nShallow Copy:")
print(shallow_copy)    

This will produce the following result −

Original Array:
[[100   2   3]
 [  4   5   6]]

Shallow Copy:
[[100   2   3]
 [  4   5   6]]

Deep Copy

A deep copy, on the other hand, creates a new array object along with copies of all the data it contains. This means that any changes made to the new array will not affect the original array, and vice versa. The data in the new array is completely independent of the data in the original array.

  • Full Duplication − In the context of NumPy, a deep copy involves duplicating the entire data buffer of the array, ensuring that the new array is entirely separate from the original.
  • Usage − Deep copies are important when you need to work with data independently of the original array, especially when the data may be modified in a way that should not impact the original.

Example

In this case, modifying deep_copy does not affect original_array demonstrating the independence of the two arrays −

import numpy as np

# Original array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Deep copy
deep_copy = original_array.copy()

# Modify an element in the deep copy
deep_copy[0, 0] = 100

print("Original Array:")
print(original_array)

print("\nDeep Copy:")
print(deep_copy)   

Following is the output of the above code −

Original Array:
[[1 2 3]
[4 5 6]]

Deep Copy:
[[100   2   3]
 [  4   5   6]]

Copying Subarrays

To avoid modifying the original array when working with a subarray, you should create a copy of the subarray. This is useful when you need to manipulate or analyze the subarray independently of the original data.

A subarray is simply a portion of an existing NumPy array. You can extract subarrays using slicing techniques.

For example, if you have a 2D array, you can extract a smaller 2D subarray by slicing along its rows and columns. However, by default, slicing creates a view of the original array, not a separate copy. This means that changes to the subarray will also affect the original array unless you explicitly create a copy.

Example

In the example below, sub_array is a completely independent array due to the use of copy() function −

import numpy as np

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

# Creating a copy of the subarray
sub_array = original_array[0:2].copy()
sub_array[0] = 20

print("Original Array after subarray copy:", original_array)  
print("Subarray:", sub_array)                                 

The output obtained is as shown below −

Original Array after subarray copy: 
[[1 2 3]
 [4 5 6]
 [7 8 9]]
Subarray: 
[[20 20 20]
 [ 4  5  6]]

Creating Views in NumPy

Views are created when you slice an array or perform certain operations like reshaping. The data is not copied; instead, the new array is just a different way of viewing the original data.

In other words, a view is a new array object that looks at the same data as the original array. This means that if you modify the view, the changes will be reflected in the original array, and vice versa.

Example

In this example, modifying view_array directly affects original_array, showing that they share the same data −

import numpy as np

# Original array
original_array = np.array([1, 2, 3])

view_array = original_array[0:2]

# Modifying the view
view_array[0] = 30

print("Original Array after view modification:", original_array) 
print("View Array:", view_array)                                   

After executing the above code, we get the following output −

Original Array after view modification: [30  2  3]
View Array: [30  2]

When Views are Returned?

Not all slicing or operations result in a view. If the memory layout of the array changes, NumPy might return a copy instead of a view.

Views from Slicing

The most common scenario where views are returned in NumPy is when you slice an array. Slicing is a way to extract a portion of an array by specifying a range of indices. Instead of creating a new array with its own data, NumPy returns a view, meaning the sliced array shares the same data as the original array.

Example

In this example, view_array is a view of original_array. The data is not copied, and both arrays share the same underlying memory. This means that any changes made to "view_array" will also affect "original_array" −

import numpy as np

# Original array
original_array = np.array([1, 2, 3, 4, 5])

# Creating a view by slicing the original array
view_array = original_array[1:4]

print("Original Array:")
print(original_array)

print("\nView Array (Sliced):")
print(view_array)

The result produced is as follows −

Original Array:
[1 2 3 4 5]

View Array (Sliced):
[2 3 4]

Views from Reshaping

Another common scenario where views are returned is when you reshape an array. Reshaping changes the shape of the array (i.e., the number of elements in each dimension) without altering the underlying data. When possible, NumPy returns a view of the original array in the new shape.

Example

Here, reshaped_array is a view of original_array, simply presented in a "2x3" format. The data remains the same, and modifying the "reshaped_array" will also modify the "original_array" −

import numpy as np

# Original 1D array
original_array = np.array([1, 2, 3, 4, 5, 6])

# Reshaping the array into a 2x3 matrix
reshaped_array = original_array.reshape(2, 3)

print("Original Array:")
print(original_array)

print("\nReshaped Array (View):")
print(reshaped_array)

We get the output as shown below −

Original Array:[1 2 3 4 5 6]
Reshaped Array (View):
[[1 2 3]
 [4 5 6]]

Views from Transposing

Transposing an array involves flipping it over its diagonal, converting rows to columns and vice versa. When you transpose an array using functions like np.transpose() or the .T attribute, NumPy returns a view, not a copy, whenever possible.

Example

In this case, transposed_array is a view of original_array, but with the axes swapped. The underlying data remains the same, and changes to "transposed_array" will reflect in "original_array" −

import numpy as np

# Original 2D array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Transposing the array
transposed_array = original_array.T

print("Original Array:")
print(original_array)

print("\nTransposed Array (View):")
print(transposed_array)

Following is the output obtained −

Original Array:
[[1 2 3]
 [4 5 6]]

Transposed Array (View):
[[1 4]
 [2 5]
 [3 6]]

The Base Attribute

In NumPy, the base attribute of an array examines whether the array is a view or a copy of another array. It is a reference to the original array from which the current array was derived.

If the current array is a view of another array, "base" will point to that original array. If the current array is not a view (i.e., it is either the original array or a deep copy), "base" will be None.

Example: Base Attribute of an Original Array

When you create an array, its base attribute will be None because it is the original array −

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Checking the base attribute
print("Base of original array:", original_array.base)

This will produce the following result −

Base of original array: None

Example: Base Attribute of a View

When you create a view of an array (for example, by slicing), the base attribute of the view will point to the original array −

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Creating a view of the original array
view_array = original_array[1:4]

# Checking the base attribute
print("Base of view array:", view_array.base)

Following is the output of the above code −

Base of view array: [10 20 30 40 50]

Example: Base Attribute of a Copy

If you create a copy of an array, the base attribute will be None, indicating that the copied array is independent of the original −

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Creating a copy of the original array
copy_array = original_array.copy()

# Checking the base attribute
print("Base of copy array:", copy_array.base)

Following is the output of the above code −

Base of copy array: None
Advertisements