Calidad del Agua del Río Liberia con cálculo de Índice Holandés¶
Descripción del Proyecto y justificación¶
En el presente documento se resume la aplicación de los conocimientos adquiridos en el curso "Python para ciencia de datos" impartido y apoyado con el esfuerzo de diversas instituciones como la Red de Ciencia de Datos para la Conservación de la biodiversidad Mesoamericana (Redbioma), Tecnológico de Costa Rica (TEC), Centro Internacional de Investigaciones para el Desarrollo (IDRC), Consejo Superior Universitario Centroamericano (CSUCA), Comisión para el Desarrollo Científico y Tecnológico de Centroamérica, Panamá y República Dominicana (CTCAP) y el Sistema de Integración Centroamericana (SICA).
De esta forma, el desarrollo del proyecto se enfoca en la demostración de procesamiento de datos para el desarrollo de gráficos y proyecciones en polígonos georreferenciados basándose en cálculo del Índice Holandés del Reglamento N°33903-MINAE-SALUD, mediante los datos recopilados durante 11 años en los muestreos y análisis fisico-químicos de la cuenca del Río Liberia, en el cantón de Liberia, Guanacaste. Esto con el fin de generar un producto que refleje la colaboración desde la ciencia de datos para la facilitación de la conversión de datos a información en el campo de la contaminación hídrica.
Antecedentes¶
Costa Rica presenta retos en materia de conservación de la biodiversidad, especialmente para asegurar la integridad ecológica de los cuerpos de agua, así como, el mantenimiento de los flujos de bienes y servicios ambientales a la población que a raíz de las presiones antrópicas se exacerban, generando consecuencias negativas que afectan particularmente a las poblaciones más vulnerables.
El río Liberia es una subcuenca del río Tempisque que abarca una extensión de 201.91 km2 y es esencial para diversas actividades, incluido el suministro de agua para consumo humano. Desde 2013, el HIDROCEC, un centro de investigación adscrito a la Universidad Nacional de Costa Rica, ha estado realizando un monitoreo exhaustivo de la calidad del agua en la zona media-alta. El monitoreo se conforma de ocho puntos de muestreo estratégicamente ubicados, que abarcan desde las zonas altas en el Parque Nacional Rincón de la Vieja hasta áreas urbanas en la ciudad de Liberia y puntos intermedios en la ruta nacional N°21.
Una forma de solventar las problemáticas ambientales es a partir de un monitoreo continuo del recurso hídrico que permita identificar, prevenir y revertir los procesos de degradación y contaminación, para dar soporte a la implementación y seguimiento de las políticas y programas nacionales. Para evaluar la calidad del agua, se aplican los indicadores establecidos en el Reglamento para la Evaluación y Clasificación de Cuerpos de Agua Superficial N°33903-MINAE-S, particularmente el apartado del Índice Holandés.
En este sentido, la ciencia de datos posibilita el monitoreo oportuno de la calidad del agua al generar información en tiempo real, lo cual resulta crucial para identificar riesgos para la salud y el medio ambiente (Discover Data Science, s.f.). Además, facilita el involucramiento de los ciudadanos a través de hackatones y talleres que proponen herramientas para abordar esta problemática, permitiendo su integración en sistemas de informes locales e internacionales (Warner et al., 2024).
El uso de Python ofrece grandes ventajas a los científicos interesados en aprender a programar para aplicarlo en sus estudios ambientales. Es un lenguaje de programación que se asemeja al lenguaje humano, lo que facilita la comprensión del software y reduce la complejidad de la curva de aprendizaje. Además, cuenta con una amplia gama de funciones, lo que permite utilizar paquetes para Sistemas de Información Geográfica (SIG), análisis matemáticos e inteligencia artificial (Oscar, 2017; Montoya, 2014).
Descripción del problema¶
El índice que se desea aplicar se basa en una clasificación que describe el nivel de contaminación presente en las condiciones y el tiempo en que se tómo una muestra determinada. Requiere conocer la concentración de DBO, N-NH3 y porcentaje de saturación; a partir del rango en el analito se encuentre se le otorgará un número del 1 al 5 a cada uno, la sumatoria de estos 3 valores brindará la clase, interpretación y color representativo según la calidad del agua de la cuenca.
El HIDROCEC desea conocer cómo se ha comportado históricamente la calidad del agua del Río Liberia en cada punto, sin embargo la base que recopila sus datos requiere de mantenimiento en las siguientes áreas:
- Selección de columnas de los parámetros de interés
- Eliminación de filas con datos nulos.
- Homologar valores descriptivos.
- Añadir columnas para el cálculo del índice.
- Corrección en la lectura de tipo de datos.
Además se desea que el producto final indique:
- Comportamiento de cada parámetro, es de especial interés visualizarlo a nivel histórico y por época o estación.
- Conocer el valor del índice Holandés para cada punto.
- Georreferenciar los puntos de la cuenca.
Objetivo¶
Desarrollar un proceso automatizado en Python que permita la preparación, estimación y visualización de los datos de variables fisicoquimicas muestreadas en la cuenca Río Liberia, para facilitar la comprensión del comportamiento histórico de la calidad del agua del rio.
Descripción del conjunto de datos a utilizar¶
Se utilizará un archivo en formato .csv llamado "RioLiberia.csv" que contiene, entre otras columnas que no se utilizarán, la siguiente información:
- Punto: se refiere a los sitios donde se tomaron las muestras, se componen de 8 en total.
- Fecha: la fecha puntual de la toma de cada muestra.
- % O: Porcentaje disponible de oxígeno en el agua.
- N-NH3 (mg/L): Nitrógeno amoniacal en unidades de miligramo por litro.
- DBO 5 (mg/L): Demanda Bioquímica de Oxígeno en un ensayo durante 5 días en miligramo por litro. *Los últimos 3 parámetros serán utilizados para calcular el Índice Holandés (HIDROCEC, 2024).
Además se utilizará un archivo en formato .csv llamado "test[1]", que describe las coordenadas de cada punto de muestreo (HIDROCEC, 2024).
También se utilizará el archivo "gadm41_CRI_3_shp" que contiene diversos archivos en formato .cpg., .dbf, .prj, .shp, y .shx que contienen información que generará mapas de Costa Rica para ubicar los puntos de muestreo (GADM, s.f.).
Las bibliotecas de Python:
- Pandas: Herramienta para análisis y manipulación de datos.
- Numpy: maneja operaciones matemáticas y lógicas en matrices y arreglos.
- Seaborn: visualización de datos en Python con gráficos estadísticos.
- Scikit-learn: proporciona herramientas para Machine Learning y análisis de datos en Python.
- Matplotlib: creación de gráficos y visualizaciones variadas.
- Ydata-profiling: herramienta de Python que facilita el análisis exploratorio de datos.
- Geopy: para trabajar con datos Geoespaciales.
ANALISIS DE DATOS DEL RIO LIBERIA¶
1. Instalación e importación de bibliotecas¶
!pip install numpy
!pip install pandas
!pip install seaborn
!pip install scikit-learn
!pip install matplotlib
!pip install ydata_profiling
import numpy as np
import pandas as pd
import geopandas as gp
import seaborn as sns
from sklearn import datasets
from ydata_profiling import ProfileReport
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
%matplotlib inline
# Importar geopandas y geodatasets
import geopandas as gpd
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (1.25.2) 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) Requirement already satisfied: seaborn in /usr/local/lib/python3.10/dist-packages (0.13.1) Requirement already satisfied: numpy!=1.24.0,>=1.20 in /usr/local/lib/python3.10/dist-packages (from seaborn) (1.25.2) Requirement already satisfied: pandas>=1.2 in /usr/local/lib/python3.10/dist-packages (from seaborn) (2.0.3) Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in /usr/local/lib/python3.10/dist-packages (from seaborn) (3.7.1) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.2.1) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.52.4) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.5) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (24.0) Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (9.4.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.1.2) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.8.2) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.2->seaborn) (2023.4) Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.2->seaborn) (2024.1) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.16.0) Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-packages (1.2.2) Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.10/dist-packages (from scikit-learn) (1.25.2) Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.10/dist-packages (from scikit-learn) (1.11.4) Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn) (1.4.2) Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn) (3.5.0) Requirement already satisfied: matplotlib in /usr/local/lib/python3.10/dist-packages (3.7.1) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.2.1) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (4.52.4) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.4.5) Requirement already satisfied: numpy>=1.20 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (1.25.2) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (24.0) Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (9.4.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (3.1.2) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib) (2.8.2) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib) (1.16.0) Collecting ydata_profiling Downloading ydata_profiling-4.8.3-py2.py3-none-any.whl (359 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 359.5/359.5 kB 2.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 10.6 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 14.2 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 27.4 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=ff36c3ba7f241b7c30f5e6420b7138eabd083ce2eafb45f93ba9bb9823aebd63 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
2. Importar datos de entrada¶
#Se separa la delimitación o tabulación por ; y en utf-8
archivo = '/content/RiosLiberia.csv'
Rio = pd.read_csv(archivo, sep=';', encoding='utf-8')
display(Rio)
Punto | pH | CE (µs/cm)\t\t | T (C°) | OD (mg/L) | % O | Chlorophyll RFU\t\t | Salinidad | N-NH3 (mg/L) | NO2 (mg/L) | ... | STD (mg/L) | ST (mg/L) | Turbidity FNU\t\t | Sólidos sedimentables totales (mg/L) | Sólidos suspendidos totales (mg/L) | Coliformes Totales (NMP/100mL) | Coliformes Fecales (NMP/100mL) | Escherichia coli (NMP/100mL) | Enterobacterias (NMP/100mL) | Fecha | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | NaN | NaN | NaN | NaN | 106.8 | NaN | NaN | 0.082 | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1/3/2013 |
1 | Pt2 | NaN | NaN | NaN | NaN | 92.3 | NaN | NaN | 0.034 | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1/3/2013 |
2 | Pt3 | NaN | NaN | NaN | NaN | 70.5 | NaN | NaN | 0.95 | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1/3/2013 |
3 | Pt4 | NaN | NaN | NaN | NaN | 97.7 | NaN | NaN | 0.78 | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1/3/2013 |
4 | Pt5 | NaN | NaN | NaN | NaN | 67.9 | NaN | NaN | 5.586 | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1/3/2013 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
201 | Pt4 | 7.00 | 170.0 | 26.94 | 5.01 | 85.0 | NaN | NaN | 1.256 | 1.346 | ... | NaN | NaN | NaN | NaN | NaN | 32550 | NaN | NaN | NaN | 19/9/2023 |
202 | Pt5 | 7.30 | 165.0 | 27.14 | 6.52 | 82.0 | NaN | NaN | 0.146 | 0.545 | ... | NaN | NaN | NaN | NaN | NaN | 5650 | NaN | NaN | NaN | 19/9/2023 |
203 | Pt6 | 7.20 | 342.0 | 27.61 | 4.71 | 171.0 | NaN | NaN | 6.969 | 0.379 | ... | NaN | NaN | NaN | NaN | NaN | 12100 | NaN | NaN | NaN | 19/9/2023 |
204 | Pt7 | 7.21 | 358.0 | 26.55 | 3.03 | 179.0 | NaN | NaN | 6.963 | 0.762 | ... | NaN | NaN | NaN | NaN | NaN | 4100 | NaN | NaN | NaN | 19/9/2023 |
205 | Puente Real | 7.03 | 125.0 | 26.92 | 3.92 | 63.0 | NaN | NaN | 0.148 | ND | ... | NaN | NaN | NaN | NaN | NaN | 38900 | NaN | NaN | NaN | 19/9/2023 |
206 rows × 26 columns
3. Analisis exploratorio de los datos¶
#Informe
Profile = ProfileReport(Rio, title="Rio", explorative=True)
# Mostrar el informe en un notebook
Profile.to_notebook_iframe()
Rio.describe()
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]
pH | CE (µs/cm)\t\t | T (C°) | OD (mg/L) | % O | Chlorophyll RFU\t\t | Salinidad | DBO 5 (mg/L) | DQO (mg/L) | fDOM QSU\t\t | fDOM RFU\t\t | STD (mg/L) | ST (mg/L) | Turbidity FNU\t\t | Sólidos sedimentables totales (mg/L) | Sólidos suspendidos totales (mg/L) | Enterobacterias (NMP/100mL) | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 128.000000 | 128.000000 | 121.000000 | 128.000000 | 174.000000 | 19.000000 | 28.000000 | 158.000000 | 1.000 | 12.00000 | 19.000000 | 68.000000 | 0.0 | 26.000000 | 0.0 | 7.000000 | 0.0 |
mean | 7.221484 | 121.101953 | 25.399587 | 6.507109 | 82.516667 | 0.655995 | 0.041486 | 6.231418 | 5.601 | 104.50000 | 10.101642 | 58.466912 | NaN | 26.325662 | NaN | 9.385714 | NaN |
std | 0.392071 | 87.456600 | 2.182249 | 2.154502 | 28.838934 | 1.476543 | 0.028386 | 10.519920 | NaN | 70.11873 | 13.076871 | 38.019095 | NaN | 31.992831 | NaN | 5.491032 | NaN |
min | 5.430000 | 24.610000 | 20.020000 | 0.460000 | 6.200000 | 0.000000 | 0.010000 | 0.018000 | 5.601 | 4.00000 | 0.000000 | 5.000000 | NaN | 0.000000 | NaN | 3.400000 | NaN |
25% | 7.037500 | 67.000000 | 24.600000 | 5.372500 | 67.000000 | 0.000000 | 0.020000 | 1.192500 | 5.601 | 69.50000 | 1.500000 | 33.407500 | NaN | 4.000000 | NaN | 5.525000 | NaN |
50% | 7.245000 | 85.345000 | 25.340000 | 6.540000 | 84.050000 | 0.000000 | 0.040000 | 3.120000 | 5.601 | 93.00000 | 7.000000 | 43.160000 | NaN | 11.841950 | NaN | 8.150000 | NaN |
75% | 7.420000 | 157.850000 | 26.600000 | 7.482500 | 95.125000 | 0.464900 | 0.040000 | 5.910000 | 5.601 | 119.00000 | 11.704250 | 82.865000 | NaN | 33.611150 | NaN | 11.925000 | NaN |
max | 8.150000 | 460.100000 | 33.800000 | 16.860000 | 219.200000 | 6.000000 | 0.130000 | 66.000000 | 5.601 | 253.00000 | 47.385600 | 168.000000 | NaN | 104.000000 | NaN | 19.250000 | NaN |
4. Limpieza y modificación de datos¶
4.1. Observar los datos de entrada
#Cantidad de valores únicos en cada columna
Rio.nunique()
Punto 8 pH 74 CE (µs/cm)\t\t 115 T (C°) 77 OD (mg/L) 107 % O 148 Chlorophyll RFU\t\t 10 Salinidad 9 N-NH3 (mg/L) 103 NO2 (mg/L) 5 NO3 (mg/L) 40 P-PO4-3 (mg/L) 35 DBO 5 (mg/L) 141 DQO (mg/L) 1 fDOM QSU\t\t 12 fDOM RFU\t\t 13 STD (mg/L) 59 ST (mg/L) 0 Turbidity FNU\t\t 21 Sólidos sedimentables totales (mg/L) 0 Sólidos suspendidos totales (mg/L) 7 Coliformes Totales (NMP/100mL) 64 Coliformes Fecales (NMP/100mL) 82 Escherichia coli (NMP/100mL) 55 Enterobacterias (NMP/100mL) 0 Fecha 27 dtype: int64
#Cantidad de valores nulos en cada columna
print(Rio.isnull().sum())
Punto 0 pH 78 CE (µs/cm)\t\t 78 T (C°) 85 OD (mg/L) 78 % O 32 Chlorophyll RFU\t\t 187 Salinidad 178 N-NH3 (mg/L) 51 NO2 (mg/L) 199 NO3 (mg/L) 164 P-PO4-3 (mg/L) 164 DBO 5 (mg/L) 48 DQO (mg/L) 205 fDOM QSU\t\t 194 fDOM RFU\t\t 187 STD (mg/L) 138 ST (mg/L) 206 Turbidity FNU\t\t 180 Sólidos sedimentables totales (mg/L) 206 Sólidos suspendidos totales (mg/L) 199 Coliformes Totales (NMP/100mL) 131 Coliformes Fecales (NMP/100mL) 111 Escherichia coli (NMP/100mL) 139 Enterobacterias (NMP/100mL) 206 Fecha 0 dtype: int64
#Tipos de datos, cantidad de columnas, datos no nulos
Rio.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 206 entries, 0 to 205 Data columns (total 26 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Punto 206 non-null object 1 pH 128 non-null float64 2 CE (µs/cm) 128 non-null float64 3 T (C°) 121 non-null float64 4 OD (mg/L) 128 non-null float64 5 % O 174 non-null float64 6 Chlorophyll RFU 19 non-null float64 7 Salinidad 28 non-null float64 8 N-NH3 (mg/L) 155 non-null object 9 NO2 (mg/L) 7 non-null object 10 NO3 (mg/L) 42 non-null object 11 P-PO4-3 (mg/L) 42 non-null object 12 DBO 5 (mg/L) 158 non-null float64 13 DQO (mg/L) 1 non-null float64 14 fDOM QSU 12 non-null float64 15 fDOM RFU 19 non-null float64 16 STD (mg/L) 68 non-null float64 17 ST (mg/L) 0 non-null float64 18 Turbidity FNU 26 non-null float64 19 Sólidos sedimentables totales (mg/L) 0 non-null float64 20 Sólidos suspendidos totales (mg/L) 7 non-null float64 21 Coliformes Totales (NMP/100mL) 75 non-null object 22 Coliformes Fecales (NMP/100mL) 95 non-null object 23 Escherichia coli (NMP/100mL) 67 non-null object 24 Enterobacterias (NMP/100mL) 0 non-null float64 25 Fecha 206 non-null object dtypes: float64(17), object(9) memory usage: 42.0+ KB
# Selección de columnas de interés, que permitan determinar el índice holandes, de acuerdo a reglamento.
Riocolumnas = ['Punto', '% O', 'N-NH3 (mg/L)', 'DBO 5 (mg/L)','Fecha']
Riosred= Rio[Riocolumnas]
Riosred.head()
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | |
---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 1/3/2013 |
1 | Pt2 | 92.3 | 0.034 | 0.930 | 1/3/2013 |
2 | Pt3 | 70.5 | 0.95 | 1.160 | 1/3/2013 |
3 | Pt4 | 97.7 | 0.78 | 1.465 | 1/3/2013 |
4 | Pt5 | 67.9 | 5.586 | 16.107 | 1/3/2013 |
4.2. Homologar nombres
#Homologar nombres
Riosred['Punto'].unique()
array(['Pt1', 'Pt2', 'Pt3', 'Pt4', 'Pt5', 'Pt6', 'Pt7', 'Puente Real'], dtype=object)
#Reemplazar nombre Puente Real para homogenizar los nombres de los puntos de muestreo.
new_df = Riosred.copy()
new_df['Punto'].replace({"Puente Real":"Pt8"}, inplace=True)
print(new_df['Punto'].value_counts())
Punto Pt1 27 Pt2 27 Pt3 27 Pt4 27 Pt5 27 Pt6 27 Pt7 27 Pt8 17 Name: count, dtype: int64
4.3. Eliminar filas nulas
#Eliminar filas nulas
df_sin_nulos = new_df.dropna(subset=['% O', 'N-NH3 (mg/L)', 'DBO 5 (mg/L)'])
print(df_sin_nulos)
#Comprobando eliminación de nulos
print(df_sin_nulos.isnull().sum())
Punto % O N-NH3 (mg/L) DBO 5 (mg/L) Fecha 0 Pt1 106.8 0.082 0.560 1/3/2013 1 Pt2 92.3 0.034 0.930 1/3/2013 2 Pt3 70.5 0.95 1.160 1/3/2013 3 Pt4 97.7 0.78 1.465 1/3/2013 4 Pt5 67.9 5.586 16.107 1/3/2013 .. ... ... ... ... ... 193 Pt4 86.4 0.07 3.400 12/4/2023 194 Pt5 78.9 0.133 3.530 12/4/2023 195 Pt6 72.0 5.698 11.510 12/4/2023 196 Pt7 25.6 0.665 2.910 12/4/2023 197 Pt8 52.9 0.031 3.920 12/4/2023 [148 rows x 5 columns] Punto 0 % O 0 N-NH3 (mg/L) 0 DBO 5 (mg/L) 0 Fecha 0 dtype: int64
4.4. Cambiar los "No Detectables" (ND) por valor 0
#Reemplazar valores ND (no detectable) por 0, para limpiar los datos y que se puedan analizar de mejor manera.
SinND = df_sin_nulos.copy()
SinND['N-NH3 (mg/L)'].replace({"ND": 0}, inplace=True)
print(SinND.value_counts())
Punto % O N-NH3 (mg/L) DBO 5 (mg/L) Fecha Pt1 58.4 0.086 0.478 13/6/2013 1 Pt5 125.0 0.26 5.080 29/7/2014 1 Pt6 45.9 0.27 1.200 24/10/2017 1 47.9 1.34 6.130 8/9/2022 1 51.8 0.085 11.710 13/6/2013 1 .. Pt3 96.4 0.1 0.670 9/9/2020 1 214.1 0.057 2.550 22/6/2022 1 Pt4 50.3 0.085 3.030 13/6/2013 1 54.7 1.68 8.900 10/10/2013 1 Pt8 172.4 0.121 2.750 22/6/2022 1 Name: count, Length: 148, dtype: int64
4.5. Corrregir lectura de tipo de datos
#Corroborar tipo de datos
SinND.dtypes
Punto object % O float64 N-NH3 (mg/L) object DBO 5 (mg/L) float64 Fecha object dtype: object
Tipodedato = SinND.copy()
Tipodedato
#Cambiar Tipo de datos: Fecha
Tipodedato['Fecha'] = pd.to_datetime(Tipodedato['Fecha'], format= '%d/%m/%Y')
print("Tipo de dato modificado fecha:", Tipodedato['Fecha'].dtypes)
#Cambiar Tipo de datos: N-NH3 (mg/L)
Tipodedato['N-NH3 (mg/L)'] = Tipodedato['N-NH3 (mg/L)'].astype(float)
print("Tipo de dato modificado N-NH3:", Tipodedato['N-NH3 (mg/L)'].dtypes)
Tipo de dato modificado fecha: datetime64[ns] Tipo de dato modificado N-NH3: float64
4.6. Agrupar valores por orden de Punto y Hora
#Agrupar por Valores de Punto y Fecha; y tener un mejor orden y visualización
NewRio = Tipodedato.copy()
NRio = NewRio.groupby(['Punto','Fecha']).apply(lambda x: x.sort_values(by='Punto', ascending =False)).reset_index(drop=True)
print(NRio)
Punto % O N-NH3 (mg/L) DBO 5 (mg/L) Fecha 0 Pt1 106.8 0.082 0.560 2013-03-01 1 Pt1 58.4 0.086 0.478 2013-06-13 2 Pt1 120.6 0.162 0.569 2013-10-10 3 Pt1 115.4 0.070 0.503 2013-11-21 4 Pt1 100.8 0.010 1.100 2014-02-04 .. ... ... ... ... ... 143 Pt8 172.4 0.121 2.750 2022-06-22 144 Pt8 66.5 0.035 4.860 2022-09-08 145 Pt8 63.0 0.011 1.830 2022-11-15 146 Pt8 76.3 0.036 2.250 2023-02-14 147 Pt8 52.9 0.031 3.920 2023-04-12 [148 rows x 5 columns]
4.7. Generar las columnas para el índice Holandés
#Crear columnas vacías necesarias para determinar el indice holandés
NRio["VAmonio"] = None
NRio["VDBO"] = None
NRio["VOxigeno"] = None
NRio['Sumatoria'] = None
NRio['Clase'] = None
NRio['Codigo de color'] = None
NRio['Interpretacion de Calidad'] = None
NRio.head()
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | None | None | None | None | None | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | None | None | None | None | None | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | None | None | None | None | None | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | None | None | None | None | None | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | None | None | None | None | None | None | None |
5. Generación del Indice Holandés¶
5.1. Introducir valores: VAmonio, VDBO, VOxigeno
#Insertar datos en VAmonio según el Reglamento
NRio['VAmonio'] = NRio['N-NH3 (mg/L)'].apply(
lambda x: 1 if x < 0.500 else (
2 if 0.500 <= x < 1.099 else (
3 if 1.100 <= x < 2.099 else (
4 if 2.100 <= x < 5.000 else 5
)
)
)
)
print("Valores únicos en la columna:", NRio['VAmonio'].unique())
NRio.head(10)
Valores únicos en la columna: [1 2 3 5 4]
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | None | None | None | None | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | None | None | None | None | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | None | None | None | None | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | None | None | None | None | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | None | None | None | None | None | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | None | None | None | None | None | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | None | None | None | None | None | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | None | None | None | None | None | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | None | None | None | None | None | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | None | None | None | None | None | None |
#Insertar datos en VDBO según el Reglamento
NRio['VDBO'] = NRio['DBO 5 (mg/L)'].apply(
lambda x: 1 if x <= 3.099 else (
2 if 3.100 <= x < 6.099 else (
3 if 6.100 <= x < 9.099 else (
4 if 9.100 <= x < 15.000 else 5
)
)
)
)
print("Valores únicos en la columna:", NRio['VDBO'].unique())
NRio.head(10)
Valores únicos en la columna: [1 3 2 4 5]
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | None | None | None | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | None | None | None | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | None | None | None | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | None | None | None | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | None | None | None | None | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | None | None | None | None | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | None | None | None | None | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | None | None | None | None | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | None | None | None | None | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | None | None | None | None | None |
#Insertar datos en VOxígeno según el Reglamento
NRio['VOxigeno'] = NRio['% O'].apply(
lambda x: 5 if x <= 30.9 else(
4 if 31.0 <= x < 50.9 else(
3 if 51.0 <= x < 70.9 else (
2 if 71.0 <= x < 90.9 else (
1 if 91.0 <= x < 100.9 else (
2 if 101.0 <= x < 120.9 else (
3 if 121.0 <= x < 130.0 else 5
)
)
)
)
)
)
)
print("Valores únicos en la columna:", NRio['VOxigeno'].unique())
NRio.head(10)
Valores únicos en la columna: [2 3 1 5 4]
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | 2 | None | None | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | 3 | None | None | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | 2 | None | None | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | 2 | None | None | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | 1 | None | None | None | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | 2 | None | None | None | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | 2 | None | None | None | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | 2 | None | None | None | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | 3 | None | None | None | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | 2 | None | None | None | None |
5.2. Introducir valores: Sumatoria de cada valor asignado según el rango
# Sumar los valores para completar la columna Sumatoria, que permita determinar estimar posteriormente si cumple con el reglamento de calidad de cuerpos de agua superficial.
NRio['Sumatoria'] = NRio['VAmonio'] + NRio['VDBO'] + NRio['VOxigeno']
NRio.head(10)
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | 2 | 4 | None | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | 3 | 5 | None | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | 2 | 4 | None | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | 2 | 4 | None | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | 1 | 3 | None | None | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | 2 | 4 | None | None | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | 2 | 4 | None | None | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | 2 | 6 | None | None | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | 3 | 5 | None | None | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | 2 | 4 | None | None | None |
5.3. Introducir valores: Clase
#Asignar un Valor a la columna Clase, según la columna Sumatoria que se basa en el Reglamento de cuerpos de agua superficial
NRio['Clase'] = NRio['Sumatoria'].apply(
lambda x: 1 if x <= 3 else(
2 if 4 <= x <= 6 else(
3 if 7 <= x <= 9 else (
4 if 10 <= x <= 12 else 5
)
)
)
)
print("Valores únicos en la columna:", NRio['Clase'].unique())
NRio.head(20)
Valores únicos en la columna: [2 1 3 5 4]
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | 2 | 4 | 2 | None | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | 3 | 5 | 2 | None | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | 2 | 4 | 2 | None | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | 2 | 4 | 2 | None | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | 1 | 3 | 1 | None | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | 2 | 4 | 2 | None | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | 2 | 4 | 2 | None | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | 2 | 6 | 2 | None | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | 3 | 5 | 2 | None | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | 2 | 4 | 2 | None | None |
10 | Pt1 | 103.1 | 0.100 | 0.260 | 2020-09-09 | 1 | 1 | 2 | 4 | 2 | None | None |
11 | Pt1 | 96.8 | 0.000 | 2.190 | 2021-02-02 | 1 | 1 | 1 | 3 | 1 | None | None |
12 | Pt1 | 103.1 | 0.100 | 1.000 | 2021-06-22 | 1 | 1 | 2 | 4 | 2 | None | None |
13 | Pt1 | 98.4 | 0.040 | 1.770 | 2021-10-27 | 1 | 1 | 1 | 3 | 1 | None | None |
14 | Pt1 | 100.6 | 0.040 | 4.060 | 2021-12-01 | 1 | 2 | 1 | 4 | 2 | None | None |
15 | Pt1 | 105.3 | 0.010 | 0.930 | 2022-03-23 | 1 | 1 | 2 | 4 | 2 | None | None |
16 | Pt1 | 219.2 | 0.019 | 3.190 | 2022-06-22 | 1 | 2 | 5 | 8 | 3 | None | None |
17 | Pt1 | 86.8 | 0.000 | 4.440 | 2022-09-08 | 1 | 2 | 2 | 5 | 2 | None | None |
18 | Pt1 | 68.4 | 0.000 | 1.550 | 2022-11-15 | 1 | 1 | 3 | 5 | 2 | None | None |
19 | Pt1 | 89.7 | 0.003 | 0.750 | 2023-02-14 | 1 | 1 | 2 | 4 | 2 | None | None |
5.4. Introducir valores: Codigo de color
#Asignar un Valor a la columna de Codigo de color, según Clase acorde a resultados del Reglamento de calidad de cuerpos de agua superficial.
NRio['Codigo de color'] = NRio['Clase'].apply(
lambda x: "Azul" if x == 1 else(
"Verde" if x == 2 else(
"Amarillo" if x == 3 else (
"Anaranjado" if x == 4 else "Rojo"
)
)
)
)
print("Valores únicos en la columna:", NRio['Codigo de color'].unique())
NRio.head(20)
Valores únicos en la columna: ['Verde' 'Azul' 'Amarillo' 'Rojo' 'Anaranjado']
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | 2 | 4 | 2 | Verde | None |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | 3 | 5 | 2 | Verde | None |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | 2 | 4 | 2 | Verde | None |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | 2 | 4 | 2 | Verde | None |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | 1 | 3 | 1 | Azul | None |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | 2 | 4 | 2 | Verde | None |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | 2 | 4 | 2 | Verde | None |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | 2 | 6 | 2 | Verde | None |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | 3 | 5 | 2 | Verde | None |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | 2 | 4 | 2 | Verde | None |
10 | Pt1 | 103.1 | 0.100 | 0.260 | 2020-09-09 | 1 | 1 | 2 | 4 | 2 | Verde | None |
11 | Pt1 | 96.8 | 0.000 | 2.190 | 2021-02-02 | 1 | 1 | 1 | 3 | 1 | Azul | None |
12 | Pt1 | 103.1 | 0.100 | 1.000 | 2021-06-22 | 1 | 1 | 2 | 4 | 2 | Verde | None |
13 | Pt1 | 98.4 | 0.040 | 1.770 | 2021-10-27 | 1 | 1 | 1 | 3 | 1 | Azul | None |
14 | Pt1 | 100.6 | 0.040 | 4.060 | 2021-12-01 | 1 | 2 | 1 | 4 | 2 | Verde | None |
15 | Pt1 | 105.3 | 0.010 | 0.930 | 2022-03-23 | 1 | 1 | 2 | 4 | 2 | Verde | None |
16 | Pt1 | 219.2 | 0.019 | 3.190 | 2022-06-22 | 1 | 2 | 5 | 8 | 3 | Amarillo | None |
17 | Pt1 | 86.8 | 0.000 | 4.440 | 2022-09-08 | 1 | 2 | 2 | 5 | 2 | Verde | None |
18 | Pt1 | 68.4 | 0.000 | 1.550 | 2022-11-15 | 1 | 1 | 3 | 5 | 2 | Verde | None |
19 | Pt1 | 89.7 | 0.003 | 0.750 | 2023-02-14 | 1 | 1 | 2 | 4 | 2 | Verde | None |
Introducir valores: Interpretación de calidad
#Asignar un Valor a Interpretacion de Calidad, según Clase acorde al Reglamento
NRio['Interpretacion de Calidad'] = NRio['Clase'].apply(
lambda x: "Sin Contaminación" if x == 1 else(
"Contaminación incipiente" if x == 2 else(
"Contaminación moderada" if x == 3 else (
"Contaminación severa" if x == 4 else "Contaminación muy severa"
)
)
)
)
print("Valores únicos en la columna:", NRio['Interpretacion de Calidad'].unique())
NRio.head(20)
Valores únicos en la columna: ['Contaminación incipiente' 'Sin Contaminación' 'Contaminación moderada' 'Contaminación muy severa' 'Contaminación severa']
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pt1 | 106.8 | 0.082 | 0.560 | 2013-03-01 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
1 | Pt1 | 58.4 | 0.086 | 0.478 | 2013-06-13 | 1 | 1 | 3 | 5 | 2 | Verde | Contaminación incipiente |
2 | Pt1 | 120.6 | 0.162 | 0.569 | 2013-10-10 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
3 | Pt1 | 115.4 | 0.070 | 0.503 | 2013-11-21 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
4 | Pt1 | 100.8 | 0.010 | 1.100 | 2014-02-04 | 1 | 1 | 1 | 3 | 1 | Azul | Sin Contaminación |
5 | Pt1 | 110.8 | 0.020 | 0.018 | 2014-07-29 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
6 | Pt1 | 85.2 | 0.260 | 2.823 | 2015-05-28 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
7 | Pt1 | 101.7 | 0.340 | 6.130 | 2017-04-25 | 1 | 3 | 2 | 6 | 2 | Verde | Contaminación incipiente |
8 | Pt1 | 70.8 | 0.240 | 0.830 | 2017-10-24 | 1 | 1 | 3 | 5 | 2 | Verde | Contaminación incipiente |
9 | Pt1 | 76.0 | 0.100 | 0.370 | 2020-07-30 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
10 | Pt1 | 103.1 | 0.100 | 0.260 | 2020-09-09 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
11 | Pt1 | 96.8 | 0.000 | 2.190 | 2021-02-02 | 1 | 1 | 1 | 3 | 1 | Azul | Sin Contaminación |
12 | Pt1 | 103.1 | 0.100 | 1.000 | 2021-06-22 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
13 | Pt1 | 98.4 | 0.040 | 1.770 | 2021-10-27 | 1 | 1 | 1 | 3 | 1 | Azul | Sin Contaminación |
14 | Pt1 | 100.6 | 0.040 | 4.060 | 2021-12-01 | 1 | 2 | 1 | 4 | 2 | Verde | Contaminación incipiente |
15 | Pt1 | 105.3 | 0.010 | 0.930 | 2022-03-23 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
16 | Pt1 | 219.2 | 0.019 | 3.190 | 2022-06-22 | 1 | 2 | 5 | 8 | 3 | Amarillo | Contaminación moderada |
17 | Pt1 | 86.8 | 0.000 | 4.440 | 2022-09-08 | 1 | 2 | 2 | 5 | 2 | Verde | Contaminación incipiente |
18 | Pt1 | 68.4 | 0.000 | 1.550 | 2022-11-15 | 1 | 1 | 3 | 5 | 2 | Verde | Contaminación incipiente |
19 | Pt1 | 89.7 | 0.003 | 0.750 | 2023-02-14 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente |
6. Analisis de datos¶
6.1. Profile con los nuevos valores
# Creamos el informe con pandas-profiling
profile = ProfileReport(NRio, title="NRio", 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]
7. Gráficos¶
7.1. Distribución y tendencia central de DBO, Nitrógeno Amoniacal y Porcentaje de saturación
fig, axes = plt.subplots(1, 3, figsize=(21,7))
# Boxplot para Valores de DBO
sns.boxplot(x="Punto", y="DBO 5 (mg/L)", data=NRio, ax=axes[0])
axes[0].set_title('Boxplot del comportamiento de DBO por punto')
#Boxplot para valores de N-NH3
sns.boxplot(x="Punto", y="N-NH3 (mg/L)", data=NRio, ax=axes[1])
axes[1].set_title('Boxplot del comportamiento de N-NH3 por punto')
#Boxplot para valores de Porcentaje de Oxígeno
sns.boxplot(x="Punto", y="% O", data=NRio, ax=axes[2])
axes[2].set_title('Boxplot del comportamiento de Porcentaje de saturación de Oxígeno por punto')
plt.tight_layout() #
plt.show()
7.2. Creación de histogramas para observar la distribución de todos los parámetros
# Crear histogramas para todas las columnas numéricas del dataframe NRio
NRio.hist(edgecolor='black', linewidth=0.5, figsize=(12, 8))
plt.show()
7.3 Diagrama de dispersión para observar la clase que obtuvo cada punto, durante los 11 años de monitoreo.
# Crear el gráfico
plt.figure(figsize=(10, 6))
sns.scatterplot(x='Fecha', y='Clase', hue='Punto', data=NRio, s=100, palette='deep')
# Personalizar el gráfico
plt.xlabel('Fecha')
plt.ylabel('Clase')
plt.title('Gráfico de Fecha vs Clase')
plt.legend(title='Punto', loc='best')
plt.ylim(0.5,6)
# Mostrar el gráfico
plt.show()
7.4 Inserción de una nueva columna para determinar Epoca del año
# Columna Epoca
NRio["Epoca"] = None
#Definir la función para determinar la época
def determinar_epoca(fecha):
if (fecha.month == 1) or (fecha.month == 2) or (fecha.month == 3) or (fecha.month == 4 and fecha.day <= 15):
return 'Seca'
elif (fecha.month == 4 and fecha.day > 15) or (fecha.month == 5 and fecha.day <= 15):
return 'Seca-Lluviosa'
elif (fecha.month == 5 and fecha.day > 15) or (fecha.month in [6, 7, 8, 9, 10] and fecha.day <= 31):
return 'Lluviosa'
elif (fecha.month == 11) or (fecha.month == 12):
return 'Lluviosa-Seca'
# Aplicar la función a la columna 'Fecha'
NRio['Epoca'] = NRio['Fecha'].apply(determinar_epoca)
# Mostrar los primeros registros para verificar
print(NRio.head())
Punto % O N-NH3 (mg/L) DBO 5 (mg/L) Fecha VAmonio VDBO \ 0 Pt1 106.8 0.082 0.560 2013-03-01 1 1 1 Pt1 58.4 0.086 0.478 2013-06-13 1 1 2 Pt1 120.6 0.162 0.569 2013-10-10 1 1 3 Pt1 115.4 0.070 0.503 2013-11-21 1 1 4 Pt1 100.8 0.010 1.100 2014-02-04 1 1 VOxigeno Sumatoria Clase Codigo de color Interpretacion de Calidad \ 0 2 4 2 Verde Contaminación incipiente 1 3 5 2 Verde Contaminación incipiente 2 2 4 2 Verde Contaminación incipiente 3 2 4 2 Verde Contaminación incipiente 4 1 3 1 Azul Sin Contaminación Epoca 0 Seca 1 Lluviosa 2 Lluviosa 3 Lluviosa-Seca 4 Seca
7.5. Selección de datos por Punto
# Seleccionando datos por condicion (Punto=Pt1)
condicion1 = NRio["Punto"] == 'Pt1'
registros_pt1 = NRio[condicion1]
registros_pt1
# Seleccionando datos por condicion (Punto=Pt2)
condicion2 = NRio["Punto"] == 'Pt2'
registros_pt2 = NRio[condicion2]
registros_pt2
# Seleccionando datos por condicion (Punto=Pt3)
condicion3 = NRio["Punto"] == 'Pt3'
registros_pt3 = NRio[condicion3]
registros_pt3
# Seleccionando datos por condicion (Punto=Pt4)
condicion4 = NRio["Punto"] == 'Pt4'
registros_pt4 = NRio[condicion4]
registros_pt4
# Seleccionando datos por condicion (Punto=Pt5)
condicion5 = NRio["Punto"] == 'Pt5'
registros_pt5 = NRio[condicion5]
registros_pt5
# Seleccionando datos por condicion (Punto=Pt6)
condicion6 = NRio["Punto"] == 'Pt6'
registros_pt6 = NRio[condicion6]
registros_pt6
# Seleccionando datos por condicion (Punto=Pt7)
condicion7 = NRio["Punto"] == 'Pt7'
registros_pt7 = NRio[condicion7]
registros_pt7
# Seleccionando datos por condicion (Punto=Pt8)
condicion8 = NRio["Punto"] == 'Pt8'
registros_pt8 = NRio[condicion8]
registros_pt8
Punto | % O | N-NH3 (mg/L) | DBO 5 (mg/L) | Fecha | VAmonio | VDBO | VOxigeno | Sumatoria | Clase | Codigo de color | Interpretacion de Calidad | Epoca | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
136 | Pt8 | 95.8 | 0.100 | 1.85 | 2020-09-09 | 1 | 1 | 1 | 3 | 1 | Azul | Sin Contaminación | Lluviosa |
137 | Pt8 | 75.5 | 0.000 | 3.03 | 2021-02-02 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente | Seca |
138 | Pt8 | 61.9 | 0.100 | 2.00 | 2021-06-22 | 1 | 1 | 3 | 5 | 2 | Verde | Contaminación incipiente | Lluviosa |
139 | Pt8 | 24.7 | 0.050 | 6.00 | 2021-08-27 | 1 | 2 | 5 | 8 | 3 | Amarillo | Contaminación moderada | Lluviosa |
140 | Pt8 | 87.6 | 0.040 | 3.61 | 2021-10-27 | 1 | 2 | 2 | 5 | 2 | Verde | Contaminación incipiente | Lluviosa |
141 | Pt8 | 89.3 | 0.040 | 4.54 | 2021-12-01 | 1 | 2 | 2 | 5 | 2 | Verde | Contaminación incipiente | Lluviosa-Seca |
142 | Pt8 | 79.7 | 0.010 | 7.22 | 2022-03-23 | 1 | 3 | 2 | 6 | 2 | Verde | Contaminación incipiente | Seca |
143 | Pt8 | 172.4 | 0.121 | 2.75 | 2022-06-22 | 1 | 1 | 5 | 7 | 3 | Amarillo | Contaminación moderada | Lluviosa |
144 | Pt8 | 66.5 | 0.035 | 4.86 | 2022-09-08 | 1 | 2 | 3 | 6 | 2 | Verde | Contaminación incipiente | Lluviosa |
145 | Pt8 | 63.0 | 0.011 | 1.83 | 2022-11-15 | 1 | 1 | 3 | 5 | 2 | Verde | Contaminación incipiente | Lluviosa-Seca |
146 | Pt8 | 76.3 | 0.036 | 2.25 | 2023-02-14 | 1 | 1 | 2 | 4 | 2 | Verde | Contaminación incipiente | Seca |
147 | Pt8 | 52.9 | 0.031 | 3.92 | 2023-04-12 | 1 | 2 | 3 | 6 | 2 | Verde | Contaminación incipiente | Seca |
7.6 Gráfico de barras de clase por punto, según época del año
#Grafico de Barras para cada punto
clasifpt1 = registros_pt1.groupby('Epoca')['Clase'].mean()
clasifpt2 = registros_pt2.groupby('Epoca')['Clase'].mean()
clasifpt3 = registros_pt3.groupby('Epoca')['Clase'].mean()
clasifpt4 = registros_pt4.groupby('Epoca')['Clase'].mean()
clasifpt5 = registros_pt5.groupby('Epoca')['Clase'].mean()
clasifpt6 = registros_pt6.groupby('Epoca')['Clase'].mean()
clasifpt7 = registros_pt7.groupby('Epoca')['Clase'].mean()
clasifpt8 = registros_pt8.groupby('Epoca')['Clase'].mean()
# Crear figura y ejes para dos subplots
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
# Gráfico de barras para registros_pt1
clasifpt1.plot(kind='bar', ax=axes[0,0])
axes[0,0].set_title('Clasificación Punto 1,San María')
axes[0,0].set_xlabel("Epoca")
axes[0,0].set_ylabel('Clase')
axes[0,0].set_xticks(range(len(clasifpt1)))
axes[0,0].set_xticklabels(clasifpt1.index, rotation=45)
axes[0,0].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt2
clasifpt2.plot(kind='bar', ax=axes[0,1])
axes[0,1].set_title('Clasificación Punto 2, El Yugo')
axes[0,1].set_xlabel("Epoca")
axes[0,1].set_ylabel('Clase')
axes[0,1].set_xticks(range(len(clasifpt2)))
axes[0,1].set_xticklabels(clasifpt2.index, rotation=45)
axes[0,1].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt3
clasifpt3.plot(kind='bar', ax=axes[0,2])
axes[0,2].set_title('Clasificación Punto 3, Saca de Agua')
axes[0,2].set_xlabel("Epoca")
axes[0,2].set_ylabel('Clase')
axes[0,2].set_xticks(range(len(clasifpt3)))
axes[0,2].set_xticklabels(clasifpt3.index, rotation=45)
axes[0,2].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt4
clasifpt4.plot(kind='bar', ax=axes[0,3])
axes[0,3].set_title('Clasificación Punto 4, Condega')
axes[0,3].set_xlabel("Epoca")
axes[0,3].set_ylabel('Clase')
axes[0,3].set_xticks(range(len(clasifpt4)))
axes[0,3].set_xticklabels(clasifpt4.index, rotation=45)
axes[0,3].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt5
clasifpt5.plot(kind='bar', ax=axes[1,0])
axes[1,0].set_title('Clasificación Punto 5,Capulin')
axes[1,0].set_xlabel("Epoca")
axes[1,0].set_ylabel('Clase')
axes[1,0].set_xticks(range(len(clasifpt5)))
axes[1,0].set_xticklabels(clasifpt5.index, rotation=45)
axes[1,0].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt6
clasifpt6.plot(kind='bar', ax=axes[1,1])
axes[1,1].set_title('Clasificación Punto 6, PTAR')
axes[1,1].set_xlabel("Epoca")
axes[1,1].set_ylabel('Clase')
axes[1,1].set_xticks(range(len(clasifpt6)))
axes[1,1].set_xticklabels(clasifpt6.index, rotation=45)
axes[1,1].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt7
clasifpt7.plot(kind='bar', ax=axes[1,2])
axes[1,2].set_title('Clasificación Punto 7,Aforo')
axes[1,2].set_xlabel("Epoca")
axes[1,2].set_ylabel('Clase')
axes[1,2].set_xticks(range(len(clasifpt7)))
axes[1,2].set_xticklabels(clasifpt7.index, rotation=45)
axes[1,2].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt8
clasifpt8.plot(kind='bar', ax=axes[1,3])
axes[1,3].set_title('Clasificación Punto 8, Puente Real')
axes[1,3].set_xlabel("Epoca")
axes[1,3].set_ylabel('Clase')
axes[1,3].set_xticks(range(len(clasifpt8)))
axes[1,3].set_xticklabels(clasifpt8.index, rotation=45)
axes[1,3].set_ylim(0.5, 6)
# Ajustar el layout para que no haya solapamiento
plt.tight_layout()
plt.show()
7.7 Grafico de barras de interpretación de calidad por punto
# Agrupar datos por especie y obtener el promedio de la columna sepal length
clasifpt1 = registros_pt1.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt2 = registros_pt2.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt3 = registros_pt3.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt4 = registros_pt4.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt5 = registros_pt5.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt6 = registros_pt6.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt7 = registros_pt7.groupby('Interpretacion de Calidad')['Clase'].mean()
clasifpt8 = registros_pt8.groupby('Interpretacion de Calidad')['Clase'].mean()
# Crear figura y ejes para dos subplots
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
# Gráfico de barras para registros_pt1
clasifpt1.plot(kind='bar', ax=axes[0,0])
axes[0,0].set_title('Calidad hidrica Punto 1')
axes[0,0].set_xlabel("Interpretacion de Calidad")
axes[0,0].set_ylabel('Clase')
axes[0,0].set_xticks(range(len(clasifpt1)))
axes[0,0].set_xticklabels(clasifpt1.index, rotation=50)
axes[0,0].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt2
clasifpt2.plot(kind='bar', ax=axes[0,1])
axes[0,1].set_title('Calidad hidrica Punto 2')
axes[0,1].set_xlabel("Interpretacion de Calidad")
axes[0,1].set_ylabel('Clase')
axes[0,1].set_xticks(range(len(clasifpt2)))
axes[0,1].set_xticklabels(clasifpt2.index, rotation=50)
axes[0,1].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt3
clasifpt3.plot(kind='bar', ax=axes[0,2])
axes[0,2].set_title('Calidad hidrica Punto 3')
axes[0,2].set_xlabel("Interpretacion de Calidad")
axes[0,2].set_ylabel('Clase')
axes[0,2].set_xticks(range(len(clasifpt3)))
axes[0,2].set_xticklabels(clasifpt3.index, rotation=50)
axes[0,2].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt4
clasifpt4.plot(kind='bar', ax=axes[0,3])
axes[0,3].set_title('Calidad hidrica Punto 4')
axes[0,3].set_xlabel("Interpretacion de Calidad")
axes[0,3].set_ylabel('Clase')
axes[0,3].set_xticks(range(len(clasifpt4)))
axes[0,3].set_xticklabels(clasifpt4.index, rotation=50)
axes[0,3].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt5
clasifpt5.plot(kind='bar', ax=axes[1,0])
axes[1,0].set_title('Calidad hidrica Punto 5')
axes[1,0].set_xlabel("Interpretacion de Calidad")
axes[1,0].set_ylabel('Clase')
axes[1,0].set_xticks(range(len(clasifpt5)))
axes[1,0].set_xticklabels(clasifpt5.index, rotation=50)
axes[1,0].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt6
clasifpt6.plot(kind='bar', ax=axes[1,1])
axes[1,1].set_title('Calidad hidrica Punto 6')
axes[1,1].set_xlabel("Interpretacion de Calidad")
axes[1,1].set_ylabel('Clase')
axes[1,1].set_xticks(range(len(clasifpt6)))
axes[1,1].set_xticklabels(clasifpt6.index, rotation=50)
axes[1,1].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt7
clasifpt7.plot(kind='bar', ax=axes[1,2])
axes[1,2].set_title('Calidad hidrica Punto 7')
axes[1,2].set_xlabel("Interpretacion de Calidad")
axes[1,2].set_ylabel('Clase')
axes[1,2].set_xticks(range(len(clasifpt7)))
axes[1,2].set_xticklabels(clasifpt7.index, rotation=50)
axes[1,2].set_ylim(0.5, 6)
# Gráfico de barras para registros_pt8
clasifpt8.plot(kind='bar', ax=axes[1,3])
axes[1,3].set_title('Calidad hidrica Punto 8')
axes[1,3].set_xlabel("Interpretacion de Calidad")
axes[1,3].set_ylabel('Clase')
axes[1,3].set_xticks(range(len(clasifpt8)))
axes[1,3].set_xticklabels(clasifpt8.index, rotation=50)
axes[1,3].set_ylim(0.5, 6)
# Ajustar el layout para que no haya solapamiento
plt.tight_layout()
plt.show()
7.8. Gráfico de líneas del comportamiento en el tiempo de la clase de cada punto
fig, axes = plt.subplots(2, 4, figsize=(21, 14)) # 2 filas, 4 columnas
# Lista de registros y títulos
registros = [registros_pt1, registros_pt2, registros_pt3, registros_pt4, registros_pt5, registros_pt6, registros_pt7, registros_pt8]
titulos = [
'Gráfico de líneas de Fecha vs. Clase para Punto 1 Santa María',
'Gráfico de líneas de Fecha vs. Clase para Punto 2 El Yugo',
'Gráfico de líneas de Fecha vs. Clase para Punto 3 Saca de Agua',
'Gráfico de líneas de Fecha vs. Clase para Punto 4 Condega',
'Gráfico de líneas de Fecha vs. Clase para Punto 5 El Capulín',
'Gráfico de líneas de Fecha vs. Clase para Punto 6 PTAR',
'Gráfico de líneas de Fecha vs. Clase para Punto 7 Aforo',
'Gráfico de líneas de Fecha vs. Clase para Punto 8 Puente Real'
]
# Crear cada subplot
for i, ax in enumerate(axes.flatten()):
ax.plot(registros[i]['Fecha'], registros[i]['Clase'], marker='o', linestyle='-')
ax.set_title(titulos[i])
ax.set_xlabel('Fecha')
ax.set_ylabel('Clase')
ax.set_ylim(0.5, 5.5)
# Ajustar los espacios entre los subplots
plt.tight_layout()
# Mostrar los gráficos
plt.show()
8. Georeferenciación¶
8.1 Leer archivo de mapa de Costa Rica
cr_mapa = gpd.read_file('gadm41_CRI_3.shp')
# Obtener el área, límite y centroide por provincia
cr_mapa["centroid"] = cr_mapa.centroid
cr_mapa
geometry | centroid | |
---|---|---|
0 | POLYGON ((-84.18735 10.05284, -84.18024 10.052... | POINT (-84.20609 10.02408) |
1 | POLYGON ((-84.18024 10.05246, -84.18735 10.052... | POINT (-84.17419 10.08557) |
2 | POLYGON ((-84.20731 10.00834, -84.20686 10.008... | POINT (-84.18495 10.02750) |
3 | POLYGON ((-84.27063 9.97580, -84.27134 9.97544... | POINT (-84.30354 9.98748) |
4 | POLYGON ((-84.21308 9.99937, -84.21285 9.99892... | POINT (-84.26058 9.95930) |
... | ... | ... |
481 | POLYGON ((-83.88311 9.97049, -83.88330 9.97049... | POINT (-83.92888 10.03244) |
482 | POLYGON ((-84.02831 10.06006, -84.02830 10.082... | POINT (-83.97975 10.08888) |
483 | POLYGON ((-84.02446 9.96711, -84.02461 9.96712... | POINT (-84.02234 9.97465) |
484 | POLYGON ((-83.99859 9.96849, -83.99884 9.96852... | POINT (-83.99224 9.97255) |
485 | POLYGON ((-83.90474 9.97574, -83.90556 9.97540... | POINT (-83.95146 9.97853) |
486 rows × 2 columns
cr_mapa.plot(figsize = (8,8), edgecolor="w", linewidth=0.3)
<Axes: >
8.2. Obtener un distrito con Geopy
# !pip install geopy
from geopy.geocoders import Nominatim
# Initialize Nominatim API
geolocator = Nominatim(user_agent="geoPandas")
ubicaciones = {
"liberia": "10.626442159158902, -85.44803406302779",
}
# Get location with geocode
location = geolocator.geocode(ubicaciones["liberia"])
print("Dirección completa:", location[0])
print("Longitud y Latitud:", location[1])
print("\nNivel administrativo 1:", location[0].split(',')[-5][1:])
Dirección completa: Walmart, Calle 26, El Capulín, Liberia, Cantón Liberia, Provincia Guanacaste, 50101, Costa Rica Longitud y Latitud: (10.6266216, -85.44820014915676) Nivel administrativo 1: Liberia
8.3.
# Import module
from geopy.geocoders import Nominatim
def get_prov(p):
global geolocator
location = geolocator.geocode(f"{p.y}, {p.x}")
return location[0].split(",")[-3][11:]
def get_canton(p):
global geolocator
location = geolocator.geocode(f"{p.y}, {p.x}")
return location[0].split(',')[-4][7:]
def get_distrito(p):
global geolocator
location = geolocator.geocode(f"{p.y}, {p.x}")
return location[0].split(',')[-5][1:]
# Initialize Nominatim API
geolocator = Nominatim(user_agent="geoPandas")
8.4. Buscar centroid por distrito
cr_mapa["Distrito"] = cr_mapa.centroid.map(lambda p: get_distrito(p))
cr_mapa
WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=10.398246425960604%2C+-84.38437575861356&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=10.564728151068248%2C+-84.63249835859824&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=10.326186578000762%2C+-84.41256656330046&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=10.566035139229172%2C+-84.76453080745506&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=9.965588587303277%2C+-84.47656791978665&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=9.92639462174031%2C+-83.99251996588586&format=json&limit=1 WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)")': /search?q=9.935076431571288%2C+-84.10726779453026&format=json&limit=1
geometry | centroid | Distrito | |
---|---|---|---|
0 | POLYGON ((-84.18735 10.05284, -84.18024 10.052... | POINT (-84.20609 10.02408) | Alajuela |
1 | POLYGON ((-84.18024 10.05246, -84.18735 10.052... | POINT (-84.17419 10.08557) | Carrizal |
2 | POLYGON ((-84.20731 10.00834, -84.20686 10.008... | POINT (-84.18495 10.02750) | Desamparados |
3 | POLYGON ((-84.27063 9.97580, -84.27134 9.97544... | POINT (-84.30354 9.98748) | Garita |
4 | POLYGON ((-84.21308 9.99937, -84.21285 9.99892... | POINT (-84.26058 9.95930) | Guácima |
... | ... | ... | ... |
481 | POLYGON ((-83.88311 9.97049, -83.88330 9.97049... | POINT (-83.92888 10.03244) | Cascajal |
482 | POLYGON ((-84.02831 10.06006, -84.02830 10.082... | POINT (-83.97975 10.08888) | Dulce Nombre de Jesús |
483 | POLYGON ((-84.02446 9.96711, -84.02461 9.96712... | POINT (-84.02234 9.97465) | Patalillo |
484 | POLYGON ((-83.99859 9.96849, -83.99884 9.96852... | POINT (-83.99224 9.97255) | San Isidro |
485 | POLYGON ((-83.90474 9.97574, -83.90556 9.97540... | POINT (-83.95146 9.97853) | San Rafael |
486 rows × 3 columns
8.5. Visualizar polígono: Liberia
cr_mapa[cr_mapa.Distrito == 'Liberia'].plot()
<Axes: >
8.6. Puntos geográficos desde el csv
df = pd.read_csv("test.csv")
df
Punto | Estación | x | y | MSNM | Características del sitio | |
---|---|---|---|---|---|---|
0 | Pt1 | Santa María | 10.738333 | -85.314167 | 690 | Ubicado aguas abajo del derivador de aguas ubi... |
1 | Pt2 | El Yugo | 10.670000 | -85.391944 | 260 | Rodeado por cañones muy pronunciados en la mes... |
2 | Pt3 | Saca de agua | 10.647500 | -85.416111 | 180 | Inmediatamente aguas arriba de la planta potab... |
3 | Pt4 | Condega | 10.622500 | -85.439167 | 145 | Ubicado en extremo suroeste del centro históri... |
4 | Pt5 | Capulin | 10.626667 | -85.460000 | 125 | Aguas debajo de la confluencia entre la quebra... |
5 | Pt6 | PTAR | 10.626389 | -85.460833 | 124 | Aguas debajo de la descarga de la planta munic... |
6 | Pt7 | Aforo | 10.613611 | -85.485000 | 113 | Ubicado fuera del área urbana y aguas arriba d... |
7 | Pt8 | Puente Real | 10.625000 | -85.435556 | 150 | Localizado en calle central sobre puente decla... |
my_geometry = gpd.points_from_xy(df.y, df.x) # ejes invertidos por convenciones cartográficas
print(my_geometry)
<GeometryArray> [<POINT (-85.314 10.738)>, <POINT (-85.392 10.67)>, <POINT (-85.416 10.648)>, <POINT (-85.439 10.622)>, <POINT (-85.46 10.627)>, <POINT (-85.461 10.626)>, <POINT (-85.485 10.614)>, <POINT (-85.436 10.625)>] Length: 8, dtype: geometry
obvs_gdf = gpd.GeoDataFrame(df, geometry=my_geometry)
obvs_gdf.plot()
<Axes: >
8.7. Selección de polígono de Liberia
cr_mapa[(cr_mapa["Distrito"] == 'Mogote') | (cr_mapa["Distrito"] == "Liberia")]
# | = or
# & = and
geometry | centroid | Distrito | |
---|---|---|---|
172 | POLYGON ((-85.29365 10.77595, -85.29248 10.775... | POINT (-85.27194 10.69542) | Mogote |
194 | POLYGON ((-85.32543 10.81208, -85.32526 10.811... | POINT (-85.45043 10.58659) | Liberia |
8.8. Colocar los puntos en el polígono
# Plot sólo los registros Distrito Liberia
distrito = "Liberia"
# mapa_distrito = cr_mapa[(cr_mapa["Distrito"] == 'Mogote') | (cr_mapa["Distrito"] == "Liberia")]
mapa_distrito = cr_mapa[cr_mapa["Distrito"] == distrito]
lienzo = mapa_distrito.plot(
# Ploteo del mapa
figsize = (7,7),
color="lightgreen",
edgecolor="black",
linewidth = 0.6
)
# Asignar título
title = f'Ubicación de los puntos muestreados en el río Liberia {distrito}'
lienzo.set_title(title)
# Labels
lienzo.set_ylabel("Latitud")
lienzo.set_xlabel("Longitud")
# # Ploteo de los puntos del río Liberia
obvs_gdf.plot(
ax=lienzo,
column="Estación",
categorical=True,
legend=True,
legend_kwds={"loc": "center right", "bbox_to_anchor": (1.4, .84), },
)
<Axes: title={'center': 'Obvservaciones registradas en el distrito de Liberia'}, xlabel='Longitud', ylabel='Latitud'>
8. Resultados¶
Aplicando los conocimientos adquiridos en el curso, se logró desarrollar una automatización que permite preparar los datos y convertirlos en información para analizar la calidad del agua de la subcuenca Río Liberia.
Según los resultados obtenidos mediante la estimación del Índice Holandés, se observa que, en términos generales, la mayor categoría de contaminación en los puntos de muestreo ubicados en las partes más altas de la cuenca, como Santa María, El Yugo, Saca de Agua y Condega, corresponde a la clase 3, "Contaminación Moderada"; sin embargo la mayoría de los resultados han marcado una "contaminación incipiente". En contraste, los sitios situados en las zonas más pobladas, como Capulín, PTAR y Aforo, han alcanzado el nivel más alto de contaminación, clase 5, "Contaminación Muy Severa".
De igual manera, se observa un aumento considerable de contaminación en los puntos PTAR y Aforo durante la transición de la época seca a la lluviosa. Esto sugiere que, con las primeras lluvias del año y el consiguiente aumento del caudal del río, se produce una mayor carga de materia transportada por las aguas.
9. Conclusiones¶
El buen manejo de una base de datos es crucial para el tratamiento efectivo de la información, ya que, facilita la organización, almacenamiento y acceso a los datos de manera eficiente, permitiendo realizar análisis precisos y tomar decisiones informadas. Además, la automatización en el procesamiento de datos mejora la rapidez y exactitud en la generación de información, lo cual en un contexto de monitoreo de calidad de aguas superficiales es fundamental para detectar y mejorar la respuesta a las alteraciones que puedan presentarse, de manera oportuna.
La automatización de los datos permitió determinar resultados en conjunto en lineas de tiempo en las que se realizaron los monitoreos de las distintas épocas climáticas, esto generó una mayor comprensión de las problemáticas en cuanto a contaminación, para cada uno de los sitios estudiados, logrando determinarse que, según el indice holandés, el rio presenta una contaminación incipiente en los puntos más alejados mientras que en la sección urbana hay presencia de contaminación muy severa.
10. Referencias bibliográficas¶
Discover Data Science. (s.f.). How is data science being used to tackle the global problem of clean water? https://www.discoverdatascience.org/social-good/clean-water/
Dyson, J.R., Gyori, B.M., & Beaman, A.L. (2021). An open-source package for data profiling in Python. Journal of Open Source Software, 6(63), 3306.
GADM. (s.f.) gadm41_CRI_shp [Archivo .zip de geolocalización]. https://gadm.org/download_country.html
GeoPy Community. (2022). GeoPy 2.2.0 documentation. Retrieved from https://geopy.readthedocs.io/en/stable/.
Harris, C.R., Millman, K.J., van der Walt, S.J., Gommers, R., Virtanen, P., Cournapeau, D., ... & de Jong, J. (2020). Array programming with NumPy. Nature, 585(7825), 357-362.
HIDROCEC. (2024). RioLiberia [Archivo de Excel].
HIDROCEC. (2024). Test[1] [Archivo de Excel].
Hunter, J.D. (2007). Matplotlib: A 2D graphics environment. Computing in Science & Engineering, 9(3), 90-95.
Montoya, S. (2014, 28 de mayo). ¿Por qué los especialistas ambientales deberían programar? Gidahatari. Recuperado de https://gidahatari.com/nh-es/diplomado-en-python-para-recursos-hdricos-y-geociencias-asincrnico
Oscar. (2017, 8 de enero). Python en consultoría medioambiental. Alburen. Recuperado de https://www.alburen.com/2017/01/python-en-consultoria-medioambiental/)
Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., ... & Vanderplas, J. (2011). Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12(Oct), 2825-2830.
Reglamento Para la Evaluación y Clasificación de la Calidad de Cuerpos de Agua Superficiales N°33903-MINAE-S (17 de setiembre de 2007). Diario Oficial La Gaceta N. 178. https://www.aya.go.cr/centroDocumetacion/catalogoGeneral/Reglamento%20evaluaci%C3%B3n%20y%20clasificaci%C3%B3n%20de%20calidad%20de%20cuerpos%20de%20agua%20superficiales.pdf
Warner, S., Blanco Ramírez, S., de Vries, S., Marangu, N., Ateba Bessa, H., Toranzo, C., Imaralieva, M., Abrate, T., Kiminta, E., Castro, J., de Souza, M. L., Ghaffar Memon, A., Loiselle, S., & Juanah, M. S. E. (2024). Empowering citizen scientists to improve water quality: From monitoring to action. Frontiers in Water, 6. https://doi.org/10.3389/frwa.2024.1367198
Waskom, M. (2021). seaborn: statistical data visualization. Journal of Open Source Software, 6(60), 3021