Análisis descriptivos

XII Congreso Colombiano de Botánica

Álex Espinosa Correa

Universidad de Antioquia

Bladimir Vera Marín

Universidad de Antioquia

Proposito

  • Enseñar el manejo y análisis de datos etnobotánicos

Para poder hacer una tarta de
manzana a partir de cero hay
que inventar primero el universo.

Carl Sagan

Tidyverse y la ciencia de datos

El Tidyverse tiene multipes paquetes que nos ayudaran durante todo el proceso de analizar los datos. Tomado de Çetinkaya-Rundel (2023).

Datos ordenados

Hay tres reglas interrelacionadas que hacen que un conjunto de datos esté ordenado:

  1. Cada variable es una columna; cada columna es una variable.
  2. Cada observación es una fila; cada fila es una observación.
  3. Cada valor es una celda; cada celda es un único valor.

image/svg+xml

Las siguientes tres reglas hacen que un conjunto de datos esté ordenado: las variables son columnas, las observaciones son filas y los valores son celdas. Tomado de Wickham et al. (2023).

Análisis exploratorio de datos (EDA)

El EDA no es un proceso formal con una serie de normas estrictas. Más que nada, EDA es un estado mental. EDA es un ciclo iterativo. Donde:

  • Generar preguntas sobre sus datos.
  • Buscar respuestas visualizando, transformando y modelizando sus datos.
  • Utilizar lo que aprende para refinar sus preguntas y/o generar otras nuevas.

Durante las fases iniciales del AED debes sentirte libre para investigar todas las ideas que se te ocurran. Algunas de estas ideas resultarán y otras serán callejones sin salida.

Configuración

Siempre es buena idea cargar los paquetes que usaremos durante el análisis.

if (!require("here")) install.packages("here"); library("here")
if (!require("tidyverse")) install.packages("tidyverse"); library("tidyverse")
if (!require("readxl")) install.packages("readxl"); library("readxl")
if (!require("janitor")) install.packages("janitor"); library("janitor")
if (!require("visdat")) install.packages("visdat"); library("visdat")
if (!require("ggthemes")) install.packages("ggthemes"); library("ggthemes")
if (!require("gt")) install.packages("gt"); library("gt")
if (!require("flextable")) install.packages("flextable"); library("flextable")
if (!require("ethnobotanyR")) install.packages("ethnobotanyR"); library("ethnobotanyR")

Esta forma de cargar los datos asegura cierta reproducibilidad, aunque no se recomienda si se usa renv. Sólo sería necesario instalar los paquetes una vez y cargarlos con library("nombre_paquete").

Importando los datos

Normalmente, los datos estas en .xlsx. Los podemos importar con readxl::read_excel().

raw_data <- 
  readxl::read_excel(
    "raw-data/raw_data.xlsx"
  )

raw_data
# A tibble: 278 × 26
   `Nombre comùn` Familia     Genero    Especie Sexo  Generacion Conocedor  Edad
   <chr>          <chr>       <chr>     <chr>   <chr> <chr>      <chr>     <dbl>
 1 Curibano       Acanthaceae Justicia  Justic… F     J          J13          15
 2 Penicilina     Acanthaceae Justicia  Justic… F     J          J5           15
 3 Penicilina     Acanthaceae Justicia  Justic… F     J          J7           15
 4 Penicilina     Acanthaceae Justicia  Justic… F     J          J8           15
 5 Penicilina     Acanthaceae Justicia  Justic… F     J          J9           16
 6 Penicilina     Acanthaceae Justicia  Justic… F     J          J10          15
 7 Genciano       Acanthaceae Thunberg… Thunbe… F     J          J6           16
 8 Genciano       Acanthaceae Thunberg… Thunbe… F     J          J7           15
 9 Genciano       Acanthaceae Thunberg… Thunbe… F     J          J8           15
10 Genciano       Acanthaceae Thunberg… Thunbe… F     J          J9           16
# ℹ 268 more rows
# ℹ 18 more variables: Mezclada <dbl>, Cultivada <dbl>, H <dbl>, T <dbl>,
#   Fl <dbl>, Fr <dbl>, R <dbl>, Sem <dbl>, Lat <dbl>, AlH <dbl>, Me <dbl>,
#   Orn <dbl>, Fi <dbl>, AlA <dbl>, Am <dbl>, UC <dbl>, Com <dbl>, Otr <dbl>

Exportando los datos

Tener datos en formatos privativos nunca es buena idea. Mejor en texto plano.

readr::write_csv(
  raw_data,
  here::here(
    "raw-data",
    "raw_data.csv"
  )
)

Volvemos a cargar los datos.

raw_data <-
  readr::read_csv(
    here::here(
      "raw-data",
      "raw_data.csv"
    )
  )

Explorando los datos

Comenzamos explorando los datos con dplyr::glimpse(), base::str(), utils::View().

raw_data |>
  dplyr::glimpse()
Rows: 278
Columns: 26
$ `Nombre comùn` <chr> "Curibano", "Penicilina", "Penicilina", "Penicilina", "…
$ Familia        <chr> "Acanthaceae", "Acanthaceae", "Acanthaceae", "Acanthace…
$ Genero         <chr> "Justicia", "Justicia", "Justicia", "Justicia", "Justic…
$ Especie        <chr> "Justicia  pectoralis", "Justicia  adhatoda", "Justicia…
$ Sexo           <chr> "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", …
$ Generacion     <chr> "J", "J", "J", "J", "J", "J", "J", "J", "J", "J", "J", …
$ Conocedor      <chr> "J13", "J5", "J7", "J8", "J9", "J10", "J6", "J7", "J8",…
$ Edad           <dbl> 15, 15, 15, 15, 16, 15, 16, 15, 15, 16, 15, 15, 15, 15,…
$ Mezclada       <dbl> 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0…
$ Cultivada      <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1…
$ H              <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1…
$ T              <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1…
$ Fl             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0…
$ Fr             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ R              <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0…
$ Sem            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ Lat            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ AlH            <dbl> 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0…
$ Me             <dbl> 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0…
$ Orn            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ Fi             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ AlA            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1…
$ Am             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ UC             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ Com            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0…
$ Otr            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…

Explorando los datos

Podemos hacer algo más visual usando visdat::vis_dat(raw_data)

raw_data |> 
  visdat::vis_dat()

Explorando los datos

Limpiando los datos

clean_data <-
  raw_data |>
  janitor::clean_names() |>
  stats::na.omit() |>
  dplyr::mutate(
    especie = stringr::str_squish(especie),
    nombre_comun = stringr::str_to_sentence(stringr::str_squish(nombre_comun)),
    nombre_comun = dplyr::case_when(
      especie == "Mentha rotundifolia" ~ " Menta guatavita",
      .default = nombre_comun
    )
  ) |> 
  dplyr::arrange(
    familia,
    especie
  )

clean_data

Después de organizar los datos, es recomendable guardarlos como datos procesados.

Limpiando los datos

# A tibble: 276 × 26
   nombre_comun familia genero especie sexo  generacion conocedor  edad mezclada
   <chr>        <chr>   <chr>  <chr>   <chr> <chr>      <chr>     <dbl>    <dbl>
 1 Penicilina   Acanth… Justi… Justic… F     J          J5           15        0
 2 Penicilina   Acanth… Justi… Justic… F     J          J7           15        0
 3 Penicilina   Acanth… Justi… Justic… F     J          J8           15        0
 4 Penicilina   Acanth… Justi… Justic… F     J          J9           16        1
 5 Penicilina   Acanth… Justi… Justic… F     J          J10          15        0
 6 Curibano     Acanth… Justi… Justic… F     J          J13          15        0
 7 Genciano     Acanth… Thunb… Thunbe… F     J          J6           16        1
 8 Genciano     Acanth… Thunb… Thunbe… F     J          J7           15        1
 9 Genciano     Acanth… Thunb… Thunbe… F     J          J8           15        0
10 Genciano     Acanth… Thunb… Thunbe… F     J          J9           16        0
# ℹ 266 more rows
# ℹ 17 more variables: cultivada <dbl>, h <dbl>, t <dbl>, fl <dbl>, fr <dbl>,
#   r <dbl>, sem <dbl>, lat <dbl>, al_h <dbl>, me <dbl>, orn <dbl>, fi <dbl>,
#   al_a <dbl>, am <dbl>, uc <dbl>, com <dbl>, otr <dbl>

Conociendo a los conocedores: edad

resumen_edad <- 
  clean_data |> 
  dplyr::group_by(
    sexo
  ) |> 
  dplyr::summarise(
    n = dplyr::n(),
    media_edad = base::mean(edad, na.rm = TRUE),
    mediana_edad = stats::median(edad, na.rm = TRUE),
    edad_min = base::min(edad, na.rm = TRUE),
    edad_max = base::max(edad, na.rm = TRUE),
    rango_edad = edad_max - edad_min
  )

resumen_edad
# A tibble: 2 × 7
  sexo      n media_edad mediana_edad edad_min edad_max rango_edad
  <chr> <int>      <dbl>        <dbl>    <dbl>    <dbl>      <dbl>
1 F       224       14.6           15        8       18         10
2 M        52       14.6           15       11       18          7

Conociendo a los conocedores: edad por sexo

figura_edad <- 
  clean_data |> 
  ggplot2::ggplot(
    ggplot2::aes(
      forcats::as_factor(edad),
      fill = sexo
    )
  ) + 
  ggplot2::geom_bar(
    position = "dodge"
  )

figura_edad

Conociendo a los conocedores: edad por sexo

Frecuencia por especies: cálculo

frecuencia_especies <- 
  clean_data |>
  dplyr::count(especie, sort = TRUE) |> 
  dplyr::mutate(
    porcentaje = n / sum(n),
    especie = forcats::fct_reorder(especie, -n)
  )

frecuencia_especies 
# A tibble: 36 × 3
   especie                     n porcentaje
   <fct>                   <int>      <dbl>
 1 Allium fistulosum          23     0.0833
 2 Brassica oleracea          17     0.0616
 3 Tagetes patula             17     0.0616
 4 Calendula officinalis      15     0.0543
 5 Lycopersicon esculentum    14     0.0507
 6 Mentha spicata             13     0.0471
 7 Sambucus mexicana          13     0.0471
 8 Urtica ballotifolia        13     0.0471
 9 Cymbopogon citratus        12     0.0435
10 Plantago major             12     0.0435
# ℹ 26 more rows

Frecuencia por especies: figura

figura_frecuencia_especies_1 <- 
  frecuencia_especies |> 
  ggplot2::ggplot(
    ggplot2::aes(
      especie, porcentaje
    )
  ) + 
  ggplot2::geom_bar(stat = "identity") + 
  ggplot2::scale_y_continuous(
    labels = scales::label_percent()
  ) + 
  ggplot2::theme(
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

figura_frecuencia_especies_1

Frecuencia por especies: figura

Frecuencia por especies: tabla

tabla_frecuencia_especies <- 
  frecuencia_especies |>
  flextable::flextable() |>
  flextable::set_table_properties(width = 1, layout = "autofit") |> 
  flextable::labelizor(
    part = "header",
    labels = c(
      especie = "Especie",
      porcentaje = "Porcentaje"
    )
  ) |>
  flextable::theme_apa() |>
  flextable::mk_par(
    j = "especie",
    value = flextable::as_paragraph(flextable::as_i(especie))
  ) |>
  flextable::mk_par(
    j = "porcentaje",
    value = flextable::as_paragraph(flextable::fmt_pct(porcentaje))
  ) |>
  flextable::autofit()

tabla_frecuencia_especies

Frecuencia por especies: tabla

Especie

n

Porcentaje

Allium fistulosum

23

8.3%

Brassica oleracea

17

6.2%

Tagetes patula

17

6.2%

Calendula officinalis

15

5.4%

Lycopersicon esculentum

14

5.1%

Mentha spicata

13

4.7%

Sambucus mexicana

13

4.7%

Urtica ballotifolia

13

4.7%

Cymbopogon citratus

12

4.3%

Plantago major

12

4.3%

Taraxacum dens-leonis

9

3.3%

Achillea millefolium

8

2.9%

Melissa officinalis

8

2.9%

Alternanthera pubiflora

7

2.5%

Bidens pilosa

7

2.5%

Impatiens walleriana

7

2.5%

Stachytarpheta cayennensis

7

2.5%

Trichanthera gigantea

7

2.5%

Borago officinalis

6

2.2%

Cnicus benedictus

6

2.2%

Lippia americana

6

2.2%

Phyla scaberrima

6

2.2%

Thunbergia alata

6

2.2%

Verbena litoralis

6

2.2%

Justicia adhatoda

5

1.8%

Mentha piperita

4

1.4%

Mentha rotundifolia

4

1.4%

Symphytum officinale

4

1.4%

Jacaranda mimosifolia

3

1.1%

Malva parviflora

2

0.7%

Ocimum basilicum

2

0.7%

Polygonum punctatum

2

0.7%

Portulaca oleracea

2

0.7%

Ageratum conyzoides

1

0.4%

Justicia pectoralis

1

0.4%

Matricaria chamomilla

1

0.4%

Frecuencia por especies: otra forma

figura_frecuencia_especies_2 <- 
  clean_data |> 
  ggplot2::ggplot(
    ggplot2::aes(
      forcats::fct_infreq(especie)
    )
  ) +
  ggplot2::geom_bar() + 
  ggplot2::theme(axis.text.x = element_text(angle = 45, hjust = 1))

figura_frecuencia_especies_2

Frecuencia por especies: otra forma

Especies por sexo: cálculo

especies_por_sexo <- 
  clean_data |> 
  dplyr::group_by(
    especie, sexo
  ) |> 
  dplyr::count() |> 
  dplyr::ungroup() |> 
  dplyr::group_by(
    especie
  ) |> 
  dplyr::mutate(
    porcentaje = n / sum(n),
    especie = forcats::fct_reorder(especie, -n)
  )

especies_por_sexo
# A tibble: 60 × 4
# Groups:   especie [36]
   especie                 sexo      n porcentaje
   <fct>                   <chr> <int>      <dbl>
 1 Achillea millefolium    F         6      0.75 
 2 Achillea millefolium    M         2      0.25 
 3 Ageratum conyzoides     M         1      1    
 4 Allium fistulosum       F        17      0.739
 5 Allium fistulosum       M         6      0.261
 6 Alternanthera pubiflora F         6      0.857
 7 Alternanthera pubiflora M         1      0.143
 8 Bidens pilosa           F         5      0.714
 9 Bidens pilosa           M         2      0.286
10 Borago officinalis      F         6      1    
# ℹ 50 more rows

Especies por sexo: figura

figura_especies_por_sexo <- 
  especies_por_sexo |> 
  ggplot2::ggplot(
    ggplot2::aes(
      especie,
      n,
      fill = sexo
    )
  ) + 
  ggplot2::geom_bar(
    stat = "identity",
    position = "dodge"
  ) + 
  ggplot2::theme(
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

figura_especies_por_sexo

Especies por sexo: figura

Especies y géneros por familia: cálculo

numeros_por_familia_wide <-
  clean_data |>
  dplyr::group_by(
    familia
  ) |>
  dplyr::summarise(
    n_generos = dplyr::n_distinct(genero),
    n_especies = dplyr::n_distinct(especie)
  )

numeros_por_familia_wide
# A tibble: 18 × 3
   familia        n_generos n_especies
   <chr>              <int>      <int>
 1 Acanthaceae            3          4
 2 Amaranthaceae          1          1
 3 Amaryllidaceae         1          1
 4 Asteraceae             8          8
 5 Balsaminaceae          1          1
 6 Bignoniaceae           1          1
 7 Boraginaceae           2          2
 8 Brassicaceae           1          1
 9 Lamiaceae              3          5
10 Malvaceae              1          1
11 Plantaginaceae         1          1
12 Poaceae                1          1
13 Polygonaceae           1          1
14 Portulacaceae          1          1
15 Solanaceae             1          1
16 Urticaceae             1          1
17 Verbenaceae            4          4
18 Virbuneaceae           1          1

Especies y géneros por familia: pivotear

numeros_por_familia_long <-
  numeros_por_familia_wide |>
  tidyr::pivot_longer(
    cols = c(n_generos, n_especies),
    names_to = "tipo",
    values_to = "conteo"
  )

numeros_por_familia_long
# A tibble: 36 × 3
   familia        tipo       conteo
   <chr>          <chr>       <int>
 1 Acanthaceae    n_generos       3
 2 Acanthaceae    n_especies      4
 3 Amaranthaceae  n_generos       1
 4 Amaranthaceae  n_especies      1
 5 Amaryllidaceae n_generos       1
 6 Amaryllidaceae n_especies      1
 7 Asteraceae     n_generos       8
 8 Asteraceae     n_especies      8
 9 Balsaminaceae  n_generos       1
10 Balsaminaceae  n_especies      1
# ℹ 26 more rows

Especies y géneros por familia: figura

fig_numeros_por_familia <-
  numeros_por_familia_long |>
  ggplot2::ggplot(
    ggplot2::aes(
      x = familia,
      y = conteo,
      fill = tipo
    )
  ) +
  ggplot2::geom_bar(
    stat = "identity",
    position = "dodge"
  ) +
  ggplot2::labs(
    # title = "Número de géneros y especies por familia",
    x = NULL,
    y = "Conteo",
    fill = NULL
  ) +
  ggplot2::theme(
    axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)
  ) +
  ggthemes::scale_fill_colorblind(
    labels = c("Especies", "Género")
  )

fig_numeros_por_familia

Especies y géneros por familia: figura

Especies y géneros por familia: tabla

tabla_numeros_por_familia <- 
  numeros_por_familia_wide |> 
  flextable::flextable() |> 
  flextable::set_table_properties(width = 1, layout = "autofit") |> 
  flextable::labelizor(
    part = "header",
    labels = c(
      especie = "Especie",
      n_generos = "Géneros",
      n_especies = "Especies"
    )
  ) |>
  flextable::theme_apa() |>
  flextable::autofit()

tabla_numeros_por_familia

Especies y géneros por familia: tabla

familia

Géneros

Especies

Acanthaceae

3

4

Amaranthaceae

1

1

Amaryllidaceae

1

1

Asteraceae

8

8

Balsaminaceae

1

1

Bignoniaceae

1

1

Boraginaceae

2

2

Brassicaceae

1

1

Lamiaceae

3

5

Malvaceae

1

1

Plantaginaceae

1

1

Poaceae

1

1

Polygonaceae

1

1

Portulacaceae

1

1

Solanaceae

1

1

Urticaceae

1

1

Verbenaceae

4

4

Virbuneaceae

1

1

Partes usadas: pivotear

parte_usada_data_long <-
  clean_data |>
  dplyr::select(
    familia:especie,
    h:lat
  ) |>
  tidyr::pivot_longer(
    -c(familia, genero, especie),
    names_to = "parte",
    values_to = "uso"
  )

parte_usada_data_long
# A tibble: 1,932 × 5
   familia     genero   especie           parte   uso
   <chr>       <chr>    <chr>             <chr> <dbl>
 1 Acanthaceae Justicia Justicia adhatoda h         1
 2 Acanthaceae Justicia Justicia adhatoda t         0
 3 Acanthaceae Justicia Justicia adhatoda fl        0
 4 Acanthaceae Justicia Justicia adhatoda fr        0
 5 Acanthaceae Justicia Justicia adhatoda r         0
 6 Acanthaceae Justicia Justicia adhatoda sem       0
 7 Acanthaceae Justicia Justicia adhatoda lat       0
 8 Acanthaceae Justicia Justicia adhatoda h         1
 9 Acanthaceae Justicia Justicia adhatoda t         0
10 Acanthaceae Justicia Justicia adhatoda fl        0
# ℹ 1,922 more rows

Partes usadas: crear vectores

vector_especies_principales <-
  frecuencia_especies |>
  dplyr::slice_max(n, n = 8) |>
  dplyr::pull(especie) |>
  base::as.character()

vector_especies_principales
[1] "Allium fistulosum"       "Brassica oleracea"      
[3] "Tagetes patula"          "Calendula officinalis"  
[5] "Lycopersicon esculentum" "Mentha spicata"         
[7] "Sambucus mexicana"       "Urtica ballotifolia"    
vector_partes_etiquetas <- 
  c(
    "fl" = "Flor", 
    "fr" = "Fruto", 
    "h" = "Hoja", 
    "r" = "Raíz", 
    "t" = "Tallo"
  )

vector_partes_etiquetas
     fl      fr       h       r       t 
 "Flor" "Fruto"  "Hoja"  "Raíz" "Tallo" 

Partes usadas: figura

fig_parte_usada <- 
  parte_usada_data_long |>
  dplyr::filter(
    especie %in% vector_especies_principales,
    uso != 0
  ) |>
  ggplot2::ggplot() +
  ggplot2::aes(
    x = parte
  ) +
  ggplot2::geom_bar() +
  ggplot2::scale_x_discrete(
    labels = vector_partes_etiquetas
  ) + 
  ggplot2::facet_wrap(
    ~ base::factor(especie),
    ncol = 4
  ) + 
  ggplot2::theme_minimal() +
  ggplot2::theme(
    strip.text = ggplot2::element_text(face = "italic")
  )

Partes usadas: figura

Actividad

  • Crear un documento .qmd en la carpeta analisis-descriptivos
  • Realizar un EDA de datos_san-cristobal.xlsx que se encuentran en la carpeta data en analisis-01



40:00
Çetinkaya-Rundel, M. (2023). Teaching the tidyverse in 2023. https://www.tidyverse.org/blog/2023/08/teach-tidyverse-23/
Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. (2023). R for data science: import, tidy, transform, visualize, and model data (2nd edition). O’Reilly. https://r4ds.hadley.nz