Exploración rápida de datos

Gabriel Massaine Moulatlet

Instituto de Ecología, A.C.

here() starts at /home/luisd/Dropbox/darcyDB/PDRF/curso-R-biodiversidad

Exploración de datos

  • Buscar información general sobre los datos
  • Con Big Data es poco probable lograr visualizar toda la información en las matrices
  • Identificar problemas de forma y contenido

Paquetes

Vamos a utilizar los siguientes paquetes en R

  • skimr
  • janitor
  • report

“Los científicos de datos dedican entre el 50 % y el 80 % de su tiempo a este labor más mundano de recopilar y preparar datos digitales rebeldes, antes de que puedan explorarse en busca de pepitas útiles.” - Internet (2023)

Pasos para la exploración de datos

  • El primer paso es importar los datos en R.
  • Antes de analizar los datos, hay que prepararlos y limpiarlos
  • Luego, explorar los datos de manera rápida y eficiente para saber si:
    1. Fueron importados correctamente
    2. Resumir características de los datos

En R

  • R identifica de manera automática las diferencias entre los tipos de información
  • Números, caracteres, espacios, factores…
  • Por eso la importancia de visualizar rápidamente los posibles problemas

Funciones de visualización de datos

skimR

  • Es un paquete de básicamente una única función skim()
  • La función skim() es útil para resumir conjuntos de datos
  • Es un combo de algunas funciones de R base, como str(), class(), summary()
  • Documentación aquí y aquí

Vamos a aplicar la función summary() sobre nuestro conjunto de datos df

df <- data.frame(nacionalidad = c("ARG","NOR","FRA","ARG","ARG","ARG","ARG"),
                 jugadores = c('Messi', 'Haaland', 'Benzema', 'Alvarez', 'Lautaro',
                               'DiMaria','Maradona'),
                 goles = c(12, 30, 30, 12, 12,30, NA),
                 partidos = c(15, 20, 20, 20, 18,20, NA))
summary(df)
 nacionalidad        jugadores             goles       partidos    
 Length:7           Length:7           Min.   :12   Min.   :15.00  
 Class :character   Class :character   1st Qu.:12   1st Qu.:18.50  
 Mode  :character   Mode  :character   Median :21   Median :20.00  
                                       Mean   :21   Mean   :18.83  
                                       3rd Qu.:30   3rd Qu.:20.00  
                                       Max.   :30   Max.   :20.00  
                                       NA's   :1    NA's   :1      

Ahora vamos aplicar la función skim() sobre un conjunto de datos df

  • Me ayudan con la interpretación
library(tidyverse)
library(skimr)

df%>%
  skim()

Separando los elementos

  • la función yank()
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.2     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(skimr)

df%>%
  skim()%>%
  yank("numeric")

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
goles 1 0.86 21.00 9.86 12 12.0 21 30 30 ▇▁▁▁▇
partidos 1 0.86 18.83 2.04 15 18.5 20 20 20 ▂▁▂▁▇

Conjuntos de datos muy grandes

  • Estos son datos de mi posdoc, de interacción (binarias: 0s o 1s) aves-plantas
  • Son más de 140 mil filas…
library(rio)
birds = import(file = here("dados","birds.csv"))

QUIZ:

  • Use la función skim() y conteste:
  • ¿Cuál el número de especies plantas y de aves?
  • ¿Hay datos faltantes?
  • ¿Hay datos raros en las interaciones?

Vamos a identificar los NAs

  • ¿Alguién sugiere alguna manera de hacer eso?
library(tidyr)

birds%>%
  filter(is.na(birds))
  birds               plants interaction
1  <NA> Canthium oligocarpum           0

¿En cuál fila están los valores faltantes?

  • ¿Sugerencias de cómo hacerlo?
which(birds$birds=="")
[1] 140748 140749 140750

Funciónes adicionales: dplyr::glimpse()

library(dplyr)

df%>%
  glimpse()
Rows: 7
Columns: 4
$ nacionalidad <chr> "ARG", "NOR", "FRA", "ARG", "ARG", "ARG", "ARG"
$ jugadores    <chr> "Messi", "Haaland", "Benzema", "Alvarez", "Lautaro", "DiM…
$ goles        <dbl> 12, 30, 30, 12, 12, 30, NA
$ partidos     <dbl> 15, 20, 20, 20, 18, 20, NA

library(dplyr)

birds%>%
  glimpse()
Rows: 140,750
Columns: 3
$ birds       <chr> "Aburria aburri", "Aburria aburri", "Aburria aburri", "Abu…
$ plants      <chr> "Alchornea grandiflora", "Asteraceae sp", "Bellusia pentam…
$ interaction <int> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0…

easystats::report()

library(easystats)
# Attaching packages: easystats 0.6.0
✔ bayestestR  0.13.1   ✔ correlation 0.8.4 
✔ datawizard  0.8.0    ✔ effectsize  0.8.3 
✔ insight     0.19.2   ✔ modelbased  0.8.6 
✔ performance 0.10.4   ✔ parameters  0.21.1
✔ report      0.5.7    ✔ see         0.8.0 
df%>%
  report()
The data contains 7 observations of the following 4 variables:

  - nacionalidad: 3 entries, such as ARG (n = 5); FRA (n = 1); NOR (n = 1) (0
missing)
  - jugadores: 7 entries, such as Alvarez (n = 1); Benzema (n = 1); DiMaria (n =
1) and 4 others (0 missing)
  - goles: n = 7, Mean = 21.00, SD = 9.86, Median = , MAD = 13.34, range: [12,
30], Skewness = 0.00, Kurtosis = -3.33, 1 missing
  - partidos: n = 7, Mean = 18.83, SD = 2.04, Median = , MAD = 0.00, range: [15,
20], Skewness = -1.78, Kurtosis = 2.77, 1 missing

  • ¿Qué otra información les pareció relevant de la función report?
library(easystats)

birds%>%
  report()
The data contains 140750 observations of the following 3 variables:

  - birds: 1265 entries, such as Turdus rufiventris (0.80%); Turdus merula
(0.80%); Vireo olivaceus (0.76%) and 1262 others (1 missing)
  - plants: 2902 entries, such as Trema micrantha (0.51%); Sorbus aucuparia
(0.50%); Myrsine coriacea (0.49%) and 2899 others (0 missing)
  - interaction: n = 140750, Mean = 0.18, SD = 0.39, Median = 0.00, MAD = 0.00,
range: [0, 5], Skewness = 1.64, Kurtosis = 0.83, 0% missing

  • La función report es extramadamente útil cuando aplicada a modelos estadísticos, pero sirve para visualizaciones también
model <- lm(Sepal.Length ~ Species, data = iris)
report(model)

We fitted a linear model (estimated using OLS) to predict Sepal.Length with
Species (formula: Sepal.Length ~ Species). The model explains a statistically
significant and substantial proportion of variance (R2 = 0.62, F(2, 147) =
119.26, p < .001, adj. R2 = 0.61). The model's intercept, corresponding to
Species = setosa, is at 5.01 (95% CI [4.86, 5.15], t(147) = 68.76, p < .001).
Within this model:

  - The effect of Species [versicolor] is statistically significant and positive
(beta = 0.93, 95% CI [0.73, 1.13], t(147) = 9.03, p < .001; Std. beta = 1.12,
95% CI [0.88, 1.37])
  - The effect of Species [virginica] is statistically significant and positive
(beta = 1.58, 95% CI [1.38, 1.79], t(147) = 15.37, p < .001; Std. beta = 1.91,
95% CI [1.66, 2.16])

Standardized parameters were obtained by fitting the model on a standardized
version of the dataset. 95% Confidence Intervals (CIs) and p-values were
computed using a Wald t-distribution approximation.

Extraer información de los datos

Janitor

  • Paquete con dos tipos de funciones
    • Limpieza de datos (vamos ver en la siguiente clase)
    • Preparación de tablas de conteos (contingencia)
    • Documentación

Uso de la función table() del R base

Esta función sirve para generar tablas de contingencia, o sea, con frecuencias, proporciones o conteos entre los objetos

# vamos usar nuestro df
df <- data.frame(nacionalidad = c("ARG","NOR","FRA","ARG","ARG","ARG","ARG"),
                 jugadores = c('Messi', 'Haaland', 'Benzema', 'Alvarez', 'Lautaro',
                               'DiMaria','Maradona'),
                 goles = c(12, 30, 30, 12, 12,30, NA),
                 partidos = c(15, 20, 20, 20, 18,20, NA))

Vamos a probar algunas de sus habilidades en R…

  • Usando la función table():
    • Cuantos tipos de “goles” hay?
    • Cuantos goles cada jugador ha hecho?

¿Cuantos tipos de “goles” hay?

table(df$goles)

12 30 
 3  3 

¿Cuántos goles ha hecho cada jugador?

# Cruzar la información "jugadores" y "goles"
table(df$jugadores,df$goles)
          
           12 30
  Alvarez   1  0
  Benzema   0  1
  DiMaria   0  1
  Haaland   0  1
  Lautaro   1  0
  Maradona  0  0
  Messi     1  0

La función table() permite calcular proporciones

prop.table(table(df$jugadores,df$goles))
          
                  12        30
  Alvarez  0.1666667 0.0000000
  Benzema  0.0000000 0.1666667
  DiMaria  0.0000000 0.1666667
  Haaland  0.0000000 0.1666667
  Lautaro  0.1666667 0.0000000
  Maradona 0.0000000 0.0000000
  Messi    0.1666667 0.0000000

Desventajas de usar la función table()

  • El output no es un data.frame
  • No funciona bien con los pipes (%>%)
  • Los resultados son complicados de extraer y formatear . . .
tab <- table(df$jugadores,df$goles)
class(tab)
[1] "table"

Uso de la función janitor::tabyl()

  • Usa la estructura tidyverse
  • El output nos da informaciones más relevantes
  • Es más fácil de trabajar que la función table()

La función janitor::tabyl()

  • Con `tabyl()´
library(janitor)

Attaching package: 'janitor'
The following object is masked from 'package:insight':

    clean_names
The following objects are masked from 'package:datawizard':

    remove_empty, remove_empty_rows
The following objects are masked from 'package:stats':

    chisq.test, fisher.test
# Tablas con una variable
df %>%
  tabyl(jugadores)
 jugadores n   percent
   Alvarez 1 0.1428571
   Benzema 1 0.1428571
   DiMaria 1 0.1428571
   Haaland 1 0.1428571
   Lautaro 1 0.1428571
  Maradona 1 0.1428571
     Messi 1 0.1428571
  • Con table()
# Tablas con una variable - comparar con la función `table()`
  table(df$jugadores)

 Alvarez  Benzema  DiMaria  Haaland  Lautaro Maradona    Messi 
       1        1        1        1        1        1        1 

Tablas con NA

  • La función tabyl()te indica la presencia de NAs, mientras que table() no lo hace por defecto
df %>%
  tabyl(goles)
 goles n   percent valid_percent
    12 3 0.4285714           0.5
    30 3 0.4285714           0.5
    NA 1 0.1428571            NA

table(df$goles, useNA = "always")

  12   30 <NA> 
   3    3    1 

QUIZ:

  • Use la función tabyl() para saber cuál es la especie de ave con más occurrencias
  • TIP: para visualizar, miren la función View() o la función arrange()
birds %>%
  tabyl(birds)%>%
  arrange(desc(n))%>%
  head()
                birds    n     percent valid_percent
   Turdus rufiventris 1120 0.007957371   0.007957428
        Turdus merula 1119 0.007950266   0.007950323
      Vireo olivaceus 1070 0.007602131   0.007602185
      Thraupis sayaca 1021 0.007253996   0.007254048
 Pitangus sulphuratus  997 0.007083481   0.007083532
    Turdus albicollis  996 0.007076377   0.007076427

Algunas funciones derivadas útiles

  • adorn_totals()
  • adorn_percentages()
  • adorn_rounding(digits=1)

adorn_totals()

df %>%
  filter(nacionalidad == "ARG") %>%
  tabyl(goles) %>%
  adorn_totals()
 goles n percent valid_percent
    12 3     0.6          0.75
    30 1     0.2          0.25
  <NA> 1     0.2            NA
 Total 5     1.0          1.00

Tablas con dos variables

df %>%
  filter(nacionalidad == "ARG") %>%
  tabyl(goles,partidos) %>%
  adorn_percentages()
 goles        15        18        20 NA_
    12 0.3333333 0.3333333 0.3333333   0
    30 0.0000000 0.0000000 1.0000000   0
    NA 0.0000000 0.0000000 0.0000000   1

QUIZ

Al usar la función adorn_percentages(), qué porcentajes se están calculando? Hay un argumento llamado denominator = c(“all”, “row”, “col”) en la función adorn_percentages(). Cuál es la diferencia en usar cada una de las opciones?

No confundir:

  • adorn_percentages() - calcular porcentajes
  • adorn_pct_formatting() - formatar los valores calculados
  • ambas funciones pueden ser usadas una trás otra

df %>%
  filter(nacionalidad == "ARG") %>%
  tabyl(goles,partidos) %>%
  adorn_percentages() %>%
  adorn_pct_formatting() # Colocar los Ns en el output
 goles    15    18     20    NA_
    12 33.3% 33.3%  33.3%   0.0%
    30  0.0%  0.0% 100.0%   0.0%
    NA  0.0%  0.0%   0.0% 100.0%

Función adicional: adorn_ns()

df %>%
  filter(nacionalidad == "ARG") %>%
  tabyl(goles,partidos) %>%
  adorn_percentages() %>%
  adorn_pct_formatting() %>%
  adorn_ns()
 goles        15        18         20        NA_
    12 33.3% (1) 33.3% (1)  33.3% (1)   0.0% (0)
    30  0.0% (0)  0.0% (0) 100.0% (1)   0.0% (0)
    NA  0.0% (0)  0.0% (0)   0.0% (0) 100.0% (1)

función bonus top_levels()

  • Sirve para obtener la tabla de frecuencias de un vector de factores
  • Separa los factores en 3 grupos bajo, medio, alto
  • La función top_levels() te devuelve un data.frame

fac <- as.factor(c("muy fuerte","muy fuerte","muy fuerte","fuerte","débil","débil","muy débil","muy débil"))
top_levels(fac,n=1) # n es el numero de níveles en las categorias *bajo* y *alto*
               fac n percent
             débil 2   0.250
 fuerte, muy débil 3   0.375
        muy fuerte 3   0.375

Janitor Miscellaneous functions

  • clean_names() para limpiar y ajustar nombres de columnas en data.frames
  • compare_df_cols() para comparar si columnas de dos data.frames son iguales
  • remove_empty() para remover celdas vacías en data.frames
  • otras funciones aqui

Tarea

Contexto

Un colega está investigando el efecto de la construcción de una represa en el rio Madeira, Amazonía, sobre la comunidad de hormigas. Hay un efecto fuerte de cambio de la comunidad en el “antes” y “después” de la construcción (medido con el indice Bray-Curtis). Sin embargo, las variables climatológicas y edáficas no han cambiado, por lo que este colega tiene la hipótesis del cambio de nível freático.

Para cada parcela se calculó 3 valores (tipo 2, 3 y 4) de un descriptor de la hidrología local llamado HAND. Los valores fueron calculados para antes y después de la inundación.

El colega necesita saber cuál de los 3 valores podrá ser usado como variable descriptora en su estudio. En la figura abajo se ve el efecto de la inundación en el Rio Madeira sobre un modelo digital de elevación

{fig-align=“center”,fig-size=“80%”}

Instrucciones

  • Importar el conjunto de datos: “ej_visualizacion_datos.xlsx”. Usar la función rio::import()
  • Es necesario estandarizar los nombres de las columnas? Caso positivo, usar la función janitor::clean_names()

Usando única función vista en clase, contestar a las preguntas:

  • ¿Cuál de las columnas tiene mayor promedio?
  • Cuál tiene el mayor valor
  • ¿Cuál tiene una distribución que más se acerca a una “normal”?
  • ¿Cuántos grupos de parcelas hay?

Códigos para contestar a las preguntas

library(rio)
library(skimr)

topo = import("dados/ej_visualizacion_datos.xlsx")

topo = topo %>%
  janitor::clean_names()

topo %>%
  skimr::skim()