Percursos
/
R
R
/
Exercícios
/
Transformação de coordenadas
Transformação de coordenadas

Transformação de coordenadas

Exercício de aprendizagem

Introdução

As funções já foram introduzidas no Conceito Básico, com exemplos como este:

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

squareit(3)
#> [1] 9

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

Se olharmos com mais atenção para a definição de squareit, conseguimos identificar várias partes:

  • A função recebe um argumento formal x.
  • Existe um corpo da função, normalmente entre chavetas { }.
  • Depois de criado o objeto função, este é atribuído a uma variável squareit.

Em R, uma função é um objeto de primeira classe, tal como os números e as strings. Assim, squareit <- function... é uma atribuição sintaticamente igual a x <- 42.

Argumentos

Ao contrário de outras linguagens de script como Python e Julia, o R não faz uma distinção clara entre argumentos posicionais e argumentos de palavra-chave.

As chamadas a funções podem passar valores posicionalmente ou por nome. Esta última forma é útil para funções complexas com muitos argumentos, em que é difícil recordar a respetiva ordem.

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

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

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

Argumentos opcionais

Os valores predefinidos dos argumentos podem ser especificados na definição da função, mas têm de vir depois de todos os argumentos sem valor predefinido.

Podemos então escolher se aceitamos o valor predefinido ou se o substituímos.

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

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

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

Argumentos extra

Para aceitar um número arbitrário de argumentos adicionais, usa um ... (reticências) na definição. É possível converter quaisquer valores extra da chamada à função num vetor, mas continua a ler para descobrires uma forma alternativa de usar estes "dot args" (designados por "varargs" em várias outras linguagens).

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

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

Ambiente da função

Anteriormente, dissemos que os argumentos formais e o corpo são ambos componentes de uma função.

Na verdade, existe um terceiro componente: o ambiente em que a função é definida.

Isto pode ser ilustrado com o caso das funções aninhadas:

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

outer_func(5)
#> [1] 15

A chamada à função passa x = 5 à função externa, e este valor está disponível dentro desse corpo da função.

A função interna é parte do corpo da função externa e tem acesso ao valor de x. Dito de outra forma, x = 5 está no ambiente da função interna.

Tecnicamente, isto é conhecido como uma closure.

O ambiente é especialmente importante com os dot args, pois quaisquer valores fornecidos desta forma podem ser passados adiante para chamadas a funções dentro do corpo da função. A função externa não precisa de saber nem de se preocupar com o significado dos dot args.

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 é muito usada por bibliotecas do Tidyverse como o stringr. Muitas das funções do stringr são uma camada amigável em torno de funções de baixo nível do stringi e do R base.

Os argumentos extra fornecidos às funções str_*() são simplesmente passados adiante para essas funções de baixo nível.

Funções como valores devolvidos

Uma função externa pode definir uma função interna (com nome ou anónima, como se descreve abaixo) e usá-la como valor devolvido.

A função devolvida incluirá o ambiente em que foi definida.

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

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

f(4)
#> [1] 12

Funções anónimas

Quando definimos uma função, normalmente associamos o objeto função resultante a uma variável.

x <- 2

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

Isto facilita a utilização da função mais adiante no script, mas essa associação a um nome não é obrigatória. Uma função curta, para usar uma só vez, pode ser útil no contexto imediato. Sem associação a um nome, chama-se função anónima.

O uso de funções anónimas é tão comum que (desde o R v4.1.0) existe uma sintaxe abreviada para as definir: substitui a palavra function por uma barra invertida \.

Foi usado um exemplo disto na secção anterior, sobre valores devolvidos.

Cópia ao modificar

O R permite atribuir valores a elementos individuais de um vetor. Se passarmos um vetor como argumento de uma função e o modificarmos no corpo da função antes de o devolver, obtemos um vetor modificado.

Mas o que aconteceu ao vetor 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

O R é uma linguagem concebida para a ciência de dados. Recolher esses dados pode custar muito tempo, esforço e, potencialmente, uma quantia exorbitante de dinheiro: é importante não os corromper!

A política geral (com algumas exceções) é a cópia ao modificar. Se um objeto (como um vetor) for alterado de uma forma que possa causar problemas mais tarde, o R devolve uma cópia modificada e deixa o original intacto.

Copiar estruturas de dados grandes pode ser computacionalmente dispendioso, mas, em geral, este é o mal menor quando a alternativa é a corrupção dos dados.

Caution

Tem cuidado com as operações que levam à cópia repetida dos mesmos dados.

Parafraseando Hadley Wickham (autor do Tidyverse):

Os ciclos não são intrinsecamente lentos, mas tornam perigosamente fácil incluir operações lentas dentro do ciclo.

A vetorização ou as funções de ordem superior podem ajudar a proteger-te deste tipo de assassino do desempenho.

Instruções

A tua empresa tem vindo a trabalhar com dados de mapeamento para construir páginas web. Depois de alguma discussão, decidiu-se começar a usar R para fazer alguns cálculos de forma dinâmica.

Tenta trabalhar diretamente com os pontos vetoriais: não é preciso desdobrá-los em coordenadas x e y separadas.

1. Escala as coordenadas

Implementa a função scale(point, s), que recebe um ponto com um número arbitrário de dimensões e o escala pelo s predefinido.

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

2. Translada as coordenadas

Implementa a função translate(point, ...), que devolve um novo ponto, com cada coordenada deslocada pelos valores indicados nas reticências.

Esta função tem de ser capaz de lidar com pontos de qualquer dimensão, com o número de argumentos nas reticências a corresponder a essa dimensão.

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 um ponto 2D

Alguns dos teus colegas de equipa têm menos experiência com R, por isso decides usar um fecho de função para criar transformações reutilizáveis para pares de coordenadas {x, y}.

Implementa a função transform2d(dx, dy, s), que devolve uma função que recorre a um fecho para fazer uma translação e um escalonamento repetíveis de um ponto.

O argumento de escalonamento s deve ser opcional, com um valor por omissão de 1.

A função devolvida deve receber um ponto 2D e, em seguida:

  1. transladá-lo pelos dx e dy predefinidos
  2. escaloná-lo por 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

A ordem das operações é importante: transladar primeiro e escalonar depois dá o resultado correto; escalonar primeiro e transladar depois não. Em linguagem técnica, as operações não comutam.

4. Transforma um ponto 3D

Os dados de mapeamento também podem conter alturas, usadas para adicionar sombreado e contornos no ecrã.

Implementa a função transform3d(dx, dy, dz, s), que devolve uma função.

O argumento de escalonamento s deve ser opcional, com um valor por omissão de 1.

A função devolvida deve receber um ponto 3D, transladá-lo pelos valores predefinidos e depois escaloná-lo por 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 via GitHub A ligação abre numa nova janela ou separador
R Exercism

Estás pronto para começar Transformação de coordenadas?

Inscreve-te no Exercism para aprenderes e dominares R com 21 conceitos111 exercícios, e mentoria humana real, tudo grátis.