Capítulo 4 Técnicas Avanzadas

4.1 Metaprogramación: escribiendo código que escribe código

En los capítulos anteriores, hemos explorado los diferentes tipos de objetos en R y cómo usar funciones para manipularlos. Ahora, vamos a adentrarnos en un concepto más avanzado: la metaprogramación.

La metaprogramación es una técnica que nos permite escribir código que genera otro código. Es como si tuviéramos una fábrica de código, donde podemos crear nuevas funciones y expresiones de forma dinámica.

¿Para qué nos sirve esto? La metaprogramación puede ser muy útil para:

  • Automatizar tareas repetitivas: Si tenemos que escribir código similar muchas veces, podemos usar la metaprogramación para generar ese código automáticamente.
  • Crear funciones más flexibles: Podemos usar la metaprogramación para crear funciones que se adapten a diferentes situaciones y tipos de datos.
  • Escribir código más conciso y expresivo: La metaprogramación nos permite expresar ideas complejas de forma más concisa.

En R, la metaprogramación se basa en la manipulación de expresiones. Una expresión es una representación del código R como un objeto. Podemos crear expresiones, modificarlas y evaluarlas para generar nuevo código.

4.1.1 Manipulación de expresiones: El arte de esculpir código

En R, la metaprogramación se basa en la manipulación de expresiones. Una expresión es una representación del código R como un objeto. En lugar de simplemente ejecutar el código, podemos manipularlo como si fuera un bloque de arcilla, dándole forma y modificándolo para crear nuevas expresiones y funciones.

Piensa en una expresión como una receta de cocina. La receta contiene una serie de instrucciones (los ingredientes y los pasos a seguir) para crear un platillo. De la misma manera, una expresión en R contiene las instrucciones para realizar una tarea.

R nos ofrece varias herramientas para manipular expresiones, como si fueran las manos de un escultor que moldea la arcilla:

  • quote(): Esta función toma código R y lo “congela” en una expresión, sin evaluarlo. Es como si tomáramos la receta de cocina y la guardáramos en un libro, sin prepararla todavía.

    mi_expresion <- quote(x + y)
    mi_expresion
    #> x + y

    En este ejemplo, quote(x + y) crea una expresión que representa la suma de x e y. La expresión se guarda en la variable mi_expresion, pero la suma no se realiza todavía.

  • substitute(): Esta función nos permite sustituir variables en una expresión por sus valores. Es como si en la receta de cocina, reemplazamos la palabra “azúcar” por la cantidad de azúcar que queremos usar.

    x <- 10
    y <- 5
    substitute(x + y)
    #> x + y

    En este ejemplo, substitute(x + y) reemplaza las variables x e y por sus valores (10 y 5, respectivamente), resultando en la expresión 10 + 5.

  • eval(): Esta función “descongela” una expresión y la evalúa, ejecutando el código que contiene. Es como si tomáramos la receta de cocina del libro y la usáramos para preparar el platillo.

    eval(quote(x + y))
    #> [1] 15

    En este ejemplo, eval(quote(x + y)) evalúa la expresión x + y, realizando la suma y devolviendo el resultado 15.

  • parse(): Esta función convierte texto en una expresión. Es como si alguien nos dictara la receta de cocina, y nosotros la escribiéramos en un papel para poder usarla después.

    texto <- "x * y"
    expresion <- parse(text = texto)
    eval(expresion)
    #> [1] 50

    En este ejemplo, parse(text = texto) convierte el texto "x * y" en una expresión que representa la multiplicación de x e y.

Con estas herramientas, podemos manipular expresiones para crear nuevas funciones, modificar el comportamiento de funciones existentes, y generar código de forma dinámica.

4.1.2 Ejemplos

La metaprogramación puede parecer un concepto abstracto al principio, pero sus aplicaciones son muy concretas y poderosas. Veamos algunos ejemplos de cómo podemos usar la metaprogramación en R para crear funciones dinámicas y generar código automáticamente.

Ejemplo 1: Crear una función que genere otras funciones

Imagina que necesitas crear varias funciones que realicen operaciones similares, pero con algunos parámetros diferentes. Por ejemplo, funciones que sumen diferentes constantes a un número. En lugar de escribir cada función por separado, puedes usar la metaprogramación para crear una función que genere estas funciones dinámicamente.

crear_funcion_suma <- function(n) {
  expresion <- substitute(function(x) x + n)
  eval(expresion)
}

suma_5 <- crear_funcion_suma(5)
suma_10 <- crear_funcion_suma(10)

suma_5(10)
#> [1] 15
suma_10(10)
#> [1] 20

En este ejemplo, la función crear_funcion_suma() recibe un número n como argumento y genera una nueva función que suma n a su argumento. La función substitute() se utiliza para crear una expresión que representa la función que queremos generar, y la función eval() se utiliza para evaluar la expresión y crear la función.

Ejemplo 2: Generar código para un análisis de datos

Supongamos que quieres realizar un análisis de datos que involucra varios pasos, como filtrar datos, calcular estadísticas y generar un gráfico. Puedes usar la metaprogramación para generar el código de este análisis de forma dinámica, en función de los parámetros que se especifiquen.

analizar_datos <- function(datos, filtro, columna_analizar, estadistica, tipo_grafico) {
  
  # Filtrar los datos
  datos_filtrados <- substitute(datos[filtro, ][[columna_analizar]])
  
  datos_filtrados <- eval(datos_filtrados)
  
  # Calcular la estadística
  estadistica_calculada <- substitute(estadistica(datos_filtrados))
  estadistica_calculada <- eval(estadistica_calculada)
  
  # Generar el gráfico
  expresion_grafico <- substitute(tipo_grafico(datos_filtrados))
  eval(expresion_grafico)
  
  # Devolver la estadística calculada
  return(estadistica_calculada)
}

# Ejemplo de uso
df <- data.frame(
  x = c(1, 3, 2, 5.5, 4, 3.5, 8, 7, 9, 10),
  y = c(10, 8, 9, 6, 7, 5, 3.6, 4, 2, 1)
)

# Queremos filtrar los datos donde x > 5, calcular la media de y y generar un histograma
resultado <- analizar_datos(df, df$x > 5, "y", mean, hist)

resultado
#> [1] 3.32

Ejemplo 3: Crear una función para generar gráficos con nombres de variables dinámicos y opciones avanzadas

Imagina que necesitas crear una función que genere diferentes tipos de gráficos (dispersión, histogramas, boxplots) con opciones personalizadas, como títulos, etiquetas, colores y leyendas, y que además pueda manejar diferentes conjuntos de datos y variables. En este caso, la metaprogramación puede ser muy útil para crear una función flexible que se adapte a estas necesidades.

crear_grafico <- function(datos, tipo_grafico, var_x, var_y = NULL, 
                          titulo = NULL, color = "blue", 
                          etiquetas_x = NULL, etiquetas_y = NULL, 
                          leyenda = NULL) {
  
  # Crear la expresión del gráfico base
  if (tipo_grafico == "dispersion") {
    expresion <- substitute(plot(datos[[var_x]], datos[[var_y]], 
                                 xlab = etiquetas_x, ylab = etiquetas_y,
                                 main = titulo, col = color))
  } else if (tipo_grafico == "histograma") {
    expresion <- substitute(hist(datos[[var_x]], main = titulo, xlab = etiquetas_x, col = color))
  } else if (tipo_grafico == "boxplot") {
    expresion <- substitute(boxplot(datos[[var_x]], main = titulo, ylab = etiquetas_y, col = color))
  } else {
    stop("Tipo de gráfico inválido.")
  }
  
  # Evaluar la expresión base
  eval(expresion)
  
  # Agregar leyenda si se especifica
  if (!is.null(leyenda)) {
    legend("topright", legend = leyenda, fill = color)
  }
}

# Ejemplo de uso
df <- data.frame(
  x = c(1, 3, 2, 5.5, 4, 3.5, 8, 7, 9, 10),
  y = c(10, 8, 9, 6, 7, 5, 3.6, 4, 2, 1)
)

crear_grafico(df, "dispersion", "x", "y", 
              titulo = "Gráfico de dispersión", color = "red", 
              etiquetas_x = "Variable X", etiquetas_y = "Variable Y")

crear_grafico(df, "histograma", "x", 
              titulo = "Histograma de X", color = "green", 
              etiquetas_x = "Variable X")

crear_grafico(df, "boxplot", "y", 
              titulo = "Boxplot de Y", color = "blue", 
              etiquetas_y = "Variable X", 
              leyenda = c("Grupo A"))

En este ejemplo, la función crear_grafico() puede generar diferentes tipos de gráficos con opciones personalizadas. La función utiliza substitute() para construir la expresión del gráfico base, y luego eval() para evaluar la expresión y generar el gráfico. Además, la función puede agregar una leyenda al gráfico si se especifica el argumento leyenda.

Este ejemplo ilustra cómo la metaprogramación puede ser útil para crear funciones más flexibles y complejas que se adapten a diferentes necesidades.

4.2 Programación funcional: un nuevo paradigma

En los capítulos anteriores, hemos explorado los diferentes tipos de objetos en R y cómo usar funciones para manipularlos. También hemos visto cómo la metaprogramación nos permite escribir código que genera otro código. Ahora, vamos a adentrarnos en un paradigma de programación diferente: la programación funcional.

La programación funcional es un estilo de programación que se basa en el uso de funciones puras y la inmutabilidad de los datos.

  • Una función pura es una función que siempre produce el mismo resultado para los mismos argumentos, y no tiene efectos secundarios (es decir, no modifica ningún dato fuera de la función).
  • La inmutabilidad de los datos significa que los datos no se modifican después de ser creados. En lugar de modificar los datos existentes, se crean nuevos datos con las modificaciones.

Estos principios hacen que la programación funcional sea más fácil de razonar, depurar y mantener. También facilita la escritura de código concurrente y paralelo, ya que las funciones puras no tienen efectos secundarios que puedan interferir con otros procesos.

4.2.1 Principios básicos de la programación funcional

  • Funciones como ciudadanos de primera clase: En la programación funcional, las funciones son tratadas como cualquier otro tipo de dato. Se pueden pasar como argumentos a otras funciones, se pueden retornar como resultados de funciones, y se pueden almacenar en variables.
  • Funciones puras: Las funciones puras siempre producen el mismo resultado para los mismos argumentos, y no tienen efectos secundarios. Esto hace que el código sea más predecible y fácil de depurar.
  • Inmutabilidad: Los datos no se modifican después de ser creados. En lugar de modificar los datos existentes, se crean nuevos datos con las modificaciones. Esto evita errores causados por la modificación accidental de datos.
  • Rechazo de los bucles: La programación funcional evita el uso de bucles for y while. En su lugar, se utilizan funciones de alto orden como map, reduce y keep para procesar colecciones de datos.

4.2.2 Funciones de alto orden en R

R ofrece varias funciones de alto orden que son especialmente útiles para la programación funcional. Estas funciones nos permiten manipular vectores, listas y otros objetos de forma concisa y eficiente, evitando el uso de bucles for y while. El paquete purrr ofrece variantes de map() para diferentes tipos de resultados: map_dbl() para obtener un vector numérico, map_chr() para obtener un vector de caracteres, map_lgl() para obtener un vector lógico, etc.

  • map(): Aplica una función a cada elemento de un vector o lista, y devuelve un nuevo vector o lista con los resultados. Es como si tuviéramos una máquina que toma cada elemento de nuestra colección de datos, lo procesa con la función que le indiquemos, y coloca el resultado en una nueva colección.

    library(purrr)
    # Crear un vector de números
    numeros <- c(1, 2, 3, 4, 5)
    
    # Calcular el cuadrado de cada número usando una función anónima
    cuadrados <- map(numeros, function(x) x^2)
    
    # Mostrar el resultado
    cuadrados
    #> [[1]]
    #> [1] 1
    #> 
    #> [[2]]
    #> [1] 4
    #> 
    #> [[3]]
    #> [1] 9
    #> 
    #> [[4]]
    #> [1] 16
    #> 
    #> [[5]]
    #> [1] 25
    
    # También podemos usar una función predefinida
    raices_cuadradas <- map(numeros, sqrt)
    
    # Mostrar el resultado
    raices_cuadradas
    #> [[1]]
    #> [1] 1
    #> 
    #> [[2]]
    #> [1] 1.414214
    #> 
    #> [[3]]
    #> [1] 1.732051
    #> 
    #> [[4]]
    #> [1] 2
    #> 
    #> [[5]]
    #> [1] 2.236068
  • reduce(): Combina los elementos de un vector o lista aplicando una función de forma acumulativa. Es como si tuviéramos una máquina que toma dos elementos de nuestra colección, los combina usando la función que le indiquemos, y luego combina el resultado con el siguiente elemento, y así sucesivamente hasta que se combinan todos los elementos.

    # Crear un vector de números
    numeros <- c(1, 2, 3, 4, 5)
    
    # Calcular la suma de los números usando reduce()
    suma <- reduce(numeros, `+`)
    
    # Mostrar el resultado
    suma
    #> [1] 15
    
    # Calcular el producto de los números
    producto <- reduce(numeros, `*`)
    
    # Mostrar el resultado
    producto
    #> [1] 120
  • keep(): Filtra los elementos de un vector o lista que cumplen una condición. Es como si tuviéramos un colador que deja pasar solo los elementos que cumplen con la condición que le indiquemos.

    # Crear un vector de números
    numeros <- c(1, 2, 3, 4, 5)
    
    # Filtrar los números pares
    pares <- keep(numeros, ~ . %% 2 == 0)
    
    # Mostrar el resultado
    pares
    #> [1] 2 4
    
    # Filtrar los números mayores que 3
    mayores_que_3 <- keep(numeros, ~ . > 3)
    
    # Mostrar el resultado
    mayores_que_3  # Output: 4 5
    #> [1] 4 5

El símbolo ~ en las funciones de alto orden se utiliza para definir una función anónima. Esto significa que estás creando una función “sobre la marcha”, sin necesidad de darle un nombre explícito. La parte que sigue al ~ es el cuerpo de esta función, donde se especifican las operaciones que se realizarán sobre cada elemento del vector o lista al que se aplica la funcion. El punto . se utiliza como un marcador de posición para referirse al elemento actual.

Estas funciones, junto con otras funciones de alto orden como map2(), pmap(), accumulate() y every(), nos brindan una gran flexibilidad para procesar datos de forma funcional en R.

4.2.3 Ejemplos

Veamos algunos ejemplos de cómo aplicar la programación funcional en R:

  • Calcular la suma de los cuadrados de los números pares de un vector:

    numeros <- c(1, 2, 3, 4, 5)
    suma_cuadrados_pares <- numeros %>%
      keep(~. %% 2 == 0) %>%
      map_dbl(~. ^2) %>%
      reduce(`+`)
    
    suma_cuadrados_pares
    #> [1] 20
  • Filtrar las ciudades con una población mayor a 5 millones:

    ciudades <- list(
      list(nombre = "Nueva York", poblacion = 8.4e6),
      list(nombre = "Los Ángeles", poblacion = 3.9e6),
      list(nombre = "Chicago", poblacion = 2.7e6)
    )
    
    ciudades_grandes <- ciudades %>%
      keep(~.x$poblacion > 5e6)
    
    ciudades_grandes
    #> [[1]]
    #> [[1]]$nombre
    #> [1] "Nueva York"
    #> 
    #> [[1]]$poblacion
    #> [1] 8400000

    En este ejemplo, la “x” actúa como un placeholder o marcador de posición para representar cada elemento de la lista ciudades a medida que se itera sobre ella. Es decir, en cada iteración, la “x” tomará el valor de una de las ciudades de la lista.

    ¿Por qué se usa “x”?

    • Función anónima: La expresión ~ .x$poblacion > 5e6 define una función anónima. Esta función toma un elemento de la lista como entrada y devuelve un valor lógico (TRUE o FALSE) dependiendo de si la población de esa ciudad es mayor a 5 millones.
    • Acceso a elementos: El símbolo $ se utiliza para acceder a los elementos de una lista. En este caso, .x$poblacion accede al elemento “poblacion” del elemento actual de la lista (representado por “x”).
    • Concisión: Usar “x” hace que el código sea más conciso y legible, evitando la necesidad de definir una función con nombre explícito para esta operación.

    Puedes usar cualquier nombre que quieras en lugar de “x”, siempre y cuando sea consistente dentro de la función anónima.

La programación funcional es un paradigma poderoso que puede ayudarte a escribir código más limpio, eficiente y fácil de mantener. A medida que te familiarices con sus principios y herramientas, podrás aplicarlos a una gran variedad de problemas de análisis de datos.

4.3 R6: El futuro de la POO en R

En R, la programación orientada a objetos (POO) se puede implementar de varias formas. Tradicionalmente, R ha utilizado sistemas llamados S3 y S4 para la POO.

S3 es un sistema informal y flexible. Se basa en la idea de funciones genéricas, que pueden tener diferentes métodos dependiendo de la clase del objeto al que se aplican. Por ejemplo, la función print() es una función genérica que tiene diferentes métodos para imprimir diferentes tipos de objetos, como vectores, listas o data frames.

# Ejemplo de función genérica en S3
print(c(1, 2, 3))  # Imprime un vector numérico
#> [1] 1 2 3
print(list(a = 1, b = 2))  # Imprime una lista
#> $a
#> [1] 1
#> 
#> $b
#> [1] 2

S4 es un sistema más formal y estructurado que S3. Define clases y métodos de forma más explícita, utilizando una sintaxis especial. S4 se utiliza a menudo en paquetes que requieren una estructura de objetos más rigurosa, como Bioconductor.

# Ejemplo de definición de clase en S4
setClass("Persona", slots = c(nombre = "character", edad = "numeric"))

# Ejemplo de creación de objeto en S4
mi_persona <- new("Persona", nombre = "Juan", edad = 30)

mi_persona
#> An object of class "Persona"
#> Slot "nombre":
#> [1] "Juan"
#> 
#> Slot "edad":
#> [1] 30

Sin embargo, tanto S3 como S4 pueden resultar algo confusos y limitados, especialmente para proyectos más complejos. Por suerte, existe una alternativa más moderna y robusta: el paquete R6. Este paquete ofrece una forma más intuitiva y eficiente de implementar la POO en R, con características que facilitan la organización, la reutilización y el mantenimiento del código. Si eres nuevo en la POO, no te preocupes por los detalles de S3 y S4 por ahora. Con R6, podrás aprender los conceptos básicos de la POO de forma más sencilla y aplicarlos a tus proyectos de análisis de datos.

4.3.1 El paquete R6: Clases, métodos, encapsulación y herencia

El paquete R6 implementa un sistema de clases y objetos similar al de otros lenguajes de programación orientados a objetos, como Python o Java. Proporciona una forma robusta y eficiente de crear objetos con atributos y métodos, permitiendo la encapsulación y la herencia.

install.packages("R6")
library(R6)

Clases:

Una clase es como un plano o plantilla para crear objetos. Define los atributos (datos) y métodos (funciones) que tendrán los objetos de esa clase. En R6, las clases se crean con la función R6Class().


# Definir una clase "Persona"
Persona <- R6Class("Persona",
  public = list(
    nombre = NULL,
    edad = NULL,
    
    # Constructor
    initialize = function(nombre, edad) {
      self$nombre <- nombre
      self$edad <- edad
    },
    
    # Método para saludar
    saludar = function() {
      cat("Hola, mi nombre es", self$nombre, "y tengo", self$edad, "años.\n")
    }
  )
)

En este ejemplo, se define una clase Persona con los atributos nombre y edad, y el método saludar(). La lista public define los miembros públicos de la clase, es decir, los atributos y métodos que se pueden acceder desde fuera del objeto.

Objetos:

Un objeto es una instancia de una clase. Es una entidad concreta que tiene los atributos y métodos definidos por la clase. En R6, los objetos se crean con el método $new().

# Crear un objeto de la clase "Persona"
juan <- Persona$new(nombre = "Juan", edad = 30)

juan
#> <Persona>
#>   Public:
#>     clone: function (deep = FALSE) 
#>     edad: 30
#>     initialize: function (nombre, edad) 
#>     nombre: Juan
#>     saludar: function ()

Métodos:

Los métodos son funciones que operan sobre los atributos de un objeto. Permiten acceder y modificar los datos del objeto, así como realizar otras acciones. En R6, los métodos se definen dentro de la lista public de la clase.

# Llamar al método saludar() del objeto "juan"
juan$saludar()
#> Hola, mi nombre es Juan y tengo 30 años.

Encapsulación:

La encapsulación es un mecanismo que permite ocultar los detalles internos de un objeto y controlar el acceso a sus atributos. Esto protege los datos del objeto y facilita su uso. En R6, la encapsulación se logra mediante la distinción entre miembros públicos y privados.

Los miembros públicos se definen en la lista public y se pueden acceder desde fuera del objeto. Los miembros privados se definen en la lista private y solo se pueden acceder desde dentro del objeto, a través de los métodos.

# Definir una clase "CuentaBancaria" con encapsulación
CuentaBancaria <- R6Class("CuentaBancaria",
  public = list(
    titular = NULL,
    
    # Constructor
    initialize = function(titular) {
      self$titular <- titular
      private$saldo <- 0
    },
    
    # Método para depositar dinero
    depositar = function(cantidad) {
      private$saldo <- private$saldo + cantidad
    },
    
    # Método para retirar dinero
    retirar = function(cantidad) {
      if (cantidad <= private$saldo) {
        private$saldo <- private$saldo - cantidad
      } else {
        stop("Saldo insuficiente.")
      }
    },
    
    # Método para consultar el saldo
    consultar_saldo = function() {
      return(private$saldo)
    }
  ),
  private = list(
    saldo = NULL
  )
)

Herencia:

La herencia es un mecanismo que permite crear nuevas clases a partir de clases existentes, heredando sus atributos y métodos. Esto facilita la reutilización de código y la creación de jerarquías de clases. En R6, la herencia se especifica con el argumento inherit de la función R6Class().

# Definir una clase "Estudiante" que hereda de "Persona"
Estudiante <- R6Class("Estudiante",
  inherit = Persona,
  public = list(
    carrera = NULL,
    
    # Constructor
    initialize = function(nombre, edad, carrera) {
      super$initialize(nombre, edad)
      self$carrera <- carrera
    },
    
    # Método para mostrar información del estudiante
    mostrar_info = function() {
      super$saludar()
      cat("Carrera:", self$carrera, "\n")
    }
  )
)

# Crear un objeto de la clase "Estudiante"
maria <- Estudiante$new(nombre = "Maria", edad = 20, carrera = "Ingeniería")

# Llamar al método mostrar_info()
maria$mostrar_info()
#> Hola, mi nombre es Maria y tengo 20 años.
#> Carrera: Ingeniería

En este ejemplo, la clase Estudiante hereda de la clase Persona. El constructor de Estudiante llama al constructor de la clase padre (super$initialize()) para inicializar los atributos heredados. El método mostrar_info() llama al método saludar() de la clase padre (super$saludar()) y luego muestra la información específica del estudiante.

Con R6, puedes crear clases y objetos con un alto grado de flexibilidad y control, lo que te permite aplicar la POO de forma efectiva en tus proyectos de análisis de datos.

4.4 Ejercicios

A continuación, encontrarás una serie de ejercicios con diferentes niveles de dificultad. Es hora de poner en práctica lo que has aprendido en este capítulo.

  1. Crea una expresión que represente la suma de dos variables a y b.
Solución
expresion <- quote(a + b)
  1. Crea una expresión que represente la multiplicación de dos variables x e y, y luego evalúala.
Solución
x <- 5
y <- 10
expresion <- quote(x * y)
eval(expresion)
#> [1] 50
  1. Crea un vector de números y usa la función map() para calcular el cuadrado de cada número.
Solución
numeros <- c(1, 2, 3, 4, 5)
cuadrados <- map(numeros, function(x) x^2)
cuadrados
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 4
#> 
#> [[3]]
#> [1] 9
#> 
#> [[4]]
#> [1] 16
#> 
#> [[5]]
#> [1] 25
  1. Crea un vector de números y usa la función filter() para obtener solo los números pares.
Solución
numeros <- c(1, 2, 3, 4, 5)
pares <- keep(numeros, ~ . %% 2 == 0)
pares
#> [1] 2 4
  1. Crea una función llamada crear_funcion_potencia() que reciba un número n como argumento y devuelva una función que eleve su argumento a la potencia n.
Solución
crear_funcion_potencia <- function(n) {
  function(x) x^n
}
  1. Crea un vector de números y usa la función reduce() para calcular el producto de todos los números.
Solución
numeros <- c(1, 2, 3, 4, 5)
producto <- reduce(numeros, `*`)
producto
#> [1] 120
  1. Crea una clase llamada Mascota con los atributos nombre, especie y edad, y los métodos presentarse() (que muestre el nombre, la especie y la edad de la mascota) y cumplir_años() (que incremente la edad de la mascota en 1).
Solución
library(R6)

Mascota <- R6Class("Mascota",
  public = list(
    nombre = NULL,
    especie = NULL,
    edad = NULL,
    
    initialize = function(nombre, especie, edad) {
      self$nombre <- nombre
      self$especie <- especie
      self$edad <- edad
    },
    
    presentarse = function() {
      cat("Hola, soy", self$nombre, ", un", self$especie, "de", self$edad, "años.\n")
    },
    
    cumplir_años = function() {
      self$edad <- self$edad + 1
    }
  )
)
  1. Crea una función llamada crear_funcion_suma_flexible() que reciba un número n como argumento y devuelva una función que sume n a la suma de todos los argumentos que se le pasen.
Solución
crear_funcion_suma_flexible <- function(n) {
  function(...) {
    sum(c(...)) + n
  }
}

# Pruebas
# Crear una función que sume 5 a cualquier conjunto de números
suma_5 <- crear_funcion_suma_flexible(5)

# Ejemplos de uso y verificación
suma_5(2, 3, 4)
#> [1] 14

suma_5(10, 20)
#> [1] 35

suma_5()
#> [1] 5
  1. Crea una función llamada crear_grafico_dinamico() que reciba un data frame, un tipo de gráfico (“dispersion”, “histograma” o “boxplot”), y una lista de opciones para el gráfico (como título, color, etiquetas, etc.). La función debe generar el gráfico especificado con las opciones dadas.
Solución
crear_grafico_dinamico <- function(datos, tipo_grafico, opciones) {
  # Crear la expresión del gráfico base
  if (tipo_grafico == "dispersion") {
    expresion <- quote(plot(datos[[opciones$var_x]], datos[[opciones$var_y]],
                            xlab = opciones$etiquetas_x, ylab = opciones$etiquetas_y,
                            main = opciones$titulo, col = opciones$color))
  } else if (tipo_grafico == "histograma") {
    expresion <- quote(hist(datos[[opciones$var_x]], 
                            main = opciones$titulo, 
                            xlab = opciones$etiquetas_x, col = opciones$color))
  } else if (tipo_grafico == "boxplot") {
    expresion <- quote(boxplot(datos[[opciones$var_x]], 
                               main = opciones$titulo, 
                               ylab = opciones$etiquetas_y, col = opciones$color))
  } else {
    stop("Tipo de gráfico inválido.")
  }
  
  # Evaluar la expresión base
  eval(expresion)
}

# Crear datos de ejemplo
datos <- data.frame(x = rnorm(100), y = rnorm(100))

# Pruebas
# Diagrama de dispersión
opciones_dispersion <- list(var_x = "x", var_y = "y",
                            titulo = "Diagrama de Dispersión",
                            etiquetas_x = "Variable X",
                            etiquetas_y = "Variable Y",
                            color = "blue")
crear_grafico_dinamico(datos, "dispersion", opciones_dispersion)

# Histograma
opciones_histograma <- list(var_x = "x",
                            titulo = "Histograma",
                            etiquetas_x = "Valores",
                            color = "green")
crear_grafico_dinamico(datos, "histograma", opciones_histograma)

# Boxplot
opciones_boxplot <- list(var_x = "y",
                         titulo = "Boxplot",
                         etiquetas_y = "Valores",
                         color = "red")
crear_grafico_dinamico(datos, "boxplot", opciones_boxplot)
  1. Crea una clase llamada Perro que herede de la clase Mascota (de ejercicios anteriores). La clase Perro debe tener un atributo adicional llamado raza y un método llamado ladrar().
Solución
Perro <- R6Class("Perro",
  inherit = Mascota,
  public = list(
    raza = NULL,
    
    initialize = function(nombre, edad, raza) {
      super$initialize(nombre, "perro", edad)
      self$raza <- raza
    },
    
    ladrar = function() {
      cat("Guau! Guau!\n")
    }
  )
)