Descripción del proyecto y justificación:¶
El presente proyecto trata sobre la exploración y análisis de un bosque secundario ubicado en la provincia de Cartago, Cantón Turrialba. El bosque se encuentra en la finca privada de el CATIE y tiene una edad aproximada de 30 años.
La base de datos proviene de la investigación a largo plazo del CATIE, en donde busca implementar y dar seguimiento a una red de parcelas permanentes en todo el país. La base de datos a estudiar incluye información de diámetros a la altura del pecho e identificación taxonómica de distintos individuos del bosque.
La importancia del estudio de la ciencia de datos y bosques secundarios radica en que son ecosistemas importantes para la conservación de la biodiversidad, por su potencial para albergar especies de bosques primarios, proveer hábitat para fauna (Chazdon et al. 2009; Turner et al. 1997) y provisión de servicios ecosistémicos (Chokkalingam et al. 2001) por lo que su monitoreo constante es esencial para observar sus dinámicas y asegurarnos que están recuperándose y no degradándose.
La exploración y análisis de la base de datos puede darle un mejor panorama a CATIE de la situación del bosque Florencia, además de proponer otro tipo de mediciones para su mejor comprensión, como por ejemplo, la incorporación de análisis de suelos o mediciones de rasgos funcionales de especies vegetales.
1. Antecendentes¶
Los bosques tropicales son ecosistemas importantes que contribuyen al bienestar de las personas, especialmente en países en vías de desarrollo, ya que proveen servicios ecosistémicos que contribuyen a la seguridad alimentaria, reducción del riesgo de desastres, reducción de enfermedades y oportunidad de generar ingresos (Brandon, 2015; Mullan, 2015). Además, contribuyen a mitigar el cambio climático a través del almacenamiento de más del 25 % del carbono del planeta, siendo los bosques tropicales de Latinoamérica los que almacenan el 49 % de la biomasa de carbono (Brandon, 2015; Saatchi et al., 2011). Sin embargo, la deforestación y degradación de estos ecosistemas se ha intensificado gracias a distintas perturbaciones, afectando la provisión de servicios ecosistémicos (Eguiguren et al., 2019; Malhi et al., 2014). Desde 1990 al 2020, se han deforestado 368 millones de hectáreas de bosques tropicales y se estima que el área de bosques degradados puede ser mayor (FAO, 2020). El monitoreo de los bosques pueden proporcionar información sobre el estado del bosque para los tomadores de decisiones, ya que es posible relacionarlo con la capacidad de adaptación al cambio climático y provisión de servicios ecosistémicos del sitio (Acharya et al., 2011), así como la elección y justificación de medidas de restauración (Putz & Romero, 2014) y pudiendo incorporar a los bosques degradados en mecanismos de REDD+, pago por servicios ambientales y otras estrategias para recuperar servicios ecosistémicos.
El monitoreo de bosques tropicales es objeto de la ciencia de los datos, debido a que se realizan mediciones de atributos cuantitativos como cualitativos en las distintas formas de vida que se encuentran en los bosques a través del tiempo. Por lo que en una unidad muestral, podemos encontrarnos con una cantidad significativa de datos, especialmente en los trópicos, en donde la variación de especies es mucho mayor que en los climas templados y boreales(Wright, 2010). Ganivet & Bloomberg (2019) expresan que los datos de las especies de bosques tanto tomados en campo como con sensores remotos son esenciales para que los tomadores de decisiones puedan implementar planes de manejo o intervención adecuados.
Sin embargo, la recolección y análisis de datos para el monitoreo de los bosques resulta una tarea compleja, la cual implica recursos y habilidades específicas. De acuerdo con los resultados de la investigación de Misiukas et al. (2021), en países tropicales, las capacidades de reporte de datos pueden ser desafiantes aunque la mayoría de países se experimentan importantes impactos antropogénicos hacia los bosques, causando una mayor necesidad de monitorearlos. Adicionalmente, en este estudio, se evidencia que la recolección y análisis de información respecto a los bosques es tan pobre en Centroamérica, que los autores no pudieron ejecutar el análisis en dicha área del trópico.
De ahí, surge la necesidad que la academia en Centroamérica pueda emplear la ciencia de datos para el monitoreo de bosques. Ejemplo de ello son estudios como el de Gerwing (2002) realizado en la cuenca del Amazonas, en donde evaluó atributos de árboles y su relación con la intensidad de distintas perturbaciones como extracción de madera y fuego. Por lo que su investigación analizó datos no solo del bosque sino también del regimen de impacto.
Otro ejemplo del manejo de datos es el del estudio de Longo et al. (2016) también realizado en el Amazonas, en donde unió los datos de 9 inventarios forestales de distintos sitios, con una muestra total de 359 parcelas para estimar la biomasa en bosques con y sin indicios de degradación. Los resultados resaltaron la importancia del monitreo de bosques para conocer las reservas de carbono y su contribución al cambio climático.
Artículos mencionados que emplean a la ciencia de datos para el monitoreo de bosques:
Ganivet, E., & Bloomberg, M. (2019). Towards rapid assessments of tree species diversity and structure in fragmented tropical forests: A review of perspectives offered by remotely-sensed and field-based data. In Forest Ecology and Management (Vol. 432). https://doi.org/10.1016/j.foreco.2018.09.003
Gerwing, J. J. (2002). Degradation of forests through logging and fire in the eastern Brazilian Amazon. Forest Ecology and Management, 157(1–3). https://doi.org/10.1016/S0378-1127(00)00644-7
Longo, M., Keller, M., dos-Santos, M. N., Leitold, V., Pinagé, E. R., Baccini, A., Saatchi, S., Nogueira, E. M., Batistella, M., & Morton, D. C. (2016). Aboveground biomass variability across intact and degraded forests in the Brazilian Amazon. Global Biogeochemical Cycles, 30(11). https://doi.org/10.1002/2016GB005465
Misiukas, J. M., Carter, S., & Herold, M. (2021). Tropical forest monitoring: Challenges and recent progress in research. Remote Sensing, 13(12). https://doi.org/10.3390/rs13122252
2. Descripción del problema y objetivo:¶
CATIE ha realizado una labor invaluable en la restauración y monitoreo de bosques secundarios en Costa Rica, en este caso específico, en el cantón Turrialba, provincia de Cartago, en donde se ha estudiado de manera consecutiva al bosque secundario "Florencia". Sin embargo, después de tomar los datos en campo, nadie se ha dado a la tarea de analizar la estructura de los datos ni tampoco procesarlos para obtener algún tipo de indicador de biodiversidad o estructura del bosque tomando en cuenta la dinámica del bosque a través del tiempo. Únicamente se han procesado datos de la última medición tomada en el año 2021, dejando por un lado al resto de los datos.
Por ello, el objetivo de este proyecto será caracterizar a los datos tomados en las parcelas de monitoreo permanente durante tres años consecutivos en el bosque secundario Florencia. Además de realizar este análisis exploratorio con el paquete Pandas y Pandas profiling, también se corregirán los datos faltantas y se calcularán algunos indicadores sobre la condición del bosque (estructura y biodiversidad) a través del tiempo.
3. Descripción del conjunto de datos:¶
El sitio de estudio es el bosque secundario Florencia, ubicado en Turrialba, Cartago, Costa Rica. El bosque posee más de 30 años desde su regeneración y CATIE ha tomado datos de diámetro a la altura del pecho en milímetros, ubicación e identificación taxonómica de cada individuo dentro de cada una de las 12 parcelas de 0.25 hectáreas establecidas en el sitio de estudio. Las mediciones se realizaron en los años 2018, 2019 y 2020.
La base de datos proporcionada posee más de 8,000 registros y solo poseen una publicación hasta la fecha (Camacho et al., 2021) pero sin tomar en cuenta la dinámica entre las mediciones. Por lo que esta presentación del conjunto de datos mostrará información inédita.
La base de datos posee diversas columnas, de las cuales solo se utilizarán las siguientes:
Parcela: número de parcela dentro del sitio evaluado
Subparcela: número de subparcela, perteneciente a una determinada parcela, dentro del sitio evaluado.
Numero_arbol: número del árbol dentro de la parcela.
Genero: genero taxonómico del individuo
Especie: especie taxonómica del individuo
Forma de vida: tipo de forma de vida del individuo la cual puede ser
- Individuo con género y especie desconocida
- Árboles
- Palmas
- Plantas de la familia Musaceae (bananos)
- No existen individuos con esta numeración en la base de datos
- Hierbas
- Árboles
- Eje: número de eje del árbol. Un árbol puede tener uno o más ejes.
- Numero_medicion: número de medición del árbol
- año 2018
- año 2019
- año 2020
- Dap(mm): medición del diámetro a la altura del pecho del eje, correspondiente a cada individuo.
4. Procesamiento y visualización de datos:¶
!pip install pandas
!pip install ydata-profiling
Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (2.0.3) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.10/dist-packages (from pandas) (2.8.2) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas) (2023.4) Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas) (2024.1) Requirement already satisfied: numpy>=1.21.0 in /usr/local/lib/python3.10/dist-packages (from pandas) (1.25.2) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.2->pandas) (1.16.0) Collecting ydata-profiling Downloading ydata_profiling-4.8.3-py2.py3-none-any.whl (359 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 359.5/359.5 kB 7.7 MB/s eta 0:00:00 Requirement already satisfied: scipy<1.14,>=1.4.1 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (1.11.4) Requirement already satisfied: pandas!=1.4.0,<3,>1.1 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (2.0.3) Requirement already satisfied: matplotlib<3.9,>=3.2 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (3.7.1) Requirement already satisfied: pydantic>=2 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (2.7.2) Requirement already satisfied: PyYAML<6.1,>=5.0.0 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (6.0.1) Requirement already satisfied: jinja2<3.2,>=2.11.1 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (3.1.4) Collecting visions[type_image_path]<0.7.7,>=0.7.5 (from ydata-profiling) Downloading visions-0.7.6-py3-none-any.whl (104 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 104.8/104.8 kB 12.2 MB/s eta 0:00:00 Requirement already satisfied: numpy<2,>=1.16.0 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (1.25.2) Collecting htmlmin==0.1.12 (from ydata-profiling) Downloading htmlmin-0.1.12.tar.gz (19 kB) Preparing metadata (setup.py) ... done Collecting phik<0.13,>=0.11.1 (from ydata-profiling) Downloading phik-0.12.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (686 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 686.1/686.1 kB 41.8 MB/s eta 0:00:00 Requirement already satisfied: requests<3,>=2.24.0 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (2.31.0) Requirement already satisfied: tqdm<5,>=4.48.2 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (4.66.4) Requirement already satisfied: seaborn<0.14,>=0.10.1 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (0.13.1) Collecting multimethod<2,>=1.4 (from ydata-profiling) Downloading multimethod-1.11.2-py3-none-any.whl (10 kB) Requirement already satisfied: statsmodels<1,>=0.13.2 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (0.14.2) Collecting typeguard<5,>=3 (from ydata-profiling) Downloading typeguard-4.3.0-py3-none-any.whl (35 kB) Collecting imagehash==4.3.1 (from ydata-profiling) Downloading ImageHash-4.3.1-py2.py3-none-any.whl (296 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 296.5/296.5 kB 26.8 MB/s eta 0:00:00 Requirement already satisfied: wordcloud>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (1.9.3) Collecting dacite>=1.8 (from ydata-profiling) Downloading dacite-1.8.1-py3-none-any.whl (14 kB) Requirement already satisfied: numba<1,>=0.56.0 in /usr/local/lib/python3.10/dist-packages (from ydata-profiling) (0.58.1) Requirement already satisfied: PyWavelets in /usr/local/lib/python3.10/dist-packages (from imagehash==4.3.1->ydata-profiling) (1.6.0) Requirement already satisfied: pillow in /usr/local/lib/python3.10/dist-packages (from imagehash==4.3.1->ydata-profiling) (9.4.0) Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2<3.2,>=2.11.1->ydata-profiling) (2.1.5) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (1.2.1) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (4.52.4) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (1.4.5) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (24.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (3.1.2) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib<3.9,>=3.2->ydata-profiling) (2.8.2) Requirement already satisfied: llvmlite<0.42,>=0.41.0dev0 in /usr/local/lib/python3.10/dist-packages (from numba<1,>=0.56.0->ydata-profiling) (0.41.1) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas!=1.4.0,<3,>1.1->ydata-profiling) (2023.4) Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas!=1.4.0,<3,>1.1->ydata-profiling) (2024.1) Requirement already satisfied: joblib>=0.14.1 in /usr/local/lib/python3.10/dist-packages (from phik<0.13,>=0.11.1->ydata-profiling) (1.4.2) Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic>=2->ydata-profiling) (0.7.0) Requirement already satisfied: pydantic-core==2.18.3 in /usr/local/lib/python3.10/dist-packages (from pydantic>=2->ydata-profiling) (2.18.3) Requirement already satisfied: typing-extensions>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from pydantic>=2->ydata-profiling) (4.12.0) Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2.24.0->ydata-profiling) (3.3.2) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2.24.0->ydata-profiling) (3.7) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2.24.0->ydata-profiling) (2.0.7) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2.24.0->ydata-profiling) (2024.2.2) Requirement already satisfied: patsy>=0.5.6 in /usr/local/lib/python3.10/dist-packages (from statsmodels<1,>=0.13.2->ydata-profiling) (0.5.6) Requirement already satisfied: attrs>=19.3.0 in /usr/local/lib/python3.10/dist-packages (from visions[type_image_path]<0.7.7,>=0.7.5->ydata-profiling) (23.2.0) Requirement already satisfied: networkx>=2.4 in /usr/local/lib/python3.10/dist-packages (from visions[type_image_path]<0.7.7,>=0.7.5->ydata-profiling) (3.3) Requirement already satisfied: six in /usr/local/lib/python3.10/dist-packages (from patsy>=0.5.6->statsmodels<1,>=0.13.2->ydata-profiling) (1.16.0) Building wheels for collected packages: htmlmin Building wheel for htmlmin (setup.py) ... done Created wheel for htmlmin: filename=htmlmin-0.1.12-py3-none-any.whl size=27080 sha256=28b05d0254387fb0f72e8dfdea69043d2bb291fa0d90f1f17ceb07e1c16bfae2 Stored in directory: /root/.cache/pip/wheels/dd/91/29/a79cecb328d01739e64017b6fb9a1ab9d8cb1853098ec5966d Successfully built htmlmin Installing collected packages: htmlmin, typeguard, multimethod, dacite, imagehash, visions, phik, ydata-profiling Successfully installed dacite-1.8.1 htmlmin-0.1.12 imagehash-4.3.1 multimethod-1.11.2 phik-0.12.4 typeguard-4.3.0 visions-0.7.6 ydata-profiling-4.8.3
# Cargo las bibliotecas necesarias
import numpy as np
import pandas as pd
import seaborn as sns
import random
import matplotlib.pyplot as plt
from ydata_profiling import ProfileReport
# Importo mi base de datos
df=pd.read_csv('/content/BD Bosque Secundario Florencia.csv')
print(df)
Identificador de experimento Nombre del experimento \ 0 8 Bosque secundario Florencia 1 8 Bosque secundario Florencia 2 8 Bosque secundario Florencia 3 8 Bosque secundario Florencia 4 8 Bosque secundario Florencia ... ... ... 9072 8 Bosque secundario Florencia 9073 8 Bosque secundario Florencia 9074 8 Bosque secundario Florencia 9075 8 Bosque secundario Florencia 9076 8 Bosque secundario Florencia Nombre del bosque Area_bosque_ha Registro de la parcela Parcela \ 0 CATIE 30 118 1 1 CATIE 30 118 1 2 CATIE 30 118 1 3 CATIE 30 118 1 4 CATIE 30 118 1 ... ... ... ... ... 9072 CATIE 30 129 12 9073 CATIE 30 129 12 9074 CATIE 30 129 12 9075 CATIE 30 129 12 9076 CATIE 30 129 12 Nombre de la parcela Altitud de la parcela y_WGS x_WGS \ 0 Bosque secundario Florencia 715 -83.671481 9.881741 1 Bosque secundario Florencia 715 -83.671481 9.881741 2 Bosque secundario Florencia 715 -83.671481 9.881741 3 Bosque secundario Florencia 715 -83.671481 9.881741 4 Bosque secundario Florencia 715 -83.671481 9.881741 ... ... ... ... ... 9072 Bosque secundario Florencia 598 -83.661325 9.874750 9073 Bosque secundario Florencia 598 -83.661325 9.874750 9074 Bosque secundario Florencia 598 -83.661325 9.874750 9075 Bosque secundario Florencia 598 -83.661325 9.874750 9076 Bosque secundario Florencia 598 -83.661325 9.874750 ... Especie Forma de vida Identificador subparcela numero_arbol eje \ 0 ... meiantha 7 46196 0 1 1 1 ... elastica 7 46197 0 2 1 2 ... lucidula 7 46198 0 3 1 3 ... pubivena 7 46199 0 4 1 4 ... pinnata 7 46200 0 5 1 ... ... ... ... ... ... ... .. 9072 ... insignis 2 50343 44 7 1 9073 ... koschnyi 7 50344 44 8 1 9074 ... koschnyi 7 50345 44 9 1 9075 ... insignis 2 50346 44 10 1 9076 ... pubivena 7 50347 44 11 1 Identificador de fecha de medicion numero_medicion Fecha dap (mm) 0 574 1 14/02/2018 570.0 1 574 1 14/02/2018 157.0 2 574 1 14/02/2018 57.0 3 574 1 14/02/2018 90.0 4 574 1 14/02/2018 103.0 ... ... ... ... ... 9072 678 3 23/11/2020 54.0 9073 678 3 23/11/2020 11.0 9074 678 3 23/11/2020 35.0 9075 678 3 23/11/2020 32.0 9076 678 3 23/11/2020 59.0 [9077 rows x 27 columns]
Pasos previos¶
# Selecciono solo las columnas que me sean útiles
print("Antes:", df.columns, "\n")
df = df[['Parcela', 'Genero', 'Especie', 'Forma de vida', 'subparcela','numero_arbol', 'eje', 'numero_medicion', 'dap (mm)']]
print("Después:", df.columns)
print(df)
Antes: Index(['Identificador de experimento', 'Nombre del experimento', 'Nombre del bosque', 'Area_bosque_ha', 'Registro de la parcela', 'Parcela', 'Nombre de la parcela', 'Altitud de la parcela', 'y_WGS', 'x_WGS', 'lat_CRTM05', 'long_CRTM05', 'Tratamiento', 'Area_parcela_ha', 'Cod_sp', 'Familia', 'Genero', 'Especie', 'Forma de vida', 'Identificador', 'subparcela', 'numero_arbol', 'eje', 'Identificador de fecha de medicion', 'numero_medicion', 'Fecha', 'dap (mm)'], dtype='object') Después: Index(['Parcela', 'Genero', 'Especie', 'Forma de vida', 'subparcela', 'numero_arbol', 'eje', 'numero_medicion', 'dap (mm)'], dtype='object') Parcela Genero Especie Forma de vida subparcela numero_arbol \ 0 1 Goethalsia meiantha 7 0 1 1 1 Castilla elastica 7 0 2 2 1 Cordia lucidula 7 0 3 3 1 Sorocea pubivena 7 0 4 4 1 Amyris pinnata 7 0 5 ... ... ... ... ... ... ... 9072 12 Ruagea insignis 2 44 7 9073 12 Virola koschnyi 7 44 8 9074 12 Virola koschnyi 7 44 9 9075 12 Ruagea insignis 2 44 10 9076 12 Sorocea pubivena 7 44 11 eje numero_medicion dap (mm) 0 1 1 570.0 1 1 1 157.0 2 1 1 57.0 3 1 1 90.0 4 1 1 103.0 ... ... ... ... 9072 1 3 54.0 9073 1 3 11.0 9074 1 3 35.0 9075 1 3 32.0 9076 1 3 59.0 [9077 rows x 9 columns]
Modifico mi base de datos para crear nuevas columnas¶
Mi base de datos carece de una columna que sea un "identificador único" de cada individuo, por ello, crearé una.
# Modificar las columas
# Creo un identificador único por individuo
df['ID'] = 'IND' + '_' + df['Parcela'].astype(str) + '_' + df['subparcela'].astype(str) + '_' + df['numero_arbol'].astype(str)
# Creo un identificador único por eje
df['ID_eje'] = 'IND' + '_' + df['Parcela'].astype(str) + '_' + df['subparcela'].astype(str) + '_' + df['numero_arbol'].astype(str) + '_' + df['eje'].astype(str)
# Uno las columnas de género y especie para tener el nombre completo
df['nombre_cientifico'] = df['Genero'] + ' ' + df['Especie']
print(df)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 0 1 Goethalsia meiantha 7 0 1 1 1 Castilla elastica 7 0 2 2 1 Cordia lucidula 7 0 3 3 1 Sorocea pubivena 7 0 4 4 1 Amyris pinnata 7 0 5 ... ... ... ... ... ... ... 9072 12 Ruagea insignis 2 44 7 9073 12 Virola koschnyi 7 44 8 9074 12 Virola koschnyi 7 44 9 9075 12 Ruagea insignis 2 44 10 9076 12 Sorocea pubivena 7 44 11 eje numero_medicion dap (mm) ID ID_eje \ 0 1 1 570.0 IND_1_0_1 IND_1_0_1_1 1 1 1 157.0 IND_1_0_2 IND_1_0_2_1 2 1 1 57.0 IND_1_0_3 IND_1_0_3_1 3 1 1 90.0 IND_1_0_4 IND_1_0_4_1 4 1 1 103.0 IND_1_0_5 IND_1_0_5_1 ... ... ... ... ... ... 9072 1 3 54.0 IND_12_44_7 IND_12_44_7_1 9073 1 3 11.0 IND_12_44_8 IND_12_44_8_1 9074 1 3 35.0 IND_12_44_9 IND_12_44_9_1 9075 1 3 32.0 IND_12_44_10 IND_12_44_10_1 9076 1 3 59.0 IND_12_44_11 IND_12_44_11_1 nombre_cientifico 0 Goethalsia meiantha 1 Castilla elastica 2 Cordia lucidula 3 Sorocea pubivena 4 Amyris pinnata ... ... 9072 Ruagea insignis 9073 Virola koschnyi 9074 Virola koschnyi 9075 Ruagea insignis 9076 Sorocea pubivena [9077 rows x 12 columns]
Visualización de los registros del DAP¶
Con anticipación, se informó que está base de datos del bosque Florencia podía contener algunos registros "anormales" es la columna llamada dap (mm). Estos registros pueden ser igual a 0, lo que indica que el individuo estaba muerto al momento de la medición y también valores "NA" los cuales requieren de su eliminación o corrección según sea el caso. Para ello, cuantificaremos los registros anómalos para DAP con Pandas.
# Identifico los DAP con valores igual a cero en mi base de datos
valores_cero = df['dap (mm)'] == 0
conteo_cero = df.loc[valores_cero, ['ID_eje', 'numero_medicion']].value_counts()
print(conteo_cero)
ID_eje numero_medicion IND_10_10_3_1 2 1 IND_2_31_8_2 2 1 IND_6_24_7_1 2 1 IND_6_24_3_1 2 1 IND_6_23_5_1 1 1 .. IND_11_43_11_1 3 1 IND_11_43_10_1 3 1 IND_11_42_3_1 2 1 IND_11_42_1_1 2 1 IND_9_4_9_1 2 1 Name: count, Length: 417, dtype: int64
# Identifico los DAP con valores NA en mi base de datos
valores_nulos = df['dap (mm)'].isna()
conteo_nulos = df.loc[valores_nulos, ['ID_eje', 'numero_medicion']].value_counts()
print(conteo_nulos)
ID_eje numero_medicion IND_1_10_2_1 2 1 IND_1_13_3_2 2 1 IND_1_1_1_2 1 1 IND_1_1_7_1 2 1 IND_1_23_3_1 2 1 IND_1_30_1_1 2 1 IND_1_3_6_1 1 1 IND_7_41_6_1 3 1 IND_8_0_3_4 2 1 IND_8_3_12_1 2 1 IND_8_41_6_1 3 1 IND_9_10_21_1 2 1 IND_9_33_10_1 2 1 Name: count, dtype: int64
Según la información mostrada gracias a Pandas, observamos que existen diversos registros con mediciones de DAP igual a cero. Así mismo, existen varias con valores NA en diversas mediciones a través del tiempo.
Corrijo datos faltantes de DAP¶
Para ello, identifico aquellas mediciones que posean NA en el dap. Dependiendo del número de medición, tomaré una decisión:
Si el NA se encuentra en la primera medición, elimino el registro, ya que no es posible saber si ese árbol realmente existía en esa primera medición o apareció hasta la siguiente..
Si el NA se encuentra en la segunda medición, y el árbol posee mediciones en la primera y tercera medición, se realizará un promedio.
Si el NA se encuentra en la segunda medición y no tiene medición anterior o siguiente, se eliminará el registro ya que es posible que el árbol pueda haber muerto.
Si el NA se encuentra en la tercera medición, se borrará el registro, ya que es probable de que haya muerto.
Es importante tomar en cuenta que estas reglas aplican para este conjunto de datos, los cuales poseen 3 mediciones. Si en el futuro se toman más mediciones, las reglas podrían cambiar.
# Elimino los registros con DAP igual a cero, ya que son indicativos de que el árbol murió
df = df[df['dap (mm)'] != 0]
# Crear una columna "reparado" para almacenar los resultados reparados
df['reparado'] = None
# Aplicamos las condicionales para establecer las reglas o decisiones que establecimos anteriormente
for index, row in df[valores_nulos].iterrows():
ID_eje = row['ID_eje']
num_medicion = row['numero_medicion'] # Especifico que quiero que tome en cuenta las columnas de ID_eje y numero_medicion
if num_medicion in [1, 3]:
df.at[index, 'reparado'] = False # Primera regla
elif num_medicion == 2:
# Segunda regla: verificar si hay valores en numero_medicion 1 y 3 para el mismo ID_eje
valor_1 = df[(df['ID_eje'] == ID_eje) & (df['numero_medicion'] == 1)]['dap (mm)'].values
valor_3 = df[(df['ID_eje'] == ID_eje) & (df['numero_medicion'] == 3)]['dap (mm)'].values
if len(valor_1) > 0 and not pd.isna(valor_1[0]) and len(valor_3) > 0 and not pd.isna(valor_3[0]):
df.at[index, 'reparado'] = True
else:
df.at[index, 'reparado'] = False # Tercera regla
<ipython-input-9-8b19e9b31009>:5: UserWarning: Boolean Series key will be reindexed to match DataFrame index. for index, row in df[valores_nulos].iterrows():
# Verificamos la columna "reparado" en los ID que tenían NA en la columna dap (mm)
conteo_reparados = df.loc[valores_nulos, ['ID_eje', 'numero_medicion', 'reparado']].value_counts()
print(conteo_reparados)
ID_eje numero_medicion reparado IND_1_10_2_1 2 True 1 IND_1_13_3_2 2 True 1 IND_1_1_1_2 1 False 1 IND_1_1_7_1 2 True 1 IND_1_23_3_1 2 False 1 IND_1_30_1_1 2 True 1 IND_1_3_6_1 1 False 1 IND_7_41_6_1 3 False 1 IND_8_0_3_4 2 False 1 IND_8_3_12_1 2 False 1 IND_8_41_6_1 3 False 1 IND_9_10_21_1 2 False 1 IND_9_33_10_1 2 False 1 Name: count, dtype: int64
Observaciones:¶
Según el resultado anterior, observamos que de todos los ID_eje que mostraron tener valores nulos en dap (mm), existen 4 de ellos que es posible aplicar una corrección. Esta corrección se realizará promediando la medición anterior con la siguiente.
Para todos los ID_eje que mostraron valores "False" se eliminarán, ya que no existe seguridad de si el árbol murió.
# Reparados los valores NA que hayan tenido "True" en la columna de reparado
for index, row in df[valores_nulos].iterrows():
ID_eje = row['ID_eje']
num_medicion = row['numero_medicion']
if num_medicion in [1, 3]:
df.at[index, 'reparado'] = False
elif num_medicion == 2:
# Verificar si hay valores en numero_medicion 1 y 3 para el mismo id_eje
valor_1 = df[(df['ID_eje'] == ID_eje) & (df['numero_medicion'] == 1)]['dap (mm)'].values
valor_3 = df[(df['ID_eje'] == ID_eje) & (df['numero_medicion'] == 3)]['dap (mm)'].values
if len(valor_1) > 0 and not pd.isna(valor_1[0]) and len(valor_3) > 0 and not pd.isna(valor_3[0]):
df.at[index, 'reparado'] = True
# Calcular el promedio
promedio = (valor_1[0] + valor_3[0]) / 2
df.at[index, 'dap (mm)'] = promedio
else:
df.at[index, 'reparado'] = False
# Eliminamos los registros que obtuvieron "False" en la columna 'reparado'
df = df[df['reparado'] != False]
<ipython-input-11-c2cb66d34907>:2: UserWarning: Boolean Series key will be reindexed to match DataFrame index. for index, row in df[valores_nulos].iterrows():
# Mostrar los registros corregidos
corregidos = df[df['reparado'] == True][['ID_eje', 'numero_medicion', 'dap (mm)']]
print("Registros corregidos:")
print(corregidos)
Registros corregidos: ID_eje numero_medicion dap (mm) 3061 IND_1_1_7_1 2 22.5 3098 IND_1_10_2_1 2 101.0 3120 IND_1_13_3_2 2 103.0 3192 IND_1_30_1_1 2 455.0
# Verifico si la corrección fue correctamente aplicada a uno de los individuos corregidos
filtro_individual = df[df['ID_eje'] == 'IND_1_1_7_1'][['numero_medicion', 'dap (mm)']]
print(filtro_individual)
numero_medicion dap (mm) 1445 1 22.0 3061 2 22.5 6307 3 23.0
Tomando como ejemplo el registro IND_1_1_7_1 podemos observar que la corrección fue ejecutada correctamente, ya que la medición 1 fue de 22 mm y la 3 de 23 mm, siendo el promedio de ambas mediciones de 22.5 mm
Suma de los diámetros de los ejes de cada individuo¶
Al medir un árbol, si éste posee más de un eje o "brazo" también debe ser medido. La suma de los ejes de un individuo es útil para calcular indicadores de estructura y funcionalidad, tales como área basal, biomasa y carbono. Por ello, se sumarán los ejes para cada individuo y eso me dará el dap final a partir del identificador único por individuo creado en el paso anterior.
# Crear la columna dap_total con ceros iniciales
df['dap_total'] = 0
# Agrupar por las columnas ID y número de medicion y sumar los ejes
grouped = df.groupby(['ID', 'numero_medicion'])['dap (mm)'].sum().reset_index()
grouped.rename(columns={'dap (mm)': 'dap_total'}, inplace=True)
# Merge con el df original
df = df.merge(grouped, on=['ID', 'numero_medicion'], how='left', suffixes=('', '_total'))
# Muestro el resultado de un individuo con más de un eje para comprobar que los resultados están bien
ind_multiples_ejes = df[df['ID'] == 'IND_3_22_1']
print(ind_multiples_ejes)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 231 3 Ocotea rivularis 7 22 1 232 3 Ocotea rivularis 7 22 1 233 3 Ocotea rivularis 7 22 1 234 3 Ocotea rivularis 7 22 1 3581 3 Ocotea rivularis 7 22 1 3582 3 Ocotea rivularis 7 22 1 3583 3 Ocotea rivularis 7 22 1 3584 3 Ocotea rivularis 7 22 1 6563 3 Ocotea rivularis 7 22 1 6564 3 Ocotea rivularis 7 22 1 6565 3 Ocotea rivularis 7 22 1 6566 3 Ocotea rivularis 7 22 1 eje numero_medicion dap (mm) ID ID_eje \ 231 1 1 256.0 IND_3_22_1 IND_3_22_1_1 232 2 1 126.0 IND_3_22_1 IND_3_22_1_2 233 3 1 102.0 IND_3_22_1 IND_3_22_1_3 234 4 1 101.0 IND_3_22_1 IND_3_22_1_4 3581 1 2 257.0 IND_3_22_1 IND_3_22_1_1 3582 2 2 150.0 IND_3_22_1 IND_3_22_1_2 3583 3 2 133.0 IND_3_22_1 IND_3_22_1_3 3584 4 2 120.0 IND_3_22_1 IND_3_22_1_4 6563 1 3 263.0 IND_3_22_1 IND_3_22_1_1 6564 2 3 164.0 IND_3_22_1 IND_3_22_1_2 6565 3 3 151.0 IND_3_22_1 IND_3_22_1_3 6566 4 3 131.0 IND_3_22_1 IND_3_22_1_4 nombre_cientifico reparado dap_total dap_total_total 231 Ocotea rivularis None 0 585.0 232 Ocotea rivularis None 0 585.0 233 Ocotea rivularis None 0 585.0 234 Ocotea rivularis None 0 585.0 3581 Ocotea rivularis None 0 660.0 3582 Ocotea rivularis None 0 660.0 3583 Ocotea rivularis None 0 660.0 3584 Ocotea rivularis None 0 660.0 6563 Ocotea rivularis None 0 709.0 6564 Ocotea rivularis None 0 709.0 6565 Ocotea rivularis None 0 709.0 6566 Ocotea rivularis None 0 709.0
Observaciones:¶
Observamos que el ID: IND_3_22_1, de la especie Ocotea rivularis, posee 4 ejes, la suma de los 4 ejes da un total de 585 mm en la primera medición (182+134+143+136+= 585 mm); 660mm en la segunda medición y 709 mm en la tercera medición. Por lo que la columna dap_total_total muestra el resultado de la suma de los ejes de manera correcta. Lo que procede es eliminar los datos duplicados y columnas innecesarias.
Re ordeno y limpio mi base de datos¶
# Elimino columna de dap_total
df = df.drop(columns=['dap_total'])
# Elimino elementos duplicados basado en el ID, numero_medicion y dap_total_total
df = df.drop_duplicates(subset=['ID', 'numero_medicion', 'dap_total_total'])
# Le cambio el nombre a la columna dap_total_total
df = df.rename(columns={'dap_total_total': 'dap_cm'})
print(df)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 0 1 Goethalsia meiantha 7 0 1 1 1 Castilla elastica 7 0 2 2 1 Cordia lucidula 7 0 3 3 1 Sorocea pubivena 7 0 4 4 1 Amyris pinnata 7 0 5 ... ... ... ... ... ... ... 8646 12 Ruagea insignis 2 44 7 8647 12 Virola koschnyi 7 44 8 8648 12 Virola koschnyi 7 44 9 8649 12 Ruagea insignis 2 44 10 8650 12 Sorocea pubivena 7 44 11 eje numero_medicion dap (mm) ID ID_eje \ 0 1 1 570.0 IND_1_0_1 IND_1_0_1_1 1 1 1 157.0 IND_1_0_2 IND_1_0_2_1 2 1 1 57.0 IND_1_0_3 IND_1_0_3_1 3 1 1 90.0 IND_1_0_4 IND_1_0_4_1 4 1 1 103.0 IND_1_0_5 IND_1_0_5_1 ... ... ... ... ... ... 8646 1 3 54.0 IND_12_44_7 IND_12_44_7_1 8647 1 3 11.0 IND_12_44_8 IND_12_44_8_1 8648 1 3 35.0 IND_12_44_9 IND_12_44_9_1 8649 1 3 32.0 IND_12_44_10 IND_12_44_10_1 8650 1 3 59.0 IND_12_44_11 IND_12_44_11_1 nombre_cientifico reparado dap_cm 0 Goethalsia meiantha None 570.0 1 Castilla elastica None 157.0 2 Cordia lucidula None 57.0 3 Sorocea pubivena None 90.0 4 Amyris pinnata None 103.0 ... ... ... ... 8646 Ruagea insignis None 54.0 8647 Virola koschnyi None 11.0 8648 Virola koschnyi None 35.0 8649 Ruagea insignis None 32.0 8650 Sorocea pubivena None 59.0 [8365 rows x 14 columns]
# Verifico nuevamente el ID con ejes múltiples para verificar que no hay duplicados
ind_multiples_ejescm = df[df['ID'] == 'IND_3_22_1']
print(ind_multiples_ejescm)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 231 3 Ocotea rivularis 7 22 1 3581 3 Ocotea rivularis 7 22 1 6563 3 Ocotea rivularis 7 22 1 eje numero_medicion dap (mm) ID ID_eje \ 231 1 1 256.0 IND_3_22_1 IND_3_22_1_1 3581 1 2 257.0 IND_3_22_1 IND_3_22_1_1 6563 1 3 263.0 IND_3_22_1 IND_3_22_1_1 nombre_cientifico reparado dap_cm 231 Ocotea rivularis None 585.0 3581 Ocotea rivularis None 660.0 6563 Ocotea rivularis None 709.0
Convierto el dap de milímetros a centímetros¶
Para una mejor comprensión de las mediciones:
# Divido entre 10 para convertir los milímetros a centímetros
df['dap_cm'] = df['dap_cm'] / 10
print(df)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 0 1 Goethalsia meiantha 7 0 1 1 1 Castilla elastica 7 0 2 2 1 Cordia lucidula 7 0 3 3 1 Sorocea pubivena 7 0 4 4 1 Amyris pinnata 7 0 5 ... ... ... ... ... ... ... 8646 12 Ruagea insignis 2 44 7 8647 12 Virola koschnyi 7 44 8 8648 12 Virola koschnyi 7 44 9 8649 12 Ruagea insignis 2 44 10 8650 12 Sorocea pubivena 7 44 11 eje numero_medicion dap (mm) ID ID_eje \ 0 1 1 570.0 IND_1_0_1 IND_1_0_1_1 1 1 1 157.0 IND_1_0_2 IND_1_0_2_1 2 1 1 57.0 IND_1_0_3 IND_1_0_3_1 3 1 1 90.0 IND_1_0_4 IND_1_0_4_1 4 1 1 103.0 IND_1_0_5 IND_1_0_5_1 ... ... ... ... ... ... 8646 1 3 54.0 IND_12_44_7 IND_12_44_7_1 8647 1 3 11.0 IND_12_44_8 IND_12_44_8_1 8648 1 3 35.0 IND_12_44_9 IND_12_44_9_1 8649 1 3 32.0 IND_12_44_10 IND_12_44_10_1 8650 1 3 59.0 IND_12_44_11 IND_12_44_11_1 nombre_cientifico reparado dap_cm 0 Goethalsia meiantha None 57.0 1 Castilla elastica None 15.7 2 Cordia lucidula None 5.7 3 Sorocea pubivena None 9.0 4 Amyris pinnata None 10.3 ... ... ... ... 8646 Ruagea insignis None 5.4 8647 Virola koschnyi None 1.1 8648 Virola koschnyi None 3.5 8649 Ruagea insignis None 3.2 8650 Sorocea pubivena None 5.9 [8365 rows x 14 columns]
# Verifico el registro reparado en cm
ind_multiples_ejescm = df[df['ID'] == 'IND_3_22_1']
print(ind_multiples_ejescm)
Parcela Genero Especie Forma de vida subparcela numero_arbol \ 231 3 Ocotea rivularis 7 22 1 3581 3 Ocotea rivularis 7 22 1 6563 3 Ocotea rivularis 7 22 1 eje numero_medicion dap (mm) ID ID_eje \ 231 1 1 256.0 IND_3_22_1 IND_3_22_1_1 3581 1 2 257.0 IND_3_22_1 IND_3_22_1_1 6563 1 3 263.0 IND_3_22_1 IND_3_22_1_1 nombre_cientifico reparado dap_cm 231 Ocotea rivularis None 58.5 3581 Ocotea rivularis None 66.0 6563 Ocotea rivularis None 70.9
Limpiar la base de datos¶
En este caso, la columna "eje" ya no me es relevante, ya que ya tengo el dap total (dap_cm). Así mismo, no me es útil la columna "ID_eje", "dap (mm) y "reparado". Por lo que procedo a eliminarlas para simplificar mi base de datos
# Elimino columnas
df = df.drop(columns=['eje', 'ID_eje', 'dap (mm)', 'reparado'])
print('Columnas actuales:', df.columns)
Columnas actuales: Index(['Parcela', 'Genero', 'Especie', 'Forma de vida', 'subparcela', 'numero_arbol', 'numero_medicion', 'ID', 'nombre_cientifico', 'dap_cm'], dtype='object')
Creación de Pandas-profiling¶
Con la creación de este informe podemos ver de manera gráfica otros atributos de la base de datos junto con sus estadísticas.
# Creamos el informe con pandas-profiling
nombre = "Bosque Florencia"
profile = ProfileReport(df, title=nombre, explorative=True)
# Mostrar el informe en un notebook
profile.to_notebook_iframe()
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
Indicadores y gráficos¶
A continuación se mostrarán una serie de gráficos para observar la distribución o comportamiento de nuestros datos respecto a los indicadores de biodiversidad y estructura calculados
# Calculamos la frecuencia de las distitns formas de vida en mi df
frecuencia = df['Forma de vida'].value_counts()
# Calculamos los porcentajes para mejor entendimiento
porcentajes = (frecuencia / frecuencia.sum()) * 100
# Gráfico de barras
plt.figure(figsize=(10, 6))
sns.barplot(x=porcentajes.index, y=porcentajes.values, palette='viridis')
plt.title('Frecuencia de las Formas de Vida')
plt.xlabel('Forma de Vida')
plt.ylabel('Porcentaje (%)')
plt.show()
<ipython-input-22-4671fbe296f3>:9: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(x=porcentajes.index, y=porcentajes.values, palette='viridis')
Observamos que cerca del 90 % de los individios pertenecen a la forma de vida con código 7, los cuales son árboles. Cerca del 10 % a la forma de vida 2, los cuales también son árboles y un ínfimo porcentaje son palmas, herbáceas, musáceas e individuos sin identificación taxonómica conocida.
2. Riqueza¶
# Riqueza de especies
# Calcular la riqueza de especies por parcela y numero_medicion
riqueza_especies = df.groupby(['Parcela', 'numero_medicion'])['nombre_cientifico'].nunique().reset_index()
riqueza_especies.columns = ['Parcela', 'numero_medicion', 'riqueza_especies']
print(riqueza_especies)
Parcela numero_medicion riqueza_especies 0 1 1 37 1 1 2 36 2 1 3 37 3 2 1 29 4 2 2 30 5 2 3 30 6 3 1 44 7 3 2 43 8 3 3 42 9 4 1 42 10 4 2 40 11 4 3 39 12 5 1 54 13 5 2 54 14 5 3 53 15 6 1 47 16 6 2 46 17 6 3 46 18 7 1 50 19 7 2 52 20 7 3 55 21 8 1 40 22 8 2 39 23 8 3 40 24 9 1 57 25 9 2 57 26 9 3 59 27 10 1 50 28 10 2 42 29 10 3 47 30 11 1 46 31 11 2 46 32 11 3 47 33 12 1 49 34 12 2 48 35 12 3 49
# Riqueza de especies
# Creo diferentes estilos de líneas para diferenciar las parcelas
linestyles = ['-', '--', '-.', ':', '-', '--', '-.', ':', '-', '--', '-.', ':']
markers = ['o', 's', 'D', '^', 'v', '<', '>', 'p', 'h', '8', '*', 'X']
# Defino los colores
palette = sns.color_palette("husl", len(riqueza_especies['Parcela'].unique()))
# Creo el gráfico
plt.figure(figsize=(12, 8))
for i, (parcela, group) in enumerate(riqueza_especies.groupby('Parcela')):
sns.lineplot(data=group, x='numero_medicion', y='riqueza_especies', marker=markers[i], linestyle=linestyles[i],
label=parcela, color=palette[i])
plt.title('Riqueza de especies por parcela a través del tiempo')
plt.xlabel('Número de medición')
plt.ylabel('Riqueza de especies')
plt.legend(title='Parcela')
plt.grid(True)
Como vemos en el gráfico de riqueza, la parcela 9 es la que mayor riqueza de especies posee y la parcela 2 la que menos tiene. Así mismo,vemos que en la mayoría de parcelas, la dinámica de la riqueza parece ir en aumento.
3. Índice de diversidad de Shannon¶
# Índice de Diversidad de Shannon
# Calcular el índice de Shannon para cada parcela y numero_medicion
def shannon_index(group):
proportions = group.value_counts(normalize=True)
return -sum(proportions * np.log(proportions))
shannon_diversity = df.groupby(['Parcela', 'numero_medicion'])['nombre_cientifico'].apply(shannon_index).reset_index()
shannon_diversity.columns = ['Parcela', 'numero_medicion', 'shannon_index']
print(shannon_diversity)
Parcela numero_medicion shannon_index 0 1 1 2.661694 1 1 2 2.672911 2 1 3 2.695538 3 2 1 2.341219 4 2 2 2.367814 5 2 3 2.425674 6 3 1 3.231613 7 3 2 3.228367 8 3 3 3.198772 9 4 1 3.348448 10 4 2 3.297483 11 4 3 3.265917 12 5 1 3.523031 13 5 2 3.533413 14 5 3 3.514452 15 6 1 3.192095 16 6 2 3.159595 17 6 3 3.140759 18 7 1 3.157759 19 7 2 3.223356 20 7 3 3.257773 21 8 1 2.919093 22 8 2 2.904007 23 8 3 2.888724 24 9 1 3.065676 25 9 2 3.085156 26 9 3 3.096669 27 10 1 2.803773 28 10 2 2.853916 29 10 3 2.867757 30 11 1 3.234971 31 11 2 3.165768 32 11 3 3.121807 33 12 1 3.434803 34 12 2 3.382448 35 12 3 3.363404
# Escojo la paleta de colores y diferenciación de líneas
plt.figure(figsize=(12, 8))
for i, (parcela, group) in enumerate(shannon_diversity.groupby('Parcela')):
sns.lineplot(data=group, x='numero_medicion', y='shannon_index', marker=markers[i], linestyle=linestyles[i],
label=parcela, color=palette[i])
# Creo el gráfico
plt.title('Índice de Diversidad de Shannon')
plt.xlabel('Número de medición')
plt.ylabel('Índice de Diversidad de Shannon')
plt.legend(title='Parcela')
plt.grid(True)
La riqueza de especies es el número de distintas especies que existen en un área estudiada. Por otro lado, el índice de diversidad de Shannon no solo toma en cuenta la riqueza sino la equitatividad con la que se distribuyen. Como observamos en la gráfica, la parcela 5 es la que posee el índice más alto, lo que indica que las especies están más equitativamente distribuidas en esta parcela. El índice de Shannon hace la pregunta: ¿qué probabilidad hay de que si selecciono por segunda vez a un individuo, sea de la misma especie que el individuo que seleccioné primero?
Indicadores de estructura¶
A continuación, mediremos los indicadores de estructura, los cuales incluyen a la caracterización de número de individuos, diámetros y de área basal por parcela.
# Número de individuos
# Agrupa por parcela y toma el número máximo
max_num_indiviudos = df.groupby('Parcela')['numero_arbol'].max().reset_index()
# Crear el gráfico de barras
plt.figure(figsize=(10, 6))
sns.barplot(x='Parcela', y='numero_arbol', data=max_num_indiviudos, palette='viridis')
plt.title('Número de individuos por parcela')
plt.xlabel('Parcela')
plt.ylabel('No. Individuos')
plt.show()
<ipython-input-27-2e520ba35725>:7: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(x='Parcela', y='numero_arbol', data=max_num_indiviudos, palette='viridis')
# Vemos los datos del gráfico
max_num_individuos = df.groupby('Parcela')['numero_arbol'].max().reset_index()
print(max_num_individuos)
Parcela numero_arbol 0 1 20 1 2 22 2 3 16 3 4 19 4 5 24 5 6 25 6 7 28 7 8 22 8 9 23 9 10 29 10 11 22 11 12 17
Tanto en el gráfico como en los datos, observamos que las parcelas con mayor cantidad de individuos son las parcelas 7 (28 individuos) y parcela 10 (29 individuos). Mientras que la parcela que posee la menor cantidad es la parcela 3 (16 individuos).
# Diámetros en las parcelas
# Calcular el diámetro mínimo, promedio y máximo para cada parcela
diametros = df.groupby('Parcela')['dap_cm'].agg(['min', 'mean', 'max']).reset_index()
# Renombrar las columnas para mayor claridad
diametros.columns = ['Parcela', 'Diámetro mínimo (cm)', 'Diámetro promedio (cm)', 'Diámetro máximo (cm)']
# Mostrar el DataFrame resultante
print(diametros)
Parcela Diámetro mínimo (cm) Diámetro promedio (cm) \ 0 1 0.7 13.746068 1 2 0.5 10.936883 2 3 0.6 13.685937 3 4 0.1 13.646417 4 5 0.5 14.135704 5 6 0.1 10.219034 6 7 0.6 12.258529 7 8 0.7 13.732081 8 9 0.5 9.868354 9 10 0.6 10.224724 10 11 0.6 10.203091 11 12 0.5 13.333435 Diámetro máximo (cm) 0 91.7 1 80.0 2 79.0 3 75.8 4 81.7 5 60.4 6 73.3 7 106.1 8 61.0 9 80.1 10 91.0 11 94.5
# Encontrar el máximo, mínimo y promedio
# Calcular el valor mínimo del diámetro mínimo, el valor máximo del diámetro máximo y el valor promedio del diámetro promedio
valor_min_diametro_min = diametros['Diámetro mínimo (cm)'].min()
valor_max_diametro_max = diametros['Diámetro máximo (cm)'].max()
valor_prom_diametro_prom = diametros['Diámetro promedio (cm)'].mean()
print(f'Valor mínimo del diámetro mínimo (cm): {valor_min_diametro_min}')
print(f'Valor máximo del diámetro máximo (cm): {valor_max_diametro_max}')
print(f'Valor promedio del diámetro promedio (cm): {valor_prom_diametro_prom:.2f}')
Valor mínimo del diámetro mínimo (cm): 0.1 Valor máximo del diámetro máximo (cm): 106.1 Valor promedio del diámetro promedio (cm): 12.17
# Área basal
# Calcular el área basal para cada árbol
df['area_basal'] = np.pi * (df['dap_cm'] / 200) ** 2
# Calcular el área basal total por parcela y número de medición
area_basal_total = df.groupby(['Parcela', 'numero_medicion'])['area_basal'].sum().reset_index()
area_basal_total.columns = ['Parcela', 'numero_medicion', 'area_basal_total']
print(area_basal_total)
Parcela numero_medicion area_basal_total 0 1 1 6.874764 1 1 2 7.241561 2 1 3 7.337370 3 2 1 6.070906 4 2 2 6.459865 5 2 3 6.622305 6 3 1 5.678742 7 3 2 5.281251 8 3 3 4.955061 9 4 1 5.348217 10 4 2 5.003114 11 4 3 4.732304 12 5 1 6.088758 13 5 2 6.180558 14 5 3 6.197104 15 6 1 4.107674 16 6 2 4.442741 17 6 3 4.627872 18 7 1 6.300415 19 7 2 5.215439 20 7 3 4.490798 21 8 1 7.190916 22 8 2 7.197110 23 8 3 7.485630 24 9 1 5.437246 25 9 2 5.034596 26 9 3 4.480501 27 10 1 5.389372 28 10 2 2.879881 29 10 3 5.258837 30 11 1 6.065030 31 11 2 4.338992 32 11 3 4.048172 33 12 1 6.470091 34 12 2 4.932245 35 12 3 5.236323
# Escojo una paleta de colores
palette = sns.color_palette("tab20", 12)
# Creo distintos estilos para diferenciar las líneas
linestyles = ['-', '--', '-.', ':', '-', '--', '-.', ':', '-', '--', '-.', ':']
markers = ['o', 's', 'D', '^', 'v', '<', '>', 'p', 'h', '8', '*', 'X']
# Crear un gráfico de líneas con la paleta de colores personalizada y estilos
plt.figure(figsize=(12, 8))
for i, (parcela, group) in enumerate(area_basal_total.groupby('Parcela')):
sns.lineplot(data=group, x='numero_medicion', y='area_basal_total', marker=markers[i], linestyle=linestyles[i],
label=parcela, color=palette[i])
# Configuración del gráfico
plt.title('Área basal por parcela')
plt.xlabel('Número de medición')
plt.ylabel('Área basal (m²)')
plt.legend(title='Parcela')
plt.grid(True)
El área basal (G) puede ser calculada a nivel de parcela o a nivel del individuo. A nivel del invidiuo, es el área de la sección transversal del tronco del árbol a la altura del pecho. A nivel de parcela, es la suma de todas las áreas basales de todos los individuos por hectárea (Mercker, 2023). El área basal es una variable utilizada para estimar el volumen de madera, la biomasa arriba del suelo y carbono almacenado en bosques (Chiba, 1998; Torres & Lovett, 2013).
Observamos según la gráfica que la mayor área basal está en la parcela 8, en la cual también muestra un incremento a lo largo de los años. Mientras que la parcela 10 fue la que tuvo un drececimiento abrupto en la segunda medición y luego una recuperación en la tercera.
Por otro lado, se observa que no necesariamente todas las áreas basales están en aumento, sino que algunas parcelas están disminuyendo, como en la 3, 7, 9 y 11.
Descripción de los resultados obtenidos:¶
El bosque Florencia es un bosque secundario tropical ubicado en Turrialba, Costa Rica. Es un bosque relativamente "nuevo" y a pesar de ello, cuenta con tres mediciones a través del tiempo, las cuales evidencian distintas dinámicas entre las 12 parcelas medidas por el CATIE. A continuación se presentan los principales resultados obtenidos a través de las mediciones de indicadores estructurales y de biodiversidad.
1. Indicadores estructurales¶
- Individuos por parcela
La parcela que posee mayor cantidad de individuos es la 9, mientras que la que menos posee es la número 3. Por lo que la parcela 9 no solo posee la mayor cantidad de especies presentes sino también la mayor cantidad de individuos muestreados. Sin embargo, la parcela 3 no mostró anteriormente índices bajos en sus indicadores de biodiversidad, por lo que pueda no tener la mayor cantidad de individuos muestreados pero en términos de cantidad de especies, se encuentra relativamente bien.
Además, con profiling también fue posible observar que para la última medición, fue mayor la cantidad de individuos muestreados respecto a la segunda y primera. Con ello, se evidencia que la complejidad y biodiversidad del bosque aumentan conforme pasa el tiempo, tal y como lo relatan diversos autores (Chokkalingam, et al. 2001; Poorter et al., 2021; Chazdon et al., 2009).
- Diámetro a la altura del pecho de los individuos
Antes de observar las dinámicas de las parcelas a través del tiempo, fue necesario realizar correcciones para el DAP, ya que los valores nulos e igual a cero no eran posibles de procesar. Para ello, se corrigieron aquellos datos que poseían mediciones antes y después para realizar un promedio. Las mediciones donde no fue posible ejecutar esta acción fueron eliminados.
Únicamente se lograron corregir 4 DAP de los valores nulos, mientras que 9 restantes fueron eliminados junto con los 417 registros con valores igual a cero.
Los individuos dentro de las parcelas poseían mediciones de DAP por cada eje. Para cálculos posteriores como el área basal, es necesario realizar una sumatoria de ejes por individuo. Por ello, se creó un identificador único para cada individuo el cual fue útil para determinar si tenía uno o varios ejes en cada medición. De poseer más de un eje se procedió a realizar una sumatoria para obtener el DAP total en centímetros.
Gracias a Panda-profiling y la utilización de Pandas, se evidenció que el diámetro mínimo registrado en el bosque Florencia fue de 0.1 cm y el más grande de 106.1 cm, siendo el promedio general de 12.03 cm. La parcela 5 la que posee un diámetro promedio más alto y la parcela 9 la que posee el diámetro promedio más bajo. Por otro lado, el árbol con mayor diámetro en el bosque Florencia estudiado se encuentra en la parcela 7.
El histograma muestra que muy pocos individuos llegan al diámetro mínimo de corta para el país de 60 cm (MINAE, 1996) por lo que categoriza al bosque Florencia en un bosque en recuperación, aún no apto para un aprovechamiento.
- Área basal
Para el área basal, existen valores desde 2m2 hasta poco más de 7m2 por parcela. Existen fluctuaciones para ciertas parcelas a través del tiempo, como el caso de la parcela 10, en donde en la segunda medición tuvo una recaida y luego se recuperó al año siguiente, lo cual no es extraño en bosques altamente dinámicos.
La parcela que posee una mayor área basal es la 8, la cual pueda tener una distribución más equitativa de los diámetros promedio o arriba del promedio. Además, otro aspecto importante en la dinámica del área basal es que no todas las parcelas parecen tener una tendencia fija a aumentar su área basal. Sin embargo, por el dinamismo de los bosques tropicales, es necesario obtener más mediciones a través del tiempo para observa una tendencia más clara.
2. Indicadores de biodiversidad¶
- Formas de vida
A lo largo de toda la base de datos, observamos que la forma de vida más abundante fueron los árboles, en una proporción casi insigificante encontramos palmas, musáceas, herbáceas e individuos sin identificación taxonómica. Al estar estudiando un bosque y no un pastizal, sabana o párramo, es normal encontrar que las especies dominantes sean árboles.
- Dominancia de especies
A través de profiling fue posible observar otras características de la base de datos. Por ejemplo, el género más frecuente fue Virola (26.6 %) seguido por Guarea (9.5 %) lo que indica una alta dominancia del género Virola respecto al resto de géneros.
- Riqueza e Índice de Shannon
De los indicadores de biodiversidad, el cálculo de riqueza reflejó que la tendencia en la mayoría de las parcelas ha sido el aumento de la riqueza con el tiempo. Siendo la parcela 9 la que posee mayor riqueza. Sin embargo, observando los resultados del índice de diversidad de Shannon, observamos que la parcela 5 es la que posee un índice más alto y la 9 posee un índice medio. Por lo que a pesar de que existen mayor número de especies distintas en la parcela 9, las abundancias de las especies se encuentran mejor distribuidas (más equitativamente) en la parcela 5. Observamos que pueda que al ver los datos numéricos sea un tanto complicado de diferenciar cuál es la parcela con mayores indicadores, pero al realizar las gráficas se evidencia mejor la información por parcela y a través del tiempo.
Conclusiones:¶
- El bosque Florencia es un bosque secundario en recuperación y eso se evidencia con las tendencias de crecimiento en el área basal, riqueza y bioridiversidad, sin embargo, los bosques tropicales son altamente dinámicos por lo que los incrementos de estos índices no siempre son lineales.
- Los indicadores de biodiversidad y de estructura no necesarimente son directamente proporcionales. Es posible obtener mayores áreas basales con un mayor número de individuos o mayores diámetros sin necesariamente tener mayor cantidad y equitatividad de especies y viceversa.
- La utilización de Pandas-profiling facilita la visualización de datos, sobre todo aquellos en los que las medidas estadísticas clásicas son útiles o bien, aquellos datos en formato de texto, ya que profiling mejora su visualización, como fue en el caso de los nombres científicos más comunes en nuestra base de datos.
- Pandas comprende una herramienta sumamente útil para la visualización, transformación y presentación de datos. Gracias a ello fue posible visualizar datos faltantes, corregir, depurar la base de datos, calcular métricas y presentar los resultados no solo en formato numérico sino también con gráficos, lo que facilitaba su comprensión.
Bibliografía:¶
- Acharya, K. P., Dangi, R. B., & Acharya, M. (2011). Understanding forest degradation in Nepal. Unasylva, 62(238).
- Brandon, K. (2015). Ecosystem Services from Tropical Forests: Review of Current Science. SSRN Electronic Journal. https://doi.org/10.2139/ssrn.2622749
- Camacho-Calvo, M., Delgado-Rodríguez, D., Valera Mejías, V., & Serrano-Molina, J. J. (2021). Potencial productivo de cuatro bosques secundarios en América Central y pautas para su manejo silvícola. Centro Agronómico Tropical de Investigación y Enseñanza (CATIE).
- Chazdon, R. L., Peres, C. A., Dent, D., Sheil, D., Lugo, A. E., Lamb, D., Stork, N. E., & Miller, S. E. (2009). The potential for species conservation in tropical secondary forests. Conservation Biology, 23(6). https://doi.org/10.1111/j.1523-1739.2009.01338.x
- Chiba, Y. (1998). Architectural analysis of relationship between biomass and basal area based on pipe model theory. Ecological Modelling, 108(1–3). https://doi.org/10.1016/S0304-3800(98)00030-1
- Chokkalingam, U., & De Jong, W. (2001). Secondary forest: A working definition and typology. International Forestry Review, 3(1).
- Eguiguren, P., Fischer, R., & Günter, S. (2019). Degradation of ecosystem services and deforestation in landscapes with and without incentive-based forest conservation in the Ecuadorian Amazon. Forests, 10(5). https://doi.org/10.3390/f10050442
- FAO. (2020). Global Forest Resources Assessment 2020. In Global Forest Resources Assessment 2020. https://doi.org/10.4060/ca8753en
- Ganivet, E., & Bloomberg, M. (2019). Towards rapid assessments of tree species diversity and structure in fragmented tropical forests: A review of perspectives offered by remotely-sensed and field-based data. In Forest Ecology and Management (Vol. 432). https://doi.org/10.1016/j.foreco.2018.09.003
- Gerwing, J. J. (2002). Degradation of forests through logging and fire in the eastern Brazilian Amazon. Forest Ecology and Management, 157(1–3). https://doi.org/10.1016/S0378-1127(00)00644-7
- Longo, M., Keller, M., dos-Santos, M. N., Leitold, V., Pinagé, E. R., Baccini, A., Saatchi, S., Nogueira, E. M., Batistella, M., & Morton, D. C. (2016). Aboveground biomass variability across intact and degraded forests in the Brazilian Amazon. Global Biogeochemical Cycles, 30(11). https://doi.org/10.1002/2016GB005465
- Malhi, Y., Gardner, T. A., Goldsmith, G. R., Silman, M. R., & Zelazowski, P. (2014). Tropical forests in the anthropocene. In Annual Review of Environment and Resources (Vol. 39). https://doi.org/10.1146/annurev-environ-030713-155141
- Mercker, D. (2023). A simple guide to common forest measurements.
- MINAE, C. R. (1996). Ley Forestal No 7575: Los Principios, Criterios e Indicadores para el Manejo de Bosques Naturales y su Certificación en Costa Rica.
- Misiukas, J. M., Carter, S., & Herold, M. (2021). Tropical forest monitoring: Challenges and recent progress in research. Remote Sensing, 13(12). https://doi.org/10.3390/rs13122252
- Mullan, K. (2015). The Value of Forest Ecosystem Services to Developing Economies. SSRN Electronic Journal. https://doi.org/10.2139/ssrn.2622748
- Poorter, L., Rozendaal, D. M. A., Bongers, F., de Jarcilene, S. A., Àlvarez, F. S., Luìs Andrade, J., Arreola Villa, L. F., Becknell, J. M., Bhaskar, R., Boukili, V., Brancalion, P. H. S., Cèsar, R. G., Chave, J., Chazdon, R. L., Colletta, G. D., Craven, D., de Jong, B. H. J., Denslow, J. S., Dent, D. H., … Westoby, M. (2021). Functional recovery of secondary tropical forests. Proceedings of the National Academy of Sciences of the United States of America, 118(49). https://doi.org/10.1073/pnas.2003405118
- Putz, F. E., & Romero, C. (2014). Futures of tropical forests (sensu lato). Biotropica, 46(4). https://doi.org/10.1111/btp.12124
- Saatchi, S. S., Harris, N. L., Brown, S., Lefsky, M., Mitchard, E. T. A., Salas, W., Zutta, B. R., Buermann, W., Lewis, S. L., Hagen, S., Petrova, S., White, L., Silman, M., & Morel, A. (2011). Benchmark map of forest carbon stocks in tropical regions across three continents. Proceedings of the National Academy of Sciences of the United States of America, 108(24). https://doi.org/10.1073/pnas.1019576108
- Torres, A. B., & Lovett, J. C. (2013). Using basal area to estimate aboveground carbon stocks in forests: La Primavera Biosphere’s Reserve, Mexico. Forestry, 86(2). https://doi.org/10.1093/forestry/cps084
- Turner, I. M., Wong, Y. K., Chew, P. T., & Bin Ibrahim, A. (1997). Tree species richness in primary and old secondary tropical forest in Singapore. Biodiversity and Conservation, 6(4). https://doi.org/10.1023/A:1018381111842
- Wright, S. J. (2010). The future of tropical forests. In Annals of the New York Academy of Sciences (Vol. 1195). https://doi.org/10.1111/j.1749-6632.2010.05455.x