<- function(arg1, arg2, ...) {
name_fun
código a ejecutar
return(var_salida)
}
Funciones en R
Cuadernos prácticos de Software II del Grado en Ciencia de Datos Aplicada (curso 2024-2025)
1 Funciones en R
Cuando programamos no solo podemos usar funciones predeterminadas que vienen ya cargadas en paquetes, además podemos crear nuestras propias funciones para automatizar tareas o poder incluso exportarlo para que otras personas las usen.
1.1 Estructura básica
¿Cómo crear nuestra propia función? Veamos su esquema básico:
Nombre: por ejemplo
name_fun
(sin espacios ni caracteres extraños). Al nombre le asignamos la palabra reservadafunction()
.Definir argumentos de entrada (dentro de
function()
).Cuerpo de la función dentro de
{ }
.Finalizamos la función con los argumentos de salida con
return()
.
Si te fijas en el código anterior, arg1, arg2, ...
serán los argumentos de entrada, los argumentos que toma la función para ejecutar el código que tiene dentro.
Además hay otra parte que normalemnte siempre aparecerá en la función (no es necesario pero suele ser habitual que esté): return(var_salida)
, de manera que dentro de return()
se introducirán los argumentos de salida, lo que queremos que devuelva la función
Todas las variables que definamos dentro de la función son variables LOCALES: solo existirán dentro de la función salvo que especifiquemos lo contrario.
1.1.1 Ejemplo: cálcular área
Veamos un ejemplo muy simple de función para calcular el área de un rectángulo.
Dado que el área de un rectángulo se calcula como el producto de sus lados, necesitaremos precisamente eso, sus lados: esos serán los argumentos de entrada y el valor a devolver será justo su área (\(lado_1 * lado_2\)).
# Definición del nombre de función y argumentos de entrada
<- function(lado_1, lado_2) {
calcular_area
<- lado_1 * lado_2
area return(area)
}
También podemos hacer una definición directa de las variables sin almacenar por el camino.
# Definición del nombre de función y argumentos de entrada
<- function(lado_1, lado_2) {
calcular_area
return(lado_1 * lado_2)
}
¿Cómo aplicar la función? Simplemente usando el nombre que le hemos dado y dentro de los paréntesis, separados por comas, los argumentos de entrada.
calcular_area(5, 3) # área de un rectángulo 5 x 3
[1] 15
calcular_area(1, 5) # área de un rectángulo 1 x 5
[1] 5
Aunque no sea necesario, es recomendable hacer explícita la llamada de los argumentos, especificando en el código qué valor es para cada argumento para que no dependa de su orden, haciendo el código más legible
calcular_area(lado_1 = 5, lado_2 = 3) # área de un rectángulo 5 x 3
[1] 15
calcular_area(lado_2 = 3, lado_1 = 5) # área de un rectángulo 5 x 3
[1] 15
1.2 Argumentos por defecto
Imagina ahora que nos damos cuenta que el 90% de las veces usamos dicha función para calcular por defecto el área de un cuadrado (es decir, solo necesitamos un lado). Para ello, podemos definir argumentos por defecto en la función: tomarán dicho valor salvo que le asignemos otro.
¿Por qué no asignar lado_2 = lado_1
por defecto, para ahorrar líneas de código y tiempo?
<- function(lado_1, lado_2 = lado_1) {
calcular_area
# Cuerpo de la función
<- lado_1 * lado_2
area
# Resultado que devolvemos
return(area)
}
Ahora por defecto el segundo lado será igual al primero (si se lo añadimos usará ambos).
calcular_area(lado_1 = 5) # cuadrado
[1] 25
calcular_area(lado_1 = 5, lado_2 = 7) # rectángulo
[1] 35
1.3 Salida múltiple
Compliquemos un poco la función y añadamos en la salida los valores de cada lado, etiquetados como lado_1
y lado_2
, empaquetando la salida en una vector.
Podemos complicar un poco más la salida añadiendo una cuarta variable que nos diga, en función de los argumentos, si rectángulo o cuadrado, teniendo que añadir en la salida una variable que de tipo caracter (o lógica).
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
calcular_area <- function(lado_1, lado_2 = lado_1) {
# Cuerpo de la función
area <- lado_1 * lado_2
# Resultado
return(c("area" = area, "lado_1" = lado_1, "lado_2" = lado_2,
"tipo" = if_else(lado_1 == lado_2, "cuadrado", "rectángulo")))
}
calcular_area(5, 3)
area lado_1 lado_2 tipo
"15" "5" "3" "rectángulo"
Pero fíjate que tenemos un problema: al intentar juntar números y texto, lo convierte todo a números. Podríamos guardarlo todo en un tibble()
como hemos aprendido o en un objeto conocido en R
como listas
1.4 Breve introducción a listas
Veamos un pequeño resumen de los datos que ya conocemos:
vectores: colección de elementos de igual tipo. Pueden ser números, caracteres o valores lógicos, entre otros.
matrices: colección BIDIMENSIONAL de elementos de igual tipo e igual longitud.
data.frame / tibble: colección BIDIMENSIONAL de elementos de igual longitud pero de cualquier tipo.
Las listas serán colecciones de variables de diferente tipo y diferente longitud, con estructuras totalmente heterógeneas (incluso una lista puede tener dentro a su vez otra lista).
Vamos a crear nuestra primera lista con list()
con tres elementos: el nombre de nuestros padres/madres, nuestro lugar de nacimiento y edades de nuestros hermanos.
<- c("Paloma", "Gregorio")
var_1 <- "Madrid"
var_2 <- c(25, 30, 26)
var_3
<- list("progenitores" = var_1, "lugar_nac" = var_2, "edad_hermanos" = var_3)
lista lista
$progenitores
[1] "Paloma" "Gregorio"
$lugar_nac
[1] "Madrid"
$edad_hermanos
[1] 25 30 26
Si observas el objeto que hemos definido como lista, su longitud del es de 3 ya que tenemos guardados tres elementos: un vector de caracteres (de longitud 2), un caracter (vector de longitud 1), y un vector de números (de longitud 3)
length(lista)
[1] 3
En una lista solemos tener guardados elementos de distinto tipo (algo que ya podíamos hacer) pero, además, de longitudes dispares.
dim(lista) # devolverá NULL al no tener dos dimensiones
NULL
class(lista) # de tipo lista
[1] "list"
Si los juntásemos con un tibble()
, al tener distinta longitud, obtendríamos un error.
library(tibble)
tibble("progenitores" = var_1, "lugar_nac" = va_2, "edad_hermanos" = var_3)
Error in eval_tidy(xs[[j]], mask): object 'va_2' not found
1.4.1 Operaciones básicas
La más básica es poder acceder por índice a sus elementos, con el operador [[i]]
, accediendo al elemento i-ésimo de la lista.
1]] lista[[
[1] "Paloma" "Gregorio"
También podemos acceder por nombre con $nombre_elemento
.
$progenitores lista
[1] "Paloma" "Gregorio"
En contraposición, el corchete simple nos permite acceder a varios elementos a la vez
# Varios elementos
1:2] lista[
$progenitores
[1] "Paloma" "Gregorio"
$lugar_nac
[1] "Madrid"
1.4.2 Salida múltiple: listas
Así haciendo uso de listas podemos hacer que la función devuelva de manera conjunta múltiples argumentos (sin importar su tipo ni longitud)
# Definición del nombre de función y argumentos de entrada
calcular_area <- function(lado_1, lado_2 = lado_1) {
# Cuerpo de la función
area <- lado_1 * lado_2
# Resultado
return(list("area" = area, "lado_1" = lado_1, "lado_2" = lado_2,
"tipo" = if_else(lado_1 == lado_2, "cuadrado", "rectángulo")))
}
calcular_area(5, 3)
$area
[1] 15
$lado_1
[1] 5
$lado_2
[1] 3
$tipo
[1] "rectángulo"
Antes nos daba igual el orden de los argumentos pero ahora el orden de los argumentos de entrada importa, ya que en la salida incluimos lado_1
y lado_2
.
Como se comentaba, altamente recomendable hacer la llamada a la función indicando explícitamente los argumentos para mejorar legibilidad e interpretabilidad.
# Equivalente a calcular_area(5, 3)
calcular_area(lado_1 = 5, lado_2 = 3)
$area
[1] 15
$lado_1
[1] 5
$lado_2
[1] 3
$tipo
[1] "rectángulo"
1.5 Variables locales vs globales
Un aspecto importante sobre el que reflexionar con las funciones: ¿qué sucede si nombramos a una variable dentro de una función a la que se nos ha olvidado asignar un valor dentro de la misma?
Debemos ser cautos al usar funciones en R
, ya que debido a la «regla lexicográfica», si una variable no se define dentro de la función, R
buscará dicha variable en el entorno de variables.
<- 1
x <- function() {
funcion_ejemplo
print(x) # No devuelve nada, solo realiza la acción
}funcion_ejemplo()
[1] 1
Si una variable ya está definida fuera de la función (entorno global), y además es usada dentro de cambiando su valor, el valor solo cambia dentro pero no en el entorno global.
<- 1
x <- function() {
funcion_ejemplo
<- 2
x print(x) # lo que vale dentro
}
# lo que vale dentro
funcion_ejemplo() #<<
[1] 2
# lo que vale fuera
print(x) #<<
[1] 1
Si queremos que además de cambiar localmente lo haga globalmente deberemos usar la doble asignación (<<-
).
<- 1
x <- 2
y <- function() {
funcion_ejemplo
# no cambia globalmente, solo localmente
<- 3
x # cambia globalmente
<<- 0 #<<
y
print(x)
print(y)
}
funcion_ejemplo() # lo que vale dentro
[1] 3
[1] 0
# lo que vale fuera x
[1] 1
# lo que vale fuera y
[1] 0
1.6 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Modifica el código inferior para definir una función llamada funcion_suma
, de forma que dados dos elementos, devuelve su suma.
<- function(x, y) {
nombre <- # código a ejecutar
suma return()
}# Aplicamos la función
suma(3, 7)
Code
<- function(x, y) {
funcion_suma <- x + y
suma return(suma)
}funcion_suma(3, 7)
📝 Modifica el código inferior para definir una función llamada funcion_producto
, de forma que dados dos elementos, devuelve su producto, pero que por defecto calcule el cuadrado
<- function(x, y) {
nombre <- # código de la multiplicación
producto return()
}producto(3)
producto(3, -7)
Code
<- function(x, y = x) {
funcion_producto <- x * y
producto return(producto)
}funcion_producto(3)
funcion_producto(3, -7)
📝 Define una función llamada igualdad_nombres
que, dados dos nombres, nos diga si son iguales o no. Hazlo considerando importantes las mayúsculas, y sin que importen las mayúsculas. Usa el paquete {stringr}
.
Code
# Distinguiendo mayúsculas
<- function(persona_1, persona_2) {
igualdad_nombres return(persona_1 == persona_2)
}igualdad_nombres("Javi", "javi")
igualdad_nombres("Javi", "Lucía")
# Sin importar mayúsculas
<- function(persona_1, persona_2) {
igualdad_nombres return(toupper(persona_1) == toupper(persona_2))
}igualdad_nombres("Javi", "javi")
igualdad_nombres("Javi", "Lucía")
📝 Crea una función llamada calculo_IMC
que, dados dos argumentos (peso y estatura en metros) y un nombre, devuelva una lista con el IMC (\(peso/(estatura_m^2)\)) y el nombre.
Code
<- function(nombre, peso, estatura) {
calculo_IMC
return(list("nombre" = nombre, "IMC" = peso/(estatura^2)))
}
📝 Repite el ejercicio anterior pero con otro argumento opcional que se llame unidades (por defecto, unidades = "metros"
). Desarrolla la función de forma que haga lo correcto si unidades = "metros"
y si unidades = "centímetros"
.
Code
<- function(nombre, peso, estatura, unidades = "metros") {
calculo_IMC
return(list("nombre" = nombre,
"IMC" = peso/(if_else(unidades == "metros", estatura, estatura/100)^2)))
}
📝 Crea un tibble ficticio de 7 personas, con tres variables (inventa nombre, y simula peso, estatura en centímetros), y aplica la función definida de forma que obtengamos una cuarta columna con su IMC.
Code
<-
datos tibble("nombres" = c("javi", "sandra", "laura",
"ana", "carlos", "leo", NA),
"peso" = rnorm(n = 7, mean = 70, sd = 1),
"estatura" = rnorm(n = 7, mean = 168, sd = 5))
|>
datos mutate(IMC = calculo_IMC(nombres, peso, estatura, unidades = "centímetros")$IMC)
📝 Crea una función llamada atajo
que tenga dos argumentos numéricos x
e y
. Si ambos son iguales, debes devolver "iguales"
y hacer que la función acaba automáticamente (piensa cuándo una función sale). OJO: x
e y
podrían ser vectores. Si son distintos (de igual de longitud) calcula la proporción de elementos diferentes. Si son distintos (por ser distinta longitud), devuelve los elementos que no sean comunes.
Code
<- function(x, y) {
atajo
if (all(x == y) & length(x) == length(y)) { return("iguales") }
else {
if (length(x) == length(y)) {
<- sum(x != y) / length(x)
n_diff return(n_diff)
else {
}
<- unique(c(setdiff(x, y), setdiff(y, x)))
diff_elem return(diff_elem)
}
} }
2 🐣 Caso práctico: conversor de temperaturas
Para practicar con el uso de funciones vamos a crear un conversor de temperaturas. Empecemos por lo sencillo. Intenta conceptuar la idea antes en un papel.
2.1 Pregunta 1
Define una función llamada
celsius_to_kelvin
que, dada una temperatura en Celsius (por ejemplo,temp
como argumento) la convierta a Kelvin según la fórmula de conversión inferior. Tras definir la función aplícala a un vector de temperaturas.
\[K = °C + 273.15\]
Code
# definimos nombre de la función y argumentos
<- function(temp) {
celsius_to_kelvin
# convertimos
<- temp + 273.15
kelvin
# devolvemos
return(kelvin)
}
<- c(-15, -3, 0, 15, 27.5)
x celsius_to_kelvin(x)
2.2 Pregunta 2
Crea la función contraria
kelvin_to_celsius
y aplícala a otro vector de temperaturas. Tendrás que asegurarte que la temperatura en Kelvin no toma valores negativos (ya que es una escala absoluta). En caso de que no se cumpla, devolverNA
.
Code
# definimos nombre de la función y argumentos
<- function(temp) {
kelvin_to_celsius
# si es negativa en Kelvin, paramos y devolvemos ausente
# en caso contrario, convertimos
<- if_else(temp < 0, NA, temp - 273.15)
celsius
# Piensa porque no lo hemos hecho con un if (...) else (...)
# devolvemos
return(celsius)
}
<- c(0, 250, 300, 350)
y kelvin_to_celsius(y)
2.3 Pregunta 3
Crea una función conjunta
conversor_temp
que tenga dos argumentos: temperatura y un argumento de texto que nos diga si es kelvin o celsius (y que por defecta la temperatura de entrada sea Celsius). La función debe usar ese string para decidir en que dirección convierte (controla que en el argumento de texto no haya una opción distinta a las dos permitidas; en caso contrario, devolver error usando el comando stop(“mensaje de error…”)). Aplícala a los vectores anteriores y chequea que da lo mismo.
Code
# definimos nombre de la función y argumentos
# por defecto, unidades en celsius
<- function(temp, unidades = "celsius") {
conversor_temp
# chequeamos que unidades es correcto
# dentro de los valroes permitidos
if (unidades %in% c("celsius", "kelvin")) {
if (unidades == "celsius") {
<- celsius_to_kelvin(temp)
temp_out
else {
}
<- kelvin_to_celsius(temp)
temp_out
}
else {
}
# en caso contrario paramos la función con un mensaje de error
stop("Error: solo se permiten como unidades 'celsius' o 'kelvin'")
}
# devolvemos
return(temp_out)
}
# Fíjate que no hemos usado `if_else()` porque el número de elementos
# a evaluar en la condición debe ser igual al número de elementos que # devuelve, al hacerlo de manera vectorial.
conversor_temp(x)
conversor_temp(y, unidades = "kelvin")
2.4 Pregunta 4
Repite la función anterior pero sin que importe si unidades está en mayúscula o minúscula
conversor_temp(y, unidades = "Kelvin")
Error in conversor_temp(y, unidades = "Kelvin"): could not find function "conversor_temp"
Code
# definimos nombre de la función y argumentos
# por defecto, unidades en celsius
library(stringr)
<- function(temp, unidades = "celsius") {
conversor_temp
# usamos str_to_lower para pasarlo todo a minúscula
if (str_to_lower(unidades) %in% c("celsius", "kelvin")) {
if (unidades == "celsius") {
<- celsius_to_kelvin(temp)
temp_out
else {
}
<- kelvin_to_celsius(temp)
temp_out
}
else {
}
# en caso contrario paramos la función con un mensaje de error
stop("Error: solo se permiten como unidades 'celsius' o 'kelvin'")
}
# devolvemos
return(temp_out)
}
conversor_temp(y, unidades = "Kelvin")
2.5 Pregunta 5
Repite todo el proceso anterior creando
conversor_temp2
pero para convertir entre Celsius y Fahrenheit siguiendo la fórmula inferior
\[ºC = (ºF − 32) * \frac{5}{9}, \quad ºF = 32 + ºC * \frac{9}{5}\]
Code
# definimos las funciones de celsius a fahr y viceversa
<- function(temp) {
celsius_to_fahr
# convertimos
<- 32 + temp * (9/5)
fahr
# devolvemos
return(fahr)
}celsius_to_fahr(x)
<- function(temp) {
fahr_to_celsius
# convertimos
<- (temp - 32) * (5/9)
celsius
# devolvemos
return(celsius)
}
<- c(40, 60, 80, 100)
z fahr_to_celsius(z)
# definimos nombre de la función y argumentos
# por defecto, unidades en celsius
<- function(temp, unidades = "celsius") {
conversor_temp2
# usamos str_to_lower para pasarlo todo a minúscula
if (str_to_lower(unidades) %in% c("celsius", "fahr")) {
if (unidades == "celsius") {
<- celsius_to_fahr(temp)
temp_out
else {
}
<- fahr_to_celsius(temp)
temp_out
}
else {
}
# en caso contrario paramos la función con un mensaje de error
stop("Error: solo se permiten como unidades 'celsius' o 'fahr'")
}
# devolvemos
return(temp_out)
}
conversor_temp2(x)
conversor_temp2(z, unidades = "fahr")
2.6 Pregunta 6
Para acabar, crea la superfunción
conversor_temp_total
que permita como argumento de entrada una temperatura en alguna de las 3 unidades, un texto que indique en qué unidades viene y otro que indique en qué unidades se quiere sacar. Por defecto que convierta de celsius a kelvin.
Code
<-
conversor_temp_total function(temp, unidades_entrada = "celsius",
unidades_salida = "kelvin") {
if (str_to_lower(unidades_entrada) %in% c("celsius", "fahr", "kelvin") &
str_to_lower(unidades_salida) %in% c("celsius", "fahr", "kelvin")) {
# Si las de salida son iguales que las de entrada, no hacemos nada
if (unidades_entrada == unidades_salida) {
return(temp)
}
else if (unidades_entrada == "celsius") {
if (unidades_salida == "kelvin") {
<- celsius_to_kelvin(temp)
temp_out
# si no es kelvin (ni celsius porque entrada es distinto a salida)
# solo queda una funcion (que sea fahr)
else {
}
<- celsius_to_fahr(temp)
temp_out
}
else if (unidades_entrada == "kelvin") {
}
if (unidades_salida == "celsius") {
<- kelvin_to_celsius(temp)
temp_out
else {
}
<- celsius_to_fahr(kelvin_to_celsius(temp))
temp_out
}
else {
}
if (unidades_salida == "celsius") {
<- fahr_to_celsius(temp)
temp_out
else {
}
<- celsius_to_kelvin(fahr_to_celsius(temp))
temp_out
}
}
else {
}
# en caso contrario paramos la función con un mensaje de error
stop("Error: solo se permiten como unidades de entrada/salida 'celsius', 'kelvin' o 'fahr'")
}
# devolvemos
return(temp_out)
}
conversor_temp_total(x, unidades_entrada = "celsius",
unidades_salida = "celsius")
conversor_temp_total(y, unidades_entrada = "kelvin",
unidades_salida = "kelvin")
conversor_temp_total(z, unidades_entrada = "fahr",
unidades_salida = "fahr")
conversor_temp_total(x, unidades_entrada = "celsius",
unidades_salida = "kelvin")
conversor_temp_total(y, unidades_entrada = "kelvin",
unidades_salida = "celsius")
conversor_temp_total(x, unidades_entrada = "celsius",
unidades_salida = "fahr")
conversor_temp_total(z, unidades_entrada = "fahr",
unidades_salida = "celsius")
conversor_temp_total(z, unidades_entrada = "fahr",
unidades_salida = "celsius")
conversor_temp_total(conversor_temp_total(z, unidades_entrada = "fahr",
unidades_salida = "kelvin"),
unidades_entrad = "kelvin",
unidades_salida = "celsius")