Approfondimenti sulla programmazione funzionale (ancora sul package purrr)

Author

Domenico Vistocco

Riepilogo delle iterazioni con il ciclo for

Il ciclo for permette di effettuare delle iterazioni in modo abbastanza intuitivo. Consideriamo la famosa tabella degli iris di Fisher, disponibile in R:

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Consideriamo solo le variabili numeriche:

library(tidyverse)
iris_num <- iris |> select(-Species)
mode(iris_num)
[1] "list"
class(iris_num)
[1] "data.frame"
length(iris_num)
[1] 4

Sfruttando il ciclo for ed una variabile di accumulo vet_medie possiamo calcolare la media di ciascuna colonna; possiamo sfruttare la funzione names per assegnare a ciascuna media un’etichetta che contiene il nome della corrispondente variabile:

n_col <- length(iris_num)
vet_medie <- numeric(n_col)
for(i in 1:n_col){
  vet_medie[i] <- mean(iris_num[[i]])
  names(vet_medie)[i] <- colnames(iris_num)[i]  
}
vet_medie
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

Riepilogo delle iterazioni sfruttando map

Le funzioni delle classe map``{purrr} permettono di ottenere lo stesso risultato in un modo più conciso:

map_vec(iris_num, mean)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

Le funzioni map hanno un argomento .progress che può essere utile nel caso di lunghe iterazioni per controllarne l’avanzamento. Per mostrarne la funzionalità sfruttiamo la funzione Sys.sleep che mette in pausa R per il numero di secondi specificato in input:

Sys.sleep(3)
map(1:5, ~Sys.sleep(3), .progress = "punto 1")
punto 1 ■■■■■■■                           20% |  ETA: 12s
punto 1 ■■■■■■■■■■■■■                     40% |  ETA:  9s
punto 1 ■■■■■■■■■■■■■■■■■■■               60% |  ETA:  6s
punto 1 ■■■■■■■■■■■■■■■■■■■■■■■■■         80% |  ETA:  3s
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL

La funzione map_df organizza l’output di map in un forma tabellare laddove possibile. Ecco un esempio in cui calcoliamo alcune statistiche di sintesi sulle colonne della tabella iris_num:

map_df(iris_num,
    \(x) c(min = min(x), 
           media = mean(x), 
           mediana = median(x), 
           massimo = max(x)),
    .id = "variabile")
# A tibble: 4 × 5
  variabile      min media mediana massimo
  <chr>        <dbl> <dbl>   <dbl>   <dbl>
1 Sepal.Length   4.3  5.84    5.8      7.9
2 Sepal.Width    2    3.06    3        4.4
3 Petal.Length   1    3.76    4.35     6.9
4 Petal.Width    0.1  1.20    1.3      2.5

Le funzioni map possono essere utili anche per effettuare simulazioni con numeri casuali. Creo una tabella che contiene 5 vettori di numeri casuali estratti da una distribuzione uniforme impostando opportunamente i nomi di colonna:

set.seed(20)
campione_casuale <- map_dfc(1:5, 
        function(i){
          x = data.frame(runif(n = 50))
          colnames(x) <- paste0("X", i)
          return(x)
        }
          )
campione_casuale
            X1          X2          X3         X4           X5
1  0.877521389 0.070501890 0.864210863 0.65119595 0.7081688305
2  0.768533209 0.484438388 0.949721249 0.50027154 0.8679017408
3  0.278963138 0.275534324 0.487667807 0.44007984 0.4107538208
4  0.529163693 0.952878217 0.996799519 0.52106892 0.3032164532
5  0.962907031 0.125752792 0.575478415 0.32061610 0.8882199631
6  0.980354585 0.510583590 0.670667730 0.06337458 0.4715613995
7  0.091332588 0.006668597 0.909095620 0.08683341 0.7583236676
8  0.070749478 0.460394623 0.744746018 0.16130041 0.0001990756
9  0.327593952 0.269770059 0.767473679 0.48819871 0.8191078596
10 0.370074542 0.022252804 0.929983500 0.80908994 0.7170727539
11 0.715527557 0.414372471 0.522409517 0.40486777 0.5436036822
12 0.757796354 0.107802400 0.199942206 0.59319026 0.9774630913
13 0.001927939 0.944099025 0.908126419 0.97269079 0.4562077005
14 0.742802090 0.048347950 0.984085380 0.80981047 0.8656263545
15 0.192418547 0.940162991 0.341592805 0.53869071 0.3319672628
16 0.452099627 0.039637949 0.829413106 0.12503855 0.8593533814
17 0.322147280 0.866166428 0.206605202 0.53673812 0.7091970481
18 0.109071407 0.589797459 0.061953065 0.99565707 0.9478042857
19 0.289262362 0.136245993 0.640182136 0.57477324 0.0648794547
20 0.819456486 0.573330572 0.433552259 0.76660514 0.9259552327
21 0.491967700 0.031399939 0.523410868 0.26180994 0.8014013288
22 0.030256489 0.404374545 0.289332744 0.35656140 0.0102601945
23 0.440231532 0.180469089 0.472321052 0.48570111 0.9575430995
24 0.077285102 0.571679818 0.618921664 0.46717122 0.7118095460
25 0.264960458 0.893538646 0.350186775 0.54466607 0.1708956973
26 0.069590674 0.386749120 0.023548393 0.20123847 0.7081544192
27 0.907119023 0.535003930 0.190971064 0.88704096 0.0605164107
28 0.992276785 0.493176297 0.338867607 0.41632251 0.5826460677
29 0.064085941 0.664028152 0.884216228 0.92512354 0.2765684326
30 0.675109118 0.270410918 0.096864462 0.70214908 0.2900455464
31 0.330900538 0.206540713 0.048336685 0.98639330 0.1759492564
32 0.447822332 0.895834943 0.478974978 0.99993726 0.0404361649
33 0.834120666 0.061467828 0.878033419 0.02453254 0.0713546842
34 0.185976545 0.941401074 0.892427634 0.19310203 0.2359168257
35 0.511257709 0.710854290 0.142385739 0.71173269 0.9723311614
36 0.474987397 0.444833833 0.979012316 0.02688548 0.3477863763
37 0.465819779 0.356053063 0.818294201 0.41429421 0.4682117978
38 0.909994747 0.504495509 0.151621991 0.69203493 0.4807822919
39 0.651441227 0.147471793 0.093834303 0.47298115 0.3538047133
40 0.257842275 0.051248514 0.233800850 0.13666260 0.2104749056
41 0.593550333 0.507252342 0.743904513 0.60500657 0.7746777271
42 0.038198369 0.295338828 0.862255224 0.89165000 0.8264930574
43 0.442576428 0.811078469 0.254507627 0.90074766 0.6093551978
44 0.466793342 0.970930521 0.115700500 0.45208581 0.6425854834
45 0.764923366 0.811074144 0.839298956 0.86456509 0.8534477518
46 0.435673689 0.870581772 0.898480647 0.99135380 0.7392062391
47 0.644274049 0.847611516 0.247232798 0.27574986 0.9476491089
48 0.748955447 0.757254093 0.877045069 0.57450175 0.4807338817
49 0.404364386 0.351486927 0.868924421 0.56165173 0.7915550559
50 0.657915460 0.034560596 0.003034063 0.66835386 0.1049062570

Posso ottenere lo stesso risultato usando la funzione map ed organizzando l’output in formato tabellare usando la funzione list_cbind:

map(1:5, function(x) tibble(X = runif(n = 50))) |> list_cbind()
New names:
• `X` -> `X...1`
• `X` -> `X...2`
• `X` -> `X...3`
• `X` -> `X...4`
• `X` -> `X...5`
# A tibble: 50 × 5
    X...1  X...2  X...3  X...4  X...5
    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
 1 0.643  0.859  0.0837 0.351  0.702 
 2 0.151  0.592  0.888  0.432  0.466 
 3 0.754  0.196  0.0695 0.139  0.0362
 4 0.874  0.734  0.408  0.772  0.359 
 5 0.0368 0.793  0.648  0.761  0.479 
 6 0.132  0.706  0.682  0.270  0.0529
 7 0.248  0.0516 0.911  0.193  0.572 
 8 0.486  0.474  0.519  0.779  0.184 
 9 0.160  0.876  0.347  0.991  0.282 
10 0.224  0.584  0.681  0.0446 0.207 
# ℹ 40 more rows

E’ possibile sfruttare l’approccio visto per i nomi dinamici e l’operatore := per ottenere dei nomi di colonna migliori:

map(1:5, \(x) tibble("X{x}" := runif(n = 50))) |> list_cbind()
# A tibble: 50 × 5
       X1    X2    X3     X4      X5
    <dbl> <dbl> <dbl>  <dbl>   <dbl>
 1 0.772  0.238 0.605 0.0959 0.151  
 2 0.553  0.658 0.893 0.142  0.00576
 3 0.0616 0.694 0.571 0.428  0.518  
 4 0.496  0.986 0.798 0.825  0.347  
 5 0.442  0.345 0.708 0.395  0.846  
 6 0.620  0.449 0.725 0.607  0.594  
 7 0.863  0.885 0.619 0.596  0.00408
 8 0.596  0.407 0.314 0.0324 0.913  
 9 0.309  0.934 0.660 0.0443 0.932  
10 0.483  0.994 0.648 0.785  0.828  
# ℹ 40 more rows

Lo stesso si può fare ricorrendo direttamente alla funzione map_dfc invece che alla combinazione map e list_cbind:

map_dfc(1:10, \(x) tibble("X{x}" := runif(n = 50)))
# A tibble: 50 × 10
      X1     X2    X3    X4    X5     X6     X7    X8     X9    X10
   <dbl>  <dbl> <dbl> <dbl> <dbl>  <dbl>  <dbl> <dbl>  <dbl>  <dbl>
 1 0.812 0.154  0.533 0.227 0.372 0.705  0.495  0.122 0.254  0.784 
 2 0.141 0.0673 0.874 0.398 0.700 0.585  0.406  0.843 0.633  0.857 
 3 0.777 0.275  0.861 0.799 0.590 0.940  0.103  0.966 0.301  0.653 
 4 0.714 0.997  0.393 0.139 0.981 0.829  0.401  0.987 0.562  0.451 
 5 0.268 0.518  0.985 0.904 0.245 0.151  0.111  0.591 0.752  0.0480
 6 0.747 0.604  0.204 0.739 0.172 0.791  0.0930 0.350 0.686  0.759 
 7 0.674 0.868  0.206 0.978 0.330 0.0165 0.0457 0.737 0.456  0.612 
 8 0.869 0.728  0.434 0.614 0.733 0.659  0.937  0.182 0.654  0.750 
 9 0.427 0.585  0.902 0.542 0.791 0.760  0.963  0.849 0.0582 0.649 
10 0.866 0.594  0.924 0.315 0.939 0.700  0.220  0.142 0.797  0.773 
# ℹ 40 more rows

“Visualizziamo” lo spazio campionario

Sfruttando opportunamente map è possibile costruire una tabella per “visualizzare” lo spazio campionario, ovvero l’insieme (o meglio un sottoinsieme in questo caso) dei possibili campioni che è possibile osservare in un dato problema inferenziale.

Consideriamo ad esempio un DGP (data generating process) uniforme tra 10 e 20

set.seed(123)
a <- 10
b <- 20

E generiamo 500 campioni di ampiezza 20 a partire da un’uniforme continua tra 10 e 20, aggiungendo una colonna media che calcola la media di ciascuno dei 500 campioni (nota: per calcolare le medie per riga possiamo sfruttare la funzione rowwise):

nr_campioni <- 500
spazio_campionario <- map(1:20, 
    \(x) tibble("X{x}" := runif(n = nr_campioni, a, b))) |> 
  list_cbind() |> 
  rowwise() |> 
  mutate(media = mean(c_across(everything()))) 

Ciascuna colonna rappresenta una variabile casuale osservazione campionaria, i.i.d. come il DGP. Ecco un istogramma della v.c. \(X_1\) prima osservazione campionaria:

hist(spazio_campionario$X1)

Dal teorema del limite centrale sappiamo che la media campionaria converge ad una normale:

hist(spazio_campionario$media)

Anche se il campione è solo di ampiezza 20, il DGP è simmetrico per cui l’effetto del limite centrale è già presente. Possiamo sfruttare un grafico quantile-quantile (qqnorm) per apprezzare la convergenza alla normale:

qqnorm(spazio_campionario$media)

Da quanto noto sulla v.c. uniforme, possiamo calcolare la media del DGP:

(a + b) / 2
[1] 15

e la varianza del DGP:

(((b - a)^2) / 12)
[1] 8.333333

Il valore atteso della media campionaria è uguale alla media del DGP, mentre la varianza è pari alla varianza del DGP diviso per n:

(((b - a)^2) / 12) / 20
[1] 0.4166667

Possiamo verificare empiricamente il valore della media e della varianza dei 500 campioni generati:

mean(spazio_campionario$media)
[1] 14.97549
var(spazio_campionario$media) * ((nr_campioni - 1) / nr_campioni)
[1] 0.4331538

Le varianti map2 e pmap

Esistono due varianti della funzione map che permettono di scorrere in parallelo una coppia di lista (map2) o un insieme di liste (pmap).

Mostriamo un esempio partendo map per generare numeri casuali da un modello normale con medie diverse:

vet_medie <- c(200, 300, 400)
sigma <- 3
n <- 20
map(vet_medie, function(media) rnorm(n, media, sigma))
[[1]]
 [1] 198.5175 203.3828 196.5592 204.4431 202.7486 201.0054 201.7240 200.6109
 [9] 198.6589 198.9694 198.1885 203.7160 201.7985 199.6738 196.5834 200.9596
[17] 196.9804 197.4452 206.1164 195.8121

[[2]]
 [1] 297.3510 298.2576 298.2127 296.6234 299.4368 296.8859 304.0274 300.3705
 [9] 302.5744 295.7837 299.9566 301.6367 300.2943 300.0528 296.8972 298.1138
[17] 298.7853 297.5929 302.2914 305.2788

[[3]]
 [1] 403.9455 401.2685 399.6451 401.7280 397.6342 400.7948 400.4994 392.9649
 [9] 405.6899 396.0053 392.1224 399.9990 400.3222 399.9760 400.4217 396.4452
[17] 401.7775 404.1421 397.4838 400.9176

Possiamo sfruttare la funzione map2 che effettua un’iterazione su una coppia di vettori in input (primi due argomenti .x e y) applicando la funzione .f (terzo argomento) a ciascuna coppia di elementi dei due vettori:

vet_sigma <- c(2, 3, 4)
numeri_normali <- map2(.x = vet_medie, 
                       .y = vet_sigma, 
                       .f = \(x, y) rnorm(n = n, mean = x, sd = y))

Possiamo verificare empiricamente media e standard deviation dei numeri generati:

map_vec(numeri_normali, mean)
[1] 200.6205 300.6847 399.5618
map_vec(numeri_normali, sd)
[1] 1.380892 2.030393 3.364595

La funzione pmap permette di generalizzare l’iterazione su input multipli organizzati in una lista passata alla funzione come primo argomento. Possiamo generalizzare facilmente l’esempio precedente generando tre vettori di numeri casuali di differente numerosità (vet_n), con differenti medie (vet_medie) e differenti standard deviation (vet_sigma):

vet_n <- c(20, 30, 40)
numeri_normali <- pmap(.l = list(vet_n, vet_medie, vet_sigma),
     .f = function(el_1, el_2, el_3) rnorm(n = el_1, mean = el_2, sd = el_3))

Possiamo ispezionare la lista contenente i tre vettori:

numeri_normali
[[1]]
 [1] 200.9360 200.3138 199.7583 200.7576 198.4632 200.4540 198.3326 198.3912
 [9] 200.2134 200.8229 198.2618 196.8980 197.9473 195.9996 200.2580 201.1695
[17] 203.6039 198.2105 199.0549 199.7830

[[2]]
 [1] 300.7127 297.6050 302.7880 298.8609 302.9555 295.1123 299.5133 301.0373
 [9] 298.5234 298.5639 298.0868 300.3103 303.7869 296.9271 299.7030 298.5523
[17] 304.2396 305.7832 301.5022 300.1989 304.0987 298.6152 299.8295 303.1507
[25] 300.3171 298.5738 300.5550 298.6571 305.6098 298.8503

[[3]]
 [1] 403.7631 400.0046 405.3214 403.7229 397.4050 401.1282 399.4167 402.0111
 [9] 399.8146 401.8813 399.5321 403.8226 401.2527 394.7365 399.9171 398.6533
[17] 404.4657 395.3933 391.8200 401.8707 403.7678 401.8116 405.4111 403.6078
[25] 396.2788 403.7221 397.6449 404.4204 403.9715 399.8008 404.2717 398.9284
[33] 404.0440 390.7743 395.7944 400.5902 397.9574 389.3282 396.6591 406.4558

così come ispezionare la cardinalità, le medie e le deviazioni standard dei numeri casuali genera

map_vec(numeri_normali, length)
[1] 20 30 40
map_vec(numeri_normali, mean)
[1] 199.4815 300.4340 400.2793
map_vec(numeri_normali, sd)
[1] 1.721591 2.606318 4.128880

Le varianti walk, walk2 e pwalk

Nel package purrr sono disponibili tre funzioni (walk, walk2 e pwalk) che permettono di effettuare iterazioni allo stesso modo delle controparti map, map2 e pmap ma che non restituiscono oggetti in output. Le funzioni della famiglia walk sono utili quando si è interessati perciò agli effetti collaterali prodotti dalla funzioni piuttosto che ai valori restituiti in output. Esempi tipici di questi casi sono iterazioni che permettono di creare un insieme di grafici o di scrivere un insieme di file.

Proviamo ad esempio ad effettuare un’iterazione sulla lista numeri_normali costruita nella sezione precedente per ottenere una rappresentazione grafica di ciascun vettore di numeri casuali sfruttando la funzione walk:

walk(.x = numeri_normali,
     .f = ~hist(.x, breaks = 7, col = "darkblue"))

Possiamo migliorare i grafici inserendo un titolo che descriva le caratteristiche dei dati rappresentati, ovvero numerosità dei dati e media e standard deviation usata per generarli. In questo caso è utile sfruttare la funzione pwalk iterando sui vettori

pwalk(.l = list(vet_n, vet_medie, vet_sigma, numeri_normali),
      .f = function(el_1, el_2, el_3, el_4) {
        title_plot <- bquote(paste(
          "N(", mu == .(el_2), ", ", sigma == .(el_3), ")  | n = ", .(el_1)))
        hist(el_4, breaks = 7, col = "darkblue", main = title_plot)
      })

map - esempio pratico 1: lettura e combinazione di fogli multipli da una cartella MS-Excel

La funzione map può essere utilizzata per leggere fogli multipli da una cartella di lavoro MS-Excel. Per testare il codice in questa sezione puoi scaricare da questo link la cartella MS-Excel di esempio e copiarlo all’interno della cartella del progetto di lavoro o della cartella di lavoro.

Per leggere un file MS-Excel utilizziamo il package readxl. Il seguente codice leggere il foglio MSDUE15 dalla cartella di lavoro, impostando la prima colonna come formato date e la seconda come formato numeric; una volta letto il file impostiamo i nomi delle due colonne rispettivamente come data e price sfruttando la funzione set_names:

library(readxl)
library(tidyverse)

foglio_prova <- read_excel("../data/dati-fondi-comuni_esempio-01.xls", 
                           sheet = "MSDUE15", 
                           col_types = c("date", "numeric")) |> 
  set_names(c("data", "price")) |> 
  mutate(data = ymd(data)) 
head(foglio_prova)
# A tibble: 6 × 2
  data       price
  <date>     <dbl>
1 1999-01-06 1212.
2 1999-01-13 1123.
3 1999-01-20 1180.
4 1999-01-27 1145.
5 1999-02-03 1166.
6 1999-02-10 1119.

La funzione excel_sheets``{readxl} restituisce i nomi dei fogli di lavoro:

nomi_fogli <- excel_sheets("../data/dati-fondi-comuni_esempio-01.xls")
nomi_fogli
 [1] "Description" "MSDUE15"     "MSRUENR"     "MSRUMAT"     "MSRUIND"    
 [6] "MSRUCDIS"    "MSRUCSTA"    "MSRUHC"      "MSRUFNCL"    "MSRUIT"     
[11] "MSRUTEL"     "MSRUUTI"    

Il primo foglio contiene una legenda: possiamo eliminare il corrispondente nome in modo da automatizzare la lettura:

nomi_fogli <- nomi_fogli[-1]

Sfruttiamo a tal fine la funzione map iterando sul vettore dei nomi:

dati_ms <- map(nomi_fogli,
               function(nome_foglio) {
                 read_excel("../data/dati-fondi-comuni_esempio-01.xls", 
                            sheet = nome_foglio, 
                            col_types = c("date", "numeric")) |> 
                   set_names(c("data", "price")) |> 
                   mutate(data = ymd(data))
               }
               ) 

Otteniamo una lista di tabelle, i dati di ciascun foglio di lavoro corrispondono ad un elemento della lista:

class(dati_ms)
[1] "list"
length(dati_ms)
[1] 11

Le elaborazioni possono essere effettuate agevolemnte sulle singole tabelle accedendo ai singoli elementi. Il seguente codice calcola il rendimento dei titoli a partire dalle colonne dei prezzi dei singoli indici sfruttando la funzione lag per effettuare la differenza tra il valore al tempo t e quello al tempo t-1. La funzione slice permette di eliminare la prima riga della tabella, per la quale ovviamente non può essere calcolato il rendimento:

dati_ms[[1]] |> 
  mutate(return = log(price) - lag(log(price))) |> 
  slice(-1)
# A tibble: 172 × 3
   data       price  return
   <date>     <dbl>   <dbl>
 1 1999-01-13 1123. -0.0764
 2 1999-01-20 1180.  0.0495
 3 1999-01-27 1145. -0.0300
 4 1999-02-03 1166.  0.0183
 5 1999-02-10 1119. -0.0410
 6 1999-02-17 1147.  0.0245
 7 1999-02-24 1191.  0.0373
 8 1999-03-03 1160. -0.0262
 9 1999-03-10 1188.  0.0238
10 1999-03-17 1200.  0.0103
# ℹ 162 more rows

Sfruttando la funzione map è immediato applicare lo stesso calcolo a tutti gli elementi della lista, ovvero alle singole tabelle:

dati_ms <- map(dati_ms,
               function(tb_ms){ 
                 tb_ms <- tb_ms |> 
                   mutate(return = log(price) - lag(log(price)))
                 return(slice(tb_ms, -1))
               }
)
head(dati_ms[[1]])
# A tibble: 6 × 3
  data       price  return
  <date>     <dbl>   <dbl>
1 1999-01-13 1123. -0.0764
2 1999-01-20 1180.  0.0495
3 1999-01-27 1145. -0.0300
4 1999-02-03 1166.  0.0183
5 1999-02-10 1119. -0.0410
6 1999-02-17 1147.  0.0245

La lista delle tabelle ottenuta è una lista senza nomi, come è possibile verificare sfruttando la funzione names:

names(dati_ms)
NULL

Possiamo sfruttare la funzione set_names per assegnare a ciascun elemento della lista il nome corrispondente al foglio di lavoro da cui sono stati letti i dati:

dati_ms <- dati_ms |> set_names(nomi_fogli)
names(dati_ms)
 [1] "MSDUE15"  "MSRUENR"  "MSRUMAT"  "MSRUIND"  "MSRUCDIS" "MSRUCSTA"
 [7] "MSRUHC"   "MSRUFNCL" "MSRUIT"   "MSRUTEL"  "MSRUUTI" 

Questo ci consente di identificare i dati inserendo la colonna dei nomi nel caso siamo interessati ad impilare le singole tabelle in un’unica tabella sfruttando la funzione list_rbind:

dati_ms_table <- dati_ms |> list_rbind(names_to = "nome_indice")
dati_ms_table
# A tibble: 1,892 × 4
   nome_indice data       price  return
   <chr>       <date>     <dbl>   <dbl>
 1 MSDUE15     1999-01-13 1123. -0.0764
 2 MSDUE15     1999-01-20 1180.  0.0495
 3 MSDUE15     1999-01-27 1145. -0.0300
 4 MSDUE15     1999-02-03 1166.  0.0183
 5 MSDUE15     1999-02-10 1119. -0.0410
 6 MSDUE15     1999-02-17 1147.  0.0245
 7 MSDUE15     1999-02-24 1191.  0.0373
 8 MSDUE15     1999-03-03 1160. -0.0262
 9 MSDUE15     1999-03-10 1188.  0.0238
10 MSDUE15     1999-03-17 1200.  0.0103
# ℹ 1,882 more rows

walk - esempio pratico 2: scrittura di file .csv multipli

Sfruttando lo stesso file utilizzato nella precedente sezione (disponibile su questo link), possiamo mostrare la funzione walk per salvare un singolo file in formato .csv dove archiviare le tabelle dei dati presenti in ciascun foglio di lavoro. In questo caso siamo interessati solo all’effetto collaterale, che consiste nel salvataggio dei file, piuttosto che ai valori di output di ciascun funzione, per cui la funzione walk è più opportuna della funzione map. In particolare la funzione walk2 ci permette di iterare sulla lista della tabella e sul vettore dei nomi per scrivere le singole tabelle:

dir.create("tmp_data", showWarnings = FALSE)
walk2(.x = dati_ms, 
      .y = nomi_fogli, 
      ~ write_csv(.x, file = paste0("tmp_data/", .y, ".csv")))

Possiamo verificare la creazione dei file sfruttando la funzione dir:

dir("tmp_data")
 [1] "MSDUE15.csv"  "MSRUCDIS.csv" "MSRUCSTA.csv" "MSRUENR.csv"  "MSRUFNCL.csv"
 [6] "MSRUHC.csv"   "MSRUIND.csv"  "MSRUIT.csv"   "MSRUMAT.csv"  "MSRUTEL.csv" 
[11] "MSRUUTI.csv" 

walk - esempio pratico 3: creazione di report parametrici multipli

Per questo esempio dell’utilizzo delle funzioni walk, walk2 e pwalk è necessario caricare il package quarto in modo da compilare il report via riga di comando sfruttando la funzione quarto_render (l’equivalente della compilazione del report sfruttando il tasto Render dell’interfaccia):

library(quarto)

Consideriamo ai fini di questo esempio la tabella gapminder disponibile nel package gapminder:

library(gapminder)
str(gapminder)
tibble [1,704 × 6] (S3: tbl_df/tbl/data.frame)
 $ country  : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ year     : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
 $ lifeExp  : num [1:1704] 28.8 30.3 32 34 36.1 ...
 $ pop      : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880372 12881816 13867957 16317921 22227415 ...
 $ gdpPercap: num [1:1704] 779 821 853 836 740 ...
head(gapminder)
# A tibble: 6 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
6 Afghanistan Asia       1977    38.4 14880372      786.

e il semplice modello di report parametrico che è possibile scaricare da questo link.

La funzione quarto_render permette la compilazione (rendering) di un report Quarto sfruttando il codice: è l’equivalente del tasto render dell’interfaccia. Se non utilizziamo i parametri previsti nel modello di report parametrico, viene compilato il report con i valori di default presenti nella sezione YAML del file .qmd:

# NOTA: se vuoi provare questo comando scarica in locale il template del 
# del report parametrico
quarto_render(input = "lab_18-report-parametrico.qmd",
              output_format = "html",
              output_file = "report-parametrico-default.html")

Sfruttando la funzione quarto_render è possibile personalizzare il report passando in input i parametri di interesse del particolare report sfruttando l’argomento execute_params. Ecco un esempio di codice che permette di ottenere un report per l’anno 1952 per il continente Asia:

# NOTA: se vuoi provare questo comando scarica in locale il template del 
# del report parametrico
quarto_render(input = "lab_18-report-parametrico.qmd",
              execute_params = list(anno = 1952,
                                    continente = "Asia"),
              output_format = "html",
              output_file = "report-asia-1952.html")

Quanti sono i possibili report? Il numero di report che posso ottenere a partire da questo semplice modello dipendono dal numero di anni distinti presenti nella tabella. Utilizziamo la funzione pull() per ottenere un output di tipo atomico:

anni <- gapminder::gapminder |> distinct(year) |> pull()
anni
 [1] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007

e dal numero di continenti:

continenti <- gapminder::gapminder |> distinct(continent) |> pull()
continenti
[1] Asia     Europe   Africa   Americas Oceania 
Levels: Africa Americas Asia Europe Oceania

Poichè i continenti sono codificati come una variabile di tipo factor e il parametro del report parametrico è di tipo stringa converto il vettore atomico in un vettore di caratteri:

continenti <- gapminder::gapminder |> 
  distinct(continent) |> 
  pull() |> 
  as.character()
continenti
[1] "Asia"     "Europe"   "Africa"   "Americas" "Oceania" 

Il numero di possibili report è pari al prodotto tra il numero di continenti:

length(continenti)
[1] 5

e il numero di anni presenti nella tabella dati:

length(anni)
[1] 12

Posso ottenere una tabella che riporta tutti i possibili incroci sfruttando la funzione expand_grid passando in input i due vettori atomici:

incroci <- expand_grid(continenti, anni)
incroci
# A tibble: 60 × 2
   continenti  anni
   <chr>      <int>
 1 Asia        1952
 2 Asia        1957
 3 Asia        1962
 4 Asia        1967
 5 Asia        1972
 6 Asia        1977
 7 Asia        1982
 8 Asia        1987
 9 Asia        1992
10 Asia        1997
# ℹ 50 more rows
nrow(incroci)
[1] 60

Sarebbe possibile ottenere l’insieme di tutti i possibili incroci sfruttando il join incrociato (cross_join):

cross_join(distinct(gapminder, continent),
           distinct(gapminder, year))
# A tibble: 60 × 2
   continent  year
   <fct>     <int>
 1 Asia       1952
 2 Asia       1957
 3 Asia       1962
 4 Asia       1967
 5 Asia       1972
 6 Asia       1977
 7 Asia       1982
 8 Asia       1987
 9 Asia       1992
10 Asia       1997
# ℹ 50 more rows

Possiamo costruire i report per un dato continente per tutti i possibili anni. Posso in particolare costruire un vettore con i nomi dei file di output desiderati

nomi_file <- paste0("report-Europa-", anni, ".html")
nomi_file
 [1] "report-Europa-1952.html" "report-Europa-1957.html"
 [3] "report-Europa-1962.html" "report-Europa-1967.html"
 [5] "report-Europa-1972.html" "report-Europa-1977.html"
 [7] "report-Europa-1982.html" "report-Europa-1987.html"
 [9] "report-Europa-1992.html" "report-Europa-1997.html"
[11] "report-Europa-2002.html" "report-Europa-2007.html"

e sfruttare la funzione walk2 (o map2):

# NOTA: se vuoi provare questo comando scarica in locale il template del 
# del report parametrico
walk2(.x = anni, 
      .y = nomi_file,
      .f = ~quarto_render(input = "lab_18-report-parametrico.qmd",
                          execute_params = list(anno = .x,
                                                continente = "Europe"),
                          output_format = "html",
                          output_file = .y))

Per ottenere i report per tutti i possibili incroci sfrutto la funzione expand_grid aggiungendo prima di una colonna con i nomi dei file di output:

incroci <- expand_grid(continenti, anni) |> 
  mutate(nomi = paste0("report-", continenti, "-", anni, ".html"))
incroci
# A tibble: 60 × 3
   continenti  anni nomi                 
   <chr>      <int> <chr>                
 1 Asia        1952 report-Asia-1952.html
 2 Asia        1957 report-Asia-1957.html
 3 Asia        1962 report-Asia-1962.html
 4 Asia        1967 report-Asia-1967.html
 5 Asia        1972 report-Asia-1972.html
 6 Asia        1977 report-Asia-1977.html
 7 Asia        1982 report-Asia-1982.html
 8 Asia        1987 report-Asia-1987.html
 9 Asia        1992 report-Asia-1992.html
10 Asia        1997 report-Asia-1997.html
# ℹ 50 more rows

Possiamo sfruttare la funzione pwalk (o pmap) per ottenere tutti i report. Ecco un esempio di pmap: creazione un vettore con i tre elementi presenti su ogni riga della tabella incroci:

pmap(incroci, 
    ~c(a = ..1, b = ..2, c = ..3))
[[1]]
                      a                       b                       c 
                 "Asia"                  "1952" "report-Asia-1952.html" 

[[2]]
                      a                       b                       c 
                 "Asia"                  "1957" "report-Asia-1957.html" 

[[3]]
                      a                       b                       c 
                 "Asia"                  "1962" "report-Asia-1962.html" 

[[4]]
                      a                       b                       c 
                 "Asia"                  "1967" "report-Asia-1967.html" 

[[5]]
                      a                       b                       c 
                 "Asia"                  "1972" "report-Asia-1972.html" 

[[6]]
                      a                       b                       c 
                 "Asia"                  "1977" "report-Asia-1977.html" 

[[7]]
                      a                       b                       c 
                 "Asia"                  "1982" "report-Asia-1982.html" 

[[8]]
                      a                       b                       c 
                 "Asia"                  "1987" "report-Asia-1987.html" 

[[9]]
                      a                       b                       c 
                 "Asia"                  "1992" "report-Asia-1992.html" 

[[10]]
                      a                       b                       c 
                 "Asia"                  "1997" "report-Asia-1997.html" 

[[11]]
                      a                       b                       c 
                 "Asia"                  "2002" "report-Asia-2002.html" 

[[12]]
                      a                       b                       c 
                 "Asia"                  "2007" "report-Asia-2007.html" 

[[13]]
                        a                         b                         c 
                 "Europe"                    "1952" "report-Europe-1952.html" 

[[14]]
                        a                         b                         c 
                 "Europe"                    "1957" "report-Europe-1957.html" 

[[15]]
                        a                         b                         c 
                 "Europe"                    "1962" "report-Europe-1962.html" 

[[16]]
                        a                         b                         c 
                 "Europe"                    "1967" "report-Europe-1967.html" 

[[17]]
                        a                         b                         c 
                 "Europe"                    "1972" "report-Europe-1972.html" 

[[18]]
                        a                         b                         c 
                 "Europe"                    "1977" "report-Europe-1977.html" 

[[19]]
                        a                         b                         c 
                 "Europe"                    "1982" "report-Europe-1982.html" 

[[20]]
                        a                         b                         c 
                 "Europe"                    "1987" "report-Europe-1987.html" 

[[21]]
                        a                         b                         c 
                 "Europe"                    "1992" "report-Europe-1992.html" 

[[22]]
                        a                         b                         c 
                 "Europe"                    "1997" "report-Europe-1997.html" 

[[23]]
                        a                         b                         c 
                 "Europe"                    "2002" "report-Europe-2002.html" 

[[24]]
                        a                         b                         c 
                 "Europe"                    "2007" "report-Europe-2007.html" 

[[25]]
                        a                         b                         c 
                 "Africa"                    "1952" "report-Africa-1952.html" 

[[26]]
                        a                         b                         c 
                 "Africa"                    "1957" "report-Africa-1957.html" 

[[27]]
                        a                         b                         c 
                 "Africa"                    "1962" "report-Africa-1962.html" 

[[28]]
                        a                         b                         c 
                 "Africa"                    "1967" "report-Africa-1967.html" 

[[29]]
                        a                         b                         c 
                 "Africa"                    "1972" "report-Africa-1972.html" 

[[30]]
                        a                         b                         c 
                 "Africa"                    "1977" "report-Africa-1977.html" 

[[31]]
                        a                         b                         c 
                 "Africa"                    "1982" "report-Africa-1982.html" 

[[32]]
                        a                         b                         c 
                 "Africa"                    "1987" "report-Africa-1987.html" 

[[33]]
                        a                         b                         c 
                 "Africa"                    "1992" "report-Africa-1992.html" 

[[34]]
                        a                         b                         c 
                 "Africa"                    "1997" "report-Africa-1997.html" 

[[35]]
                        a                         b                         c 
                 "Africa"                    "2002" "report-Africa-2002.html" 

[[36]]
                        a                         b                         c 
                 "Africa"                    "2007" "report-Africa-2007.html" 

[[37]]
                          a                           b 
                 "Americas"                      "1952" 
                          c 
"report-Americas-1952.html" 

[[38]]
                          a                           b 
                 "Americas"                      "1957" 
                          c 
"report-Americas-1957.html" 

[[39]]
                          a                           b 
                 "Americas"                      "1962" 
                          c 
"report-Americas-1962.html" 

[[40]]
                          a                           b 
                 "Americas"                      "1967" 
                          c 
"report-Americas-1967.html" 

[[41]]
                          a                           b 
                 "Americas"                      "1972" 
                          c 
"report-Americas-1972.html" 

[[42]]
                          a                           b 
                 "Americas"                      "1977" 
                          c 
"report-Americas-1977.html" 

[[43]]
                          a                           b 
                 "Americas"                      "1982" 
                          c 
"report-Americas-1982.html" 

[[44]]
                          a                           b 
                 "Americas"                      "1987" 
                          c 
"report-Americas-1987.html" 

[[45]]
                          a                           b 
                 "Americas"                      "1992" 
                          c 
"report-Americas-1992.html" 

[[46]]
                          a                           b 
                 "Americas"                      "1997" 
                          c 
"report-Americas-1997.html" 

[[47]]
                          a                           b 
                 "Americas"                      "2002" 
                          c 
"report-Americas-2002.html" 

[[48]]
                          a                           b 
                 "Americas"                      "2007" 
                          c 
"report-Americas-2007.html" 

[[49]]
                         a                          b 
                 "Oceania"                     "1952" 
                         c 
"report-Oceania-1952.html" 

[[50]]
                         a                          b 
                 "Oceania"                     "1957" 
                         c 
"report-Oceania-1957.html" 

[[51]]
                         a                          b 
                 "Oceania"                     "1962" 
                         c 
"report-Oceania-1962.html" 

[[52]]
                         a                          b 
                 "Oceania"                     "1967" 
                         c 
"report-Oceania-1967.html" 

[[53]]
                         a                          b 
                 "Oceania"                     "1972" 
                         c 
"report-Oceania-1972.html" 

[[54]]
                         a                          b 
                 "Oceania"                     "1977" 
                         c 
"report-Oceania-1977.html" 

[[55]]
                         a                          b 
                 "Oceania"                     "1982" 
                         c 
"report-Oceania-1982.html" 

[[56]]
                         a                          b 
                 "Oceania"                     "1987" 
                         c 
"report-Oceania-1987.html" 

[[57]]
                         a                          b 
                 "Oceania"                     "1992" 
                         c 
"report-Oceania-1992.html" 

[[58]]
                         a                          b 
                 "Oceania"                     "1997" 
                         c 
"report-Oceania-1997.html" 

[[59]]
                         a                          b 
                 "Oceania"                     "2002" 
                         c 
"report-Oceania-2002.html" 

[[60]]
                         a                          b 
                 "Oceania"                     "2007" 
                         c 
"report-Oceania-2007.html" 

Ecco invece un esempio di pwalk: stampo i tre elementi presenti su ogni riga della tabella incroci:

pwalk(incroci, 
      ~print(paste0("El. 1 -> ", ..1, 
                    " | El. 2 -> ", ..2, 
                    "| El. 3 -> ", ..3)))
[1] "El. 1 -> Asia | El. 2 -> 1952| El. 3 -> report-Asia-1952.html"
[1] "El. 1 -> Asia | El. 2 -> 1957| El. 3 -> report-Asia-1957.html"
[1] "El. 1 -> Asia | El. 2 -> 1962| El. 3 -> report-Asia-1962.html"
[1] "El. 1 -> Asia | El. 2 -> 1967| El. 3 -> report-Asia-1967.html"
[1] "El. 1 -> Asia | El. 2 -> 1972| El. 3 -> report-Asia-1972.html"
[1] "El. 1 -> Asia | El. 2 -> 1977| El. 3 -> report-Asia-1977.html"
[1] "El. 1 -> Asia | El. 2 -> 1982| El. 3 -> report-Asia-1982.html"
[1] "El. 1 -> Asia | El. 2 -> 1987| El. 3 -> report-Asia-1987.html"
[1] "El. 1 -> Asia | El. 2 -> 1992| El. 3 -> report-Asia-1992.html"
[1] "El. 1 -> Asia | El. 2 -> 1997| El. 3 -> report-Asia-1997.html"
[1] "El. 1 -> Asia | El. 2 -> 2002| El. 3 -> report-Asia-2002.html"
[1] "El. 1 -> Asia | El. 2 -> 2007| El. 3 -> report-Asia-2007.html"
[1] "El. 1 -> Europe | El. 2 -> 1952| El. 3 -> report-Europe-1952.html"
[1] "El. 1 -> Europe | El. 2 -> 1957| El. 3 -> report-Europe-1957.html"
[1] "El. 1 -> Europe | El. 2 -> 1962| El. 3 -> report-Europe-1962.html"
[1] "El. 1 -> Europe | El. 2 -> 1967| El. 3 -> report-Europe-1967.html"
[1] "El. 1 -> Europe | El. 2 -> 1972| El. 3 -> report-Europe-1972.html"
[1] "El. 1 -> Europe | El. 2 -> 1977| El. 3 -> report-Europe-1977.html"
[1] "El. 1 -> Europe | El. 2 -> 1982| El. 3 -> report-Europe-1982.html"
[1] "El. 1 -> Europe | El. 2 -> 1987| El. 3 -> report-Europe-1987.html"
[1] "El. 1 -> Europe | El. 2 -> 1992| El. 3 -> report-Europe-1992.html"
[1] "El. 1 -> Europe | El. 2 -> 1997| El. 3 -> report-Europe-1997.html"
[1] "El. 1 -> Europe | El. 2 -> 2002| El. 3 -> report-Europe-2002.html"
[1] "El. 1 -> Europe | El. 2 -> 2007| El. 3 -> report-Europe-2007.html"
[1] "El. 1 -> Africa | El. 2 -> 1952| El. 3 -> report-Africa-1952.html"
[1] "El. 1 -> Africa | El. 2 -> 1957| El. 3 -> report-Africa-1957.html"
[1] "El. 1 -> Africa | El. 2 -> 1962| El. 3 -> report-Africa-1962.html"
[1] "El. 1 -> Africa | El. 2 -> 1967| El. 3 -> report-Africa-1967.html"
[1] "El. 1 -> Africa | El. 2 -> 1972| El. 3 -> report-Africa-1972.html"
[1] "El. 1 -> Africa | El. 2 -> 1977| El. 3 -> report-Africa-1977.html"
[1] "El. 1 -> Africa | El. 2 -> 1982| El. 3 -> report-Africa-1982.html"
[1] "El. 1 -> Africa | El. 2 -> 1987| El. 3 -> report-Africa-1987.html"
[1] "El. 1 -> Africa | El. 2 -> 1992| El. 3 -> report-Africa-1992.html"
[1] "El. 1 -> Africa | El. 2 -> 1997| El. 3 -> report-Africa-1997.html"
[1] "El. 1 -> Africa | El. 2 -> 2002| El. 3 -> report-Africa-2002.html"
[1] "El. 1 -> Africa | El. 2 -> 2007| El. 3 -> report-Africa-2007.html"
[1] "El. 1 -> Americas | El. 2 -> 1952| El. 3 -> report-Americas-1952.html"
[1] "El. 1 -> Americas | El. 2 -> 1957| El. 3 -> report-Americas-1957.html"
[1] "El. 1 -> Americas | El. 2 -> 1962| El. 3 -> report-Americas-1962.html"
[1] "El. 1 -> Americas | El. 2 -> 1967| El. 3 -> report-Americas-1967.html"
[1] "El. 1 -> Americas | El. 2 -> 1972| El. 3 -> report-Americas-1972.html"
[1] "El. 1 -> Americas | El. 2 -> 1977| El. 3 -> report-Americas-1977.html"
[1] "El. 1 -> Americas | El. 2 -> 1982| El. 3 -> report-Americas-1982.html"
[1] "El. 1 -> Americas | El. 2 -> 1987| El. 3 -> report-Americas-1987.html"
[1] "El. 1 -> Americas | El. 2 -> 1992| El. 3 -> report-Americas-1992.html"
[1] "El. 1 -> Americas | El. 2 -> 1997| El. 3 -> report-Americas-1997.html"
[1] "El. 1 -> Americas | El. 2 -> 2002| El. 3 -> report-Americas-2002.html"
[1] "El. 1 -> Americas | El. 2 -> 2007| El. 3 -> report-Americas-2007.html"
[1] "El. 1 -> Oceania | El. 2 -> 1952| El. 3 -> report-Oceania-1952.html"
[1] "El. 1 -> Oceania | El. 2 -> 1957| El. 3 -> report-Oceania-1957.html"
[1] "El. 1 -> Oceania | El. 2 -> 1962| El. 3 -> report-Oceania-1962.html"
[1] "El. 1 -> Oceania | El. 2 -> 1967| El. 3 -> report-Oceania-1967.html"
[1] "El. 1 -> Oceania | El. 2 -> 1972| El. 3 -> report-Oceania-1972.html"
[1] "El. 1 -> Oceania | El. 2 -> 1977| El. 3 -> report-Oceania-1977.html"
[1] "El. 1 -> Oceania | El. 2 -> 1982| El. 3 -> report-Oceania-1982.html"
[1] "El. 1 -> Oceania | El. 2 -> 1987| El. 3 -> report-Oceania-1987.html"
[1] "El. 1 -> Oceania | El. 2 -> 1992| El. 3 -> report-Oceania-1992.html"
[1] "El. 1 -> Oceania | El. 2 -> 1997| El. 3 -> report-Oceania-1997.html"
[1] "El. 1 -> Oceania | El. 2 -> 2002| El. 3 -> report-Oceania-2002.html"
[1] "El. 1 -> Oceania | El. 2 -> 2007| El. 3 -> report-Oceania-2007.html"

Sfruttando la funzione pwalk possiamo ottenere tutti i 60 report in modo automatico incrociando tutti i possibili valori di anno e continente (prime due colonne della tabella incroci) e passando i nomi dei file di output desiderati (terza colonna):

# NOTA: se vuoi provare questo comando scarica in locale il template del 
# del report parametrico
pwalk(incroci, 
      ~quarto_render(input = "lab_18-report-parametrico.qmd",
                     execute_params = list(anno = ..2,
                                           continente = ..1),
                     output_format = "html",
                     output_file = ..3))

# file creati automaticamente sfruttando la funzione pwalk
list.files(pattern = "report-*.html")

# sposto i file creati nella cartella report
# NOTA: creo la directory se non già presente sul disco 
dir.create("report", showWarnings = FALSE)
file.rename(from = list.files(pattern = "report-.*\\.html"),
            to = paste0("report/", list.files(pattern = "report-.*\\.html")))