Track
/
R
R
/
Esercizi
/
Trasformazione di coordinate
Trasformazione di coordinate

Trasformazione di coordinate

Esercizio di apprendimento

Introduzione

Le funzioni sono state introdotte nel concetto Basi, con esempi come questo:

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

squareit(3)
#> [1] 9

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

Guardando più da vicino la definizione di squareit, possiamo individuare varie parti:

  • La funzione accetta un parametro formale x.
  • C'è un corpo della funzione, di solito tra parentesi graffe { }.
  • Una volta creato l'oggetto funzione, viene assegnato a una variabile squareit.

In R una funzione è un oggetto di prima classe, proprio come i numeri e le stringhe. Quindi squareit <- function... è un'assegnazione che, dal punto di vista sintattico, è identica a x <- 42.

Argomenti

A differenza di altri linguaggi di scripting come Python e Julia, R non fa una distinzione netta tra argomenti posizionali e argomenti con nome.

Le chiamate di funzione possono passare i valori in modo posizionale oppure per nome. Quest'ultimo è utile per funzioni complesse con molti argomenti, dove è difficile ricordarne l'ordine.

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

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

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

Argomenti opzionali

I valori predefiniti degli argomenti si possono specificare nella definizione della funzione, ma devono venire dopo tutti gli argomenti che non hanno un valore predefinito.

A quel punto possiamo scegliere se accettare il valore predefinito o sovrascriverlo.

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

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

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

Argomenti aggiuntivi

Per accettare un numero arbitrario di argomenti aggiuntivi, usa un ... (ellissi) nella definizione. È possibile convertire in un vettore i valori in eccesso passati nella chiamata, ma continua a leggere per scoprire un modo alternativo di usare questi «dot args» (chiamati «varargs» in diversi altri linguaggi).

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

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

L'ambiente della funzione

In precedenza abbiamo detto che i parametri formali e il corpo sono entrambi componenti di una funzione.

In realtà c'è una terza componente: l'ambiente in cui la funzione è definita.

Lo si può illustrare con il caso delle funzioni annidate:

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

outer_func(5)
#> [1] 15

La chiamata di funzione passa x = 5 alla funzione esterna, e questo valore è disponibile all'interno del corpo di quella funzione.

La funzione interna è parte del corpo della funzione esterna e ha accesso al valore di x. Detto in altri termini, x = 5 si trova nell'ambiente della funzione interna.

Tecnicamente, questo è noto come closure.

L'ambiente è particolarmente importante con i dot args, perché qualsiasi valore fornito in questo modo può essere inoltrato alle chiamate di funzione all'interno del corpo della funzione. Alla funzione esterna non serve sapere cosa significano i dot args, né 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

Questa tecnica è usata ampiamente dalle librerie del Tidyverse, come stringr. Molte funzioni di stringr sono un wrapper amichevole attorno a funzioni di basso livello di stringi e di R di base.

Gli argomenti aggiuntivi forniti alle funzioni str_*() vengono semplicemente inoltrati a quelle funzioni di livello inferiore.

Le funzioni come valori restituiti

Una funzione esterna può definire una funzione interna (con un nome oppure anonima, come spiegato più avanti) e usarne il valore come valore restituito.

La funzione restituita comprenderà l'ambiente in cui è stata definita.

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

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

f(4)
#> [1] 12

Le funzioni anonime

Quando definiamo una funzione, di solito assegniamo l'oggetto funzione risultante a una variabile.

x <- 2

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

Questo rende facile usare la funzione più avanti nello script, ma un'associazione del genere non è necessaria. Una funzione breve, usata una sola volta, può essere utile nel contesto immediato. Senza un nome associato, si chiama funzione anonima.

L'uso delle funzioni anonime è così comune che (a partire da R v4.1.0) esiste una sintassi abbreviata per definirle: sostituisci la parola function con una barra rovesciata \.

Un esempio di questa sintassi è stato usato nella sezione precedente, sui valori restituiti.

Copia alla modifica

R consente di assegnare un valore ai singoli elementi di un vettore. Se passiamo un vettore come argomento di una funzione e lo modifichiamo nel corpo della funzione prima di restituirlo, otteniamo un vettore modificato.

Ma che fine ha fatto il vettore originale?

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 è un linguaggio pensato per la data science. Raccogliere quei dati può costare molto in termini di tempo, di impegno e, potenzialmente, di una quantità di denaro da capogiro: è importante non corromperli!

La regola generale (con qualche eccezione) è la copia alla modifica. Se un oggetto (come un vettore) viene modificato in un modo che potrebbe causare problemi in seguito, R restituisce una copia modificata e lascia l'originale intatto.

Copiare grandi strutture dati può essere dispendioso dal punto di vista computazionale, ma di solito è il male minore quando l'alternativa è la corruzione dei dati.

Caution

Fai attenzione alle operazioni che portano a copiare più volte gli stessi dati.

Parafrasando (l'autore del Tidyverse) Hadley Wickham:

I cicli non sono lenti di per sé, ma rendono pericolosamente facile includere operazioni lente al loro interno.

La vettorizzazione o le funzioni di ordine superiore possono aiutarti a difenderti da questo tipo di problema che penalizza le prestazioni.

Istruzioni

La tua azienda lavora con dati di mappatura per costruire pagine web. Dopo qualche discussione, si è deciso di iniziare a usare R per eseguire alcuni calcoli in modo dinamico.

Cerca di lavorare direttamente con i punti come vettori: non c'è bisogno di scomporli in coordinate x e y separate.

1. Scala le coordinate

Implementa la funzione scale(point, s), che prende un punto con un numero arbitrario di dimensioni e lo scala in base al valore predefinito s.

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

2. Trasla le coordinate

Implementa la funzione translate(point, ...), che restituisce un nuovo punto in cui ogni coordinata è spostata dei valori contenuti nei puntini di sospensione.

Questa funzione deve saper gestire punti di qualsiasi dimensione, con un numero di argomenti passati attraverso i puntini di sospensione pari alla dimensione del punto.

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. Trasforma un punto 2D

Alcuni dei tuoi colleghi hanno meno esperienza con R, così decidi di usare una closure per creare trasformazioni riutilizzabili per coppie di coordinate {x, y}.

Implementa la funzione transform2d(dx, dy, s), che restituisce una funzione basata su una closure per eseguire in modo ripetibile la traslazione e la scalatura di un punto 2D.

L'argomento di scalatura s deve essere opzionale, con valore predefinito 1.

La funzione restituita deve prendere un punto 2D e poi:

  1. traslarlo dei valori predefiniti dx e dy
  2. scalarlo di 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

L'ordine delle operazioni è importante: traslare e poi scalare dà il risultato corretto, mentre scalare e poi traslare no. In termini tecnici, le operazioni non commutano.

4. Trasforma un punto 3D

I dati di mappatura possono anche contenere le altezze, usate per aggiungere ombreggiature e curve di livello sullo schermo.

Implementa la funzione transform3d(dx, dy, dz, s), che restituisce una funzione.

L'argomento di scalatura s deve essere opzionale, con valore predefinito 1.

La funzione restituita deve prendere un punto 3D, traslarlo dei valori predefiniti e poi scalarlo di 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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
R Exercism

Vuoi iniziare Trasformazione di coordinate?

Iscriviti a Exercism per imparare e padroneggiare R con 21 concetti111 esercizi e il mentoring di persone reali, tutto gratis.