3.AcomodoDatos

Autor/a

Jonathan V. Solórzano

Fecha de publicación

3 de octubre de 2024

Instalar paquetes de tidyverse y cargar tidyverse y readxl

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── 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(readxl)
knitr::opts_chunk$set(echo = TRUE)

Tidyverse

Conjunto de paquetes con sintáxis similar para facilitar el uso conjunto de funciones.

Ventajas:

  • Permiten encadenar una serie de procesos sin necesidad de ir guardando productos intermedios en objetos basura.
  • Código más fácil de leer.
  • Bastante amigable para principiantes.
  • Muchas funciones permiten hacer lo mismo que R base.
  • Casi todas las operaciones se realizan de manera vectorizada (evita usar tiempo de cómputo en ciclos no necesarios).
  • Sin embargo, no a todo mundo le gusta porque dice que cambia mucho la sintáxis de R base.

En inglés conocido como data wrangling.

datos <- read_excel("INFyS_2015_2020_Michoacan_de_Ocampo_lHwLKIM.xlsx",
                  sheet = "Arbolado")
Warning: Expecting numeric in BW19313 / R19313C75: got 'NULL'
Warning: Expecting numeric in BX19313 / R19313C76: got 'NULL'
#Mostrar la parte superior de los datos
head(datos)
# A tibble: 6 × 76
  UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3 CVEECON1_C3
  <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>           <dbl>
1 40277          67761        801985    2019            16 Michoacá…          14
2 40277          67761        802033    2019            16 Michoacá…          14
3 40277          67761        802948    2019            16 Michoacá…          14
4 40279          67762        135014    2015            16 Michoacá…          14
5 40279          67762        135029    2015            16 Michoacá…          14
6 40279          67762        135035    2015            16 Michoacá…          14
# ℹ 69 more variables: CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>,
#   DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>,
#   NoRama_C3 <dbl>, Azimut_C3 <dbl>, Distancia_C3 <dbl>, NombreComun_C3 <chr>,
#   Familia_APG_C3 <chr>, Genero_APG_C3 <chr>, Especie_APG_C3 <chr>, …
#Mostrar su estructura
str(datos)
tibble [26,035 × 76] (S3: tbl_df/tbl/data.frame)
 $ UPMID                         : num [1:26035] 40277 40277 40277 40279 40279 ...
 $ IdConglomerado                : num [1:26035] 67761 67761 67761 67762 67762 ...
 $ ArboladoID_C3                 : num [1:26035] 801985 802033 802948 135014 135029 ...
 $ Anio_C3                       : num [1:26035] 2019 2019 2019 2015 2015 ...
 $ Cve_Estado_C3                 : num [1:26035] 16 16 16 16 16 16 16 16 16 16 ...
 $ Estado_C3                     : chr [1:26035] "Michoacán de Ocampo" "Michoacán de Ocampo" "Michoacán de Ocampo" "Michoacán de Ocampo" ...
 $ CVEECON1_C3                   : num [1:26035] 14 14 14 14 14 14 14 14 14 14 ...
 $ CVEECON2_C3                   : num [1:26035] 14.5 14.5 14.5 14.5 14.5 14.5 14.5 14.5 14.5 14.5 ...
 $ CVEECON3_C3                   : chr [1:26035] "14.5.2" "14.5.2" "14.5.2" "14.5.2" ...
 $ CVEECON4_C3                   : chr [1:26035] "14.5.2.3" "14.5.2.3" "14.5.2.3" "14.5.2.3" ...
 $ DESECON1_C3                   : chr [1:26035] "Selvas Calido-Secas" "Selvas Calido-Secas" "Selvas Calido-Secas" "Selvas Calido-Secas" ...
 $ DESECON2_C3                   : chr [1:26035] "Planicie Costera y Lomerios del Pacifico Sur" "Planicie Costera y Lomerios del Pacifico Sur" "Planicie Costera y Lomerios del Pacifico Sur" "Planicie Costera y Lomerios del Pacifico Sur" ...
 $ DESECON3_C3                   : chr [1:26035] "Lomerios y Piedemontes del Pacifico Sur Mexicano con Selva Espinosa" "Lomerios y Piedemontes del Pacifico Sur Mexicano con Selva Espinosa" "Lomerios y Piedemontes del Pacifico Sur Mexicano con Selva Espinosa" "Lomerios y Piedemontes del Pacifico Sur Mexicano con Selva Espinosa" ...
 $ DESECON4_C3                   : chr [1:26035] "Planicie Costera y lomerios del Pacifico Sur con selva baja caducifolia" "Planicie Costera y lomerios del Pacifico Sur con selva baja caducifolia" "Planicie Costera y lomerios del Pacifico Sur con selva baja caducifolia" "Planicie Costera y lomerios del Pacifico Sur con selva baja caducifolia" ...
 $ Region_Hidrologica_C3         : chr [1:26035] "Costa de Michoacan" "Costa de Michoacan" "Costa de Michoacan" "Costa de Michoacan" ...
 $ CVE_S7_C3                     : chr [1:26035] "BPQ" "BPQ" "BPQ" "VSA/BPQ" ...
 $ X_C3                          : num [1:26035] -103 -103 -103 -102 -102 ...
 $ Y_C3                          : num [1:26035] 18.2 18.2 18.2 18.2 18.2 ...
 $ DESCRIP_S7_C3                 : chr [1:26035] "BOSQUE DE PINO-ENCINO" "BOSQUE DE PINO-ENCINO" "BOSQUE DE PINO-ENCINO" "VEGETACIÓN SECUNDARIA ARBÓREA DE BOSQUE DE PINO-ENCINO" ...
 $ FORM_S7_C3                    : chr [1:26035] "Coníferas y latifoliadas" "Coníferas y latifoliadas" "Coníferas y latifoliadas" "Coníferas y latifoliadas" ...
 $ FAO_S7_C3                     : logi [1:26035] NA NA NA NA NA NA ...
 $ ECO_S7_C3                     : chr [1:26035] "Bosques" "Bosques" "Bosques" "Bosques" ...
 $ Sitio_C3                      : num [1:26035] 1 3 4 1 1 1 1 2 2 3 ...
 $ Consecutivo_C3                : num [1:26035] 9 19 13 1 14 20 22 12 8 2 ...
 $ NoIndividuo_C3                : num [1:26035] 9 10 8 1 14 20 22 12 8 2 ...
 $ NoRama_C3                     : num [1:26035] 9 19 13 1 16 22 24 12 8 2 ...
 $ Azimut_C3                     : num [1:26035] 44 276 202 16 178 239 281 223 116 135 ...
 $ Distancia_C3                  : num [1:26035] 9.5 8.5 1 9.5 4.4 5.7 8.2 8.5 11.2 3.8 ...
 $ NombreComun_C3                : chr [1:26035] "temezquite" "tocon" "ambula" "encino" ...
 $ Familia_APG_C3                : chr [1:26035] "Fabaceae" "ZZ Familia Desconocida" "Hernandiaceae" "Pinaceae" ...
 $ Genero_APG_C3                 : chr [1:26035] "Lysiloma" "ZZ Genero Desconocido" "Gyrocarpus" "Pinus" ...
 $ Especie_APG_C3                : chr [1:26035] "divaricatum" "NULL" "jatrophifolius" "oocarpa" ...
 $ Categoria_APG_C3              : chr [1:26035] "NULL" "NULL" "NULL" "NULL" ...
 $ Infraespecie_APG_C3           : chr [1:26035] "NULL" "NULL" "NULL" "NULL" ...
 $ NombreCientifico_APG_C3       : chr [1:26035] "Lysiloma divaricatum" "ZZ Genero Desconocido" "Gyrocarpus jatrophifolius" "Pinus oocarpa" ...
 $ Forma_Biologica_Cat_C3        : chr [1:26035] "Arbol" "Indeterminada" "Arbol" "Arbol" ...
 $ FormaFuste_C3                 : chr [1:26035] "Individuo curvo con dos o más fustes" "No capturado" "Individuo curvo con dos o más fustes" "individuo recto con un fuste" ...
 $ Condicion_C3                  : chr [1:26035] "Arbol vivo" "Tocon sin marca" "Arbol vivo" "Arbol vivo" ...
 $ MuertoPie_C3                  : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ GradoPutrefaccion_C3          : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ TipoTocon_C3                  : chr [1:26035] "No capturado" "Tocón madera seca (madera dura sin evidencias de descomposición)" "No capturado" "No capturado" ...
 $ DiametroNormal_C3             : num [1:26035] 7.9 41.2 11.2 16.4 8.7 17.8 25.4 17.4 27.5 36.4 ...
 $ AlturaTotal_C3                : num [1:26035] 8.5 0.97 4.65 8.8 4.5 10.2 13.8 11 15 8.7 ...
 $ AnguloInclinacion_C3          : num [1:26035] 999993 999993 999993 -1 -1 ...
 $ AlturaFusteLimpio_C3          : num [1:26035] 4.3 1.0e+06 1.4 6.7 2.4 ...
 $ AlturaComercial_C3            : num [1:26035] 1.0e+06 1.0e+06 1.4 5.0 1.0e+06 ...
 $ DiametroCopaNS_C3             : num [1:26035] 4.2 1.0e+06 3.2 2.8 3.0 ...
 $ DiametroCopaEO_C3             : num [1:26035] 3.4 1.0e+06 2.9 2.6 2.7 ...
 $ DiametroCopa_C3               : num [1:26035] 3.80 1.00e+06 3.05 2.70 2.85 ...
 $ ProporcionCopaViva_C3         : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ ExposicionCopa_C3             : chr [1:26035] "No capturado" "No capturado" "No capturado" "SI" ...
 $ PosicionCopa_C3               : chr [1:26035] "No capturado" "No capturado" "No capturado" "Suprimido" ...
 $ DensidadCopa_C3               : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ MuerteRegresiva_C3            : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ TransparenciaFollaje_C3       : chr [1:26035] "No capturado" "No capturado" "No capturado" "No capturado" ...
 $ VigorEtapa_C3                 : chr [1:26035] "Árbol joven" "No capturado" "Árbol joven" "Árbol joven" ...
 $ NivelVigor_C3                 : chr [1:26035] "Moderado" "No capturado" "Moderado" "No aplica" ...
 $ AreaBasal_C3                  : num [1:26035] 0.0049 0.13332 0.00985 0.02112 0.00594 ...
 $ AreaCopa_C3                   : num [1:26035] 1.13e+01 1.00e+06 7.30 5.73 6.38 ...
 $ AgenteDanio1_C3               : chr [1:26035] "Ausencia de daño" "Actividades humanas" "Ausencia de daño" "Ausencia de daño" ...
 $ AgenteDanio1_EST_C3           : chr [1:26035] "Ausencia de daño" "Actividades humanas" "Ausencia de daño" "Ausencia de daño" ...
 $ Severidad1_C3                 : chr [1:26035] "No aplica" "No aplica" "No aplica" "No aplica" ...
 $ AgenteDanio2_C3               : chr [1:26035] "Ausencia de daño" "Actividades humanas" "Ausencia de daño" "No capturado" ...
 $ AgenteDanio2_EST_C3           : chr [1:26035] "Ausencia de daño" "Actividades humanas" "Ausencia de daño" "No capturado" ...
 $ Severidad2_C3                 : chr [1:26035] "No aplica" "No aplica" "No aplica" "No aplica" ...
 $ EsSubmuestra_C3               : num [1:26035] 0 0 0 0 0 0 0 0 0 0 ...
 $ DiametroBasalSub_validacion_C3: num [1:26035] 1e+06 1e+06 1e+06 1e+06 1e+06 ...
 $ Edad_C3                       : num [1:26035] 1e+06 1e+06 1e+06 1e+06 1e+06 ...
 $ NumeroAnillos25_C3            : num [1:26035] 1e+06 1e+06 1e+06 1e+06 1e+06 ...
 $ LongitudAnillos10_C3          : num [1:26035] 1e+06 1e+06 1e+06 1e+06 1e+06 ...
 $ GrosorCorteza_C3              : num [1:26035] 1e+06 1e+06 1e+06 1e+06 1e+06 ...
 $ es_para_estimacion_Volumen_C3 : num [1:26035] 1 0 1 1 1 1 1 1 1 1 ...
 $ VolumenVRTA_m3_C3             : num [1:26035] 2.64e-02 1.00e+06 2.75e-02 1.26e-01 1.24e-02 ...
 $ es_para_estimacion_ByC        : num [1:26035] 1 1 1 1 1 1 1 1 1 1 ...
 $ biomasa_kg_C3                 : num [1:26035] 21 35.3 10.5 72.1 18.2 ...
 $ carbono_kg_C3                 : num [1:26035] 9.22 15.76 4.7 35.29 8.05 ...

Tres paquetes esenciales para el data wrangling o acomodo de datos: tibble, dplyr y tidyr.

  • Tibble: paquete con un nuevo tipo de objeto similar a data.frame, pero con ciertas mejoras. Los tibbles son la base de trabajo del tidyverse.

  • Dplyr: Es un paquete que permite modificar y agregar variables, y hacer resumenes.

  • Tidyr: Es un paquete que permite acomodar datos y eliminar/rellenar datos faltantes.

El pipe

La base del tidyverse. Primero inventado en el paquete “magrittr” (%>%). A partir de R 4.0 ya tiene una implementación base (|>).

El pipe es equivalente a decir que lo que va antes del pipe es el primer argumento de la función.

mean(datos$UPMID)
[1] 66822.59
datos$UPMID |>
  mean()
[1] 66822.59

Tibble

Es como un data.frame pero hace menos cosas:

  • No cambia el tipo de las entradas (p.ej., caracter a factor).
  • No cambia el nombre de las variables.
  • Nunca crea nombres de filas.

Además, tiene una versión mejorada de print para facilitar revisar los datos.

datos2 <- datos |>
  as_tibble()
datos2
# A tibble: 26,035 × 76
   UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3          
   <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>              
 1 40277          67761        801985    2019            16 Michoacán de Ocampo
 2 40277          67761        802033    2019            16 Michoacán de Ocampo
 3 40277          67761        802948    2019            16 Michoacán de Ocampo
 4 40279          67762        135014    2015            16 Michoacán de Ocampo
 5 40279          67762        135029    2015            16 Michoacán de Ocampo
 6 40279          67762        135035    2015            16 Michoacán de Ocampo
 7 40279          67762        135037    2015            16 Michoacán de Ocampo
 8 40279          67762        135060    2015            16 Michoacán de Ocampo
 9 40279          67762        135056    2015            16 Michoacán de Ocampo
10 40279          67762        135069    2015            16 Michoacán de Ocampo
# ℹ 26,025 more rows
# ℹ 70 more variables: CVEECON1_C3 <dbl>, CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>,
#   CVEECON4_C3 <chr>, DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>,
#   DESECON4_C3 <chr>, Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>,
#   X_C3 <dbl>, Y_C3 <dbl>, DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>,
#   FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>, Sitio_C3 <dbl>, Consecutivo_C3 <dbl>,
#   NoIndividuo_C3 <dbl>, NoRama_C3 <dbl>, Azimut_C3 <dbl>, …
class(datos2)
[1] "tbl_df"     "tbl"        "data.frame"

Aquí vemos que la clase de los datos es tibble (abreviado como tibble: tbl y tibble_dataframe: tbl_df).

class(datos2)
[1] "tbl_df"     "tbl"        "data.frame"

Si quieres nombrar columnas con caracteres especiales (p.ej., espacios, acentos, comenzar con números) se puede poner el nombre entre ` ` (backticks).

Hay dos formas básicas de crear tibbles a partir de datos.

  • tibble. Como data.frame, i.e., vector por atributo.
  • tribble. tibble transpuesto, i.e., vectores por filas.
tibble(a = 1:5,
       b = letters[1:5])
# A tibble: 5 × 2
      a b    
  <int> <chr>
1     1 a    
2     2 b    
3     3 c    
4     4 d    
5     5 e    
tribble(~a, ~b,
        1,"a",
        2, "b",
        3, "c",
        4, "d",
        5, "e")
# A tibble: 5 × 2
      a b    
  <dbl> <chr>
1     1 a    
2     2 b    
3     3 c    
4     4 d    
5     5 e    

Dplyr

Notar que los nombres de las variables se escriben sin comillas. Si tienen caracteres especiales, con ` `.

Ver algunas características de nuestros datos.

Ver niveles de Tipos de vegetación. Recuerden que CVE_S7_C3 contiene la clave de tipos de vegetación.

Seleccionar columnas

Seleccionar ciertos atributos.

datos |>
  distinct(CVE_S7_C3)
# A tibble: 32 × 1
   CVE_S7_C3
   <chr>    
 1 BPQ      
 2 VSA/BPQ  
 3 BQ       
 4 VSa/SBC  
 5 VSA/SMS  
 6 SBC      
 7 VM       
 8 VSa/BQ   
 9 PC       
10 VSa/BPQ  
# ℹ 22 more rows
datos |>
  select(UPMID, CVE_S7_C3) 
# A tibble: 26,035 × 2
   UPMID CVE_S7_C3
   <dbl> <chr>    
 1 40277 BPQ      
 2 40277 BPQ      
 3 40277 BPQ      
 4 40279 VSA/BPQ  
 5 40279 VSA/BPQ  
 6 40279 VSA/BPQ  
 7 40279 VSA/BPQ  
 8 40279 VSA/BPQ  
 9 40279 VSA/BPQ  
10 40279 VSA/BPQ  
# ℹ 26,025 more rows

Select es similar a seleccionar por número de columnas, pero haciendo referencia al nombre de la columna. Los nombres de las columnas se ponen sin concatenarlos en un vector (es decir, sin c).

Obtener valores distintos o únicos

Principal diferencia de distinct con unique es que retorna un tibble, en lugar de vector. Si se quiere obtener un vector se puede usar pull.

datos |>
  distinct(CVE_S7_C3) |>
  pull(CVE_S7_C3)
 [1] "BPQ"     "VSA/BPQ" "BQ"      "VSa/SBC" "VSA/SMS" "SBC"     "VM"     
 [8] "VSa/BQ"  "PC"      "VSa/BPQ" "VSa/BP"  "BP"      "VSA/SBC" "VSA/BQ" 
[15] "VSA/BP"  "BQP"     "VSa/BQP" "TAP"     "RAP"     "TA"      "AH"     
[22] "BA"      "BC"      "VSa/BA"  "BM"      "VSA/BA"  "VSA/BM"  "PI"     
[29] "RP"      "VSA/BQP" "RA"      "TP"     

Ver número de valores por columna.

datos |>
  select(CVE_S7_C3) |>
  n_distinct()
[1] 32

Agregar nuevas columnas

Agregar nuevas columnas se hace con mutate. Primero se pone el nuevo nombre de la columna y luego a lo que equivale.

datos |>
  select(DiametroNormal_C3) |>
  mutate(DiametroNormal_C3_m = DiametroNormal_C3/100)
# A tibble: 26,035 × 2
   DiametroNormal_C3 DiametroNormal_C3_m
               <dbl>               <dbl>
 1               7.9               0.079
 2              41.2               0.412
 3              11.2               0.112
 4              16.4               0.164
 5               8.7               0.087
 6              17.8               0.178
 7              25.4               0.254
 8              17.4               0.174
 9              27.5               0.275
10              36.4               0.364
# ℹ 26,025 more rows

Renombrar columnas.

Renombrar se hace con rename. Primero se pone nuevo nombre y luego el viejo.

datos |>
  select(DiametroNormal_C3) |>
  rename("DAP" = "DiametroNormal_C3")
# A tibble: 26,035 × 1
     DAP
   <dbl>
 1   7.9
 2  41.2
 3  11.2
 4  16.4
 5   8.7
 6  17.8
 7  25.4
 8  17.4
 9  27.5
10  36.4
# ℹ 26,025 more rows

Ordenar datos

Se usa arrange. Por default los ordena de menor a mayor (ascendente). Si no se quiere esto, se usa desc para ponerlos en orden descendente.

datos |>
  select(DiametroNormal_C3) |>
  arrange(DiametroNormal_C3)
# A tibble: 26,035 × 1
   DiametroNormal_C3
               <dbl>
 1               7.6
 2               7.6
 3               7.6
 4               7.6
 5               7.6
 6               7.6
 7               7.6
 8               7.6
 9               7.6
10               7.6
# ℹ 26,025 more rows
datos |>
  select(DiametroNormal_C3) |>
  arrange(desc(DiametroNormal_C3))
# A tibble: 26,035 × 1
   DiametroNormal_C3
               <dbl>
 1            999993
 2            999993
 3            999993
 4            999993
 5            999993
 6            999993
 7            999993
 8            999993
 9            999993
10            999993
# ℹ 26,025 more rows

Filtrar

Quedarse con una parte de los datos en función de algún criterio en particular.

datos |>
  select(DiametroNormal_C3) |>
  filter(DiametroNormal_C3 >= 99999)
# A tibble: 625 × 1
   DiametroNormal_C3
               <dbl>
 1            999993
 2            999993
 3            999993
 4            999993
 5            999993
 6            999993
 7            999993
 8            999993
 9            999993
10            999993
# ℹ 615 more rows

Si queremos más de una condición (i.e., y) se usa &, operador ó es |.

datos |>
  select(DiametroNormal_C3) |>
  filter(DiametroNormal_C3 <= 99999 & DiametroNormal_C3 >= 20)
# A tibble: 6,488 × 1
   DiametroNormal_C3
               <dbl>
 1              41.2
 2              25.4
 3              27.5
 4              36.4
 5              28.9
 6              39.8
 7              25.8
 8              20  
 9              30.4
10              32.3
# ℹ 6,478 more rows
datos |>
  select(DiametroNormal_C3) |>
  filter(DiametroNormal_C3 <= 5 | DiametroNormal_C3 >= 20)
# A tibble: 7,113 × 1
   DiametroNormal_C3
               <dbl>
 1              41.2
 2              25.4
 3              27.5
 4              36.4
 5              28.9
 6              39.8
 7              25.8
 8              20  
 9              30.4
10              32.3
# ℹ 7,103 more rows

Sobreescribir valores o modificar un columna en su lugar

Esto se hace con mutate(across()).

Quitar valores de 99999 como NA

datos |>
  filter(DiametroNormal_C3 >= 99999)
# A tibble: 625 × 76
   UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3          
   <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>              
 1 48449          65760        197636    2015            16 Michoacán de Ocampo
 2 40287          67766        802997    2019            16 Michoacán de Ocampo
 3 40289          67767        134939    2015            16 Michoacán de Ocampo
 4 44863          66591        405491    2016            16 Michoacán de Ocampo
 5 46036          66301        406677    2016            16 Michoacán de Ocampo
 6 46132          66349        128928    2015            16 Michoacán de Ocampo
 7 41362          67481        141665    2015            16 Michoacán de Ocampo
 8 40287          67766        802966    2019            16 Michoacán de Ocampo
 9 40287          67766        802967    2019            16 Michoacán de Ocampo
10 38204          68270        143190    2015            16 Michoacán de Ocampo
# ℹ 615 more rows
# ℹ 70 more variables: CVEECON1_C3 <dbl>, CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>,
#   CVEECON4_C3 <chr>, DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>,
#   DESECON4_C3 <chr>, Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>,
#   X_C3 <dbl>, Y_C3 <dbl>, DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>,
#   FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>, Sitio_C3 <dbl>, Consecutivo_C3 <dbl>,
#   NoIndividuo_C3 <dbl>, NoRama_C3 <dbl>, Azimut_C3 <dbl>, …
datos2 <- datos |>
  mutate(across(DiametroNormal_C3, ~ifelse(.x >= 99999, NA, .x)))

Across usa la sintáxis de ~ para determinar una función que le aplicas a lo que le pases, referenciado como .x. El usar la notación ~(.x) es similar a usar function(x) con procesos más simples.

datos2 |>
  filter(DiametroNormal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …
datos2 <- datos |>
  mutate(across(DiametroNormal_C3, function(x) {
    resul <- ifelse(x >= 99999, NA, x)
    return(resul)
    }))

datos2 |>
  filter(DiametroNormal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …

Lo mismo en R base

datos2 <- datos
datos2$DiametroNormal_C3[datos2$DiametroNormal_C3 >= 99999] <- NA

datos2 |>
  filter(DiametroNormal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …

Hacer lo mismo para Altura.

datos2 <- datos |>
  mutate(across(AlturaTotal_C3, ~ifelse(.x >= 99999, NA, .x)))

datos2 |>
  filter(AlturaTotal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …

Varias transformaciones a la vez.

datos2 <- datos |>
  mutate(across(c(AlturaTotal_C3, DiametroNormal_C3), ~ifelse(.x >= 99999, NA, .x)))

datos2 |>
  filter(AlturaTotal_C3 >= 99999 | DiametroNormal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …

Seleccionar varios tipos de variables por tipo o por nombre para modificarlas en su lugar. Eso se puede hacer con mutate(across()) y la ayuda de algunas de las siguientes funciones.

  • starts_with
  • ends_with
  • everything
  • where + is.numeric | is.character
datos2 <- datos |>
  mutate(across(starts_with("Diametro"), ~ifelse(.x >= 99999, NA, .x)))

datos2 |>
  filter(DiametroNormal_C3 >= 99999 | DiametroCopaNS_C3 >= 99999 | DiametroCopaEO_C3 >= 99999 | DiametroCopa_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …
datos2 <- datos |>
  mutate(across(where(is.numeric), ~ifelse(.x >= 99999, NA, .x)))

datos2 |>
  select(UPMID, DiametroNormal_C3, AlturaTotal_C3) |>
  mutate(across(everything(), ~ifelse(.x >= 99999, NA, .x)))
# A tibble: 26,035 × 3
   UPMID DiametroNormal_C3 AlturaTotal_C3
   <dbl>             <dbl>          <dbl>
 1 40277               7.9           8.5 
 2 40277              41.2           0.97
 3 40277              11.2           4.65
 4 40279              16.4           8.8 
 5 40279               8.7           4.5 
 6 40279              17.8          10.2 
 7 40279              25.4          13.8 
 8 40279              17.4          11   
 9 40279              27.5          15   
10 40279              36.4           8.7 
# ℹ 26,025 more rows

Selección de columnas con vectores.

Este caso se usa cuando los nombres de las columnas no los sabes de antemano, pero los quieres pasar como un vector. En este caso se usa all_of. Noten que cuando se usa all_of, los nombres de las variables van entre comillas (como caracter). En cambio, si se ponen directamente en across, van sin comillas.

datos2 <- datos |>
  mutate(across(all_of(c("AlturaTotal_C3", "DiametroNormal_C3")), ~ifelse(.x >= 99999, NA, .x)))

datos2 <- datos |>
  mutate(across(c(AlturaTotal_C3, DiametroNormal_C3), ~ifelse(.x >= 99999, NA, .x)))

datos2 |>
  filter(AlturaTotal_C3 >= 99999 | DiametroNormal_C3 >= 99999)
# A tibble: 0 × 76
# ℹ 76 variables: UPMID <dbl>, IdConglomerado <dbl>, ArboladoID_C3 <dbl>,
#   Anio_C3 <dbl>, Cve_Estado_C3 <dbl>, Estado_C3 <chr>, CVEECON1_C3 <dbl>,
#   CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>, CVEECON4_C3 <chr>, DESECON1_C3 <chr>,
#   DESECON2_C3 <chr>, DESECON3_C3 <chr>, DESECON4_C3 <chr>,
#   Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>, X_C3 <dbl>, Y_C3 <dbl>,
#   DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>, FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>,
#   Sitio_C3 <dbl>, Consecutivo_C3 <dbl>, NoIndividuo_C3 <dbl>, …

Ya tenemos nuestros datos un poco más limpios. Veamos algunas estadísticas por grupo.

Agrupar y resumir

Para agrupar datos por algún nivel se usa group_by. Al imprimir un tibble con group_by nos indica que está agrupado y nos dice el número de grupos.

datos <- datos |>
  mutate(across(where(is.numeric), ~ifelse(.x >= 99999, NA, .x)))

datos |>
  group_by(CVE_S7_C3)
# A tibble: 26,035 × 76
# Groups:   CVE_S7_C3 [32]
   UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3          
   <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>              
 1 40277          67761            NA    2019            16 Michoacán de Ocampo
 2 40277          67761            NA    2019            16 Michoacán de Ocampo
 3 40277          67761            NA    2019            16 Michoacán de Ocampo
 4 40279          67762            NA    2015            16 Michoacán de Ocampo
 5 40279          67762            NA    2015            16 Michoacán de Ocampo
 6 40279          67762            NA    2015            16 Michoacán de Ocampo
 7 40279          67762            NA    2015            16 Michoacán de Ocampo
 8 40279          67762            NA    2015            16 Michoacán de Ocampo
 9 40279          67762            NA    2015            16 Michoacán de Ocampo
10 40279          67762            NA    2015            16 Michoacán de Ocampo
# ℹ 26,025 more rows
# ℹ 70 more variables: CVEECON1_C3 <dbl>, CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>,
#   CVEECON4_C3 <chr>, DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>,
#   DESECON4_C3 <chr>, Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>,
#   X_C3 <dbl>, Y_C3 <dbl>, DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>,
#   FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>, Sitio_C3 <dbl>, Consecutivo_C3 <dbl>,
#   NoIndividuo_C3 <dbl>, NoRama_C3 <dbl>, Azimut_C3 <dbl>, …

Para calcular nuevos valores se usa summarise si se quiere resumir varias filas en una sola. summarise quita todas las columnas que no sean grupos.

Para hacer conteos se puede usar count o summarise(n = n()) En este ejemplo n() calcula el número de filas por grupo.

datos |>
  group_by(CVE_S7_C3) |>
  count()
# A tibble: 32 × 2
# Groups:   CVE_S7_C3 [32]
   CVE_S7_C3     n
   <chr>     <int>
 1 AH           14
 2 BA          354
 3 BC          158
 4 BM          153
 5 BP         3099
 6 BPQ        5513
 7 BQ         1846
 8 BQP         882
 9 PC          181
10 PI          466
# ℹ 22 more rows
datos |>
  group_by(CVE_S7_C3) |>
  summarise(n = n())
# A tibble: 32 × 2
   CVE_S7_C3     n
   <chr>     <int>
 1 AH           14
 2 BA          354
 3 BC          158
 4 BM          153
 5 BP         3099
 6 BPQ        5513
 7 BQ         1846
 8 BQP         882
 9 PC          181
10 PI          466
# ℹ 22 more rows
datos |>
  group_by(CVE_S7_C3) |>
  summarise(mean = mean(biomasa_kg_C3),
            sd = sd(biomasa_kg_C3))
# A tibble: 32 × 3
   CVE_S7_C3  mean     sd
   <chr>     <dbl>  <dbl>
 1 AH        479.  1135. 
 2 BA        537.   973. 
 3 BC         99.6  165. 
 4 BM        230.   603. 
 5 BP        211.   649. 
 6 BPQ       210.   544. 
 7 BQ        116.   221. 
 8 BQP       194.   458. 
 9 PC        149.   559. 
10 PI         45.9   61.6
# ℹ 22 more rows

Por default se quedan los datos agrupados a excepción del último nivel. Para quitarlos. Sugiero siempre poner .groups = "drop" para que no se queden los datos agrupados. El que se queden los datos agrupados puede tener repercusiones no deseadas más abajo en la cadena de procesos. Revisar que el resultado no dice “groups” para asegurarnos que no se quedan agrupados. Mi recomendación es antes de hacer una operación agrupen para que esté muy claro la definición de grupos. También para desagrupar existe la función ungroup.

Se puede agrupar los datos por el número de factores que se quieran.

Por ejemplo, veamos como cambian los resultados dejando los datos agrupados o no. En el primer caso se agrupan los datos por CVE_S7_C3, DESECON3_C3, mientras que en el segundo por ninguna variable.

datos |>
  group_by(CVE_S7_C3, DESECON3_C3,DESECON4_C3) |>
  summarise(mean = mean(biomasa_kg_C3),
            sd = sd(biomasa_kg_C3)) |>
  count()
`summarise()` has grouped output by 'CVE_S7_C3', 'DESECON3_C3'. You can
override using the `.groups` argument.
# A tibble: 68 × 3
# Groups:   CVE_S7_C3, DESECON3_C3 [68]
   CVE_S7_C3 DESECON3_C3                                                       n
   <chr>     <chr>                                                         <int>
 1 AH        Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     1
 2 BA        Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     1
 3 BC        Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     1
 4 BM        Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     1
 5 BP        Bosques de Coniferas, Encino y Mixtos de la Sierra Madre del…     1
 6 BP        Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     1
 7 BPQ       Depresion del Balsas con Selva Caducifolia y Matorral Xerofi…     1
 8 BPQ       Lomerios y Piedemontes del Pacifico Sur Mexicano con Selva E…     1
 9 BPQ       Lomerios y Planicies del Interior con Matorral Xerofilo y Bo…     1
10 BPQ       Lomerios y Sierras con Bosques de Coniferas, Encino y Mixtos…     2
# ℹ 58 more rows
datos |>
  group_by(CVE_S7_C3, DESECON3_C3, DESECON4_C3) |>
  summarise(mean = mean(biomasa_kg_C3),
            sd = sd(biomasa_kg_C3),
            .groups = "drop") |>
  count()
# A tibble: 1 × 1
      n
  <int>
1    70
datos |>
  group_by(CVE_S7_C3, DESECON3_C3,DESECON4_C3) |>
  summarise(mean = mean(biomasa_kg_C3),
            sd = sd(biomasa_kg_C3)) |>
  ungroup() |>
  count()
`summarise()` has grouped output by 'CVE_S7_C3', 'DESECON3_C3'. You can
override using the `.groups` argument.
# A tibble: 1 × 1
      n
  <int>
1    70
datos |>
  group_by(CVE_S7_C3, DESECON3_C3, DESECON4_C3) |>
  summarise(mean = mean(biomasa_kg_C3),
            sd = sd(biomasa_kg_C3),
            .groups = "drop") |>
  count()
# A tibble: 1 × 1
      n
  <int>
1    70

Esto es biomasa promedio por árbol.

Para hacerlo por parcela:

datos |>
  group_by(IdConglomerado) |>
  summarise(AGB = sum(biomasa_kg_C3),
            .groups = "drop")
# A tibble: 348 × 2
   IdConglomerado    AGB
            <dbl>  <dbl>
 1          56011  5220.
 2          56504   690.
 3          56794   156.
 4          57005  3241.
 5          57008  2246.
 6          57259 17343.
 7          57260 14427.
 8          57274  4292.
 9          57503  3504.
10          57512  4489.
# ℹ 338 more rows

Biomasa total por parcela (1600 m^2) Pasarlo a Mg/ha. Primero extrapolar de 1600 m^2 a 10 000 m^2 y luego de kg a Mg. Generar nueva columna con la biomasa por ha.

df_agb_sit <- datos |>
  group_by(CVE_S7_C3, UPMID) |>
  # Resumir por parcela
  summarise(AGB = sum(biomasa_kg_C3),
            .groups = "drop") |>
  # Escalar a 1 ha. Parcelas son 1600 m2 de muestreo
  mutate(scale = 10000 / 1600) |>
  # Multiplicar por el factor de escalamiento
  mutate(AGB.ha = AGB * scale) |>
  # Transformar de kg a Mg.
  mutate(across(AGB.ha, ~.x/1000))

Ahora sí calcular el AGB promedio pr tipo de vegetación.

Alternativa para desagrupar: ungroup

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  summarise(mean = mean(AGB.ha),
            sd = sd(AGB.ha)) |>
  ungroup()
# A tibble: 32 × 3
   CVE_S7_C3  mean     sd
   <chr>     <dbl>  <dbl>
 1 AH         41.9  NA   
 2 BA        238.  204.  
 3 BC         49.2   6.33
 4 BM        220.   NA   
 5 BP        117.   72.3 
 6 BPQ       111.   79.1 
 7 BQ         58.4  36.8 
 8 BQP        97.3  64.1 
 9 PC         42.2  45.4 
10 PI         19.1   7.38
# ℹ 22 more rows

Hay algunas que salen NA, seguramente porque hay NA en esas parcelas. No usarlas para los calculos

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  summarise(mean = mean(AGB.ha, na.rm = TRUE),
            sd = sd(AGB.ha, na.rm = TRUE)) |>
  ungroup()
# A tibble: 32 × 3
   CVE_S7_C3  mean     sd
   <chr>     <dbl>  <dbl>
 1 AH         41.9  NA   
 2 BA        238.  204.  
 3 BC         49.2   6.33
 4 BM        220.   NA   
 5 BP        117.   72.3 
 6 BPQ       111.   79.1 
 7 BQ         58.4  36.8 
 8 BQP        97.3  64.1 
 9 PC         42.2  45.4 
10 PI         19.1   7.38
# ℹ 22 more rows

Algunas tienen sd = NA, seguramente tienen un solo sitio de muestreo (no hay variación). Veamos con count para contar

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  count()
# A tibble: 32 × 2
# Groups:   CVE_S7_C3 [32]
   CVE_S7_C3     n
   <chr>     <int>
 1 AH            1
 2 BA            5
 3 BC            2
 4 BM            1
 5 BP           35
 6 BPQ          65
 7 BQ           23
 8 BQP          11
 9 PC            4
10 PI            7
# ℹ 22 more rows

Rebanar o sacar ciertas filas

Sacar valores más altos o más bajos por grupo se puede hacer con arrange y slice. O también existen las versiones slice_head y slice_tail.

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  arrange(desc(AGB.ha)) |>
  slice(1)
# A tibble: 32 × 5
# Groups:   CVE_S7_C3 [32]
   CVE_S7_C3 UPMID    AGB scale AGB.ha
   <chr>     <dbl>  <dbl> <dbl>  <dbl>
 1 AH        73554  6701.  6.25   41.9
 2 BA        74619 92682.  6.25  579. 
 3 BC        71551  8583.  6.25   53.6
 4 BM        73604 35120.  6.25  220. 
 5 BP        70552 54020.  6.25  338. 
 6 BPQ       71523 66583.  6.25  416. 
 7 BQ        74589 25589.  6.25  160. 
 8 BQP       69510 39233.  6.25  245. 
 9 PC        42488 16929.  6.25  106. 
10 PI        71509  5304.  6.25   33.1
# ℹ 22 more rows

Lo mismo, pero con slice_head.

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  arrange(desc(AGB.ha)) |>
  slice_head(n = 1)
# A tibble: 32 × 5
# Groups:   CVE_S7_C3 [32]
   CVE_S7_C3 UPMID    AGB scale AGB.ha
   <chr>     <dbl>  <dbl> <dbl>  <dbl>
 1 AH        73554  6701.  6.25   41.9
 2 BA        74619 92682.  6.25  579. 
 3 BC        71551  8583.  6.25   53.6
 4 BM        73604 35120.  6.25  220. 
 5 BP        70552 54020.  6.25  338. 
 6 BPQ       71523 66583.  6.25  416. 
 7 BQ        74589 25589.  6.25  160. 
 8 BQP       69510 39233.  6.25  245. 
 9 PC        42488 16929.  6.25  106. 
10 PI        71509  5304.  6.25   33.1
# ℹ 22 more rows

Para pasar de un objeto tipo tibble a uno de tipo vector se puede hacer con pull.

df_agb_sit |>
  group_by(CVE_S7_C3) |>
  arrange(desc(AGB.ha)) |>
  slice_head(n = 1) |>
  pull(AGB.ha)
 [1]  41.881903 579.260686  53.646391 219.502453 337.624463 416.146763
 [7] 159.930558 245.206817 105.803482  33.148318   2.293781   1.540609
[13]  32.690510  72.806867  66.026971  41.906103  18.885354  16.576570
[19]  36.662336  55.051676  87.933281 179.437483 156.779441 114.498957
[25]  56.464526 102.589533  67.534450 413.846176  89.985899  95.626391
[31] 161.376240  69.519826

Otras funciones útiles :

Algunas otras funciones de dplyr.

  • rowwise. Hacer operaciones por cada fila.
  • bind_rows, bind_cols. Pegar listas por filas o por columnas. Función bastante útil para cuando tienes múltiples entradas con read.csv o algo similar.
  • any_of. Como all_of pero para cuando quieres evaluar una condición sobre alguna en lugar de todas las variables.
  • left_join, right_join, full_join, anti_join. Útiles para unir dos tablas que comparten un campo en común. Diferentes formas de hacer estas uniones.
  • lag, lead. Para hacer operaciones “rolling”, por ejemplo, restar a cada valor el anterior o sumar. Usados para hacer sumas acumulativas.

Tidyr

Dos formatos principales en datos tabulados: largo (long) y ancho (wide). Hay dos funciones para cambiar rápida y fácilmente de un formato a otro:

Pasar de formato largo a ancho o viceversa

Para pasar de formato ancho a largo: pivot_longer, para pasar de largo a ancho: pivot_wider.

Esto es formato largo

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -IdConglomerado,
              names_to = "Variable",
               values_to = "Valor")
# A tibble: 52,070 × 3
   IdConglomerado Variable          Valor
            <dbl> <chr>             <dbl>
 1          67761 biomasa_kg_C3      21.0
 2          67761 DiametroNormal_C3   7.9
 3          67761 biomasa_kg_C3      35.3
 4          67761 DiametroNormal_C3  41.2
 5          67761 biomasa_kg_C3      10.5
 6          67761 DiametroNormal_C3  11.2
 7          67762 biomasa_kg_C3      72.1
 8          67762 DiametroNormal_C3  16.4
 9          67762 biomasa_kg_C3      18.2
10          67762 DiametroNormal_C3   8.7
# ℹ 52,060 more rows

Para generar exactamente el mismo formato necesitamos un id por planta para que cada árbol tenga su propio altura y diámetro. Para regresar a los datos como los teníamos en original (formato ancho).

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  # Agregar un identificador único para volver a generar bien los datos en formato ancho
  mutate(id = row_number()) |>
  pivot_longer(cols = -c(IdConglomerado, id),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado, id),
              names_from = "Variable",
              values_from = "Valor")
# A tibble: 26,035 × 4
   IdConglomerado    id biomasa_kg_C3 DiametroNormal_C3
            <dbl> <int>         <dbl>             <dbl>
 1          67761     1          21.0               7.9
 2          67761     2          35.3              41.2
 3          67761     3          10.5              11.2
 4          67762     4          72.1              16.4
 5          67762     5          18.2               8.7
 6          67762     6          89.4              17.8
 7          67762     7         227.               25.4
 8          67762     8          84.2              17.4
 9          67762     9         280.               27.5
10          67762    10         165.               36.4
# ℹ 26,025 more rows

¿Qué pasa si no ponemos el id?

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -c(IdConglomerado),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado),
              names_from = "Variable",
              values_from = "Valor")
Warning: Values from `Valor` are not uniquely identified; output will contain list-cols.
• Use `values_fn = list` to suppress this warning.
• Use `values_fn = {summary_fun}` to summarise duplicates.
• Use the following dplyr code to identify duplicates.
  {data} |>
  dplyr::summarise(n = dplyr::n(), .by = c(IdConglomerado, Variable)) |>
  dplyr::filter(n > 1L)
# A tibble: 348 × 3
   IdConglomerado biomasa_kg_C3 DiametroNormal_C3
            <dbl> <list>        <list>           
 1          67761 <dbl [79]>    <dbl [79]>       
 2          67762 <dbl [74]>    <dbl [74]>       
 3          67766 <dbl [51]>    <dbl [51]>       
 4          67767 <dbl [23]>    <dbl [23]>       
 5          68781 <dbl [33]>    <dbl [33]>       
 6          68270 <dbl [180]>   <dbl [180]>      
 7          67481 <dbl [37]>    <dbl [37]>       
 8          67484 <dbl [71]>    <dbl [71]>       
 9          67491 <dbl [219]>   <dbl [219]>      
10          67493 <dbl [84]>    <dbl [84]>       
# ℹ 338 more rows

En automático cuando no tenemos una entrada única por los niveles de la columna marcada como id_cols va a meter los datos en una lista. Vean el aviso. Las listas se van a ver como < tipo de variable y cuántos datos tiene>. En este caso el tipo de variable es “double” dbl.

Para evitar el aviso podemos indicarle que esperamos que no haya entradas únicas por los niveles de id_cols y que si esto pasa lo meta dentro de una lista.

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -c(IdConglomerado),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado),
              names_from = "Variable",
              values_from = "Valor",
              values_fn = list)
# A tibble: 348 × 3
   IdConglomerado biomasa_kg_C3 DiametroNormal_C3
            <dbl> <list>        <list>           
 1          67761 <dbl [79]>    <dbl [79]>       
 2          67762 <dbl [74]>    <dbl [74]>       
 3          67766 <dbl [51]>    <dbl [51]>       
 4          67767 <dbl [23]>    <dbl [23]>       
 5          68781 <dbl [33]>    <dbl [33]>       
 6          68270 <dbl [180]>   <dbl [180]>      
 7          67481 <dbl [37]>    <dbl [37]>       
 8          67484 <dbl [71]>    <dbl [71]>       
 9          67491 <dbl [219]>   <dbl [219]>      
10          67493 <dbl [84]>    <dbl [84]>       
# ℹ 338 more rows

Ahora, ¿cómo sacamos las entradas de una lista sin perder el formato tabular? Esto lo hacemos con unnest. Hay dos versiones: unnest_longer y unnest_wider. unnest_wider hace algo similar a unnest_longer pero en formato ancho, para usarlo hay que indicar los nombres de las nuevas columnas.

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -c(IdConglomerado),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado),
              names_from = "Variable",
              values_from = "Valor",
              values_fn = list) |>
  unnest_longer(c(biomasa_kg_C3, DiametroNormal_C3))
# A tibble: 26,035 × 3
   IdConglomerado biomasa_kg_C3 DiametroNormal_C3
            <dbl>         <dbl>             <dbl>
 1          67761         21.0                7.9
 2          67761         35.3               41.2
 3          67761         10.5               11.2
 4          67761         23.2                9.4
 5          67761          9.42               7.7
 6          67761         21.3               10  
 7          67761         14.5                8.2
 8          67761         12.9                8.7
 9          67761         62.0               12.4
10          67761         20.0               10  
# ℹ 26,025 more rows

Este ejemplo sirve para ver que los tibbles pueden guardar listas en sus celdas.

Ya quedó como antes. Los dos pivots son muy útiles para acomodar datos para hacer ciertas operaciones o acomodos. Por ejemplo, en formato ancho es muy fácil calcular el porcentaje para cada valor, mientras que usualmente el formato largo es el más apropiado para hacer gráficas con ggplot.

Anidar y desanidar

Ahora si queremos meter datos dentro de una lista en un tibble se puede usar nest.

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -c(IdConglomerado),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado),
              names_from = "Variable",
              values_from = "Valor",
              values_fn = list) |>
  unnest_longer(c(biomasa_kg_C3, DiametroNormal_C3)) |>
  group_by(IdConglomerado) |>
  nest(data = biomasa_kg_C3) 
# A tibble: 18,874 × 3
# Groups:   IdConglomerado [348]
   IdConglomerado DiametroNormal_C3 data            
            <dbl>             <dbl> <list>          
 1          67761               7.9 <tibble [2 × 1]>
 2          67761              41.2 <tibble [1 × 1]>
 3          67761              11.2 <tibble [1 × 1]>
 4          67761               9.4 <tibble [1 × 1]>
 5          67761               7.7 <tibble [3 × 1]>
 6          67761              10   <tibble [5 × 1]>
 7          67761               8.2 <tibble [3 × 1]>
 8          67761               8.7 <tibble [5 × 1]>
 9          67761              12.4 <tibble [2 × 1]>
10          67761              14.9 <tibble [1 × 1]>
# ℹ 18,864 more rows

Y ahora veamos como podemos sacar los datos de una lista.

datos |>
  select(IdConglomerado,biomasa_kg_C3,DiametroNormal_C3) |>
  pivot_longer(cols = -c(IdConglomerado),
              names_to = "Variable",
               values_to = "Valor") |>
  pivot_wider(id_cols = c(IdConglomerado),
              names_from = "Variable",
              values_from = "Valor",
              values_fn = list) |>
  unnest_longer(c(biomasa_kg_C3, DiametroNormal_C3)) |>
  group_by(IdConglomerado) |>
  nest(data = biomasa_kg_C3) |>
  unnest(data)
# A tibble: 26,035 × 3
# Groups:   IdConglomerado [348]
   IdConglomerado DiametroNormal_C3 biomasa_kg_C3
            <dbl>             <dbl>         <dbl>
 1          67761               7.9         21.0 
 2          67761               7.9         17.0 
 3          67761              41.2         35.3 
 4          67761              11.2         10.5 
 5          67761               9.4         23.2 
 6          67761               7.7          9.42
 7          67761               7.7         11.7 
 8          67761               7.7          7.25
 9          67761              10           21.3 
10          67761              10           20.0 
# ℹ 26,025 more rows

Nest y unnest son útiles para hacer distintos modelos con diferentes grupos de datos o también para manejar casos no únicos al hacer pivot_wider.

Quitar NA

Hay otras funciones útiles en dplyr/tidyr para manejar datos con NA.

Quitar NA se hace con drop_na

datos |>
  mutate(across(DiametroNormal_C3, ~ifelse(.x >= 99999, NA, .x))) |>
  drop_na(DiametroNormal_C3)
# A tibble: 25,410 × 76
   UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3          
   <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>              
 1 40277          67761            NA    2019            16 Michoacán de Ocampo
 2 40277          67761            NA    2019            16 Michoacán de Ocampo
 3 40277          67761            NA    2019            16 Michoacán de Ocampo
 4 40279          67762            NA    2015            16 Michoacán de Ocampo
 5 40279          67762            NA    2015            16 Michoacán de Ocampo
 6 40279          67762            NA    2015            16 Michoacán de Ocampo
 7 40279          67762            NA    2015            16 Michoacán de Ocampo
 8 40279          67762            NA    2015            16 Michoacán de Ocampo
 9 40279          67762            NA    2015            16 Michoacán de Ocampo
10 40279          67762            NA    2015            16 Michoacán de Ocampo
# ℹ 25,400 more rows
# ℹ 70 more variables: CVEECON1_C3 <dbl>, CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>,
#   CVEECON4_C3 <chr>, DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>,
#   DESECON4_C3 <chr>, Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>,
#   X_C3 <dbl>, Y_C3 <dbl>, DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>,
#   FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>, Sitio_C3 <dbl>, Consecutivo_C3 <dbl>,
#   NoIndividuo_C3 <dbl>, NoRama_C3 <dbl>, Azimut_C3 <dbl>, …

Sustituir NA

A veces se desea sustituir los NA con algún valor. Eso se puede hacer con replace_na.

datos |>
  mutate(across(DiametroNormal_C3, ~ifelse(.x >= 99999, NA, .x))) |>
  replace_na(list(DiametroNormal_C3 = 0)) |>
  filter(DiametroNormal_C3 == 0)
# A tibble: 625 × 76
   UPMID IdConglomerado ArboladoID_C3 Anio_C3 Cve_Estado_C3 Estado_C3          
   <dbl>          <dbl>         <dbl>   <dbl>         <dbl> <chr>              
 1 48449          65760            NA    2015            16 Michoacán de Ocampo
 2 40287          67766            NA    2019            16 Michoacán de Ocampo
 3 40289          67767            NA    2015            16 Michoacán de Ocampo
 4 44863          66591            NA    2016            16 Michoacán de Ocampo
 5 46036          66301            NA    2016            16 Michoacán de Ocampo
 6 46132          66349            NA    2015            16 Michoacán de Ocampo
 7 41362          67481            NA    2015            16 Michoacán de Ocampo
 8 40287          67766            NA    2019            16 Michoacán de Ocampo
 9 40287          67766            NA    2019            16 Michoacán de Ocampo
10 38204          68270            NA    2015            16 Michoacán de Ocampo
# ℹ 615 more rows
# ℹ 70 more variables: CVEECON1_C3 <dbl>, CVEECON2_C3 <dbl>, CVEECON3_C3 <chr>,
#   CVEECON4_C3 <chr>, DESECON1_C3 <chr>, DESECON2_C3 <chr>, DESECON3_C3 <chr>,
#   DESECON4_C3 <chr>, Region_Hidrologica_C3 <chr>, CVE_S7_C3 <chr>,
#   X_C3 <dbl>, Y_C3 <dbl>, DESCRIP_S7_C3 <chr>, FORM_S7_C3 <chr>,
#   FAO_S7_C3 <lgl>, ECO_S7_C3 <chr>, Sitio_C3 <dbl>, Consecutivo_C3 <dbl>,
#   NoIndividuo_C3 <dbl>, NoRama_C3 <dbl>, Azimut_C3 <dbl>, …

Rellenar datos vacíos

Rellenar datos vación en función de las entradas que tienen próximas.

Veamos otros datos: - Recuento de especies conocidas de fauna por grupo de vertebrados y según categoría de riesgo de acuerdo con la NOM-059-ECOL-1994

Si no lo pueden descargar directamente de la página, bájenlo de mi repositorio de github: spNOM.

Trae unos signos extraños como nombre de columna.

Los datos muestran el número de especies por estado de riesgo de diferentes grupos de animales.

datos2 <- read_excel("Data/spNOM.xlsx",
                     skip = 3,
                     na = "-")
colnames(datos2)
[1] "\r\n\r\nCategoría"                       
[2] "\r\n\r\nGrupo"                           
[3] "\r\n\r\nEspecies conocidas"              
[4] "\r\nEspecies \r\nEndémicas"              
[5] "\r\nEspecies \r\namenazadas"             
[6] "Especies \r\nen peligro \r\nde extinción"
[7] "\r\n\r\nEspecies raras"                  
[8] "\r\n\r\nProtección especial"             

Vamos a usar un truco para quitar esos signos con str_replace_all de los nombre de columnas de los datos.

Usaremos una función de otro paquete que no veremos a profundidad: stringr. Como \ es un caracter especial hay que usar doble `\\ para escaparlo.

nuevosnoms <- datos2 |> 
  colnames() |>
  stringr::str_replace_all("\\r\\n\\r\\n|\\r\\n", "")
colnames(datos2) <- nuevosnoms

Ahora si podemos usar fill para rellenar datos de una columna en particular y en el sentido indicado en .direction.

datos2 |> 
  fill(all_of("Categoría"), 
       .direction = ("down"))
# A tibble: 5 × 8
  Categoría   Grupo     `Especies conocidas` `Especies Endémicas`
  <chr>       <chr>                    <dbl>                <dbl>
1 Vertebrados Anfibios                   290                  174
2 Vertebrados Aves                      1054                  111
3 Vertebrados Mamíferos                  491                  142
4 Vertebrados Peces                     2122                  163
5 Vertebrados Reptiles                   704                  368
# ℹ 4 more variables: `Especies amenazadas` <dbl>,
#   `Especies en peligro de extinción` <dbl>, `Especies raras` <dbl>,
#   `Protección especial` <dbl>

Otras funciones útiles

Otras funciones útiles que ya no da tiempo de ver:

  • unite: unir dos columnas en una sol
  • separate: Separar una columna en dos o más usando un separador.
  • expand: Expandir a usar todas las combinacinoes posibles. Esto muchas veces es similar a usar pivot_wider
  • complete. Completa observaciones que no aparecen en los datos y los rellena con NA.
  • hoist: Es como un unnest para ciertas variables
  • rowwise: Aplicar una operación para cada fila.