No, no vamos a hacer ningún vídeo X subido de tono ni nada por el estilo. Vamos a hacer una animación con una biblioteca llamada xmovie
que permite hacer vídeos, GIFs animados,…, a partir de estructuras de datos de xarray
de forma muy sencilla.
Antes he hablado muchas veces sobre distintas formas de hacer animaciones con Python. Parece que estoy un poco monotemático pero algunas animaciones son capaces de condensar tanta información de forma tan rápidamente consumible que estoy buscando la mejor forma de hacerlo dependiendo del contexto en el que me encuentre:
- https://pybonacci.org/2012/12/16/creando-una-animacion-con-matplotlib-y-ffmpeg/
- https://pybonacci.org/2020/05/12/demanda-electrica-en-espana-durante-el-confinamiento-covid-19-visto-con-python/
- https://pybonacci.org/2020/04/08/analizando-el-calentamiento-global-con-python/
- https://pybonacci.org/2019/12/13/creacion-de-gif-animado-con-imageio-y-python/
- https://pybonacci.org/2018/11/27/creacion-de-videos-al-vuelo-en-el-notebook/
Vamos al lío. Empezamos, como siempre, importando algunas cosas:
1 2 3 4 5 6 |
from calendar import isleap import numpy as np import xarray as xr import xmovie import matplotlib.pyplot as plt |
Lo que voy a animar en esta ocasión son datos de la SST (Sea Surface Temperature, agua superficial del mar) promedio anual obtenidos a partir del sensor MODIS a bordo del satélite AQUA de la Nasa. Puedes leer más aquí sobre los datos.
Los datos los voy a obtener a partir de un servidor de datos OpenDAP que me permite descargar solo lo que necesito. Los datos están a nivel global pero solo quiero mostrar información del mar Mediterráneo occidental. Para hacer el subset necesito hacer, previamente, una serie de cosas:
1 2 3 4 5 6 7 8 9 10 |
lonmin = -5.5 latmin = 35 lonmax = 9 latmax = 44.5 x_step = 360 / 8640 # 8640 es el número de datos en x y_step = 180 / 4320 # 4320 es el número de datos en y x0 = int((360 - 180 + lonmin) / x_step) x1 = int((360 - 180 + lonmax) / x_step) y0 = int((90 - latmax) / y_step) y1 = int((90 - latmin) / y_step) |
Tengo un fichero NetCDF por año desde el año 2002 hasta el año 2018. Los voy a descargar, leer como extructuras de datos de xarray
y meterlos en una lista. Los valores x0
, y0
, x1
e y1
que he definido anteriormente los utilizo para hacer el subset solo para las latitudes y longitudes que necesito:
1 2 3 4 5 6 7 8 9 10 11 12 |
datasets = [] for i in range(2002, 2019): days = 365 if isleap(i): days = 366 url = ( 'https://podaac-opendap.jpl.nasa.gov/opendap/allData/modis/' f'L3/aqua/4um/v2014.0/4km/annual/{i}/A{i}001{i}{days}.L3m_YR_SST4_sst4_4km.nc' f'?sst4[{y0}:1:{y1}][{x0}:1:{x1}],lat[{y0}:1:{y1}],lon[{x0}:1:{x1}]' ) tmp = xr.open_dataset(url) datasets.append(tmp) |
Cada uno de los objetos de datos que he metido en la lista datasets
contiene valores de la SST en dos dimensiones (latitud y longitud). Quiero añadir una tercera dimensión (tiempo) con los datos de cada año. Para ello, ahora creo una nueva estructura de las disponibles en xarray
pero ‘amontonando’ los datasets individuales que he almacenado anteriormente en la lista datasets
para crear un nuevo xarray.Dataset
con una nueva dimensión time
:
1 2 3 4 5 6 7 8 9 |
arr = np.empty((*datasets[0].sst4.shape, len(datasets))) for i, d in enumerate(datasets): arr[:,:,i] = d.sst4.values ds = xr.Dataset({ 'sst4': xr.Variable(('lat', 'lon', 'time'), arr), 'lat': xr.Variable('lat', datasets[0].lat), 'lon': xr.Variable('lon', datasets[0].lon), 'time': xr.Variable('time', range(2002, 2019)) }) |
Ahora que ya tenemos los datos como los quería podríamos crear la película con xmovie
de la siguiente forma:
1 2 |
mov = xmovie.Movie(ds.sst4) mov.save('med_sst.mp4', framerate=2) |
El resultado sería:
Muy bien, muy sencillo. Lo que hace la biblioteca es coger la dimensión time
del objeto ds
e iterar sobre la misma para crear cada fotograma.
Pero parece que el rango de temperaturas incluye cosas como lagos o ríos que están bastante más fríos que el Mediterraneo. A mí me interesa que el rango de temperaturas se restrinja más al valor del mar para resaltar más las diferencias.
No os preocupéis, podemos definir nuestra propia función y usarla para que dibuje cada fotograma. La función la tenemos que definir con tres argumentos, la figura (de matplotlib
, la variable del xarray.Dataset
que queremos dibujar y el número de fotogramas o pasos temporales. La nueva función y la generación del vídeo se muestra a continuación:
1 2 3 4 5 6 7 8 9 10 |
fig = plt.figure(figsize=(6,6)) t = 2019 - 2002 def custom_plotfunc(ds, fig, t, **kwargs): ax = fig.subplots() ds.isel(time=t).plot(ax=ax, vmin=15, vmax=30) ax.set_title(f'year = {t + 2002}') mov_custom = xmovie. Movie(ds.sst4, custom_plotfunc) mov_custom.save('med_sst_custom.mp4', framerate=2) |
En la función anterior he podido definir el rango de temperaturas que quiero utilizar, más acorde con la SST promedio del mediterráneo.
El resultado sería algo como:
Y eso es todo.
La biblioteca xmovie
es muy específica pero te puede resolver alguna papeleta si necesitas una animación rápida de una variable en un xarray.Dataset
.