Python es uno de los lenguajes de programación más utilizados del mundo y proporciona a los desarrolladores una amplia gama de bibliotecas.
De todos modos, cuando se trata de manipulación de datos y computación científica, generalmente pensamos en bibliotecas como Numpy, Pandas o SciPy.
En este artículo, te presentamos 3 bibliotecas de Python que pueden interesarte.
Tabla de contenidos
1. Dask
Presentación de Dask
Dask es una biblioteca flexible de computación paralela que permite la computación distribuida y el paralelismo para el procesamiento de datos a gran escala.
Entonces, ¿por qué deberíamos usar Dask? Como dicen en su página web:
Python se ha convertido en el lenguaje dominante tanto en el análisis de datos como en la programación general. Este crecimiento se ha visto impulsado por bibliotecas computacionales como NumPy, pandas y scikit-learn. Sin embargo, estos paquetes no estaban diseñados para escalar más allá de una sola máquina. Dask se desarrolló para escalar de forma nativa estos paquetes y el ecosistema que los rodea a máquinas multinúcleo y clústeres distribuidos cuando los conjuntos de datos exceden la memoria.
Así, uno de los usos comunes de Dask, como dicen, es:
Dask DataFrame se utiliza en situaciones en las que pandas es comúnmente necesario, por lo general cuando pandas falla debido al tamaño de los datos o la velocidad de cálculo:
- Manipulación de grandes conjuntos de datos, incluso cuando esos conjuntos de datos no caben en la memoria.
- Aceleración de cálculos largos mediante el uso de muchos núcleos.
- Computación distribuida en grandes conjuntos de datos con operaciones estándar de pandas como groupby, join y cálculos de series temporales.
Por lo tanto, Dask es una buena opción cuando tenemos que lidiar con enormes marcos de datos Pandas. Esto se debe a que Dask:
Permite a los usuarios manipular conjuntos de datos de 100GB+ en un portátil o conjuntos de datos de 1TB+ en una estación de trabajo.
Lo cual es un resultado bastante impresionante. Lo que sucede bajo el capó, es que: Los DataFrames de Dask coordinan muchos DataFrames/Series de pandas dispuestos a lo largo del índice. Un Dask DataFrame está particionado por filas, agrupando las filas por el valor del índice para mayor eficiencia. Estos objetos pandas pueden vivir en disco o en otras máquinas.
Por lo tanto, tenemos algo así:
Diferencia entre un marco de datos Dask y un marco de datos Pandas. Imagen del autor, inspirada libremente en una de la web de Dask ya citada.
Algunas características de Dask en acción:
En primer lugar, necesitamos instalar Dask. Podemos hacerlo a través de pip o conda así:
$ pip install dask[complete] or $ conda install dask
CARACTERÍSTICA UNO: ABRIR UN ARCHIVO CSV
La primera característica que podemos mostrar de Dask es cómo podemos abrir un CSV. Podemos hacerlo así:
import dask.dataframe as dd # Load a large CSV file using Dask df_dask = dd.read_csv('my_very_large_dataset.csv') # Perform operations on the Dask DataFrame mean_value_dask = df_dask['column_name'].mean().compute()
Así, como podemos ver en el código, la forma en que usamos Dask es muy similar a Pandas. En particular:
- Usamos el método read_csv() exactamente igual que en Pandas
- Interceptamos una columna exactamente igual que en Pandas. De hecho, si tuviéramos un marco de datos Pandas llamado df interceptaríamos una columna de esta forma: df[‘nombre_columna’].
- Aplicamos el método mean() a la columna interceptada de forma similar a Pandas, pero aquí también necesitamos añadir el método compute().
Además, aunque la metodología de abrir un fichero CSV es la misma que en Pandas, bajo el capó Dask está procesando sin esfuerzo un gran conjunto de datos que excede la capacidad de memoria de una sola máquina.
Esto significa que no podemos ver ninguna diferencia real, excepto el hecho de que un marco de datos grande no se puede abrir en Pandas, pero en Dask podemos.
SEGUNDA CARACTERÍSTICA: AMPLIACIÓN DE LOS FLUJOS DE TRABAJO DE MACHINE LEARNING
También podemos utilizar Dask para crear un conjunto de datos de clasificación con un gran número de muestras. A continuación, podemos dividirlo en los conjuntos de entrenamiento y prueba, ajustar el conjunto de entrenamiento con un modelo ML y calcular las predicciones para el conjunto de prueba.
Podemos hacerlo así:
import dask_ml.datasets as dask_datasets from dask_ml.linear_model import LogisticRegression from dask_ml.model_selection import train_test_split # Load a classification dataset using Dask X, y = dask_datasets.make_classification(n_samples=100000, chunks=1000) # Split the data into train and test sets X_train, X_test, y_train, y_test = train_test_split(X, y) # Train a logistic regression model in parallel model = LogisticRegression() model.fit(X_train, y_train) # Predict on the test set y_pred = model.predict(X_test).compute()
Este ejemplo pone de relieve la capacidad de Dask para manejar enormes conjuntos de datos, incluso en el caso de un problema de aprendizaje automático, mediante la distribución de los cálculos a través de múltiples núcleos.
En concreto, podemos crear un «Dask dataset» para un caso de clasificación con el método dask_datasets.make_classification(), y podemos especificar el número de muestras y chunks (incluso, ¡muy enormes!).
Al igual que antes, las predicciones se obtienen con el método compute().
NOTE: in this case, you may need to install the module dask_ml. You can do it like so: $ pip install dask_ml
CARACTERÍSTICA TRES: PROCESAMIENTO EFICAZ DE IMÁGENES
La potencia del procesamiento paralelo que utiliza Dask también puede aplicarse a las imágenes.
En concreto, podríamos abrir varias imágenes, redimensionarlas y guardarlas redimensionadas.
Podemos hacerlo así:
import dask.array as da import dask_image.imread from PIL import Image # Load a collection of images using Dask images = dask_image.imread.imread('image*.jpg') # Resize the images in parallel resized_images = da.stack([da.resize(image, (300, 300)) for image in images]) # Compute the result result = resized_images.compute() # Save the resized images for i, image in enumerate(result): resized_image = Image.fromarray(image) resized_image.save(f'resized_image_{i}.jpg')
El proceso es el siguiente:
- Abrimos todas las imágenes «.jpg» de la carpeta actual (o de una carpeta que se pueda especificar) con el método dask_image.imread.imread(«imagen*.jpg»).
- Las redimensionamos todas a 300×300 usando una comprensión de lista en el método da.stack().
- Calculamos el resultado con el método compute(), como hicimos antes.
- Guardamos todas las imágenes redimensionadas con el ciclo for.
2. SymPy
Presentación de Sympy
Si necesitas hacer cálculos matemáticos y computaciones y quieres ceñirte a Python, puedes probar Sympy.
De hecho: ¿por qué usar otras herramientas y software, cuando podemos usar nuestro querido Python?
Según lo que escriben en su sitio web, Sympy es:
una librería Python para matemática simbólica. Su objetivo es convertirse en un completo sistema de álgebra computacional (CAS), manteniendo el código lo más simple posible para que sea comprensible y fácilmente extensible. SymPy está escrito íntegramente en Python.
Pero, ¿por qué utilizar SymPy? Ellos sugieren:
SymPy es…
- Libre: Con licencia BSD, SymPy es libre tanto para hablar como para beber cerveza.
- Basado en Python: SymPy está escrito completamente en Python y utiliza Python como lenguaje.
- Ligero: SymPy sólo depende de mpmath, una biblioteca pura de Python para aritmética arbitraria en coma flotante, lo que facilita su uso.
- Una biblioteca: Más allá de su uso como herramienta interactiva, SymPy puede incrustarse en otras aplicaciones y ampliarse con funciones personalizadas.
Así que, básicamente, ¡tiene todas las características que pueden gustar a los adictos a Python!
Ahora, veamos algunas de sus características.
Algunas características de SymPy en acción
En primer lugar, tenemos que instalarlo:
$ pip install sympy PAY ATTENTION: if you write $ pip install simpy you'll install another (completely different!) library. So, the second letter is a "y", not an "i".
PRIMERA CARACTERÍSTICA: RESOLVER UNA ECUACIÓN ALGEBRAICA
Si necesitamos resolver una ecuación algebraica, podemos utilizar SymPy de la siguiente manera:
from sympy import symbols, Eq, solve # Define the symbols x, y = symbols('x y') # Define the equation equation = Eq(x**2 + y**2, 25) # Solve the equation solutions = solve(equation, (x, y)) # Print solution print(solutions) >>> [(-sqrt(25 - y**2), y), (sqrt(25 - y**2), y)]
Este es el proceso:
- Definimos los símbolos de la ecuación con el método symbols().
- Escribimos la ecuación algebraica con el método Eq.
- Resolvemos la ecuación con el método solve().
Cuando estaba en la Universidad utilicé diferentes herramientas para resolver este tipo de problemas, y tengo que decir que SymPy, como podemos ver, es muy legible y fácil de usar.
Pero, en efecto: es una biblioteca de Python, así que ¿cómo podría ser diferente?
SEGUNDA CARACTERÍSTICA: CÁLCULO DE DERIVADAS
Calcular derivadas es otra tarea que podemos necesitar matemáticamente, por muchas razones al analizar datos. A menudo, podemos necesitar cálculos por cualquier razón, y SympY realmente simplifica este proceso. De hecho, podemos hacerlo así:
from sympy import symbols, diff # Define the symbol x = symbols('x') # Define the function f = x**3 + 2*x**2 + 3*x + 4 # Calculate the derivative derivative = diff(f, x) # Print derivative print(derivative) >>> 3*x**2 + 4*x + 3
Como vemos, el proceso es muy sencillo y autoexplicativo:
- Definimos el símbolo de la función que estamos derivando con symbols().
- Definimos la función.
- Calculamos la derivada con diff() especificando la función y el símbolo del que estamos calculando la derivada (se trata de una derivada absoluta, pero podríamos realizar incluso derivadas parciales en el caso de funciones que tengan variables x e y).
Y si lo probamos, veremos que el resultado llega en cuestión de 2 ó 3 segundos. O sea, que también es bastante rápido.
CARACTERÍSTICA TRES: CÁLCULO DE INTEGRACIONES
Por supuesto, si SymPy puede calcular derivadas, también puede calcular integraciones. Hagámoslo:
from sympy import symbols, integrate, sin # Define the symbol x = symbols('x') # Perform symbolic integration integral = integrate(sin(x), x) # Print integral print(integral) >>> -cos(x)
Así que aquí utilizamos el método integrate(), especificando la función a integrar y la variable de integración.
¡¿No podría ser más fácil?!
3. Xarray
Presentación de Xarray
Xarray es una librería de Python que extiende las características y funcionalidades de NumPy, dándonos la posibilidad de trabajar con arrays y conjuntos de datos etiquetados.
Como dicen en su página web, de hecho
Xarray hace que trabajar con arrays multidimensionales etiquetados en Python sea sencillo, eficiente y divertido.
Y también:
Xarray introduce etiquetas en forma de dimensiones, coordenadas y atributos sobre las matrices multidimensionales de NumPy, lo que permite una experiencia de desarrollo más intuitiva, más concisa y menos propensa a errores.
En otras palabras, amplía la funcionalidad de las matrices NumPy añadiendo etiquetas o coordenadas a las dimensiones de la matriz. Estas etiquetas proporcionan metadatos y permiten un análisis y una manipulación más avanzados de los datos multidimensionales.
Por ejemplo, en NumPy, se accede a las matrices utilizando indexación basada en enteros.
En Xarray, en cambio, cada dimensión puede tener asociada una etiqueta, lo que facilita la comprensión y manipulación de los datos basándose en nombres significativos.
Por ejemplo, en lugar de acceder a los datos con arr[0, 1, 2], podemos utilizar arr.sel(x=0, y=1, z=2) en
Xarray, donde x, y, z son etiquetas de dimensión.
Esto hace que el código sea mucho más legible.
Veamos algunas características de Xarray.
Algunas características de Xarray en acción
Como siempre, para instalarlo:
$ pip install xarray
CARACTERÍSTICA UNO: TRABAJAR CON COORDENADAS ETIQUETADAS
Supongamos que queremos crear unos datos relacionados con la temperatura y queremos etiquetarlos con coordenadas como latitud y longitud. Podemos hacerlo así:
import xarray as xr import numpy as np # Create temperature data temperature = np.random.rand(100, 100) * 20 + 10 # Create coordinate arrays for latitude and longitude latitudes = np.linspace(-90, 90, 100) longitudes = np.linspace(-180, 180, 100) # Create an Xarray data array with labeled coordinates da = xr.DataArray( temperature, dims=['latitude', 'longitude'], coords={'latitude': latitudes, 'longitude': longitudes} ) # Access data using labeled coordinates subset = da.sel(latitude=slice(-45, 45), longitude=slice(-90, 0))
Y si los imprimimos obtenemos:
# Print data print(subset) >>> array([[13.45064786, 29.15218061, 14.77363206, ..., 12.00262833, 16.42712411, 15.61353963], [23.47498117, 20.25554247, 14.44056286, ..., 19.04096482, 15.60398491, 24.69535367], [25.48971105, 20.64944534, 21.2263141 , ..., 25.80933737, 16.72629302, 29.48307134], ..., [10.19615833, 17.106716 , 10.79594252, ..., 29.6897709 , 20.68549602, 29.4015482 ], [26.54253304, 14.21939699, 11.085207 , ..., 15.56702191, 19.64285595, 18.03809074], [26.50676351, 15.21217526, 23.63645069, ..., 17.22512125, 13.96942377, 13.93766583]]) Coordinates: * latitude (latitude) float64 -44.55 -42.73 -40.91 ... 40.91 42.73 44.55 * longitude (longitude) float64 -89.09 -85.45 -81.82 ... -9.091 -5.455 -1.818
Veamos el proceso paso a paso:
- Hemos creado los valores de temperatura como un array NumPy.
- Hemos definido los valores de latitudes y longitueas como arrays NumPy.
- Hemos almacenado todos los datos en un array Xarray con el método DataArray().
- Hemos seleccionado un subconjunto de las latitudes y longitudes con el método sel() que selecciona los valores que queremos para nuestro subconjunto.
Además, el resultado es fácilmente legible, por lo que el etiquetado es realmente útil en muchos casos.
SEGUNDA CARACTERÍSTICA: TRATAMIENTO DE LOS DATOS QUE FALTAN
Supongamos que estamos recogiendo datos relacionados con las temperaturas durante el año. Queremos saber si tenemos algunos valores nulos en nuestro array. Así es como podemos hacerlo:
import xarray as xr import numpy as np import pandas as pd # Create temperature data with missing values temperature = np.random.rand(365, 50, 50) * 20 + 10 temperature[0:10, :, :] = np.nan # Set the first 10 days as missing values # Create time, latitude, and longitude coordinate arrays times = pd.date_range('2023-01-01', periods=365, freq='D') latitudes = np.linspace(-90, 90, 50) longitudes = np.linspace(-180, 180, 50) # Create an Xarray data array with missing values da = xr.DataArray( temperature, dims=['time', 'latitude', 'longitude'], coords={'time': times, 'latitude': latitudes, 'longitude': longitudes} ) # Count the number of missing values along the time dimension missing_count = da.isnull().sum(dim='time') # Print missing values print(missing_count) >>> array([[10, 10, 10, ..., 10, 10, 10], [10, 10, 10, ..., 10, 10, 10], [10, 10, 10, ..., 10, 10, 10], ..., [10, 10, 10, ..., 10, 10, 10], [10, 10, 10, ..., 10, 10, 10], [10, 10, 10, ..., 10, 10, 10]]) Coordinates: * latitude (latitude) float64 -90.0 -86.33 -82.65 ... 82.65 86.33 90.0 * longitude (longitude) float64 -180.0 -172.7 -165.3 ... 165.3 172.7 180.0
Y así obtenemos que tenemos 10 valores nulos.
Además, si nos fijamos bien en el código, podemos ver que podemos aplicar métodos de Pandas a un Xarray como isnull.sum(), como en este caso, que cuenta el número total de valores perdidos.
PRIMERA CARACTERÍSTICA: MANEJO Y ANÁLISIS DE DATOS MULTIDIMENSIONALES
La tentación de manejar y analizar datos multidimensionales es alta cuando tenemos la posibilidad de etiquetar nuestras matrices. Así que, ¿por qué no intentarlo?
Por ejemplo, supongamos que seguimos recopilando datos relacionados con las temperaturas en determinadas latitudes y longitudes.
Quizá queramos calcular la media, la máxima y la mediana de las temperaturas. Podemos hacerlo así:
import xarray as xr import numpy as np import pandas as pd # Create synthetic temperature data temperature = np.random.rand(365, 50, 50) * 20 + 10 # Create time, latitude, and longitude coordinate arrays times = pd.date_range('2023-01-01', periods=365, freq='D') latitudes = np.linspace(-90, 90, 50) longitudes = np.linspace(-180, 180, 50) # Create an Xarray dataset ds = xr.Dataset( { 'temperature': (['time', 'latitude', 'longitude'], temperature), }, coords={ 'time': times, 'latitude': latitudes, 'longitude': longitudes, } ) # Perform statistical analysis on the temperature data mean_temperature = ds['temperature'].mean(dim='time') max_temperature = ds['temperature'].max(dim='time') min_temperature = ds['temperature'].min(dim='time') # Print values print(f"mean temperature:\n {mean_temperature}\n") print(f"max temperature:\n {max_temperature}\n") print(f"min temperature:\n {min_temperature}\n") >>> mean temperature: array([[19.99931701, 20.36395016, 20.04110699, ..., 19.98811842, 20.08895803, 19.86064693], [19.84016491, 19.87077812, 20.27445405, ..., 19.8071972 , 19.62665953, 19.58231185], [19.63911165, 19.62051976, 19.61247548, ..., 19.85043831, 20.13086891, 19.80267099], ..., [20.18590514, 20.05931149, 20.17133483, ..., 20.52858247, 19.83882433, 20.66808513], [19.56455575, 19.90091128, 20.32566232, ..., 19.88689221, 19.78811145, 19.91205212], [19.82268297, 20.14242279, 19.60842148, ..., 19.68290006, 20.00327294, 19.68955107]]) Coordinates: * latitude (latitude) float64 -90.0 -86.33 -82.65 ... 82.65 86.33 90.0 * longitude (longitude) float64 -180.0 -172.7 -165.3 ... 165.3 172.7 180.0 max temperature: array([[29.98465531, 29.97609171, 29.96821276, ..., 29.86639343, 29.95069558, 29.98807808], [29.91802049, 29.92870312, 29.87625447, ..., 29.92519055, 29.9964299 , 29.99792388], [29.96647016, 29.7934891 , 29.89731136, ..., 29.99174546, 29.97267052, 29.96058079], ..., [29.91699117, 29.98920555, 29.83798369, ..., 29.90271746, 29.93747041, 29.97244906], [29.99171911, 29.99051943, 29.92706773, ..., 29.90578739, 29.99433847, 29.94506567], [29.99438621, 29.98798699, 29.97664488, ..., 29.98669576, 29.91296382, 29.93100249]]) Coordinates: * latitude (latitude) float64 -90.0 -86.33 -82.65 ... 82.65 86.33 90.0 * longitude (longitude) float64 -180.0 -172.7 -165.3 ... 165.3 172.7 180.0 min temperature: array([[10.0326431 , 10.07666029, 10.02795524, ..., 10.17215336, 10.00264909, 10.05387097], [10.00355858, 10.00610942, 10.02567816, ..., 10.29100316, 10.00861792, 10.16955806], [10.01636216, 10.02856619, 10.00389027, ..., 10.0929342 , 10.01504103, 10.06219179], ..., [10.00477003, 10.0303088 , 10.04494723, ..., 10.05720692, 10.122994 , 10.04947012], [10.00422182, 10.0211205 , 10.00183528, ..., 10.03818058, 10.02632697, 10.06722953], [10.10994581, 10.12445222, 10.03002468, ..., 10.06937041, 10.04924046, 10.00645499]]) Coordinates: * latitude (latitude) float64 -90.0 -86.33 -82.65 ... 82.65 86.33 90.0 * longitude (longitude) float64 -180.0 -172.7 -165.3 ... 165.3 172.7 180.0
Y hemos obtenido lo que queríamos, además de una forma claramente legible.
Y de nuevo, como antes, para calcular los valores máximo, mínimo y medio de las temperaturas hemos usado las funciones de Pandas aplicadas a un array.
Conclusiones
En este artículo, hemos mostrado tres librerías para el cálculo y la computación científica
Mientras que SymPy puede ser el sustituto de otras herramientas y software, dándonos la posibilidad de utilizar código Python para realizar cálculos matemáticos, Dask y Xarray extienden las funcionalidades de otras librerías, ayudándonos en situaciones en las que podemos tener dificultades con otras librerías Python más conocidas para el análisis y manipulación de datos.