# chiamata di funzione con passaggio degli argomenti per nome
log(10, 2)[1] 3.321928
# chiamata di funzione con passaggio degli argomenti per posizione
log(x = 10, base = 2)[1] 3.321928
janitor}|> e/o %>%)Nelle precedenti lezioni abbiamo visto la sintassi standard per la chiamata di una funzione in R:
# chiamata di funzione con passaggio degli argomenti per nome
log(10, 2)[1] 3.321928
# chiamata di funzione con passaggio degli argomenti per posizione
log(x = 10, base = 2)[1] 3.321928
A partire dalle recenti versioni di R è disponibile un operatore nativo (il pipe |>), che implementa funzionalità analoghe all’operatore pipe introdotto dal package magrittr ed ampiamente sfruttato nella creazione di catene di codice (pipeline) che rovesciano il punto di vista classico della chiamata di una funzione. In particolare, il primo operando del pipe diventa il primo argomento della funzione che è al secondo operando, consentendo così di creare una catena di comandi, o funzioni innestate, che permettono di scomporre un comando in piccoli pezzi senza dover creare variabili sul workspace.
L’equivalente (inutile) della chiamata della precedente funzione sfruttando il pipe è cioè:
# il primo operando del pipe (10) diventa il primo argomento della
# funzione al secondo operando (log)
10 |> log(2)[1] 3.321928
# per sfruttare il pipe del package magrittr è necessario caricarlo
# in memoria (nota: il package è automaticamente disponibile quando
# si carica tidyverse)
library(magrittr)
10 %>% log(2)[1] 3.321928
E’ possibile spostare la posizione di utilizzo del primo operando nella funzione al secondo operando usando un placeholder (segnaposto), che è differente nei due operatori pipe:
# usando _ indico al pipe nativo che il 2 deve essere usato come
# secondo argomento della funzione
2 |> log(10, base = _)[1] 3.321928
# comando analogo per il pipe di magrittr: in questo caso il
# segnaposto è il .
2 %>% log(10, base = .)[1] 3.321928
Oltre al carattere utilizzato come segnaposto ci sono ulteriori differenze tra il pipe nativo e il pipe di magrittr, che però sono apprezzabili sono in utilizzi avanzati di questo tipo di sintassi. Vale la pena solo evidenziare la seguente:
# calcolo la media (funzione al secondo operando) di una sequenza di
# numeri (ottenuta al primo operando) con il pipe nativo
seq(1, 10, by = 2) |> mean()[1] 5
# comando analogo sfruttando il pipe di magrittr
seq(1, 10, by = 2) %>% mean()[1] 5
# quando si utilizza il pipe di magrittr è possibile omettere le parentesi
# nella funzione al secondo membro nel caso in cui non siano presenti
# ulteriori argomenti oltre a quello passato con il %>%
# non è possibile lo stesso usando il pipe nativo
seq(1, 10, by = 2) %>% mean[1] 5
Nel caso degli esempi illustrativi sopra riportati il ricorso all’operatore pipe non è molto utile, ed anzi spesso può essere anche fuorviante. Questo tipo di operatore è invece molto comodo in diversi contesti di analisi di dati (in particolare per la data manipulation) che richiedono l’utilizzo di funzioni innestate che possono essere scomposte in una catena di chiamate di funzioni successive, come si vedrà anche nel seguito di questo tutorial.
La logica del pipe è cioè rovesciata. Consideriamo a titolo illustrativo il seguente esempio di una chiamata innestata di cinque funzioni:
f5(f4(f3(f2(f1(input)))))Questa chiamata viene risolta dall’interno verso l’esterno, e diventa articolata da leggere e da comporre soprattutto quando ciascuna delle funzioni utilizzate ha diversi argomenti in input. La stessa chiamata utilizzando il pipe può essere invece scritta direttamente seguendo il flusso di esecuzione del codice, ovvero:
input |> f1() |> f2() |> f3() |> f4() |> f5()consentendo così anche la scrittura del codice seguendo il flusso dell’algoritmo risolutivo del particolare problema che si sta affrontando, che parte in questo esempio dall’utilizzo della funzione f1() fino ad arrivare alla funzione f5().
janitor: un package per la fase di pulizia ed esplorazione dei datiIl package janitor è un package di servizio che contiene alcune funzioni che si rivelano molto utili nella fase di pulizia ed esplorazione dei dati. Il package contiene diverse funzioni:
library(janitor)
ls("package:janitor") [1] "%>%" "add_totals_col" "add_totals_row"
[4] "adorn_crosstab" "adorn_ns" "adorn_pct_formatting"
[7] "adorn_percentages" "adorn_rounding" "adorn_title"
[10] "adorn_totals" "as_tabyl" "chisq.test"
[13] "clean_names" "compare_df_cols" "compare_df_cols_same"
[16] "convert_to_date" "convert_to_datetime" "convert_to_NA"
[19] "crosstab" "describe_class" "excel_numeric_to_date"
[22] "find_header" "fisher.test" "get_dupes"
[25] "get_one_to_one" "make_clean_names" "remove_constant"
[28] "remove_empty" "remove_empty_cols" "remove_empty_rows"
[31] "round_half_up" "round_to_fraction" "row_to_names"
[34] "sas_numeric_to_date" "signif_half_up" "single_value"
[37] "tabyl" "top_levels" "untabyl"
[40] "use_first_valid_of"
anche se in questa pagina facciamo riferimento solo alle funzioni clean_names, per uniformare i nomi delle colonne di una tabella, e tabyl, per tabulare i dati.
tabyl: una funzione avanzata per il conteggioL’operazione di conteggio è la più semplice e comune operazione in qualunque analisi di dati. La funzione di base per il conteggio, table(), lascia però molto a desiderare per i seguenti aspetti:
data.frame o tibble, per cui diventa complesso sfruttare la sintesi basata sugli operatori pipe (|> oppure %>%)data.frame ma una classe particolare di oggetti tabletabyl() è un approccio per la tabulazione di variabili che cerca di rispondere a questi limiti. E’ contenuto nel package janitor: l’operazione di conteggio è una parte fondamentale della fase di pulizia ed esplorazione dei dati.
tabyl() è una funzione che risponde ai principi di tidyverse (prende una tabella in input e restituisce una tabella in output) ed è basata sui package dplyr e tidyr.
tabyl() produce apparentemente tabelle di frequenza ad 1, 2 o 3 variabili. In realtà dietro le quinte tabyl() inserisce una copia di questi conteggi come attributo al data.frame risultante. In altre parole, il risultato di tabyl sembra un data.frame di conteggi, ma è un oggetto di tipo tabyl che contiene queste informazioni come metadati, e questo consente di sfruttare le funzioni di tipo adorn_ (ornamento) per inserire informazioni aggiuntive ed ottenere una migliore formattazione della tabella di output.
Sebbene le funzioni adorn_ siano state progettate per lavorare su oggetti di tipo tabyl, sono disponibili anche versioni che lavorano allo stesso modo su oggetti di tipo data.frame.
Per mostrare tabyl (e clean_names) in azione sfruttiamo la tabella titanic disponibile in formato .xlsx su questo link
# carico i package utilizzati ---------------------------------------------
library(readxl) # per importare il file MS-Excel
library(tidyverse) # per i verbi di pulizia e manipolazione dei dati {dplyr}
library(janitor) # per sfruttare clean_names e tabyl
# carico e pulisco i dati -------------------------------------------------
# importo il foglio titanic dalla cartella MS-Excel
titanic <- read_excel(paste0(here::here(), "/data/", "titanic-excel.xlsx"), sheet = "titanic")
# nomi delle colonne (variabili) della tabella dati
names(titanic) [1] "PassengerId" "Survived" "Pclass" "Name" "Sex"
[6] "Age" "SibSp" "Parch" "Ticket" "Fare"
[11] "Cabin" "Embarked"
# pulisco i nomi delle colonne
titanic <- titanic |> clean_names()
# nomi delle colonne dopo averli uniformati
names(titanic) [1] "passenger_id" "survived" "pclass" "name" "sex"
[6] "age" "sib_sp" "parch" "ticket" "fare"
[11] "cabin" "embarked"
# ricodifico i dati usando descrittori testuali e rinomino la colonna pclass
titanic <- titanic |>
mutate(survived = recode(survived, "1" = "Sì", "0 " = "No"),
pclass = recode(pclass,
"1" = "prima", "2" = "seconda", "3" = "terza"),
embarked = recode(embarked,
"S" = "Southampthon", "Q" = "Queentown",
"C" = "Cherbourg")) |>
rename(class = pclass)one-way tabyl)La tabulazione di una singola variabile è l’operazione più semplice che si può effettuare con la funzione tabyl:
# tabella di frequenza della variabile class
tabyl(titanic, class) class n percent
prima 216 0.2424242
seconda 184 0.2065095
terza 491 0.5510662
# assegno la tabella di frequenza ad un oggetto sul workspace
t1 <- titanic |> tabyl(class)
t1 class n percent
prima 216 0.2424242
seconda 184 0.2065095
terza 491 0.5510662
# si tratta di un particolare data.frame
class(t1)[1] "tabyl" "data.frame"
# su cui sono definiti alcuni attributi utili alla successiva manipolazione
attributes(t1)$names
[1] "class" "n" "percent"
$class
[1] "tabyl" "data.frame"
$row.names
[1] 1 2 3
$core
class n percent
1 prima 216 0.2424242
2 seconda 184 0.2065095
3 terza 491 0.5510662
$tabyl_type
[1] "one_way"
In caso di valori NA, tabyl() visualizza anche le percentuali “valide”, vale a dire le percentuali calcolate sui solo valori validi. Come accennato in precedenza, anche se tabyl() è progettato in maniera da considerare in input una tabella data (data.frame o tibble) e i corrispondenti nomi delle colonne da tabulare, è possibile anche passare come input un vettore per ottenere la corrispondenza tabella di frequenza univariata:
# creo un semplice vettore con un valore mancante
x <- c("basso", "basso", "alto", "alto", "alto", NA)
# e ne calcolo la tabella di frequenza
tabyl(x) x n percent valid_percent
alto 3 0.5000000 0.6
basso 2 0.3333333 0.4
<NA> 1 0.1666667 NA
# tabella di frequenza scegliendo di non visualizzare i valori mancanti
# che comunque non sono considerati nel calcolo delle percentuali
tabyl(x, show_na = FALSE) x n percent
alto 3 0.6
basso 2 0.4
Le funzioni ausiliare adorn_ sono progettate per tabyl a due vie, ma alcune possono essere utilizzate anche su tabelle univariate, ed in particolare adorn_totals() e adorn_pct_formatting():
# aggiungo una riga con i totali
t1 |> adorn_totals() class n percent
prima 216 0.2424242
seconda 184 0.2065095
terza 491 0.5510662
Total 891 1.0000000
# l'argomento per gestire come calcolare i totali è where
# per default calcola i totali di colonna (aggiunge cioè una riga)
t1 |> adorn_totals(where = "row") class n percent
prima 216 0.2424242
seconda 184 0.2065095
terza 491 0.5510662
Total 891 1.0000000
# oltre ai totali formatto adeguatamente la colonna delle percentuali
t1 |>
adorn_totals(where = "row") |>
adorn_pct_formatting() class n percent
prima 216 24.2%
seconda 184 20.7%
terza 491 55.1%
Total 891 100.0%
two-way tabyl)Le tabelle a doppia entrata, dette anche crosstab o tabelle di contingenza, si ottengono passando a tabyl un data.frame e le due colonne su cui effettuare la tabulazione:
# calcolo una tabella a doppia entrata
t2 <- titanic %>%
tabyl(class, survived)
# e la stampo a video
t2 class No Sì
prima 80 136
seconda 97 87
terza 372 119
Si ottiene lo stesso risultato combinando le funzioni dplyr::count() e tidyr::pivot_wider().
Per dettagli vedi le lezioni successive.
Poichè si tratta di un oggetto di tipo tabyl è possibile sfruttare le funzioni ausiliarie per arricchirlo in termini informativi:
# se utilizzo il valore di default della funzione adorn_totals
# viene aggiunta una nuova riga con i totali di colonna
t2 |> adorn_totals() class No Sì
prima 80 136
seconda 97 87
terza 372 119
Total 549 342
# posso aggiungere una nuova colonna con i totali di riga
t2 |> adorn_totals(where = "col") class No Sì Total
prima 80 136 216
seconda 97 87 184
terza 372 119 491
# così come posso aggiungere i totali su entrambe le dimensioni
t2 |> adorn_totals(where = c("row", "col")) class No Sì Total
prima 80 136 216
seconda 97 87 184
terza 372 119 491
Total 549 342 891
# posso calcolare le frequenze percentuali in base al gran totale
t2 |>
adorn_totals(where = c("row", "col")) |>
adorn_percentages(denominator = "all") class No Sì Total
prima 0.08978676 0.1526375 0.2424242
seconda 0.10886644 0.0976431 0.2065095
terza 0.41750842 0.1335578 0.5510662
Total 0.61616162 0.3838384 1.0000000
# posso calcolare le percentuali utilizzando come denominatori i totali di
# riga: distribuzioni percentuali della variabile in colonna condizionate
# sui vari livelli della variabile in riga (profili riga)
t2 |>
adorn_totals(where = c("row", "col")) |>
adorn_percentages(denominator = "row") class No Sì Total
prima 0.3703704 0.6296296 1
seconda 0.5271739 0.4728261 1
terza 0.7576375 0.2423625 1
Total 0.6161616 0.3838384 1
# oppure le percentuali utilizzando come denominatori i totali di colonna:
# distribuzioni percentuali della variabile in riga condizionate
# sui vari livelli della variabile in colonna (profili colonna)
t2 |>
adorn_totals(where = c("row", "col")) |>
adorn_percentages(denominator = "col") class No Sì Total
prima 0.1457195 0.3976608 0.2424242
seconda 0.1766849 0.2543860 0.2065095
terza 0.6775956 0.3479532 0.5510662
Total 1.0000000 1.0000000 1.0000000
# una volta colcolate le frequenze percentuali di interesse posso procedere
# alla formattazione
t2 |>
adorn_totals(where = c("row", "col")) |>
adorn_percentages(denominator = "row") |>
adorn_pct_formatting(digits = 2) class No Sì Total
prima 37.04% 62.96% 100.00%
seconda 52.72% 47.28% 100.00%
terza 75.76% 24.24% 100.00%
Total 61.62% 38.38% 100.00%
# e posso arricchire la tabella aggiungendo in ogni cella anche le
# le corrispondenti frequenze assolute
t2 |>
adorn_totals(where = c("row", "col")) |>
adorn_percentages("row") |>
adorn_pct_formatting(digits = 2) |>
adorn_ns() class No Sì Total
prima 37.04% (80) 62.96% (136) 100.00% (216)
seconda 52.72% (97) 47.28% (87) 100.00% (184)
terza 75.76% (372) 24.24% (119) 100.00% (491)
Total 61.62% (549) 38.38% (342) 100.00% (891)
Le funzioni di ornamento hanno una serie di opzioni che permettono di controllare gli assi, le funzioni di arrotondamento ed altre scelte relative alla formattazione (vedi sotto per dettagli in merito).
two-way tabyl)Così come la funzione table() lavora su t La funzione tabyl(), allo stesso modo della funzione table(), può lavorare anche con una terza variabile in input. In questo caso tabyl() restituisce una lista di tabyl:
# un esempio di tabella a tre vie
t3 <- titanic |> tabyl(class, survived, sex)
# il risultato è una tabella doppia divisa in una lista sui
# livelli della terza variabile
t3 $female
class No Sì
prima 3 91
seconda 6 70
terza 72 72
$male
class No Sì
prima 77 45
seconda 91 17
terza 300 47
# si tratta di un oggetto di tipo lista
class(t3)[1] "list"
# i cui elementi sono due tabyl bivariati
class(t3$female)[1] "tabyl" "data.frame"
class(t3$male)[1] "tabyl" "data.frame"
Nel caso in cui le funzioni ausiliarie adorn_ sono chiamate su una lista di data.frame, come nel caso appunto di una tabyl a tre vie, sfruttando la funzione purrr::map() lavorano automaticamente come se fossero applicate (mapping) a ciacun data.frame che compone la lista:
t3 |>
adorn_totals("row") |>
adorn_percentages("all") |>
adorn_pct_formatting(digits = 1) |>
adorn_ns() |>
adorn_title()$female
survived
class No Sì
prima 1.0% (3) 29.0% (91)
seconda 1.9% (6) 22.3% (70)
terza 22.9% (72) 22.9% (72)
Total 25.8% (81) 74.2% (233)
$male
survived
class No Sì
prima 13.3% (77) 7.8% (45)
seconda 15.8% (91) 2.9% (17)
terza 52.0% (300) 8.1% (47)
Total 81.1% (468) 18.9% (109)
Questa “mappatura” (mapping) automatica permette all’utente di riutilizzare il codice relativo alle funzioni di ornamento anche se cambia le variabili di cui richiede la tabulazione. Se ad esempio effettua la tabulazione di tre variabili tab_dati |> tabyl(var1, var2, var3) e a questa tabella aggiunge le chiamate delle funzioni adorn_, se modifica le variabili in tabulazione cambiando la parte iniziale del comando, ad esempio tab_dati |> tabyl(var1, var2, var4), non deve riscrivere le successive chiamate delle funzioni adorn_, che sfruttando la funzione map() si adattano automaticamente alla nuova tabella.
E’ possibile sfruttare anche la sintassi esplicita richiamando map() sulla lista di data.frame, map(t3, adorn_percentages), oppure utilizzare la funzione di base lapply(), lapply(t3, adorn_percentages).
Per dettagli sulle funzioni lapply e map() e sul package purr() si rimanda ad una lezione successiva.
tabylfactor, tabyl mostra tra i risultati anche i “missing levels” (livelli non presenti nei dati). Questa opzione di default può però essere cambiata:# creo una variabile factor con tre livelli ma con soli due valori presenti
var_factor <- factor(c("alto", "basso", "alto", "basso", "alto"),
levels = c("basso", "medio", "alto"))
# per default nella tabella in output vengono mostrati tutti i livelli del factor
var_factor |> tabyl() var_factor n percent
basso 2 0.4
medio 0 0.0
alto 3 0.6
# posso però nascondere i livelli non presenti nei dati
var_factor |> tabyl(show_missing_levels = FALSE) var_factor n percent
basso 2 0.4
alto 3 0.6
NA, che possono essere gestisti sfruttando l’argomento show_natabyls non visualizza i numeri di rigachisq.test() e fisher.test() su un oggetto tabyl a due vie per eseguire i corrispondenti test statistici, così come per un tradizionale oggetto table()adorn_*These modular functions build on a tabyl to approximate the functionality of a PivotTable in Microsoft Excel. They print elegant results for interactive analysis or for sharing in a report, e.g., with knitr::kable(). For example:
Le funzioni di ornamento sono funzioni modulari che sono progettate per ottenere funzionalità simili a quelle di una tabella pivot in Microsoft Excel. E’ naturalmente possibile sfruttare le funzioni di formattazione di una tabella per stamparla in un report in formato conveniente. Ecco un esempio sfruttando la funzione knitr::kable():
t2 |>
adorn_totals(c("row", "col")) |>
adorn_percentages("row") |>
adorn_pct_formatting(rounding = "half up", digits = 0) |>
adorn_ns() |>
adorn_title("combined") |>
knitr::kable()| class/survived | No | Sì | Total |
|---|---|---|---|
| prima | 37% (80) | 63% (136) | 100% (216) |
| seconda | 53% (97) | 47% (87) | 100% (184) |
| terza | 76% (372) | 24% (119) | 100% (491) |
| Total | 62% (549) | 38% (342) | 100% (891) |
Le funzioni adorn_ (ornamento) disponibili sono:
adorn_totals(): per aggiungere totali di riga, colonna o su entrambe le dimensioniadorn_percentages(): per calcolare le percentuali lungo ciascuna dimensione o sull’intera tabella (gran totale)adorn_pct_formatting(): per formattare le colonne contenenti le percentuali, controllando sia il numero di cifre decimali che la posizione del simbolo %adorn_rounding(): per arrotondare un data.frame di numeri (di solito il risultato di adorn_percentages), utilizzando la funzione base R round() o utilizzando round_half_up() di janitor per arrotondare per eccesso tutti i valori con decimale 0.5, così come succede in MS-Excel
round(10.5) in R base restituisce 10, mentre round_half_up(10.5) restituisce 11.adorn_rounding() restituisce colonne di classe numeric, che sono quindi utilizzabili ai fini di rappresentazioni grafiche e successive manipolazioni. Si tratta di un’alternativa meno aggressiva della funzione adorn_pct_formatting(). Queste due funzioni non dovrebbero pertanto essere utilizate insieme in una pipeline di codice.adorn_ns(): per aggiungere i conteggi (frequenze assolute) ad una tabella. Questi possono essere ricavati dai conteggi sottostanti all’oggetto tabyl, che sono allegati al tabyl come metadati, oppure possono essere forniti in input dall’utente.adorn_title(): per aggiungere un titolo ad un tabyl (o ad un altro tipo di data.frame). Le opzioni disponibili includono l’inserimento del nome di colonna in una nuova riga in testa al data.frame o combinando i nomi di riga e colonna nella prima cella del data.frame.NOTA: queste funzioni di ornamento dovrebbero essere chiamate in ordine logico (ad esempio aggiungendo prima i totali e poi calcolando le percentuali) per ottenere risultati coerenti.