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 right
Grá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
.plot
deDataFrames
de Pandas. - Funciona para
DataFrames
de Pandas yDataArrays
/Datasets
de 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 plot
Usando 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 plot
La 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 rio
Para 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)
data
Hacemos 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'})
data
Revisando 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 pcolormesh
Usando 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)=}')
plot
El 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)
plot
A 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)
plot
A 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)
plot
Antes 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
crs
Podemos 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)
plot
Ahora 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)
plot
Vamos a superponer la imagen en un mapa base para que podamos ver el terreno debajo.
base = gv.tile_sources.ESRI
base * plot
Finalmente, 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 * base
En este cuaderno computacional aplicamos algunas estrategias comunes para generar gráficos. Los usaremos extensamente en el resto del tutorial.