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:
ifyelse - 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 connombre(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:
- Un nombre (aquí,
reescalar01). - Los argumentos que van dentro de
function( )(aquí,x). - 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:
- 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. - En minúsculas y con guion bajo (
snake_case), como el resto del tidyverse:media_recortada, noMediaRecortadanimedia.recortada. El punto tiene otro significado en R y conviene no usarlo. - Nunca uses un nombre que R ya usa. Si defines tu propia
mean(),sum(),c(),data()ot(), 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"
## [1] "negativo"
## [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
## [1] 3.5
## [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
## 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
## [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
## [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.
## [1] 0.0 0.5 1.0
## [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"
## [1] "integer"
## [1] "double"
## [1] "character"
Coerción: cuando mezclas tipos, R convierte todo al tipo “más flexible”.
## [1] "1" "2" "tres"
## [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).
## [1] NA
## [1] 2.333333
## [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ánmutate()ysummarise(), 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.
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.
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.
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.
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.
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?
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 +.