Table of Contents
ToggleIn 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.
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.
In the following example, modifying copied_array does not affect original_array, demonstrating the independence of the two arrays −
# Open Compiler
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)
Output:
Following is the output obtained −
Original Array: [1 2 3]
Copied Array: [10 2 3]
In the context of NumPy arrays, the difference between shallow and deep copies is important for understanding how data is handled when copied.
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.
In this example, modifying shallow_copy also modifies original_array because they share the same underlying data −
# Open Compiler
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)
Output:
This will produce the following result −
Original Array:
[[100 2 3]
[ 4 5 6]]
Shallow Copy:
[[100 2 3]
[ 4 5 6]]
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.
In this case, modifying deep_copy does not affect original_array, demonstrating the independence of the two arrays −
# Open Compiler
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)
Output:
Following is the output of the above code −
Original Array:
[[1 2 3]
[4 5 6]]
Deep Copy:
[[100 2 3]
[ 4 5 6]]
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.
In the example below, sub_array is a completely independent array due to the use of copy() function −
# Open Compiler
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)
Output:
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]]
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.
In this example, modifying view_array directly affects original_array, showing that they share the same data −
# Open Compiler
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)
Output:
After executing the above code, we get the following output −
Original Array after view modification: [30 2 3]
View Array: [30 2]
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.
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.
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 −
# Open Compiler
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)
Output:
The result produced is as follows −
Original Array:
[1 2 3 4 5]
View Array (Sliced):
[2 3 4]
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.
Here, reshaped_array is a view of original_array, simply presented in a “2×3” format. The data remains the same, and modifying the reshaped_array will also modify the original_array −
# Open Compiler
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)
Output:
We get the output as shown below −
Original Array: [1 2 3 4 5 6]
Reshaped Array (View):
[[1 2 3]
[4 5 6]]
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.
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 −
# Open Compiler
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)
Output:
Following is the output obtained −
Original Array:
[[1 2 3]
[4 5 6]]
Transposed Array (View):
[[1 4]
[2 5]
[3 6]]
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.
When you create an array, its base attribute will be None because it is the original array −
# Open Compiler
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)
Output:
This will produce the following result −
Base of original array: None
When you create a view of an array (for example, by slicing), the base attribute of the view will point to the original array −
# Open Compiler
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)
Output:
Following is the output of the above code −
Base of view array: [10 20 30 40 50]
If you create a copy of an array, the base attribute will be None, indicating that the copied array is independent of the original −
# Open Compiler
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)
Output:
Following is the output of the above code −
Base of copy array: None
Key Takeaway: Master copies and views in NumPy with Vista Academy!
