7  Laboratorio de R 5

7.1 Introducción

Generar miles de lineas de código puede resultar muy tedioso e implica un arduo trabajo, por lo tanto la eficiencia y claridad en el código son cruciales para el procesamiento de conjuntos de datos complejos y la realización de análisis estadísticos elaborados. La familia de funciones apply y los operadores pipe, %>% (introducido por el paquete dplyr) y |> (de R base), son herramientas fundamentales que ofrecen una sintaxis más limpia e intuitiva. Además permite ahorrarnos varias líneas de código.

La funciones de la familia apply permiten la aplicación de diversar funciones a todo un data frame. Por otro lado, los operadores permiten generar código muchas legible. Así pués, en este capítulo se revisarán las funciones de la familia apply y el operador pipe %>% y |>.

7.2 Funciones de la familia apply()

La familia de funciones apply() pertenecen al paquete base de R. Son funciones creadas para manipular segmentos de datos de matrices, arreglos, listas y marcos de datos de forma repetitiva. Por ejemplo, con estás funciones podemos repetir una función para todas las columnas de un data frame. También, podemos utilizar funciones para grupo de datos en especifico, por ejemplo comparar los casos de los controles. Otras funciones de esta familia realizan funciones de transformación o subconjunto.

La familia de funciones apply en R, que incluye apply(), lapply() , sapply(), vapply(), mapply(), rapply() y tapply(), está diseñada para simplificar las operaciones repetitivas sobre estructuras de datos, como matrices y listas, sin la necesidad de recurrir a bucles for. Estas funciones permiten aplicar una función a los elementos de un objeto de datos de manera eficiente, lo que resulta en código más compacto y legible.

En este texto nos enfocaremos únicamente en las funciones apply(), lapply() y tapply() de forma superficial. Sin embargo, puede encontrar un tutorial más avanzado en: https://www.datacamp.com/tutorial/r-tutorial-apply-family.

7.2.1 La función apply()

Esta función nos permite aplicar una una misma función a todos los elementos de una matriz, ya sea columnas o filas.

La estructura general de la función apply() es la siguiente:

 apply(X, MARGIN, FUN)

Si prestamos atención la función necesita de tres argumentos para alimentarse. El segundo parámetro, MARGIN, determina la dimensión (o margen) sobre la cual se agruparán los elementos de X para aplicarles una función específica. Estas dimensiones se identifican mediante números: el 1 para las filas y el 2 para las columnas. Por último, el tercer parámetro, FUN, especifica la función que se aplicará sobre la dimensión seleccionada. Para una mayor claridad sobre cómo opera esta función, es útil examinar un ejemplo práctico.

Supongamos que deseamos conocer media de todas las variables del data frame Pima.tr, para ello podemos emplear el siguiente código:

#Cargamos la base de datos
library(MASS)
data(Pima.tr)
# Aplicamos la función Apply a las columnas
apply(X=Pima.tr, MARGIN=2, FUN=mean)
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
npreg   glu    bp  skin   bmi   ped   age  type 
   NA    NA    NA    NA    NA    NA    NA    NA 

Si prestamos atención al código hay dos errores que podemos corregir, el primero es que podemos incluir el argumento na.rm para omitir los valores perdidos y el segundo es que no es tiene sentido estimar la media para una variable de tipo factor type (un dato de tipo factor). Vamos a corregir este código por pasos

apply(X=Pima.tr, MARGIN=2, FUN=mean, na.rm=T)
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
Warning in mean.default(newX[, i], ...): argument is not numeric or logical:
returning NA
npreg   glu    bp  skin   bmi   ped   age  type 
   NA    NA    NA    NA    NA    NA    NA    NA 

Note como el argumento na.rm=T se aplica la función mean y no apply. Todos los argumente que se encuentren despues del argumento FUN de apply serán aplicados a la función. Sin embargo, de nueva cuenta presentamos un error, por lo tanto es necesario indicarle las columnas a las que queremos que se aplique la función, para ello vamos a utilizar los corchetes. Estamos aplicando una función a cada elemento de nuestro data frame. Los elementos son las columnas de la 1 a la 7

apply(X=Pima.tr[,1:7], MARGIN=2, FUN=mean, na.rm=T)
     npreg        glu         bp       skin        bmi        ped        age 
  3.570000 123.970000  71.260000  29.215000  32.310000   0.460765  32.110000 

Note como el filtro se realizó para el argumento X y no para otros de los argumentos. En el capitulo Capítulo 6 se revisó a detalle como filtrar columnas y filas.

¿Y si quisiéramos la suma de los datos para cada una de las columnas de Pima.tr? El siguiente código nos ayudará:

apply(X=Pima.tr[,1:7], MARGIN=2, FUN=sum, na.rm=T)
    npreg       glu        bp      skin       bmi       ped       age 
  714.000 24794.000 14252.000  5843.000  6462.000    92.153  6422.000 

7.3 La función lapply()

Esta función es muy parecida a la función apply sin embargo, esta realiza una operación para una lista y devuelve una lista. De forma simple se puede entender a una lista, con un conjunto de datos de una sola dimensión. La función lapply se puede aplicar a data frame ya que R puede coercionar los data frame a lista en algunos situaciones.

El siguiente código ejemplifica como utilizar la función lapply

lapply(X=Pima.tr[,1:7], FUN=sd)
$npreg
[1] 3.366268

$glu
[1] 31.66723

$bp
[1] 11.4796

$skin
[1] 11.72459

$bmi
[1] 6.130212

$ped
[1] 0.3072248

$age
[1] 10.97544

Si notamos en el código no es necesario utilizar el argumento MARGIN ya que las listas solo tiene una dimensión. Obtenemos el mismo resultado que el que se obtuvo con la función apply pero en forma de lista.

7.4 La función tapply

Esta función nos permite obtener resultados agrupados. La estructura básica de las función es la siguiente:

tapply(X, INDEX, FUN)

Donde X es la variable a la que se aplicará la función. INDEX es la variable de agrupación y FUN es la función que se aplicará a los datos agrupados. Por ejemplo supongamos que deseamos conocer la media de edad para las pacientes con diabetes y sin diabetes, para ello empleamos el siguiente código:

tapply(X=Pima.tr$age, INDEX=Pima.tr$type, FUN=mean)
      No      Yes 
29.23485 37.69118 

Note como fue necesario utilizar el símbolo $, de lo contrario R no sabría donde buscar el objeto para hacer las operaciones.

7.4.1 Ejercicios de utlizando las funciones de la familia apply

Ejercicio 7.1 Utilizando la base de datos Pima.tr de la librería MASS estime la media, la mediana, la desviación estándar y el rango intercuartil para cada una de las variables del data frame agrupando a las pacientes con y sin diabetes

apply(Pima.tr[,1:7], 2, tapply, Pima.tr$type, mean)
       npreg      glu       bp     skin      bmi       ped      age
No  2.916667 113.1061 69.54545 27.20455 31.07424 0.4154848 29.23485
Yes 4.838235 145.0588 74.58824 33.11765 34.70882 0.5486618 37.69118
apply(Pima.tr[,1:7], 2, tapply, Pima.tr$type, median)
    npreg   glu bp skin   bmi    ped age
No    2.0 109.5 70   27 31.05 0.3235  26
Yes   4.5 144.0 76   32 34.60 0.4495  36
apply(Pima.tr[,1:7], 2, tapply, Pima.tr$type, sd)
       npreg      glu       bp     skin      bmi       ped      age
No  2.806866 26.63759 11.08356 10.92915 6.381457 0.2671859  9.54368
Yes 3.972331 30.12059 11.58387 12.30159 4.810956 0.3590029 11.48036

Ejercicio 7.2 Utilizando la base de datos Melanoma de la librería MASS estime la media, la mediana, la desviación estándar y el rango intercuartil para cada una de las variables del data frame que sean cuantitativas

No tiene sentido estimar datos descriptivos del añó de inclusión del paciente

library(MASS)
data("Melanoma")
apply(Melanoma[, c(1,4,6)], MARGIN = 2, FUN = mean)
       time         age   thickness 
2152.800000   52.463415    2.919854 
apply(Melanoma[, c(1,4,6)], MARGIN = 2, FUN = median)
     time       age thickness 
  2005.00     54.00      1.94 
apply(Melanoma[, c(1,4,6)], MARGIN = 2, FUN = sd)
       time         age   thickness 
1122.060667   16.671711    2.959433 
apply(Melanoma[, c(1,4,6)], MARGIN = 2, FUN = IQR)
     time       age thickness 
  1517.00     23.00      2.59 

Ejercicio 7.3 Utilizando la base de datos Melanoma de la librería MASS estime la media, la mediana, la desviación estándar y el rango intercuartil para cada una de las variables del data frame agrupando por la variable status.

apply(Melanoma[, c(1,4,6)], 2, tapply, Melanoma$status, mean)
      time      age thickness
1 1252.947 55.08772  4.311053
2 2620.672 50.00746  2.244701
3 1338.286 65.28571  3.717857
apply(Melanoma[, c(1,4,6)], 2, tapply, Melanoma$status, median)
    time age thickness
1 1062.0  56     3.540
2 2374.0  52     1.355
3 1126.5  65     2.260
apply(Melanoma[, c(1,4,6)], 2, tapply, Melanoma$status, sd)
       time      age thickness
1  758.9976 17.90778  3.573814
2  948.1382 15.91684  2.326171
3 1247.8054 10.90115  3.631618
apply(Melanoma[, c(1,4,6)], 2, tapply, Melanoma$status, IQR)
    time   age thickness
1  949.0 24.00      2.60
2 1471.5 21.75      2.05
3 1766.0 14.75      4.51

7.5 Operadores pipe

En R podemos encontrar varios operados conocidos conocidos como pipe. Los más utilizados es el símbolo %>% que se encuentra en el paquete dplyr y el símbolo |> que se encuentra en el paquete base de R. Un pipe puede definirse como un símbolo que permite realizar llamadas o funciones encadenadas. De una manera más simple se puede entender como un símbolo que le permite pasar un resultado intermedio a la siguiente función y el resultado de esta función a la siguiente.

Comenzaremos explicando el operador %>% que es un poco más sencillo de entender que el operador |>

7.5.1 Operador %>%

La estructura básica del operador %>% es la siguiente:

df %>% 
  operación_1 %>% 
  operación_2 %>%
 operación_3 

El código anterior permite seleccionar una variable de un data frame (df) realizar una operación (operación 1) y tomas los resultados de esta operación para realizar la operación 2. Después toma los resultados de la operación 2 para realizar la última operación (operación 3).

El operador pipe simplemente alimenta los resultados de una operación a la siguiente operación debajo de ella. La ventaja de usar el operador de tubería es que hace que el código sea extremadamente fácil de leer.

El siguiente código permite estimar la media de la edad de lo pacientes incluidos en la base melanoma y redondear el resultado a dos dígitos

# Llamar la librería MASS para poder cargar la base melanoma
library(MASS)
data("Melanoma")
# Estimar la media redondeando a dos dígitos
round(mean(Melanoma$age), 2)
[1] 52.46

Note como necesitamos de la función round y dentro de ella estimamos la media. Utilizando el operador pipe el código podría simplificarse de la siguiente forma:

library(dplyr) #Librería para poder utilizar el operador pipe

Attaching package: 'dplyr'
The following object is masked from 'package:MASS':

    select
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
Melanoma$age %>% 
  mean() %>% 
  round(2)
[1] 52.46

El código anterior tomó el objeto Melanoma$age para estimar la media y luego redondearlo a 2 a dígitos.

Existen algunas funciones en las que no es necesario utilizar el símbolo $ para que R sepa donde buscar la variable sobre la cual va ejecutar la operación. Estás funciones pertenecen principalmente al mundo de tidyverse y a la librería rstatix que utilizaremos en futuros capítulos.

7.5.2 Operador |>

Se podría decir, que los operadores c son iguales y hasta cierto punto funcionan de la misma manera. Sin embargo, al tener orígenes distintos, en ciertas situaciones difieren en su funcionamiento. Profundizar en las diferencias de estos operados se escapa de los objetivos de este texto, pero para fines prácticos podríamos decir que el operador |> siempre necesita tener declarado la base de datos de donde tomar las variables. Por ejemplo, si quisiéramos estimar una regresión ver capitulo [Regresión lineal múltiple] utilizando el operador %>% podríamos utilizar el siguiente código:

Melanoma %>% 
  lm(age~thickness, data=.)

Call:
lm(formula = age ~ thickness, data = .)

Coefficients:
(Intercept)    thickness  
     48.968        1.197  

Sin embargo con el operador |> necesitamos declarar la base de datos, y su uso no se justificaría ya que el resultado sería un error.

Melanoma |> 
  lm(age~thickness, data=.)# No declarar la base de datos

El código anterior daría como resultado: Error in is.data.frame(data) : object '.' not found.

Si volvemos al ejemplo de la estimación de la media con dos dígitos, no tendríamos problema en usar los pipe de forma indistinta y el código sería prácticamente igual.

Melanoma$age |> 
  mean() |> 
  round(2)
[1] 52.46

7.5.3 Conclusión

Aunque es más complejo, podriamos decir que ambos operadores pueden fuincar para lo mismo. En este texto utilizaremos ambos operadores, aunque Ela mayoría de las ocasiones utilizaremos ambos operadores pero le daremos prioridad al operados |>.

7.5.4 Información extra

Puede encontrar mayor información sobre el uso de los pipe en:

  • https://www.statology.org/pipe-in-r/

  • https://www.r-bloggers.com/2017/12/pipes-in-r-tutorial-for-beginners/

  • https://towardsdatascience.com/an-introduction-to-the-pipe-in-r-823090760d64

  • https://towardsdatascience.com/understanding-the-native-r-pipe-98dea6d8b61b

  • https://towardsdatascience.com/understanding-the-native-r-pipe-98dea6d8b61b

7.6 Ejercicio final

Ejercicio 7.4 Utilice los datos del ejercicio Ejercicio 9.32 y genere estadística descriptiva que incluya: media, mediana, desviación estándar, CV, varianza, Q1, Q2, Q3, valor mínimo y máximo para todas las variables numéricas de la base Base Descriptivos.rds divido por hombres y mujeres. Genere una tabla con sus resultados