Las principales herramientas que utilizaremos para la visualización de datos provienen de la familia Holoviz de librerías Python, principalmente GeoViews y hvPlot. Estas están construidas en gran parte sobre HoloViews y soportan múltiples backends para la representación de gráficos (Bokeh para visualización interactiva y Matplotlib para gráficos estáticos con calidad de publicación.
GeoViews¶

De la documentación de GeoViews:
GeoViews es una librería de Python que facilita la exploración y visualización de conjuntos de datos geográficos, meteorológicos y oceanográficos, como los que se utilizan en la investigación meteorológica, climática y de teledetección.
GeoViews se basa en la biblioteca HoloViews y permite crear visualizaciones flexibles de datos multidimensionales. GeoViews agrega una familia de tipos de gráficos geográficos basados en la librería Cartopy, trazados con los paquetes Matplotlib o Bokeh. Con GeoViews, puedes trabajar de forma fácil y natural con grandes conjuntos de datos geográficos multidimensionales, visualizando al instante cualquier subconjunto o combinación de ellos. Al mismo tiempo, podrás acceder siempre a los datos crudos subyacentes a cualquier gráfico.
import warnings
warnings.filterwarnings('ignore')
from pathlib import Path
from pprint import pprint
import geoviews as gv
gv.extension('bokeh')
from geoviews import opts
FILE_STEM = Path.cwd().parent if 'book' == Path.cwd().parent.stem else 'book'Visualización de un mapa base¶
Un mapa base o capa de mosaico es útil cuando se muestran datos vectoriales o ráster porque nos permite superponer los datos geoespaciales relevantes sobre un mapa geográfico conocido como fondo. La principal funcionalidad que utilizaremos es gv.tile_sources. Podemos utilizar el método opts para especificar parámetros de configuración adicionales. A continuación, utilizaremos el servicio de mapas web Open Street Map (OSM) (en español, Mapas de Calles Abiertos) para crear el objeto basemap. Cuando mostramos la representación de este objeto en la celda del cuaderno computacional, el menú de Bokeh que está a la derecha permite la exploración interactiva.
basemap = gv.tile_sources.OSM.opts(width=600, height=400)
basemap # When displayed, this basemap can be zoomed & panned using the menu at the rightGráficos de puntos¶
Para empezar, vamos a definir una tupla regular en Python para las coordenadas de longitud y latitud de Tokio, Japón.
tokyo_lonlat = (139.692222, 35.689722)
print(tokyo_lonlat)La clase geoviews.Points acepta una lista de tuplas (cada una de la forma (x, y)) y construye un objeto Points que puede ser visualizado. Podemos superponer el punto creado en los mosaicos OpenStreetMap de basemap utilizando el operador * en Holoviews. También podemos utilizar geoviews.opts para establecer varias preferencias de visualización para estos puntos.
tokyo_point = gv.Points([tokyo_lonlat])
point_opts = opts.Points(
size=48,
alpha=0.5,
color='red'
)
print(type(tokyo_point))# Use Holoviews * operator to overlay plot on basemap
# Note: zoom out to see basemap (starts zoomed "all the way in")
(basemap * tokyo_point).opts(point_opts)# to avoid starting zoomed all the way in, this zooms "all the way out"
(basemap * tokyo_point).opts(point_opts, opts.Overlay(global_extent=True))Gráficos de rectángulos¶
Forma estándar de representar un rectángulo (también llamado caja delimitadora) con vértices
(suponiendo que & ) es como única cuadrupla
es decir, las coordenadas de la esquina inferior izquierda seguidas de las coordenadas de la esquina superior derecha.
Vamos a crear una función sencilla para generar un rectángulo de un ancho y altura dados, según la coordenada central.
# simple utility to make a rectangle centered at pt of width dx & height dy
def make_bbox(pt,dx,dy):
'''Returns bounding box represented as tuple (x_lo, y_lo, x_hi, y_hi)
given inputs pt=(x, y), width & height dx & dy respectively,
where x_lo = x-dx/2, x_hi=x+dx/2, y_lo = y-dy/2, y_hi = y+dy/2.
'''
return tuple(coord+sgn*delta for sgn in (-1,+1) for coord,delta in zip(pt, (dx/2,dy/2)))Podemos probar la función anterior utilizando las coordenadas de longitud y latitud de Marruecos.
# Verify that the function bounds works as intended
marrakesh_lonlat = (-7.93, 31.67)
dlon, dlat = 0.5, 0.25
marrakesh_bbox = make_bbox(marrakesh_lonlat, dlon, dlat)
print(marrakesh_bbox)La función geoviews.Rectangles acepta una lista de cajas delimitadoras (cada uno descrito por una tupla de la forma (x_min, y_min, x_max, y_max)) para el trazado. También podemos utilizar geoviews.opts para adaptar el rectángulo a nuestras necesidades.
rectangle = gv.Rectangles([marrakesh_bbox])
rect_opts = opts.Rectangles(
line_width=0,
alpha=0.1,
color='red'
)Podemos graficar un punto para Marruecos al igual que antes utilizando geoviews.Points (personalizado utilizando geoviews.opts).
marrakesh_point = gv.Points([marrakesh_lonlat])
point_opts = opts.Points(
size=48,
alpha=0.25,
color='blue'
)Por último, podemos superponer todas estas características en el mapa base con las opciones aplicadas.
(basemap * rectangle * marrakesh_point).opts( rect_opts, point_opts )Utilizaremos el método anterior para visualizar (AOIs) al construir consultas de búsqueda para los productos EarthData de la NASA. En particular, la convención de representar una caja delimitadora por ordenadas (izquierda, inferior, derecha, superior) también se utiliza en la API PySTAC.
hvPlot¶
- hvPlot está diseñado para extender la API
.plotdeDataFramesde Pandas. - Funciona para
DataFramesde Pandas yDataArrays/Datasetsde Xarray.
Graficar desde un DataFrame con hvplot.pandas¶
El código siguiente carga un DataFrame de Pandas con datos de temperatura.
import pandas as pd, numpy as np
from pathlib import Path
LOCAL_PATH = Path(FILE_STEM, 'assets/temperature.csv')df = pd.read_csv(LOCAL_PATH, index_col=0, parse_dates=[0])
df.head()Revisando la API de DataFrame.plot de Pandas¶
Vamos a extraer un subconjunto de columnas de este DataFrame y generar un gráfico.
west_coast = df[['Vancouver', 'Portland', 'San Francisco', 'Seattle', 'Los Angeles']]
west_coast.head()La API de .plot de DataFrame de Pandas proporciona acceso a varios métodos de visualización. Aquí usaremos .plot.line, pero hay otras opciones disponibles (por ejemplo, .plot.area, .plot.bar, .plot.nb, .plot.scatter, etc.). Esta API se ha repetido en varias librerías debido a su conveniencia.
west_coast.plot.line(); # This produces a static Matplotlib plotUsando la API de hvPlot DataFrame.hvplot¶
Importando hvplot.pandas, se puede generar un gráfico interactivo similar. La API para .hvplot imita esto para .plot. Por ejemplo, podemos generar la gráfica de línea anterior usando .hvplot.line. En este caso, el backend para los gráficos por defecto es Bokeh, así que el gráfico es interactivo.
import hvplot.pandas
west_coast.hvplot.line() # This produces an interactive Bokeh plotLa API .plot de DataFrame de Pandas proporciona acceso a una serie de métodos de graficación.
west_coast.hvplot.line(width=600, height=300, grid=True)La API hvplot también funciona cuando está enlazada junto con otras llamadas del método DataFrame. Por ejemplo, podemos muestrear los datos de temperatura y calcular la media para suavizarlos.
smoothed = west_coast.resample('2d').mean()
smoothed.hvplot.line(width=600, height=300, grid=True)Graficar desde un DataArray con hvplot.xarray¶
La API .plot de Pandas también se extendió a Xarray, es decir, para DataArray. de Xarray
import xarray as xr
import hvplot.xarray
import rioxarray as rioPara empezar, carga un archivo GeoTIFF local usando rioxarray en una estructura Zarray de DataArray.
LOCAL_PATH = Path(FILE_STEM, 'assets/OPERA_L3_DIST-ALERT-HLS_T10TEM_20220815T185931Z_20220817T153514Z_S2A_30_v0.1_VEG-ANOM-MAX.tif')data = rio.open_rasterio(LOCAL_PATH)
dataHacemos algunos cambios menores al DataArray.
data = data.squeeze() # to reduce 3D array with singleton dimension to 2D array
data = data.rename({'x':'easting', 'y':'northing'})
dataRevisando la API DataFrame.plot de Pandas¶
La API DataArray.plot por defecto usa el pcolormesh de Matplotlib para mostrar un arreglo de 2D almacenado dentro de un DataArray. La renderización de esta imagen moderadamente de alta resolución lleva un poco de tiempo.
data.plot(); # by default, uses pcolormeshUsando la API de hvPlot DataFrame.hvplot¶
De nuevo, la API DataArray.hvplot imita la API DataArray.plot; de forma predeterminada, utiliza una subclase derivada de holoviews.element.raster.Image.
plot = data.hvplot() # by default uses Image class
print(f'{type(plot)=}')
plotEl resultado anterior es una visualización interactiva, procesada usando Bokeh. Esto es un poco lento, pero podemos añadir algunas opciones para acelerar la renderización. También se requiere una manipulación de la misma; por ejemplo, la imagen no es cuadrada, el mapa de colores no resalta características útiles, los ejes son transpuestos, etc.
Creando opciones para mejorar los gráficos de manera incremental¶
Añadamos opciones para mejorar la imagen. Para hacer esto, iniciaremos un diccionario de Python image_opts para usar dentro de la llamada al método image. Creando opciones para mejorar los gráficos de manera incremental.
image_opts = dict(rasterize=True, dynamic=True)
pprint(image_opts)Para empezar, hagamos la llamada explícita a hvplot.image y especifiquemos la secuencia de ejes. Y apliquemos las opciones del diccionario image_opts. Utilizaremos la operación dict-unpacking **image_opts cada vez que invoquemos a data.hvplot.image.
plot = data.hvplot.image(x='easting', y='northing', **image_opts)
plotA continuación, vamos a corregir el ratio y las dimensiones de la imagen.
image_opts.update(frame_width=500, frame_height=500, aspect='equal')
pprint(image_opts)
plot = data.hvplot.image(x='easting', y='northing', **image_opts)
plotA continuación, vamos a corregir el ratio y las dimensiones de la imagen.
image_opts.update( cmap='hot_r', clim=(0,100), alpha=0.8 )
pprint(image_opts)
plot = data.hvplot.image(x='easting', y='northing', **image_opts)
plotAntes de añadir un mapa de base, tenemos que tener en cuenta el sistema de coordenadas. Esto se almacena en el archivo GeoTIFF y, cuando se lee usando rioxarray.open_rasterio, se disponibilizada mediante el atributo data.rio.crs.
crs = data.rio.crs
crsPodemos usar el CRS recuperado arriba como un argumento opcional para hvplot.image. Ten en cuenta que las coordenadas han cambiado en los ejes, pero las etiquetas no son las correctas. Podemos arreglarlo.
image_opts.update(crs=crs)
pprint(image_opts)
plot = data.hvplot.image(x='easting', y='northing', **image_opts)
plotAhora vamos a corregir las etiquetas. Utilizaremos el sistema Holoviews/GeoViews opts para especificar estas opciones.
label_opts = dict(title='VEG_ANOM_MAX', xlabel='Longitude (degrees)', ylabel='Latitude (degrees)')
pprint(image_opts)
pprint(label_opts)
plot = data.hvplot.image(x='easting', y='northing', **image_opts).opts(**label_opts)
plotVamos a superponer la imagen en un mapa base para que podamos ver el terreno debajo.
base = gv.tile_sources.ESRI
base * plotFinalmente, como los píxeles blancos distraen vamos a filtrarlos utilizando el método DataArray where.
plot = data.where(data>0).hvplot.image(x='easting', y='northing', **image_opts).opts(**label_opts)
plot * baseEn este cuaderno computacional aplicamos algunas estrategias comunes para generar gráficos. Los usaremos extensamente en el resto del tutorial.