Tracks
/
R
R
/
Ejercicios
/
Transformación de coordenadas
Transformación de coordenadas

Transformación de coordenadas

Ejercicio de aprendizaje

Introducción

Las funciones se presentaron en el concepto Fundamentos, con ejemplos como este:

squareit <- function(x) {
    x * x
}

squareit(3)
#> [1] 9

# shorter form
squareit_short <- function(x) x ^ 2

Si miramos con más atención la definición de squareit, podemos identificar varias partes:

  • La función recibe un argumento formal x.
  • Hay un cuerpo de función, normalmente entre llaves { }.
  • Una vez creado el objeto de función, se asigna a una variable squareit.

En R, una función es un objeto de primera clase, igual que los números y los strings. Por lo tanto, squareit <- function... es una asignación que, sintácticamente, es igual que x <- 42.

Argumentos

R no distingue claramente entre argumentos posicionales y argumentos con nombre, a diferencia de otros lenguajes de scripting como Python y Julia.

Las llamadas a funciones pueden pasar valores de forma posicional o por nombre. Esta última opción es útil para funciones complejas con muchos argumentos, en las que es difícil recordar su orden.

f <- function(x, y) x / y

# call positionally
f(4, 2)
#> [1] 2

# call by name
f(y = 2, x = 4)
#> [1] 2

Argumentos opcionales

Los valores predeterminados de los argumentos se pueden especificar en la definición de la función, pero deben ir después de todos los argumentos que no tienen un valor predeterminado.

Después podemos elegir si aceptamos el valor predeterminado o lo reemplazamos.

g <- function(x, y = 2) x / y

# default y value
g(6)
#> [1] 3

# explicit y value
g(6, 3)
#> [1] 2

Argumentos adicionales

Para aceptar una cantidad arbitraria de argumentos adicionales, usa un ... (puntos suspensivos) en la definición. Es posible convertir cualquier valor extra de la llamada a la función en un vector, pero sigue leyendo para conocer una forma alternativa de usar estos «argumentos con puntos» (llamados «varargs» en varios otros lenguajes).

var_f <- function(x, y, ...) {
  print(c(...))
}

var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"

Entorno de la función

Anteriormente dijimos que los argumentos formales y el cuerpo son componentes de una función.

De hecho, hay un tercer componente: el entorno en el que se define la función.

Esto se puede ilustrar con el caso de las funciones anidadas:

outer_func <- function(x) {
  inner_func <- function(y) {
    x * y
  }
  
  inner_func(3)
}

outer_func(5)
#> [1] 15

La llamada a la función pasa x = 5 a la función externa, y ese valor está disponible dentro de su cuerpo.

La función interna es parte del cuerpo de la función externa y tiene acceso al valor de x. Dicho de otra forma, x = 5 está en el entorno de la función interna.

Técnicamente, esto se conoce como una clausura.

El entorno es especialmente importante con los argumentos con puntos, ya que cualquier valor que se pase así puede transmitirse a las llamadas a funciones dentro del cuerpo de la función. La función externa no necesita saber qué significan los argumentos con puntos, ni le importa.

f_var <- function(x, ...) {
  sum(x, ...)
}

x <- c(1, 2, NA, 6)

# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA

# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9

Esta técnica la usan mucho las bibliotecas del Tidyverse, como stringr. Muchas de las funciones de stringr son una envoltura fácil de usar sobre funciones de bajo nivel de stringi y de R base.

Los argumentos adicionales que se pasan a las funciones str_*() simplemente se transmiten a esas funciones de bajo nivel.

Funciones como valores devueltos

Una función externa puede definir una función interna (ya sea con nombre, o anónima como se describe más abajo) y usarla como valor devuelto.

La función devuelta incluirá el entorno en el que se definió.

times_y <- function(x) {
  # anonymous function - see next section
  \(y) x * y
}

f <- times_y(3)
class(f)
#> [1] "function"

f(4)
#> [1] 12

Funciones anónimas

Cuando definimos una función, normalmente asignamos el objeto de función resultante a una variable.

x <- 2

# function definition captures x from the environment
doubleit_short <- function(y) x * y

Esto facilita usar la función más adelante en el script, pero esa asignación no es necesaria. Una función corta, que se usa una sola vez, puede ser útil en el contexto inmediato. Sin una asignación de nombre, se le llama función anónima.

El uso de funciones anónimas es tan común que (desde R v4.1.0) hay una sintaxis abreviada para definirlas: reemplaza la palabra function con una barra invertida \.

En la sección anterior, sobre valores devueltos, se usó un ejemplo de esto.

Copia al modificar

R permite asignar valores a elementos individuales de un vector. Si pasamos un vector como argumento de una función y lo modificamos en el cuerpo de la función antes de devolverlo, obtenemos un vector modificado.

Pero, ¿qué pasó con el vector original?

f <- function(vec) {
  vec[1] <- 42
  vec
}

vals <- c(1, 3, 4)

# f() returns a modified vector
f(vals)
#> [1] 42  3  4  

# the original vector is unchanged.
vals
#> [1] 1 3 4

R es un lenguaje diseñado para la ciencia de datos. Recopilar esos datos puede costar mucho tiempo, esfuerzo y, potencialmente, una cantidad de dinero desorbitante: ¡es importante no corromperlos!

La política general (con algunas excepciones) es copia al modificar. Si un objeto (como un vector) se modifica de una forma que podría causar problemas más adelante, R devuelve una copia modificada y deja el original intacto.

Copiar estructuras de datos grandes puede ser costoso computacionalmente, pero en general es el mal menor cuando la alternativa es la corrupción de los datos.

Caution

Ten cuidado con las operaciones que hacen que se copien repetidamente los mismos datos.

Parafraseando a Hadley Wickham (autor del Tidyverse):

Los bucles no son intrínsecamente lentos, pero hacen que sea peligrosamente fácil incluir operaciones lentas dentro del bucle.

La vectorización o las funciones de orden superior pueden ayudarte a protegerte de este tipo de problema de rendimiento.

Instrucciones

Tu empresa ha estado trabajando con datos de mapeo para crear páginas web. Después de algunas conversaciones, se decidió empezar a usar R para hacer algunos cálculos de forma dinámica.

Procura trabajar directamente con los puntos vectoriales: no es necesario separarlos en coordenadas x e y distintas.

1. Escala las coordenadas

Implementa la función scale(point, s) que toma un punto con un número arbitrario de dimensiones y lo escala según el valor predefinido s.

# A 2D example
s <- c(0.5, 0.8)
point <- c(10, 5)
scale(point, s)
#> [1] 5 4

2. Traslada las coordenadas

Implementa la función translate(point, ...) que devuelve un nuevo punto, con cada coordenada desplazada por los valores de los puntos suspensivos.

Esta función debe poder manejar puntos de cualquier dimensión, donde el número de argumentos de los puntos suspensivos coincida con esa dimensión.

point2d <- c(2, 3)

# supply dx, dy translations
translate(point2d, 0.5, 0.6)
#> [1] 2.5 3.6

point3d <- c(2, 3, 4)

# supply dx, dy, dz translations
translate(point3d, 0.5, 0.6, 0.7)
#> [1] 2.5 3.6 4.7

3. Transforma un punto 2D

Algunos de tus compañeros tienen menos experiencia con R, así que decides usar una clausura para crear transformaciones reutilizables para pares de coordenadas {x, y}.

Implementa la función transform2d(dx, dy, s) que devuelve una función que hace uso de una clausura para realizar de forma repetible una traslación y un escalado de un punto 2D.

El argumento de escalado s debe ser opcional, con un valor predeterminado de 1.

La función que se devuelve debe tomar un punto 2D y luego:

  1. trasladarlo según los valores predefinidos dx y dy
  2. escalarlo según s
# scale all coordinates by the same amount (2) in this example
# your code needs to be able to handle a vector `s`

tf2d <- transform2d(0.5, 0.6, 2)
class(tf2d)
#> [1] "function"

tf2d(c(2, 3))
#> [1] 5.0 7.2

# Accept default for `s`
tf2d <- transform2d(0.5, 0.6)

tf2d(c(2, 3))
#> 2.5 3.6

El orden de las operaciones es importante: trasladar y luego escalar da el resultado correcto; escalar y luego trasladar no. En lenguaje técnico, las operaciones no conmutan.

4. Transforma un punto 3D

Los datos de mapeo también pueden contener alturas, que se usan para añadir sombreado y contornos en pantalla.

Implementa la función transform3d(dx, dy, dz, s) que devuelve una función.

El argumento de escalado s debe ser opcional, con un valor predeterminado de 1.

La función que se devuelve debe tomar un punto 3D, trasladarlo según los valores predefinidos y luego escalarlo según s.

# using default `s`
tf3d <- transform3d(0.5, 0.6, 0.7)
point <- c(2, 3, 4)
tf3d(point)
#> [1] 2.5 3.6 4.7

# using vector `s`
tf3d <- transform3d(0.5, 0.6, 0.7, c(0.5, 0.6, 0.7))
tf3d(point)
#> [1] 1.25 2.16 3.29
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
R Exercism

¿Todo listo para empezar Transformación de coordenadas?

Regístrate en Exercism para aprender y dominar R con 21 conceptos111 ejercicios y mentoría humana real, todo gratis.