Capítulo26 Funciones

El tema proviene de los siguientes sitios.

English: https://r4ds.had.co.nz/functions.html

Español: https://es.r4ds.hadley.nz/funciones.html


Fecha de la última revisión

## [1] "2026-09-09"

26.1 Temas:

  • ¿Cuándo escribir una función?
  • La anatomía de una función: nombre, argumentos y cuerpo
  • Cómo se le pone nombre a una función
  • Ejecución condicional: if y else
  • Argumentos y valores por defecto
  • Comprobar los argumentos con stopifnot()
  • Los puntos suspensivos ...
  • Valores de retorno
  • Lo que pasa dentro se queda dentro: el ámbito
  • Un vistazo a los vectores

En el capítulo de tidyverse avanzado verás funciones más sofisticadas que usan tidy evaluation (el operador {{ }}). Aquí construimos la base.


26.2 ¿Cuándo escribir una función?

Una función te permite darle un nombre a un bloque de código para poder reutilizarlo. La regla práctica es: si copiaste y pegaste el mismo código más de dos veces, escribe una función. Copiar y pegar es una de las fuentes de errores más comunes, porque es fácil olvidar cambiar el nombre de una variable en una de las copias.

Regla de tres: si copias y pegas el mismo bloque de código más de dos veces, conviértelo en una función. Así reduces errores y solo corriges el código en un lugar.

Observa este código que reescala tres columnas para que queden entre 0 y 1:

df <- data.frame(
  a = c(2, 5, 8, 1),
  b = c(10, 20, 30, 40),
  c = c(-5, 0, 5, 10)
)

df$a <- (df$a - min(df$a)) / (max(df$a) - min(df$a))
df$b <- (df$b - min(df$b)) / (max(df$b) - min(df$b))
df$c <- (df$c - min(df$c)) / (max(df$c) - min(df$c))
df
##           a         b         c
## 1 0.1428571 0.0000000 0.0000000
## 2 0.5714286 0.3333333 0.3333333
## 3 1.0000000 0.6666667 0.6666667
## 4 0.0000000 1.0000000 1.0000000

¿Notaste el patrón repetido? Eso es una señal clara de que conviene una función.


26.3 La anatomía de una función

R es un lenguaje de programación funcional: las funciones son “ciudadanos de primera clase” (se pueden guardar en variables y pasar a otras funciones). Heredó esta filosofía del lenguaje S y de Scheme, que inspiraron a sus creadores.

function(argumentos) { cuerpo }: crear tu propia función.

  • Estructura: nombre <- function(x) { ... }; se llama luego con nombre(valor).
  • Argumentos: pueden tener valores por defecto (function(x, recorte = 0.1)).
  • Retorno: devuelve el último valor evaluado, o el que indiques con return().

Toda función tiene tres partes:

  1. Un nombre (aquí, reescalar01).
  2. Los argumentos que van dentro de function( ) (aquí, x).
  3. El cuerpo, el código dentro de las llaves { }.
reescalar01 <- function(x) {
  rango <- range(x, na.rm = TRUE)
  (x - rango[1]) / (rango[2] - rango[1])
}

# Probar la función con un ejemplo cuya respuesta ya conocemos
reescalar01(c(0, 5, 10))
## [1] 0.0 0.5 1.0

Ahora el código repetido se reduce a esto, mucho más fácil de leer y de corregir:

26.3.1 Cómo se le pone nombre a una función

El nombre de una función es parte del código, no una etiqueta: se va a leer muchas más veces de las que se escribe. Tres reglas resuelven casi todo:

  1. Un verbo, o algo que describa lo que devuelve. Las funciones hacen cosas: reescalar01(), contar_especies(), calcular_tasa(). Los sustantivos se reservan para los objetos.
  2. En minúsculas y con guion bajo (snake_case), como el resto del tidyverse: media_recortada, no MediaRecortada ni media.recortada. El punto tiene otro significado en R y conviene no usarlo.
  3. Nunca uses un nombre que R ya usa. Si defines tu propia mean(), sum(), c(), data() o t(), R usará la tuya a partir de ese momento y el resto del análisis se romperá de maneras difíciles de rastrear.

Para saber si un nombre está ocupado, escríbelo en la consola y presiona Tab, o prueba exists("mi_nombre"). Si devuelve TRUE, busca otro. Es el mismo problema del object of type 'closure' del capítulo de errores comunes, visto desde el otro lado.

df <- data.frame(
  a = c(2, 5, 8, 1),
  b = c(10, 20, 30, 40),
  c = c(-5, 0, 5, 10)
)

df$a <- reescalar01(df$a)
df$b <- reescalar01(df$b)
df$c <- reescalar01(df$c)
df
##           a         b         c
## 1 0.1428571 0.0000000 0.0000000
## 2 0.5714286 0.3333333 0.3333333
## 3 1.0000000 0.6666667 0.6666667
## 4 0.0000000 1.0000000 1.0000000

Una función siempre se debe probar con valores cuya respuesta ya conoces antes de confiar en ella.


26.4 Ejecución condicional: if y else

Dentro de una función puedes tomar decisiones con if y else.

signo <- function(x) {
  if (x > 0) {
    "positivo"
  } else if (x < 0) {
    "negativo"
  } else {
    "cero"
  }
}

signo(7)
## [1] "positivo"
signo(-3)
## [1] "negativo"
signo(0)
## [1] "cero"

26.5 Argumentos y valores por defecto

Los argumentos pueden tener un valor por defecto, que se usa cuando quien llama la función no especifica ese argumento.

# Calcular la media recortada; por defecto recorta un 10%
media_recortada <- function(x, recorte = 0.1) {
  mean(x, trim = recorte, na.rm = TRUE)
}

valores <- c(1, 2, 3, 4, 5, 100)  # el 100 es un valor atípico
media_recortada(valores)              # usa recorte = 0.1
## [1] 19.16667
media_recortada(valores, recorte = 0.2)
## [1] 3.5
mean(valores)                          # comparación: la media normal
## [1] 19.16667

26.6 Comprobar los argumentos

Una función escrita por ti la va a usar alguien que no la escribió, y ese alguien casi siempre eres tú dentro de seis meses. Conviene que falle rápido y claro cuando le pasan algo que no esperaba, en vez de devolver un resultado raro.

stopifnot(): comprobar que los argumentos son lo que deben ser.

  • stopifnot(is.numeric(x)): si la condición no se cumple, detiene la función con un mensaje que nombra la condición que falló.
  • Varias a la vez: stopifnot(is.numeric(x), length(x) > 0).
  • Con mensaje propio: stopifnot("x tiene que ser numérico" = is.numeric(x)).
reescalar_seguro <- function(x) {
  stopifnot("x tiene que ser numérico" = is.numeric(x),
            "x no puede estar vacío"   = length(x) > 0)
  rango <- range(x, na.rm = TRUE)
  (x - rango[1]) / (rango[2] - rango[1])
}

reescalar_seguro(c(0, 5, 10))
## [1] 0.0 0.5 1.0
reescalar_seguro(c("a", "b"))
## Error in `reescalar_seguro()`:
## ! x tiene que ser numérico

No hace falta comprobarlo todo. Comprueba lo que, si viene mal, produciría un resultado equivocado en silencio. Si viniera mal y diera un error claro por su cuenta, el stopifnot() sobra.


26.7 Los puntos suspensivos ...

... significa “cualquier otro argumento que me pasen, se lo entrego tal cual a la función de adentro”. Es lo que permite que tu función acepte los argumentos de otra sin tener que copiarlos todos.

promedio_limpio <- function(x, ...) {
  mean(x, na.rm = TRUE, ...)
}

valores <- c(1, 2, 3, 4, 5, 100, NA)
promedio_limpio(valores)
## [1] 19.16667
promedio_limpio(valores, trim = 0.2)   # trim viaja hasta mean()
## [1] 3.5

... tiene un precio: si escribes mal el nombre de un argumento, no hay error. promedio_limpio(valores, trimm = 0.2) se traga el trimm sin decir nada y devuelve el promedio sin recortar. Es cómodo y silencioso, en el mal sentido.


26.8 Valores de retorno

Una función en R devuelve el valor de la última línea de su cuerpo. También puedes usar return() para devolver un valor antes del final, por ejemplo para salir temprano en un caso especial.

coef_variacion <- function(x) {
  if (mean(x, na.rm = TRUE) == 0) {
    return(NA)   # evitar división por cero
  }
  sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE)
}

coef_variacion(c(2, 4, 4, 4, 5, 5, 7, 9))
## [1] 0.427618
coef_variacion(c(-5, 0, 5))   # la media es 0 -> NA
## [1] NA

26.9 Lo que pasa dentro se queda dentro

Las variables que creas dentro de una función viven solo dentro de ella. Cuando la función termina, desaparecen, y nada de lo que pasó ahí dentro afecta a tu sesión.

rango <- "yo vivo fuera"

reescalar01(c(0, 5, 10))   # por dentro crea su propio 'rango'
## [1] 0.0 0.5 1.0
rango                      # el de afuera no se tocó
## [1] "yo vivo fuera"

Eso es una ventaja, no una limitación: significa que puedes usar una función escrita por otra persona sin miedo a que te pise tus objetos. Y significa también que una función solo se comunica por dos puertas: los argumentos que entran y el valor que devuelve.

Al revés no es simétrico: una función sí puede leer los objetos de fuera si no los encuentra dentro. Eso hace que una función pueda parecer que funciona en tu sesión y fallar en la de otra persona, porque dependía en secreto de un objeto que tú tenías creado. La regla: todo lo que la función necesite tiene que entrar por un argumento.


26.10 Un vistazo a los vectores

Las funciones trabajan sobre vectores. Conocer los tipos básicos de vectores atómicos explica muchos “comportamientos raros” de R.

lgl <- c(TRUE, FALSE, NA)       # lógico
int <- c(1L, 5L, 10L)           # entero
dbl <- c(1.5, 2.7, 3.14)        # doble (números reales)
chr <- c("a", "b", "c")         # cadena de caracteres

typeof(lgl)
## [1] "logical"
typeof(int)
## [1] "integer"
typeof(dbl)
## [1] "double"
typeof(chr)
## [1] "character"

Coerción: cuando mezclas tipos, R convierte todo al tipo “más flexible”.

c(1, 2, "tres")     # todo se convierte en texto
## [1] "1"    "2"    "tres"
c(TRUE, 1L, 2.5)    # lógico -> entero -> doble
## [1] 1.0 1.0 2.5

Valores faltantes (NA): casi cualquier operación con NA da NA. Por eso muchas funciones tienen el argumento na.rm = TRUE.

Si olvidas na.rm = TRUE, basta con un solo NA en los datos para que mean(), sd() y muchas otras funciones devuelvan NA sin avisarte. Encontrarás más errores frecuentes y cómo resolverlos en el capítulo Errores comunes (14).

x <- c(1, 2, NA, 4)
mean(x)                # NA
## [1] NA
mean(x, na.rm = TRUE)  # 2.333...
## [1] 2.333333
is.na(x)               # ¿cuáles son NA?
## [1] FALSE FALSE  TRUE FALSE

26.11 ¿Cuándo no escribir una función?

La regla de tres es buena, pero tiene excepciones. No conviene envolver en una función:

  • una sola línea que ya se lee bien. function(x) mean(x) no aporta nada;
  • algo que ya existe. Antes de escribirla, busca: casi siempre alguien la escribió antes y mejor;
  • un cálculo que solo vas a hacer una vez, aunque sea largo. Una función que se usa una sola vez añade un nivel de indirección sin ahorrar nada;
  • un paso de una cadena de dplyr. Para eso están mutate() y summarise(), que ya trabajan sobre columnas enteras.

26.12 Ejercicios del libro R4DS

Hacer los ejercicios de las secciones 19.2 y 19.4 del libro en español.


26.13 Ejercicios del capítulo

Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres cosas: la función, una prueba con valores cuya respuesta ya conoces, y una explicación en palabras. La prueba no es opcional: una función sin probar no sirve de nada, y en este capítulo vale puntos. Los ejercicios usan millas y paises del paquete datos, no el df de tres columnas del capítulo. La hoja completa para entregar está en Ejercicios/Ejercicios_Capitulo_26_funciones.Rmd.

26.13.1 Ejercicio 1. Reescalar de verdad

Usa reescalar01() del capítulo sobre las columnas ciudad y autopista de millas. Después pruébala con una columna constante, por ejemplo rep(5, 10).

Verificación: en las dos columnas de millas, el mínimo del resultado tiene que ser 0 y el máximo 1. Con la columna constante sale NaN en todas las posiciones.

En palabras: ¿de dónde sale ese NaN? Escribe la operación que lo produce. ¿Debería la función avisar en ese caso, o está bien que devuelva NaN? Justifica tu respuesta.

# Escribe tu código aquí

26.13.2 Ejercicio 2. Una función que devuelve una tabla

Escribe resumen_numerico(x), que reciba un vector numérico y devuelva un tibble de una fila con cinco columnas: n, media, sd, mediana e IQR, todas calculadas ignorando los NA.

Úsala con millas$autopista y después, dentro de un group_by() por clase, con reframe() o con summarise().

Verificación: para millas completa, n debe dar 234. Por clase, la suma de los n también.

En palabras: ¿por qué conviene que devuelva un tibble y no un vector con nombres? Piensa en qué puedes hacer después con cada uno.

# Escribe tu código aquí

26.13.3 Ejercicio 3. Valores por defecto

Escribe es_atipico(x, k = 1.5), que devuelva TRUE para los valores que quedan a más de k rangos intercuartiles fuera de la caja, que es la misma regla del diagrama de caja.

Aplícala a millas$autopista con k = 1.5 y con k = 3.

Verificación: con k = 3 tiene que marcar menos valores que con k = 1.5, o los mismos, nunca más.

En palabras: ¿por qué el valor por defecto es 1.5 y no otro número? Y una pregunta más importante: ¿un valor marcado como atípico es un error del dato? Mira cuáles vehículos son.

# Escribe tu código aquí

26.13.4 Ejercicio 4. Que falle rápido y claro

Añádele a resumen_numerico() un stopifnot() que compruebe dos cosas: que x es numérico y que tiene al menos un valor no faltante. Ponle a cada comprobación un mensaje en español.

Pruébala con millas$clase (que es texto) y con c(NA, NA), las dos en un bloque con error=TRUE.

Verificación: las dos llamadas malas se detienen con tu mensaje, no con un mensaje genérico de R.

En palabras: sin el stopifnot(), ¿qué habría devuelto la función con millas$clase? Pruébalo. ¿Un error o un resultado sin sentido? Explica por qué eso justifica las tres líneas de más.

# Escribe tu código aquí

26.13.5 Ejercicio 5. Lo que pasa dentro

Escribe una función que cree dentro una variable llamada resultado. Antes de llamarla, crea en tu sesión otra variable con el mismo nombre y un valor distinto. Llama a la función y vuelve a mirar la de fuera.

Después escribe una segunda función que use una variable que no recibe como argumento y que solo existe fuera. Compruébala, bórrala con rm() y vuelve a llamarla.

Verificación: la primera función no toca la variable de fuera. La segunda funciona hasta que borras la variable, y entonces da object ... not found.

En palabras: la segunda función parecía correcta y no lo era. Explica por qué es un problema si le pasas tu script a otra persona.

# Escribe tu código aquí

26.13.6 Ejercicio 6. Los puntos suspensivos

Escribe promedio_por_grupo(datos, columna, ...) que calcule el promedio de una columna por clase en millas, pasando los ... a mean().

Llámala una vez normal y otra con trim = 0.1. Después llámala con trimm = 0.1, con dos emes.

Verificación: las dos primeras dan resultados distintos entre sí; la tercera da exactamente lo mismo que la primera, y sin ningún aviso.

En palabras: eso último es el precio de .... Explica qué pasó con el trimm. ¿Qué tipo de error del capítulo de errores comunes se parece más a este?

# Escribe tu código aquí

26.13.7 Ejercicio 7 (BONO). Una función que devuelve una gráfica

Escribe grafica_consumo(datos, clase_elegida) que filtre millas por la clase que le pasen y devuelva un ggplot de cilindrada contra autopista, con el título puesto automáticamente con el nombre de la clase.

Verificación: la función tiene que devolver el objeto gráfico, no dibujarlo por dentro. Compruébalo guardándolo en una variable: la gráfica no debe aparecer hasta que escribas el nombre de la variable.

En palabras: ¿qué ganas devolviendo el objeto en vez de dibujarlo dentro? Piensa en poder añadirle una capa después con +.

# Escribe tu código aquí