Análisis de datos explicativos sobre la calidad del agua


El acceso a agua potable segura no solo es vital para la salud individual, sino que también es un factor crucial para el desarrollo sostenible de las comunidades y las naciones. Es un derecho humano básico que no solo garantiza la supervivencia, sino que también promueve la dignidad y el bienestar de las personas. Además, se reconoce cada vez más como un componente esencial de las políticas de salud pública eficaces.

Cuando las personas tienen acceso a agua potable limpia y segura, se reduce significativamente el riesgo de enfermedades transmitidas por el agua, como diarrea, cólera y fiebre tifoidea, que son responsables de un gran número de muertes, especialmente entre los niños en los países en desarrollo. Esto, a su vez, conduce a una mejora en la calidad de vida y la productividad de la población, ya que las personas pueden dedicar más tiempo a la educación, el trabajo y otras actividades en lugar de buscar agua o lidiar con enfermedades.

Además, invertir en infraestructuras de agua y saneamiento no solo tiene beneficios directos para la salud, sino también ventajas económicas. Por ejemplo, los costos asociados con la atención médica y la pérdida de productividad debido a enfermedades relacionadas con el agua disminuyen, lo que puede generar ahorros significativos a largo plazo. Además, se ha demostrado que las mejoras en el suministro de agua pueden estimular el crecimiento económico al fomentar el desarrollo de industrias locales y mejorar la productividad agrícola.

Así pues, conocer la calidad de nuestra agua es de vital importancia, es por esto que este estudio muestra el análisis de 3276 diferentes cuerpos de agua alrededor del mundo, en donde se recogen datos de pH, dureza, solidos, etc.


Sobre los datos…


Como se mencionó anteriormente, para analizar estos cuerpos de agua (3276 en total) re tomaron datos de:

  1. pH: El pH del agua indica su nivel de acidez o alcalinidad y es crucial para evaluar su calidad. La OMS recomienda un pH entre 6.5 y 8.5. Los resultados de la investigación actual mostraron que el pH del agua estaba dentro de este rango, cumpliendo así con los estándares de la OMS.

  2. Dureza: La dureza del agua se debe principalmente a sales de calcio y magnesio disueltas de depósitos geológicos. El tiempo de contacto con estos materiales determina su nivel de dureza. Originalmente, se definió como la capacidad de precipitar jabón debido al calcio y magnesio presentes.

  3. Sólidos (Sólidos totales disueltos - TDS): El agua puede disolver una variedad de minerales inorgánicos y algunos orgánicos, como potasio, calcio, sodio, bicarbonatos, cloruros, magnesio y sulfatos. Estos minerales pueden afectar el sabor y el color del agua. La medición de la TDS es esencial para evaluar su calidad, siendo un valor alto indicativo de alta mineralización. Se establece un límite deseable de 500 mg/l y un máximo de 1000 mg/l para el consumo humano.

  4. Cloraminas: El cloro y la cloramina son los principales desinfectantes utilizados en sistemas públicos de agua. Las cloraminas se forman al agregar amoníaco al cloro para tratar el agua potable. Se considera seguro un nivel de cloro de hasta 4 miligramos por litro (mg/L o 4 partes por millón (ppm)) en el agua potable.

  5. Sulfatos: Los sulfatos son compuestos naturales presentes en minerales, suelos, rocas, aire, agua subterránea, plantas y alimentos. Se utilizan principalmente en la industria química. En el agua de mar, la concentración de sulfatos es aproximadamente de 2,700 mg/L, mientras que en la mayoría de los suministros de agua dulce varía de 3 a 30 mg/L. Sin embargo, algunas regiones pueden tener concentraciones mucho más altas, hasta 1000 mg/L.

  6. Conductividad: El agua pura es un mal conductor de electricidad y actúa como aislante. La conductividad eléctrica del agua aumenta con la concentración de iones disueltos, que están determinados principalmente por la cantidad de sólidos disueltos en el agua. La conductividad eléctrica (EC) mide la capacidad de una solución para transmitir corriente eléctrica debido a procesos iónicos. Según los estándares de la OMS, el valor de la EC no debe exceder los 400 μS/cm.

  7. Carbón_orgánico: El Carbono Orgánico Total (TOC) en aguas fuente proviene de la descomposición de materia orgánica natural y fuentes sintéticas. TOC es una medida del carbono total en compuestos orgánicos en agua pura. Según la EPA de EE. UU., el TOC debe ser < 2 mg/L en agua tratada/potable y < 4 mg/L en aguas fuente utilizadas para tratamiento.

  8. Trihalometanos: Los THMs son productos químicos presentes en el agua tratada con cloro. Su concentración en el agua potable depende del nivel de materia orgánica, la cantidad de cloro utilizada y la temperatura del agua. Hasta 80 ppm de THMs se consideran seguros en el agua potable.

  9. Turbidez: La turbidez del agua, que depende de la cantidad de materia sólida suspendida, es una medida de sus propiedades ópticas y se usa para evaluar la calidad de los desechos en términos de materia coloidal. En el Campus de Wondo Genet, el valor promedio de turbidez es de 0.98 NTU, por debajo del límite recomendado por la OMS de 5.00 NTU.

  10. Potabilidad: Indica si el agua es segura para el consumo humano, donde 1 significa Potable y 0 significa No potable.

Así pues, este presente trabajo tiene el objetivo de analizar datos sobre la calidad del agua de estas muestras usando solamente el paquete plotly, el cual permite realizar:

Para realizar este trabajo usaremos algunas de las visualizaciones y un pequeño análisis de datos.


Histograma de pH

Un histograma es una representación gráfica de la distribución de frecuencia de datos numéricos, mostrando la cantidad de veces que ocurre cada valor dentro de un rango continuo. Ayuda a identificar la forma de la distribución, outliers y la dispersión de los datos de manera visual y concisa.

library(plotly)
## Loading required package: ggplot2
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
data <- read.csv("water_potability.csv")

pH_histogram <- plot_ly(data, x = ~ph, type = "histogram", 
                        marker = list(color = "rgba(58, 71, 80, 0.6)",
                                      line = list(color = "black", width = 1)),
                        opacity = 0.7) %>%
                layout(title = "Distribución de pH del agua",
                       xaxis = list(title = "pH"),
                       yaxis = list(title = "Frecuencia"),
                       bargap = 0.05,
                       barmode = "overlay",
                       paper_bgcolor = "rgb(243, 243, 243)",
                       plot_bgcolor = "rgb(243, 243, 243)")

pH_histogram
## Warning: Ignoring 491 observations

En esta figura se puede observar cómo los datos de pH de todas las muestras presentan una mayor agrupación de frecuencias de entre los 4 a los 10 valores de pH, siendo los valores alrededor de 7 los más frecuentes.

Distribución de probabilidad

El paquete plotly nos premite crear graficos de distribución de probabilidad muy asociados a la visualización de histogramas.

data_clean <- na.omit(data$ph)

density_ph <- density(data_clean)

plot_dist_prob_ph <- plot_ly(x = density_ph$x, y = density_ph$y, type = "scatter", mode = "lines", 
                             line = list(color = "blue", width = 2)) %>%
  layout(title = "Distribución de Probabilidad del pH",
         xaxis = list(title = "pH"),
         yaxis = list(title = "Densidad de Probabilidad"))

plot_dist_prob_ph

Como se mencionaba anteriormente, este gráfico permite sostener la hipótesis de que los mayores valores se agrupan alrededor del valor 7 de pH, siendo este valor el que tiene una mayor densidad de probabilidad.

Una observación importante y que usaremos de ahora en adelante es la comparación con otras variables. En este caso analizaremos mayormente por el criterio de agua potable o no, como hacemos a continuación:

data_clean <- data[complete.cases(data$ph), ]

density_ph_potable <- density(data_clean[data_clean$Potability == 1, ]$ph)
density_ph_non_potable <- density(data_clean[data_clean$Potability == 0, ]$ph)

plot_dist_prob_ph_potability <- plot_ly() %>%
  add_lines(x = density_ph_potable$x, y = density_ph_potable$y, name = "Potable", line = list(color = "blue", width = 2)) %>%
  add_lines(x = density_ph_non_potable$x, y = density_ph_non_potable$y, name = "No Potable", line = list(color = "red", width = 2)) %>%
  layout(title = "Distribución de Probabilidad del pH en función de la Potabilidad",
         xaxis = list(title = "pH"),
         yaxis = list(title = "Densidad de Probabilidad"))

plot_dist_prob_ph_potability

Pero, no solo se puede evaluar solamente el pH, sino la totalidad de las varibales, como se hace a continuación:

library(plotly)

data_clean <- na.omit(data)

plots <- list()

color_potable <- "blue"
color_non_potable <- "red"

# Iterar sobre cada variable (excepto Potability)
for (col in names(data_clean)[!names(data_clean) %in% "Potability"]) {
  density_potable <- density(data_clean[data_clean$Potability == 1, col])
  density_non_potable <- density(data_clean[data_clean$Potability == 0, col])
  
  plot <- plot_ly() %>%
    add_lines(x = density_potable$x, y = density_potable$y, name = paste0(col, " Potable"), line = list(color = color_potable, width = 2)) %>%
    add_lines(x = density_non_potable$x, y = density_non_potable$y, name = paste0(col, " No Potable"), line = list(color = color_non_potable, width = 2))
  
  plots[[col]] <- plot
}

subplot(plots, nrows = length(plots))

Como se puede observar, solo con los gráficos de distribución de probabilidad no se puede dar un análisis profundo, por lo cual seguiremos graficando.

Pie chart

Un dato de interés es discriminar de nuestras muestras, qué porcentaje se considera potable y cuál porcetaje de las muestras no lo es. La mejor manera de lograr esto es con un gráfico circular:

library(plotly)

data <- read.csv("water_potability.csv")

potable_count <- sum(data$Potability == 1)
non_potable_count <- sum(data$Potability == 0)

pie_chart <- plot_ly(labels = c("Potable", "No Potable"),
                     values = c(potable_count, non_potable_count),
                     type = "pie",
                     marker = list(colors = c("red", "black")),
                     textinfo = "percent+label",
                     textposition = "inside",
                     hole = 0.4,
                     hoverinfo = "label+percent+value",
                     pull = c(0.1, 0)) %>%
             layout(title = "Distribución de Potabilidad",
                    showlegend = TRUE,
                    legend = list(x = 0.5, y = 0.5))

pie_chart

Otros histogramas…

No solo podemos usar histogramas para determinar cómo se comportan datos de pH, sino que se puede hacer o determinar cómo se comportan algunas ariables dependiendo su categoría. En este caso, si se trata de agua potable o no…

library(plotly)

data <- read.csv("water_potability.csv")

agua_potable <- subset(data, Potability == 1)
no_agua_potable <- subset(data, Potability == 0)

hist_agua_potable <- plot_ly(agua_potable, x = ~Organic_carbon, 
                             type = "histogram", name = "Agua Potable",
                             opacity = 0.7, marker = list(color = 'rgba(0, 0, 255, 0.6)')) %>%
                     layout(title = "Histograma de Carbono Orgánico: Impacto en Potabilidad",
                            xaxis = list(title = "Carbono Orgánico"),
                            yaxis = list(title = "Frecuencia"),
                            barmode = 'overlay',
                            plot_bgcolor = "rgb(243, 243, 243)")

hist_no_agua_potable <- plot_ly(no_agua_potable, x = ~Organic_carbon, 
                                type = "histogram", name = "No Agua Potable",
                                opacity = 0.7, marker = list(color = 'rgba(255, 0, 0, 0.6)'))

combined_hist <- subplot(hist_agua_potable, hist_no_agua_potable, nrows = 2)

combined_hist

En la gráfica recién generada podemos observar que, en efecto, parece que el agua potable tiene un poco más de cantidad de Carbono Orgánico que el agua no potable.

Por otro lado, en cuanto a la dureza, se puede decir que esta en el agua potable tiende a ser un poco mayor, puesto que el histograma muestra que para el agua potable existe cierto sesgo hacia la derecha…

library(plotly)

data <- read.csv("water_potability.csv")

agua_potable <- subset(data, Potability == 1)
no_agua_potable <- subset(data, Potability == 0)

hist_agua_potable <- plot_ly(agua_potable, x = ~Hardness, 
                             type = "histogram", name = "Agua Potable",
                             opacity = 0.7, marker = list(color = 'rgba(0, 0, 255, 0.6)'))

hist_no_agua_potable <- plot_ly(no_agua_potable, x = ~Hardness, 
                                type = "histogram", name = "No Agua Potable",
                                opacity = 0.7, marker = list(color = 'rgba(255, 0, 0, 0.6)'))

subplot(hist_agua_potable, hist_no_agua_potable, nrows = 2)

Scatterplots

Otra análisis que se puede hacer por medio del paquete plotly es graficar diferentes graficas de tipo scatter, como se muestra a continuación:

library(plotly)

data <- read.csv("water_potability.csv")

scatterplot_calidad <- plot_ly(data, x = ~Solids, y = ~Conductivity, 
                               type = "scatter", mode = "markers",
                               marker = list(color = "blue", size = 5),
                               opacity = 0.7) %>%
                      layout(title = "Relación entre Sólidos y Conductividad",
                             xaxis = list(title = "Sólidos"),
                             yaxis = list(title = "Conductividad"),
                             plot_bgcolor = "rgb(243, 243, 243)")

scatterplot_calidad

En este caso, el análisis es un poco complejo puesto que no se observa mucha relación entre las variables. Así pues, no estaría errado comentar que la conductividad no depende de los solidos presentes en el agua, por el contrario de lo que se creería…

Siguiendo con los plots de scatter, estos pueden incluso ser más “decorados” usando esta función, como se muestra a continuación:

library(plotly)

data <- read.csv("water_potability.csv")

density_plot <- plot_ly(data, x = ~Solids, y = ~Conductivity, 
                        type = "scatter", mode = "markers", 
                        marker = list(color = "rgba(0, 0, 255, 0.5)")) %>%
                layout(title = "Densidad de Puntos de Sólidos vs. Conductividad",
                       xaxis = list(title = "Sólidos"),
                       yaxis = list(title = "Conductividad"),
                       plot_bgcolor = "rgb(243, 243, 243)")

density_plot

Siguiendo con los diagramas o plots scatters, analizamos una posible relación entre pH y turbidez por medio de estos:

library(plotly)

data <- read.csv("water_potability.csv")

scatterplot <- plot_ly(data, x = ~ph, y = ~Turbidity, 
                       type = "scatter", mode = "markers",
                       marker = list(color = "blue", size = 5),
                       opacity = 0.7) %>%
              layout(title = "Relación entre Turbidez y pH",
                     xaxis = list(title = "pH"),
                     yaxis = list(title = "Turbidez"),
                     plot_bgcolor = "rgb(243, 243, 243)")

scatterplot
## Warning: Ignoring 491 observations

Nuevamente, definir una relación entre estas variables solo viendo el diagrama Scatter no es posible.

Heatmap

Los mapas de calor también pueden ser generados por el paquete plotly, como se muestra a continuación. Este tipo de mapa uede ser muy util para intentar demostrar correlaciones entre variables.

library(plotly)

data <- read.csv("water_potability.csv")

scatter_plot <- plot_ly(data, x = ~Trihalomethanes, y = ~Chloramines, 
                        type = "scatter", mode = "markers",
                        marker = list(color = ~Chloramines, colorscale = "Viridis"),
                        text = ~paste("Trihalomethanes: ", Trihalomethanes, "<br>Chloramines: ", Chloramines)) %>%
                layout(title = "Relación entre Trihalomethanes y Chloraminas",
                       xaxis = list(title = "Trihalomethanes"),
                       yaxis = list(title = "Chloramines"),
                       plot_bgcolor = "rgb(243, 243, 243)")

scatter_plot
## Warning: Ignoring 162 observations

En este caso, los mapas de calor no muestran una correlación muy clara entre los químicos, al igual que entre las variables de conductividad y turbidez, como debería pasar. Esto puede ser, quizá por la cantidad de datos graficados.

library(plotly)

data <- read.csv("water_potability.csv")

scatter_plot <- plot_ly(data, x = ~Conductivity, y = ~Turbidity, 
                        type = "scatter", mode = "markers",
                        marker = list(color = ~Turbidity, colorscale = "Viridis"),
                        text = ~paste("Conductivity: ", Conductivity, "<br>Turbidity: ", Turbidity)) %>%
                layout(title = "Relación entre Conductividad y Turbidez",
                       xaxis = list(title = "Conductividad"),
                       yaxis = list(title = "Turbidez"),
                       plot_bgcolor = "rgb(243, 243, 243)")

scatter_plot

Boxlot y diagramas de violin

Los boxplots y diagramas de violín son importantes porque proporcionan una representación visual de la distribución de datos, permitiendo identificar de manera clara la mediana, cuartiles, dispersión y posibles valores atípicos. Mientras que los boxplots son más simples y eficientes en la detección de outliers, los diagramas de violín ofrecen una representación más detallada de la densidad de los datos, agregando valor al análisis exploratorio de datos. Así pues, presentamos algunos gráficos de estos:

library(plotly)

data <- read.csv("water_potability.csv")

agua_potable <- subset(data, Potability == 1)
no_agua_potable <- subset(data, Potability == 0)

boxplot_dureza <- plot_ly() %>%
  add_boxplot(data = agua_potable, y = ~Hardness, name = "Agua Potable") %>%
  add_boxplot(data = no_agua_potable, y = ~Hardness, name = "No Agua Potable") %>%
  layout(title = "Comparación de Dureza entre Agua Potable y No Potable",
         yaxis = list(title = "Dureza"))

boxplot_dureza

En este diagrama de caja se puede observar de manera muy clara que, en cuanto a dureza, el agua no potable presenta una menor dispersión y menor rango de valores que el agua potable. Se puede decir igualmente que el agua potable tiene datos muy lejanos a diferencia del agua no potable.

En cuanto a el gráfico de violin observamos:

violin_plot_hardness <- plot_ly(data, x = ~Potability, y = ~Hardness, 
                                type = "violin", box = list(visible = TRUE),
                                color = ~factor(Potability)) %>%
                        layout(title = "Violin Plot de Hardness en función de Potability",
                               xaxis = list(title = "Potability"),
                               yaxis = list(title = "Hardness"),
                               violinmode = "overlay")

violin_plot_hardness
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
## Warning: 'layout' objects don't have these attributes: 'violinmode'
## Valid attributes include:
## '_deprecated', 'activeshape', 'annotations', 'autosize', 'autotypenumbers', 'calendar', 'clickmode', 'coloraxis', 'colorscale', 'colorway', 'computed', 'datarevision', 'dragmode', 'editrevision', 'editType', 'font', 'geo', 'grid', 'height', 'hidesources', 'hoverdistance', 'hoverlabel', 'hovermode', 'images', 'legend', 'mapbox', 'margin', 'meta', 'metasrc', 'modebar', 'newshape', 'paper_bgcolor', 'plot_bgcolor', 'polar', 'scene', 'selectdirection', 'selectionrevision', 'separators', 'shapes', 'showlegend', 'sliders', 'smith', 'spikedistance', 'template', 'ternary', 'title', 'transition', 'uirevision', 'uniformtext', 'updatemenus', 'width', 'xaxis', 'yaxis', 'barmode', 'bargap', 'mapType'

Se observa de manera menos clara los datos anómalos, pero se logra observar muy bien la distribución de los datos, donde el agua potable tiene mayor concentración con respecto a la media que el agua no potable…

De igual manera, aquí se muestra otra manera de visualización para la variable de pH.

library(plotly)

violin_plot <- plot_ly(data, x = ~Potability, y = ~ph, 
                       type = "violin", box = list(visible = TRUE),
                       color = ~factor(Potability)) %>%
               layout(title = "Violin Plot de pH en función de Potability",
                      xaxis = list(title = "Potability"),
                      yaxis = list(title = "pH"),
                      violinmode = "overlay")

violin_plot
## Warning: Ignoring 491 observations
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
## Warning: 'layout' objects don't have these attributes: 'violinmode'
## Valid attributes include:
## '_deprecated', 'activeshape', 'annotations', 'autosize', 'autotypenumbers', 'calendar', 'clickmode', 'coloraxis', 'colorscale', 'colorway', 'computed', 'datarevision', 'dragmode', 'editrevision', 'editType', 'font', 'geo', 'grid', 'height', 'hidesources', 'hoverdistance', 'hoverlabel', 'hovermode', 'images', 'legend', 'mapbox', 'margin', 'meta', 'metasrc', 'modebar', 'newshape', 'paper_bgcolor', 'plot_bgcolor', 'polar', 'scene', 'selectdirection', 'selectionrevision', 'separators', 'shapes', 'showlegend', 'sliders', 'smith', 'spikedistance', 'template', 'ternary', 'title', 'transition', 'uirevision', 'uniformtext', 'updatemenus', 'width', 'xaxis', 'yaxis', 'barmode', 'bargap', 'mapType'

Por último, queremos observar cual es la relación de las variables, ¿son estas correlacionables?

library(corrplot)
## corrplot 0.92 loaded
data <- read.csv("water_potability.csv")

# Seleccionar solo las columnas numéricas del dataframe
numeric_minerals <- data[, sapply(data
                                  , is.numeric)]

# Calcular la matriz de correlación
correlation_matrix <- cor(numeric_minerals)

# Visualizar la matriz de correlación
corrplot(correlation_matrix, method = "color")

Observamos que no se muestra mucha correlación entre los datos…

Así pues, se concluye que plotly es una herramienta invaluable para el análisis de datos debido a su capacidad para crear visualizaciones interactivas y personalizables. Permite explorar patrones, tendencias y relaciones en los datos de manera dinámica, facilitando la comunicación de hallazgos y la toma de decisiones informadas. Con su amplia gama de gráficos y opciones de personalización, Plotly se convierte en un recurso esencial para científicos de datos, analistas y profesionales en diversas disciplinas, mejorando la comprensión y la interpretación de conjuntos de datos complejos de manera eficiente y efectiva.