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:

  1. Desarrollar programas en el lenguaje de programación Python orientados al procesamiento de datos geoespaciales de biodiversidad.
  2. 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.
  3. Desarrollar soluciones reproducibles a problemas computacionales mediante Python.
  4. Integrar visualizaciones tabulares, gráficas y geoespaciales de datos de biodiversidad en documentos y aplicaciones interactivas desarrolladas en Python.

Entregables¶

  1. 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%

Desarrollo del proyecto¶

Estudiantes:

Sylvia Rodríguez Abarca

Hersson Ramírez Molina

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¶

In [ ]:
# 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
In [ ]:
# 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¶

In [ ]:
# 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¶

In [ ]:
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()
Out[ ]:
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¶

In [ ]:
#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]
In [ ]:
# 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
In [ ]:
# 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¶

In [ ]:
# Observamos el comportamiento de los registros
lachesis.plot()
Out[ ]:
<Axes: >
No description has been provided for this image
In [ ]:
# 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)
Out[ ]:
<Axes: >
No description has been provided for this image
In [ ]:
# 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()
No description has been provided for this image

Unión espacial de los geodataframes¶

In [ ]:
mapa_base.set_index('name', inplace=True)
In [ ]:
# Unión (join) espacial
lachesis_mapa_base = lachesis.sjoin(
    mapa_base,
    predicate='intersects'
)

lachesis[['gbifID', 'species', 'country', 'decimalLongitude', 'decimalLatitude', 'geometry']].head()
Out[ ]:
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¶

In [ ]:
# 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()
No description has been provided for this image
In [ ]:
# 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()
No description has been provided for this image

Vizualización de los registros en un mapa interactivo¶

In [ ]:
# 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
Out[ ]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Despliegue de una capa raster de interés¶

In [ ]:
# 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'
)
In [ ]:
# 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…
In [ ]:
# Observamos todos los metadatos de la capa
temperatura_media_anual.meta
Out[ ]:
{'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)}
In [ ]:
# Mapa de temperatura media anual
rasterio.plot.show(temperatura_media_anual)
No description has been provided for this image
Out[ ]:
<Axes: >

Despliegue del mapa interactivo junto con una capa ráster de nuestro interés¶

In [ ]:
# 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…