Volver a la lista

Vistas de arreglos y copia superficial: cuidado con la propagación de cambios.

Aprenda por qué NumPy devuelve vistas al realizar segmentación (slicing), las condiciones en las que se propagan las modificaciones y cómo evitarlo con `copy()`.

Intermedio
|
10min
|
Verificado (2026-07)
vista de arraycopia superficialcopia profundapropagación de cambiosmemoria de NumPy
Progreso0/17 (0%)

Vistas de arreglos y copias superficiales: precaución con la propagación de cambios

Al finalizar este tema

Comprenderás la diferencia entre vistas (view) y copias (copy) en NumPy, podrás explicar por qué el slicing devuelve una vista y evitarás la propagación no intencionada de cambios.


Comportamiento sorprendente

python
import numpy as np
original = np.array([1, 2, 3, 4, 5])
sliced = original[1:4]
sliced[0] = 99
print(sliced) # [99 3 4]
print(original) # [ 1 99 3 4 5] ← ¡También cambia el original!

sliced solo se modificó, pero original también cambió. Esto no es un error, sino una característica de diseño. El slicing de NumPy no copia los datos, sino que devuelve una vista (view) que comparte la misma memoria.


¿Qué es una vista (view)?

Una vista es ver los mismos datos desde una perspectiva diferente.

text
Memoria: [1] [2] [3] [4] [5]
          ↑   ↑   ↑   ↑   ↑
original: [0] [1] [2] [3] [4]

sliced = original[1:4]
          ↑   ↑   ↑
sliced:  [0] [1] [2]   ← ¡Apunta a la misma memoria!

Al no copiarse los datos:

  • Ahorro de memoria: Una vista de un array de 1 GB no requiere memoria adicional.
  • Rapidez: El tiempo de copia es cero.
  • Propagación de cambios: Si se modifica la vista, también se modifica el original (¡atención!).

Cuándo se crea una vista

python
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 1. Slicing → vista
v1 = arr[2:5] # Vista
v1.base is arr # True (es una vista de arr)
# 2. reshape → vista cuando es posible
v2 = arr.reshape(3, 3) # Vista
v2.base is arr # True
# 3. Transposición → vista
mat = np.array([[1, 2], [3, 4]])
v3 = mat.T # Vista
v3.base is mat # True
# 4. Conversión de dtype con el mismo tamaño → vista
v4 = arr.view(np.int64) # Vista

Cómo verificar las vistas

python
arr = np.array([1, 2, 3, 4, 5])
sliced = arr[1:4]
copied = arr[1:4].copy()
print(sliced.base is arr) # True: vista
print(copied.base is None) # True: copia independiente sin base

Si base es None, el propio objeto es el dueño de los datos (una copia); si no es None, es una vista de otro array.


Cuándo se produce una copia

python
arr = np.array([1, 2, 3, 4, 5])
# 1. Indexación avanzada → copia
c1 = arr[[0, 2, 4]] # ¡Copia!
c1[0] = 99
print(arr) # [1 2 3 4 5] — original sin cambios
# 2. Indexación booleana → copia
c2 = arr[arr > 3] # ¡Copia!
c2[0] = 99
print(arr) # [1 2 3 4 5] — original sin cambios
# 3. Copia explícita
c3 = arr.copy() # Copia
c3[0] = 99
print(arr) # [1 2 3 4 5] — original sin cambios
OperaciónResultado
arr[2:5] (slicing)Vista
arr[[0,2,4]] (fancy indexing)Copia
arr[arr > 3] (indexación booleana)Copia
arr.reshape(...)Vista (si es posible)
arr.copy()Copia
arr.flatten()Copia
arr.ravel()Vista (si es posible)

Diferencias con pandas

python
import pandas as pd
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
# Slicing en pandas
subset = df[df["A"] > 1]
subset["B"] = 99
# Puede producirse SettingWithCopyWarning

El comportamiento de vista/copia en pandas varía según el contexto, lo que dificulta su predicción. Por ello existe el SettingWithCopyWarning. La forma segura es:

python
# Copia inequívoca
subset = df[df["A"] > 1].copy()
subset["B"] = 99 # Original sin cambios y sin advertencia
# Si se desea modificar el original
df.loc[df["A"] > 1, "B"] = 99 # Modificación directa

Errores comunes en la práctica

Modificación del objeto original dentro de una función

python
def normalize(data):
# Mal: si data es una vista, modifica el original
data -= data.mean()
return data
original = np.array([10.0, 20.0, 30.0])
result = normalize(original[0:3]) # ¡Slicing = vista!
print(original) # [−10. 0. 10.] ← ¡El original ha cambiado!
# Bien: trabaja sobre una copia
def normalize_safe(data):
result = data.copy()
result -= result.mean()
return result

Uso de vistas en grandes volúmenes de datos

python
# Analiza solo una parte de 10 GB de datos
huge_data = np.memmap("data.bin", dtype=np.float64, shape=(1_000_000_000,))
# Vista: 0 memoria adicional
chunk = huge_data[1000:2000] # Vista: no copia memoria
# Copia: asigna memoria
chunk_copy = huge_data[1000:2000].copy() # Copia: asigna 8 KB

En conjuntos de datos de gran tamaño, se utilizan intencionalmente las vistas para ahorrar memoria. Se llama a copy() solo cuando es necesario realizar modificaciones.


Comparación con listas de Python

El slicing de las listas de Python, a diferencia de NumPy, siempre devuelve una copia.

python
# Lista de Python: slicing = siempre copia superficial
py_list = [1, 2, 3, 4, 5]
sliced = py_list[1:4]
sliced[0] = 99
print(py_list) # [1, 2, 3, 4, 5] — ¡original sin cambios!
# NumPy: slicing = vista compartida
np_arr = np.array([1, 2, 3, 4, 5])
sliced = np_arr[1:4]
sliced[0] = 99
print(np_arr) # [ 1 99 3 4 5] — ¡original modificado!

Si no se comprende esta diferencia, pueden producirse errores graves al migrar a NumPy. El código que era seguro con las listas de Python puede modificar el objeto original en NumPy.

Copia superficial vs. copia profunda

python
import copy
nested = [[1, 2], [3, 4]]
# Copia superficial: copia la lista exterior y comparte las interiores
shallow = copy.copy(nested)
shallow[0][0] = 99
print(nested) # [[99, 2], [3, 4]] — ¡la lista interior cambia!
# Copia profunda: copia también todos los objetos anidados
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
deep[0][0] = 99
print(nested) # [[1, 2], [3, 4]] — original sin cambios

La función .copy() de NumPy copia la totalidad de los datos, por lo que tiene el mismo efecto que una copia profunda. Dado que los arrays de NumPy no contienen otros objetos de Python en su interior (son datos numéricos puros), la distinción entre copia superficial y profunda carece de sentido, y .copy() es suficiente.


Diagrama de flujo de decisión

text
¿Necesitas el resultado de una operación con arrays?
├── ¿Se puede modificar el original?
│   ├── Sí → usa una vista (slicing directo)
│   └── No → llama a .copy()
└── ¿Hay memoria suficiente?
    ├── Sí → usa .copy() por seguridad
    └── No → usa una vista y evita modificarla

Resumen clave

ConceptoResumen
Vista (View)Comparte la misma memoria. Las modificaciones se propagan al original
Copia (Copy)Memoria independiente. Las modificaciones no afectan al original
SlicingDevuelve una vista (NumPy)
Indexación avanzada/booleanaDevuelve una copia
.baseSi es None, devuelve una copia; de lo contrario, una vista
.copy()Copia profunda explícita

Las vistas en NumPy están diseñadas para la optimización del rendimiento. Permiten manejar datos de varios GB sin necesidad de copiarlos, pero conllevan el efecto secundario de que "si se modifica, también cambia el original". La regla es simple: slicing = vista, indexación avanzada/booleana = copia. Si no estás seguro, utiliza .copy().

Desde pandas 2.0, se ha introducido el modo Copy-on-Write (CoW), que realiza una copia automática al modificar los resultados de un slicing. Se puede activar con pd.options.mode.copy_on_write = True y está previsto que sea el comportamiento predeterminado en el futuro. Sin embargo, NumPy sigue utilizando un modelo explícito de vistas/copias, por lo que es fundamental dominar las reglas de este tema.

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...