Why does changing a NumPy slice also change the original array?
Short answer
A basic slice of a NumPy array is a view that shares memory with the original, so writing to it changes both. Use .copy() when you need an independent array, and note that fancy indexing already returns a copy.
The short version
A plain slice of a NumPy array is a view onto the same memory, not a separate array, so assigning into the slice also changes the original. This is deliberate and makes slicing fast and memory-cheap, but it surprises people. Call .copy() when you need an independent array. Fancy indexing (with a list or a boolean mask) already returns a copy, so the behaviour is not uniform.
Seeing the shared memory
import numpy as np
a = np.array([1, 2, 3, 4])
s = a[1:3] # a view onto a's memory
s[0] = 99
print(a) # [ 1 99 3 4] <- the original changed
Writing s[0] = 99 changed a, because s is a window onto the same underlying data, not a fresh array.
Making an independent copy
If you want to work on a slice without touching the original, copy it explicitly.
b = a[1:3].copy()
b[0] = -1
print(a) # unchanged by edits to b
Now b has its own memory, so changes stay local.
Why NumPy does this
Views make slicing free: no data is duplicated, so slicing a huge array costs almost nothing. That efficiency is central to NumPy's speed. The price is that a slice is not a safe scratch copy, so any in-place edit reaches back to the parent. Once you know this, it is predictable rather than surprising.
The inconsistency to remember
Basic slicing returns a view, but fancy indexing returns a copy.
view = a[0:2] # view, shares memory
copy1 = a[[0, 1]] # copy, independent (fancy indexing)
copy2 = a[a > 1] # copy, independent (boolean mask)
So a[0:2] = 0 changes a, but assigning into the result of a[[0, 1]] would not. This split trips people up, so when in doubt, check with .base: a view's .base points at the original, while a copy's .base is None.
The link to the pandas warning
This is the same idea behind the SettingWithCopyWarning in pandas, where it is unclear whether you are writing to a view or a copy. The safe habit in both libraries is the same: if you intend to modify a subset independently, copy it first, and if you intend to modify the original, index into the original directly rather than through an intermediate slice.
A practical rule
Treat a slice as a live window on the original, not a snapshot. When you extract a piece to transform separately, add .copy() and the ambiguity disappears.