NumPy (Numerical Python) es la biblioteca fundamental para la computación científica en Python. Proporciona soporte para arrays y matrices multidimensionales, junto con una colección de funciones matemáticas de alto nivel para operar con estos arrays de manera eficiente.
En este capítulo, exploraremos los fundamentos de NumPy y aprenderemos a utilizar sus capacidades para manipular datos numéricos de manera eficiente.
Antes de comenzar a trabajar con NumPy, necesitamos instalarlo. La forma más común es utilizar pip, el administrador de paquetes de Python:
# Instalar NumPy
pip install numpy
Si estás utilizando Anaconda, NumPy ya viene incluido. Para verificar la versión instalada, puedes ejecutar:
import numpy as np
print(np.__version__)
np. Esto hace que el código sea más legible y consistente con la comunidad de ciencia de datos.
Comencemos creando nuestro primer array de NumPy:
# Importar NumPy
import numpy as np
# Crear un array simple
arr = np.array([1, 2, 3, 4, 5])
print(arr)
print(type(arr))
A diferencia de las listas de Python, los arrays de NumPy son homogéneos, lo que significa que todos los elementos deben ser del mismo tipo. Esto permite optimizaciones que hacen a NumPy mucho más rápido para operaciones numéricas.
Los arrays son la estructura de datos principal en NumPy. Podemos pensar en ellos como colecciones de elementos del mismo tipo organizados en una estructura de filas y columnas.
Existen múltiples formas de crear arrays en NumPy:
# Desde una lista
arr1 = np.array([1, 2, 3, 4])
print(f"Desde lista: {arr1}")
# Crear un array con ceros
arr2 = np.zeros(5) # Array unidimensional con 5 ceros
print(f"Array de ceros: {arr2}")
# Crear un array con unos
arr3 = np.ones((2, 3)) # Array 2x3 de unos
print(f"Array de unos:\n{arr3}")
# Crear array con valores secuenciales
arr4 = np.arange(0, 10, 2) # Valores de 0 a 10 (exclusivo) con paso 2
print(f"Array con arange: {arr4}")
# Crear array con valores espaciados linealmente
arr5 = np.linspace(0, 1, 5) # 5 valores entre 0 y 1, espaciados uniformemente
print(f"Array con linspace: {arr5}")
# Crear array con valores aleatorios
arr6 = np.random.random((2, 2)) # Array 2x2 con valores aleatorios entre 0 y 1
print(f"Array aleatorio:\n{arr6}")
# Crear matriz identidad
arr7 = np.eye(3) # Matriz identidad 3x3
print(f"Matriz identidad:\n{arr7}")
Los arrays de NumPy pueden tener múltiples dimensiones. Veamos cómo trabajar con ellas:
# Array unidimensional (vector)
vector = np.array([1, 2, 3, 4])
print(f"Vector: {vector}")
print(f"Dimensión: {vector.ndim}")
print(f"Forma: {vector.shape}")
print(f"Tamaño: {vector.size}")
# Array bidimensional (matriz)
matriz = np.array([[1, 2, 3], [4, 5, 6]])
print(f"\nMatriz:\n{matriz}")
print(f"Dimensión: {matriz.ndim}")
print(f"Forma: {matriz.shape}") # (filas, columnas)
print(f"Tamaño: {matriz.size}")
# Array tridimensional (cubo)
cubo = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(f"\nCubo:\n{cubo}")
print(f"Dimensión: {cubo.ndim}")
print(f"Forma: {cubo.shape}") # (planos, filas, columnas)
print(f"Tamaño: {cubo.size}")
shape) de un array es una tupla que indica el tamaño de cada dimensión. Por ejemplo, una forma de (2, 3) indica una matriz con 2 filas y 3 columnas.
NumPy admite diferentes tipos de datos numéricos, lo que permite optimizar el uso de memoria y mejorar el rendimiento:
# Tipos de datos en NumPy
arr_int = np.array([1, 2, 3], dtype=np.int32)
print(f"Array de enteros: {arr_int}, tipo: {arr_int.dtype}")
arr_float = np.array([1.0, 2.5, 3.7], dtype=np.float64)
print(f"Array de flotantes: {arr_float}, tipo: {arr_float.dtype}")
arr_bool = np.array([True, False, True])
print(f"Array de booleanos: {arr_bool}, tipo: {arr_bool.dtype}")
# Cambiar el tipo de datos (conversión)
arr_to_float = np.array([1, 2, 3], dtype=np.float32)
print(f"Enteros a flotantes: {arr_to_float}, tipo: {arr_to_float.dtype}")
arr_to_int = np.array([1.9, 2.8, 3.1], dtype=np.int64)
print(f"Flotantes a enteros: {arr_to_int}, tipo: {arr_to_int.dtype}") # Nótese el truncamiento
np.round() antes de la conversión.
NumPy proporciona varias formas de cambiar la estructura de un array sin modificar sus datos:
# Cambiar forma con reshape
arr = np.arange(12) # Array de 0 a 11
print(f"Array original: {arr}")
# Cambiar a matriz 3x4
matriz = arr.reshape(3, 4)
print(f"Matriz 3x4:\n{matriz}")
# Cambiar a matriz 2x2x3
cubo = arr.reshape(2, 2, 3)
print(f"Cubo 2x2x3:\n{cubo}")
# Dimensión desconocida (NumPy la calcula)
matriz_auto = arr.reshape(3, -1) # 3 filas, columnas calculadas automáticamente
print(f"Matriz 3x?:\n{matriz_auto}")
# Aplanar un array multidimensional
arr_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
arr_flat = arr_3d.flatten() # Crea una copia
print(f"Array aplanado: {arr_flat}")
# Alternativa a flatten (vista, no copia)
arr_ravel = arr_3d.ravel()
print(f"Array con ravel: {arr_ravel}")
reshape reorganiza los elementos de un array sin cambiar su contenido. La cantidad total de elementos debe permanecer igual antes y después del cambio de forma.
Similar a las listas de Python, los arrays de NumPy admiten indexación y slicing, pero con capacidades avanzadas para trabajar con arrays multidimensionales.
En NumPy, la indexación y el slicing son herramientas poderosas para acceder y manipular subconjuntos de datos. La siguiente imagen ilustra diferentes operaciones de slicing en un array multidimensional, donde cada color representa un tipo diferente de selección:
# Array unidimensional
arr = np.array([10, 20, 30, 40, 50])
# Acceso a elementos individuales
print(f"Primer elemento: {arr[0]}")
print(f"Último elemento: {arr[-1]}")
# Array bidimensional
matriz = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\nMatriz:\n{matriz}")
# Acceso a elementos en matriz
print(f"Elemento en fila 1, columna 2: {matriz[1, 2]}") # Equivalente a matriz[1][2]
print(f"Última fila: {matriz[-1]}") # Fila completa
El slicing en NumPy sigue la sintaxis inicio:fin:paso y funciona en todas las dimensiones:
# Slicing en array unidimensional
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# Sintaxis: array[inicio:fin:paso]
print(f"Elementos del 2 al 5: {arr[2:6]}")
print(f"Elementos hasta el 4: {arr[:5]}")
print(f"Elementos desde el 6: {arr[6:]}")
print(f"Elementos con paso 2: {arr[1:8:2]}")
print(f"Array invertido: {arr[::-1]}")
# Slicing en array bidimensional
matriz = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# Seleccionar submatriz (filas 0-1, columnas 1-3)
submatriz = matriz[:2, 1:3]
print(f"\nSubmatriz:\n{submatriz}")
# Seleccionar filas completas
filas_02 = matriz[[0, 2]] # Filas 0 y 2
print(f"\nFilas 0 y 2:\n{filas_02}")
# Seleccionar columnas específicas
columna_1 = matriz[:, 1] # Toda la columna 1
print(f"\nColumna 1: {columna_1}")
# Seleccionar diagonales
diagonal = np.diag(matriz) # Diagonal principal
print(f"\nDiagonal principal: {diagonal}")
copy().
NumPy permite formas sofisticadas de seleccionar elementos:
# Indexación con arrays de índices
arr = np.array([10, 20, 30, 40, 50])
indices = np.array([0, 2, 4])
print(f"Elementos en posiciones {indices}: {arr[indices]}")
# Indexación condicional (boolean masking)
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
mask = arr > 5 # Crear una máscara booleana
print(f"Máscara booleana: {mask}")
print(f"Elementos mayores que 5: {arr[mask]}")
# Forma más compacta
print(f"Elementos pares: {arr[arr % 2 == 0]}")
# Indexación con arrays booleanos en matrices
matriz = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\nMatriz:\n{matriz}")
# Seleccionar elementos mayores que 5
print(f"Elementos > 5: {matriz[matriz > 5]}")
# Reemplazar valores con condiciones
matriz_copia = matriz.copy()
matriz_copia[matriz_copia % 2 == 0] = 0 # Reemplazar pares con 0
print(f"\nMatriz con pares reemplazados:\n{matriz_copia}")
Una de las principales ventajas de NumPy es su capacidad para realizar operaciones vectorizadas, que son mucho más rápidas que los bucles explícitos en Python.
# Operaciones aritméticas con arrays
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
# Operaciones elemento a elemento
print(f"a + b = {a + b}") # Suma
print(f"a - b = {a - b}") # Resta
print(f"a * b = {a * b}") # Multiplicación elemento a elemento
print(f"a / b = {a / b}") # División
print(f"a ** 2 = {a ** 2}") # Potencia
print(f"a % 2 = {a % 2}") # Módulo
# Operaciones con escalares
print(f"\na + 2 = {a + 2}") # Suma con escalar
print(f"a * 3 = {a * 3}") # Multiplicación por escalar
# Comparaciones
print(f"\na > 2: {a > 2}")
print(f"a == 2: {a == 2}")
print(f"a < b: {a < b}")
# Funciones trigonométricas, logarítmicas, etc.
angulos = np.array([0, np.pi/4, np.pi/2, np.pi])
print(f"\nsen(angulos) = {np.sin(angulos)}")
print(f"cos(angulos) = {np.cos(angulos)}")
valores = np.array([1, 10, 100, 1000])
print(f"log10(valores) = {np.log10(valores)}")
# Operaciones estadísticas
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Estadísticas básicas
print(f"Suma total: {np.sum(arr)}")
print(f"Media: {np.mean(arr)}")
print(f"Desviación estándar: {np.std(arr)}")
print(f"Mínimo: {np.min(arr)}")
print(f"Máximo: {np.max(arr)}")
# Estadísticas por eje
print(f"\nSuma por filas: {np.sum(arr, axis=1)}")
print(f"Media por columnas: {np.mean(arr, axis=0)}")
print(f"Mínimo por filas: {np.min(arr, axis=1)}")
print(f"Máximo por columnas: {np.max(arr, axis=0)}")
# Posición del mínimo y máximo
print(f"\nPosición del mínimo: {np.argmin(arr)}")
print(f"Posición del máximo: {np.argmax(arr)}")
# Acumulativas
print(f"\nSuma acumulativa: {np.cumsum(arr.ravel())}")
print(f"Producto acumulativo: {np.cumprod(arr[:, 0])}") # Solo primera columna
# Operaciones matriciales
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# Multiplicación matricial (producto punto)
c1 = np.dot(a, b)
print(f"Producto punto (dot):\n{c1}")
# Sintaxis alternativa para producto matricial
c2 = a @ b # Disponible desde Python 3.5
print(f"\nProducto matricial (@):\n{c2}")
# Producto elemento a elemento
c3 = a * b
print(f"\nProducto elemento a elemento (*):\n{c3}")
# Determinante
det_a = np.linalg.det(a)
print(f"\nDeterminante de a: {det_a}")
# Inversa
inv_a = np.linalg.inv(a)
print(f"\nInversa de a:\n{inv_a}")
# Verificación de la inversa (debería ser cercana a la identidad)
identity = np.dot(a, inv_a)
print(f"\nA × A⁻¹ (debería ser la identidad):\n{identity}")
# Resolver sistema de ecuaciones lineales: Ax = b
b_vec = np.array([1, 2])
x = np.linalg.solve(a, b_vec)
print(f"\nSolución a Ax = b: {x}")
print(f"Verificación: {np.dot(a, x)}")
NumPy tiene integradas muchas funciones universales (ufuncs), que son esencialmente operaciones matemáticas que se pueden aplicar a todo el array de manera vectorizada. Estas funciones operan elemento por elemento y son extremadamente eficientes.
Las categorías principales de funciones universales incluyen:
Veamos algunos ejemplos de estas funciones universales en acción:
# Crear un array de ejemplo
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Calcular la raiz cuadrada de cada elemento
np.sqrt(arr)
# Calcular el exponencial (e^) de cada elemento
np.exp(arr)
# Obtener el valor máximo como una función
np.max(arr) # lo mismo arr.max()
# Calcular el seno de cada elemento
np.sin(arr)
# Calcular el logaritmo natural de cada elemento
np.log(arr)
log a valores no válidos (como 0), NumPy genera advertencias y produce resultados especiales como -inf (infinito negativo).
# Calcular el valor absoluto
np.abs(arr)
NumPy proporciona un conjunto robusto de funciones estadísticas que van más allá de las operaciones básicas como suma o media. Estas funciones son esenciales para el análisis exploratorio de datos y la comprensión de las distribuciones.
# Crear datos de ejemplo
datos = np.array([23, 45, 12, 67, 34, 89, 24, 56, 78, 45, 34, 23])
# Medidas de tendencia central
print(f"Media: {np.mean(datos)}")
print(f"Mediana: {np.median(datos)}")
print(f"Percentil 25%: {np.percentile(datos, 25)}")
print(f"Percentil 75%: {np.percentile(datos, 75)}")
# Medidas de dispersión
print(f"\nDesviación estándar: {np.std(datos)}")
print(f"Varianza: {np.var(datos)}")
print(f"Rango: {np.ptp(datos)}") # Peak to peak (max - min)
# Crear dos conjuntos de datos
x = np.array([1, 2, 3, 4, 5])
y = np.array([5, 4, 2, 4, 5])
# Calcular coeficiente de correlación
correlacion = np.corrcoef(x, y)
print(f"Matriz de correlación:\n{correlacion}")
print(f"Coeficiente de correlación entre x e y: {correlacion[0, 1]}")
# Calcular covarianza
covarianza = np.cov(x, y)
print(f"\nMatriz de covarianza:\n{covarianza}")
print(f"Covarianza entre x e y: {covarianza[0, 1]}")
# Generar datos aleatorios
datos_aleatorios = np.random.normal(loc=50, scale=10, size=1000)
# Crear un histograma
histograma, bins = np.histogram(datos_aleatorios, bins=10)
print("Histograma:")
print(f"Frecuencias: {histograma}")
print(f"Límites de bins: {bins}")
# Funciones auxiliares para análisis de frecuencias
valores_unicos, conteos = np.unique(np.round(datos_aleatorios), return_counts=True)
print(f"\nValores más frecuentes:")
# Mostrar los 5 valores más frecuentes
indices_ordenados = np.argsort(-conteos)[:5]
for i in indices_ordenados:
print(f"Valor {valores_unicos[i]}: aparece {conteos[i]} veces")
# Crear matriz de ejemplo
matriz = np.array([[1, 5, 9],
[2, 6, 10],
[3, 7, 11],
[4, 8, 12]])
print(f"Matriz original:\n{matriz}")
# Estadísticas por filas (axis=1)
print(f"\nMedia por filas: {np.mean(matriz, axis=1)}")
print(f"Desviación estándar por filas: {np.std(matriz, axis=1)}")
# Estadísticas por columnas (axis=0)
print(f"\nMedia por columnas: {np.mean(matriz, axis=0)}")
print(f"Desviación estándar por columnas: {np.std(matriz, axis=0)}")
# Valores acumulativos
print(f"\nSuma acumulativa a lo largo de las filas:\n{np.cumsum(matriz, axis=1)}")
print(f"Suma acumulativa a lo largo de las columnas:\n{np.cumsum(matriz, axis=0)}")
axis. Esto es particularmente útil para análisis de datos tabulares, donde las filas pueden representar observaciones y las columnas variables.
Notebooks interactivos en Google Colab donde podrás practicar los conceptos básicos de Python para ciencia de datos. Ideal para consolidar lo aprendido en este capítulo con ejercicios prácticos y ejemplos ejecutables
Para aprender más sobre Numpy, recomendamos consultar estos recursos oficiales: