Proyecto¶
Fecha y hora límite de entrega¶
Viernes 14 de marzo de 2025, 11:59 p.m.
Objetivos¶
Cada estudiante debe mostrar que es capaz de:
- Desarrollar programas en el lenguaje de programación Python orientados al procesamiento de datos geoespaciales de biodiversidad.
- Aplicar un enfoque de ciencia de datos en los procesos de importación, transformación, visualización, análisis y comunicación de datos geoespaciales de biodiversidad.
- Desarrollar soluciones reproducibles a problemas computacionales mediante Python.
- Integrar visualizaciones tabulares, gráficas y geoespaciales de datos de biodiversidad en documentos y aplicaciones interactivas desarrolladas en Python.
Entregables¶
- Dirección de un documento en Google Colab con el contenido especificado en la sección Desarrollo.
La entrega debe realizarse a través de la plataforma Google Classroom.
Consideraciones adicionales¶
Esta tarea puede realizarse individualmente o en parejas. Cada estudiante o equipo de trabajo debe ser capaz de explicar la solución que presenta.
Desarrollo¶
Debe desarrollar un cuaderno de notas Jupyter que describa la distribución de una especie (o de un grupo de especies) mediante texto (en Markdown), gráficos estadísticos y mapas. Los mapas (o el mapa) deben incluir registros de presencia y variables ambientales u otras capas raster relacionadas con la distribución de la especie. Utilice imágenes, referencias bibliográficas y cualquier otro elemento que considere apropiado. Puede trabajar el cuaderno de notas como un artículo científico que desea publicar.
El contenido del sitio debe ser coherente y estar bien presentado.
Calificación¶
- Coherencia y presentación general del documento: 20%
- Texto, imágenes y otros elementos en Markdown: 20%
- Gráficos estadísticos (al menos dos): 25%
- Mapas (uno o varios, deben presentar al menos una capa vectorial y una capa raster): 35%
Distribución de dos especies del genéro Lachesis¶
Las serpientes del genéro Lachesis son las serpientes venenosas más grandes del mundo y las más largas de América, así como la única vívora ovípara del nuevo mundo. Costa Rica cuenta con dos de las cuatro especies que componen este taxón ubicado solamente en el continente américano: L. stenophrys y L. melanocephala (Campbell & Lamar, 2004). L. melanocephala tiene la distribución más pequeña del genéro, ubicandose principlamente en en bosques primarios del Pacífico sur de Costa Rica y contando con unos pocos reportes en la zona adycente de Panamá, desde el nivel del mar hasta los 1600 msnm. Mientras que L. stenophrys, si bien también tiene una distribución que abarca solamente los bosques tropicales de estos dos mismos países, lo hace ea lo largo de la vertiente Caribe desde el nivel del mar hasta los 1000 msnm, siendo mucho más amplia (Solorzano, 2004; Barrio-Amorós et al.2020).
Para este proyecto se utilizan los registros de ambas especies provenientes de la base de datos GBIF, con el objetivo de comparar su distribución histórica con la zona que contempla el conjunto de datos obtenido.
Instalación y carga de las librerías¶
# Instalación de librerías externas
# Instalación de pygbif
!pip install pygbif --quiet
# Instalación de leafmap
!pip install leafmap --quiet
# Instalación de rasterio
!pip install rasterio --quiet
# Instalación de mapclassify
!pip install mapclassify --quiet
# Instalación de localtileserver
!pip install localtileserver --quiet
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))': /packages/4e/2e/8f4051119f460cfc786aa91f212165bb6e643283b533db572d7b33952bd2/requests_cache-1.2.1-py3-none-any.whl.metadata ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 70.2/70.2 kB 1.1 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.4/61.4 kB 2.9 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.4/66.4 kB 3.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 519.2/519.2 kB 9.2 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.1/219.1 kB 11.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.7/7.7 MB 18.9 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.4/41.4 kB 1.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 108.6/108.6 kB 7.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.7/2.7 MB 36.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 765.5/765.5 kB 26.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 194.2/194.2 kB 11.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 74.0/74.0 kB 4.2 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 40.4 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 22.2/22.2 MB 41.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 59.1/59.1 kB 2.3 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17.1/17.1 MB 68.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.8/2.8 MB 58.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 267.5/267.5 kB 18.3 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.8/52.8 kB 3.6 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.5/49.5 kB 3.6 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 195.0/195.0 kB 14.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.3/62.3 kB 4.5 MB/s eta 0:00:00
# Cargar las librerias y funciones requeridas
import pandas as pd
import geopandas as gpd
import requests
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import pyplot as plt
import seaborn as sns
import folium
from pygbif import occurrences
import plotly.express as px
import mapclassify
from shapely.geometry import box
import rasterio
import leafmap
import rasterio.plot
Carga desde GBIF de los registros de las dos especies del género Lachesis¶
# Lista de especies a buscar
especies = ["Lachesis stenophrys", "Lachesis melanocephala"]
# Parámetros de la solicitud
limite = 300 # Límite de registros por solicitud
registros_acumulados = [] # Lista para almacenar los datos
# Iterar sobre cada especie
for especie in especies:
offset = 0 # Reiniciar el offset para cada especie
while True:
# Solicitud a GBIF
res = occurrences.search(
scientificName=especie,
hasCoordinate=True,
hasGeospatialIssue=False,
limit=limite,
offset=offset
)
# Extraer resultados
registros = res.get("results", [])
# Si ya no hay registros, detener el ciclo
if not registros:
break
# Agregar un campo para saber a qué especie pertenece cada registro
for registro in registros:
registro["especie"] = especie
# Acumular los registros
registros_acumulados.extend(registros)
# Avanzar al siguiente "bloque" de registros
offset += limite
# Convertir la lista a un DataFrame de Pandas
presencia = pd.DataFrame(registros_acumulados)
# Mostrar la cantidad de registros recuperados
print(f"Se recuperaron {len(presencia)} registros en total para {len(especies)} especies.")
Se recuperaron 68 registros en total para 2 especies.
Convertir los registros de las especies en un Geodataframe¶
lachesis_gdf = gpd.GeoDataFrame(
presencia,
geometry=gpd.points_from_xy(presencia.decimalLongitude, presencia.decimalLatitude),
crs='EPSG:4326'
)
# Mostrar los primeros registros del GeoDataFrame,
# incluyendo la columna de geometría
lachesis_gdf[['gbifID', 'species', "country", 'decimalLongitude', 'decimalLatitude', 'geometry']].head()
gbifID | species | country | decimalLongitude | decimalLatitude | geometry | |
---|---|---|---|---|---|---|
0 | 4919343745 | Lachesis stenophrys | Costa Rica | -83.959764 | 10.123897 | POINT (-83.95976 10.1239) |
1 | 4847005386 | Lachesis stenophrys | Panama | -82.624403 | 9.395077 | POINT (-82.6244 9.39508) |
2 | 4908287768 | Lachesis stenophrys | Costa Rica | -83.190580 | 9.926262 | POINT (-83.19058 9.92626) |
3 | 4516779603 | Lachesis stenophrys | Costa Rica | -83.191955 | 9.923597 | POINT (-83.19196 9.9236) |
4 | 4153810889 | Lachesis stenophrys | Costa Rica | -83.713389 | 10.134488 | POINT (-83.71339 10.13449) |
Manejo y preprocesado del Geodataframe¶
#Creamos un nuevo dataframe seleccionando las columnas de nuestro interés
lachesis = lachesis_gdf[['gbifID', 'species', 'country', 'decimalLongitude', 'decimalLatitude', 'geometry']]
print("\nNuevo DataFrame")
print(lachesis)
Nuevo DataFrame gbifID species country decimalLongitude \ 0 4919343745 Lachesis stenophrys Costa Rica -83.959764 1 4847005386 Lachesis stenophrys Panama -82.624403 2 4908287768 Lachesis stenophrys Costa Rica -83.190580 3 4516779603 Lachesis stenophrys Costa Rica -83.191955 4 4153810889 Lachesis stenophrys Costa Rica -83.713389 .. ... ... ... ... 63 1145541042 Lachesis melanocephala Costa Rica -82.976026 64 686809207 Lachesis melanocephala Costa Rica -83.498067 65 657496906 Lachesis melanocephala Costa Rica -83.164734 66 657497044 Lachesis melanocephala Costa Rica -83.333333 67 657496834 Lachesis melanocephala Costa Rica -82.933333 decimalLatitude geometry 0 10.123897 POINT (-83.95976 10.1239) 1 9.395077 POINT (-82.6244 9.39508) 2 9.926262 POINT (-83.19058 9.92626) 3 9.923597 POINT (-83.19196 9.9236) 4 10.134488 POINT (-83.71339 10.13449) .. ... ... 63 8.654392 POINT (-82.97603 8.65439) 64 8.632006 POINT (-83.49807 8.63201) 65 8.715333 POINT (-83.16473 8.71533) 66 9.166667 POINT (-83.33333 9.16667) 67 8.650000 POINT (-82.93333 8.65) [68 rows x 6 columns]
# Mostramos los valores nulos para cada columna
print("Valores nulos por columna:")
print(lachesis.isnull().sum())
Valores nulos por columna: gbifID 0 species 0 country 0 decimalLongitude 0 decimalLatitude 0 geometry 0 dtype: int64
# Observamos los países donde existen registros de las dos especies seleccionadas
paises = lachesis['country'].unique()
print(paises)
['Costa Rica' 'Panama']
Creación del Geodataframe con el mapa vectorial base y vizualización de la distribución de las especies¶
# Observamos el comportamiento de los registros
lachesis.plot()
<Axes: >
# URL de países en Natural Earth
url = "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_admin_1_states_provinces.geojson"
# Cargar el GeoJSON
provincias = gpd.read_file(url)
# Filtrar Costa Rica y Panamá
mapa_base = provincias[provincias["admin"].isin(["Costa Rica", "Panama"])]
# Visualizar
mapa_base.plot(column="name", color="white", edgecolor="black", legend=False)
<Axes: >
# Visualizamos los registros sobre la capa vectorial
# Crear figura y ejes
fig, ax = plt.subplots(figsize=(10, 8))
# Capa base de países de América
mapa_base.plot(ax=ax, color="white", edgecolor="black")
# Filtrar registros en el hemisferio occidental
lachesis_filtrado = lachesis[(lachesis["decimalLongitude"] < 0) & (lachesis["decimalLatitude"] < 16)]
# Graficar los registros con diferentes colores según la especie
sns.scatterplot(
data=lachesis_filtrado,
x="decimalLongitude",
y="decimalLatitude",
hue="species",
palette="Set1", # Cambia el estilo de colores si deseas
edgecolor="black",
s=50, # Tamaño de los puntos
ax=ax
)
# Agregar título y leyenda
plt.title("Registros de dos especies del género Lachesis en Costa Rica y Panamá")
plt.xlabel("Longitud decimal")
plt.ylabel("Latitud decimal")
plt.legend(title="Especie")
# Mostrar gráfico
plt.show()
Unión espacial de los geodataframes¶
mapa_base.set_index('name', inplace=True)
# Unión (join) espacial
lachesis_mapa_base = lachesis.sjoin(
mapa_base,
predicate='intersects'
)
lachesis[['gbifID', 'species', 'country', 'decimalLongitude', 'decimalLatitude', 'geometry']].head()
gbifID | species | country | decimalLongitude | decimalLatitude | geometry | |
---|---|---|---|---|---|---|
0 | 4919343745 | Lachesis stenophrys | Costa Rica | -83.959764 | 10.123897 | POINT (-83.95976 10.1239) |
1 | 4847005386 | Lachesis stenophrys | Panama | -82.624403 | 9.395077 | POINT (-82.6244 9.39508) |
2 | 4908287768 | Lachesis stenophrys | Costa Rica | -83.190580 | 9.926262 | POINT (-83.19058 9.92626) |
3 | 4516779603 | Lachesis stenophrys | Costa Rica | -83.191955 | 9.923597 | POINT (-83.19196 9.9236) |
4 | 4153810889 | Lachesis stenophrys | Costa Rica | -83.713389 | 10.134488 | POINT (-83.71339 10.13449) |
Creación de gráficos estadísticos¶
# Cantidad de registros de ambas especies por país
# 1. Contar registros por país
lachesis_registros_pais = lachesis_mapa_base['country'].value_counts()
# 2. Generar un gráfico de barras
plt.figure(figsize=(10, 6))
lachesis_registros_pais.plot(kind='bar')
# 3. Título y etiquetas
plt.title('Cantidad de registros por país')
plt.xlabel('País')
plt.ylabel('Cantidad de registros')
plt.xticks(rotation=0)
# 4. Mostrar el gráfico
plt.show()
# cantidad de registros de cada especie por país
# 1. Agrupar por provincia y especie
registros_x_provincia = lachesis_mapa_base.groupby(["name", "species"]).size().unstack()
# 2. Crear el gráfico de barras agrupadas
registros_x_provincia.plot(kind="bar", figsize=(12, 5), color=["darkgreen", "brown"])
# 3. Personalizar el gráfico
plt.title("Cantidad de registros por provincia y especie")
plt.xlabel("Provincia")
plt.ylabel("Cantidad de registros")
plt.legend(title="Especie") # Agregar leyenda
plt.xticks(rotation=0) # Rotar nombres de provincias para mejor lectura
# 4. Mostrar el gráfico
plt.show()
Vizualización de los registros en un mapa interactivo¶
# Crear un mapa base
m = lachesis_mapa_base.explore(
column='species',
name='Riqueza de especies de Agalychnis',
cmap='OrRd',
tooltip=['name', 'species', "country"],
legend=True,
legend_kwds={
'caption': "Riqueza de especies de Lachesis",
'orientation': "horizontal"
},
)
# Crear una paleta de colores para cada especie
species_unique = lachesis['species'].unique()
cmap = plt.get_cmap("tab10", len(species_unique))
species_colors = {species: f'#{int(cmap(i)[0]*255):02x}{int(cmap(i)[1]*255):02x}{int(cmap(i)[2]*255):02x}'
for i, species in enumerate(species_unique)}
# Añadir registros con popups detallados
for _, row in lachesis_mapa_base.iterrows():
color = species_colors[row['species']] # Obtener el color asignado a la especie
# Crear un popup con múltiples columnas
popup_content = f"""
<b>Especie:</b> {row['species']}<br>
<b>Provincia:</b> {row['name']}<br>
<b>País:</b> {row['country']}<br>
<b>Coordenadas:</b> {row.geometry.y}, {row.geometry.x}
"""
folium.CircleMarker(
location=[row.geometry.y, row.geometry.x],
radius=5,
color=color,
fill=True,
fill_color=color,
fill_opacity=0.7,
popup=folium.Popup(popup_content, max_width=300) # Agregar popup formateado
).add_to(m)
# Agregar un control de capas al mapa
folium.LayerControl().add_to(m)
# Mostrar el mapa interactivo
m
Despliegue de una capa raster de interés¶
# Lectura de datos de temperatura media anual
temperatura_media_anual = rasterio.open(
'https://github.com/datos-geoespaciales-biodiversidad/python/raw/refs/heads/main/datos/clima/worldclim/2.1-10m-bio/wc2.1_10m_bio_1.tif'
)
# Crear un objeto Map de leafmap
m = leafmap.Map(
height="400px",
center=[temperatura_media_anual.bounds.bottom, temperatura_media_anual.bounds.left],
zoom=7
)
# Agregar el raster al mapa
m.add_raster(
'https://github.com/datos-geoespaciales-biodiversidad/python/raw/refs/heads/main/datos/clima/worldclim/2.1-10m-bio/wc2.1_10m_bio_1.tif',
colormap='viridis',
layer_name='Temperatura media anual'
)
# Desplegar el mapa
m
Map(center=[0.0, 0.0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_t…
# Observamos todos los metadatos de la capa
temperatura_media_anual.meta
{'driver': 'GTiff', 'dtype': 'float32', 'nodata': -3.3999999521443642e+38, 'width': 2160, 'height': 1080, 'count': 1, 'crs': CRS.from_wkt('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'), 'transform': Affine(0.16666666666666666, 0.0, -180.0, 0.0, -0.16666666666666666, 90.0)}
# Mapa de temperatura media anual
rasterio.plot.show(temperatura_media_anual)
<Axes: >
Despliegue del mapa interactivo junto con una capa ráster de nuestro interés¶
# Obtener las coordenadas del primer registro de especie (supuesto que es una capa de puntos)
# Si quieres centrar en todos los registros, puedes calcular el centroide de todos los puntos.
centroid = lachesis.geometry.unary_union.centroid
# Extraer las coordenadas del centro
lat = centroid.y
lon = centroid.x
# Crear el mapa de Leafmap centrado en el sitio de los registros
m = leafmap.Map(
height="400px",
center=[lat, lon],
zoom=7
)
# Agregar el raster al mapa
m.add_raster(
'https://github.com/datos-geoespaciales-biodiversidad/python/raw/refs/heads/main/datos/clima/worldclim/2.1-10m-bio/wc2.1_10m_bio_1.tif',
colormap='viridis',
layer_name='Temperatura media anual'
)
# Agregar los registros de especies al mapa (si es una capa de puntos)
m.add_gdf(lachesis, layer_name="Registros de especies")
# Desplegar el mapa
m
Map(center=[0.0, 0.0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_t…