here::here("code", "_common.R") |> source()
# Load packages
if (!requireNamespace("pacman")) install.packages("pacman")
pacman::p_load(tidyr, mice, missForest)
10 Introduzione a dplyr
10.1 Introduzione
L’obiettivo di questo capitolo è fornire un’introduzione alle funzioni principali del pacchetto dplyr
per le operazioni di data wrangling, cioè per il preprocessing e la pulizia dei dati. In R, queste operazioni sono strettamente legate al concetto di “data tidying”, che si riferisce all’organizzazione sistematica dei dati per facilitare l’analisi.
Per comprendere meglio il concetto di “data tidying”, possiamo rifarci a una citazione tratta dal testo di riferimento R for Data Science (2e):
“Happy families are all alike; every unhappy family is unhappy in its own way.” — Leo Tolstoy
“Tidy datasets are all alike, but every messy dataset is messy in its own way.” — Hadley Wickham
L’essenza del “data tidying” è organizzare i dati in un formato che sia facile da gestire e analizzare. Anche se gli stessi dati possono essere rappresentati in vari modi, non tutte le rappresentazioni sono ugualmente efficienti o facili da usare. Un dataset “tidy” segue tre principi fondamentali che lo rendono particolarmente pratico:
- Ogni variabile è una colonna: ogni colonna nel dataset rappresenta una singola variabile.
- Ogni osservazione è una riga: ogni riga nel dataset rappresenta un’unica osservazione.
- Ogni valore è una cella: ogni cella del dataset contiene un singolo valore.
Il pacchetto R {dplyr} e gli altri pacchetti del tidyverse sono progettati specificamente per lavorare con dati in formato “tidy”, permettendo agli utenti di eseguire operazioni di manipolazione e visualizzazione in modo più intuitivo ed efficiente.
10.2 Pipe
Il pacchetto dplyr
, così come l’intero ecosistema tidyverse
, fa largo uso dell’operatore pipe, che consente di concatenare una sequenza di operazioni in modo leggibile ed efficiente. In R, esistono due principali notazioni per il pipe:
-
|>
: introdotto nativamente a partire dalla versione 4.1.0 di R. -
%>%
: introdotto dal pacchettomagrittr
, ed è una delle componenti centrali deltidyverse
.
Entrambi gli operatori permettono di ottenere risultati simili e, per la maggior parte degli utilizzi, possono essere considerati intercambiabili. Tuttavia, è importante sottolineare alcune differenze:
-
|>
è integrato nel linguaggio R e non richiede pacchetti aggiuntivi. -
%>%
, essendo parte dimagrittr
, richiede che il pacchetto sia installato e caricato (library(magrittr)
o automaticamente tramitetidyverse
).
Consideriamo l’esempio seguente (che anticipa l’uso della funzione filter()
che descriveremo in seguito). Un’operazione comune è filtrare un data frame e calcolare la media di una colonna. Con il pipe, questa sequenza di operazioni diventa più leggibile:
10.2.1 Cosa Fa la Pipe?
La pipe è uno strumento potente che permette di collegare in modo diretto l’output di una funzione come input della funzione successiva. Questo approccio:
- Riduce la necessità di creare variabili intermedie.
- Migliora la leggibilità del codice.
- Rende il flusso delle operazioni più chiaro e lineare.
Ogni funzione applicata con la pipe riceve automaticamente l’output della funzione precedente come suo primo argomento. Ciò consente di scrivere sequenze di operazioni in un formato compatto e intuitivo.
Ecco un altro esempio:
# Utilizzo della pipe per trasformare un dataset
df <- data.frame(
id = 1:5,
value = c(10, 20, 30, 40, 50)
)
# Filtra i dati, seleziona colonne e calcola nuovi valori
df_clean <- df |>
dplyr::filter(value > 20) |>
dplyr::select(id, value) |>
mutate(squared_value = value^2)
In questa sequenza, il dataset originale df
viene filtrato, le colonne desiderate vengono selezionate e viene aggiunta una nuova colonna con il valore al quadrato.
head(df_clean)
#> id value squared_value
#> 1 3 30 900
#> 2 4 40 1600
#> 3 5 50 2500
In sintesi, la pipe è uno strumento fondamentale per scrivere codice R moderno e leggibile, indipendentemente dal fatto che si utilizzi |>
o %>%
.
10.3 Verbi
Le funzioni principali (“verbi) di dplyr
sono le seguenti:
Verbo dplyr | Descrizione |
---|---|
select() |
Seleziona colonne |
filter() |
Filtra righe |
arrange() |
Riordina o organizza le righe |
mutate() |
Crea nuove colonne |
summarise() |
Riassume i valori |
group_by() |
Consente di eseguire operazioni di gruppo |
I verbi di dplyr
sono suddivisi in quattro gruppi, in base all’elemento su cui operano: righe, colonne, gruppi o tabelle.
Inoltre, le diverse funzioni bind_
e _joins
permettono di combinare più tibbles (ovvero, data frame) in uno solo.
Per fare un esempio prarico, usiamo nuovamente il dataset msleep
.
Esaminiamo i dati:
glimpse(msleep)
#> Rows: 83
#> Columns: 11
#> $ name <chr> "Cheetah", "Owl monkey", "Mountain beaver", "Greater …
#> $ genus <chr> "Acinonyx", "Aotus", "Aplodontia", "Blarina", "Bos", …
#> $ vore <chr> "carni", "omni", "herbi", "omni", "herbi", "herbi", "…
#> $ order <chr> "Carnivora", "Primates", "Rodentia", "Soricomorpha", …
#> $ conservation <chr> "lc", NA, "nt", "lc", "domesticated", NA, "vu", NA, "…
#> $ sleep_total <dbl> 12.1, 17.0, 14.4, 14.9, 4.0, 14.4, 8.7, 7.0, 10.1, 3.…
#> $ sleep_rem <dbl> NA, 1.8, 2.4, 2.3, 0.7, 2.2, 1.4, NA, 2.9, NA, 0.6, 0…
#> $ sleep_cycle <dbl> NA, NA, NA, 0.1333, 0.6667, 0.7667, 0.3833, NA, 0.333…
#> $ awake <dbl> 11.9, 7.0, 9.6, 9.1, 20.0, 9.6, 15.3, 17.0, 13.9, 21.…
#> $ brainwt <dbl> NA, 0.01550, NA, 0.00029, 0.42300, NA, NA, NA, 0.0700…
#> $ bodywt <dbl> 50.000, 0.480, 1.350, 0.019, 600.000, 3.850, 20.490, …
Le colonne, nell’ordine, corrispondono a quanto segue:
Nome colonna | Descrizione |
---|---|
name | Nome comune |
genus | Rango tassonomico |
vore | Carnivoro, onnivoro o erbivoro? |
order | Rango tassonomico |
conservation | Stato di conservazione del mammifero |
sleep_total | Quantità totale di sonno, in ore |
sleep_rem | Sonno REM, in ore |
sleep_cycle | Durata del ciclo di sonno, in ore |
awake | Quantità di tempo trascorso sveglio, in ore |
brainwt | Peso del cervello, in chilogrammi |
bodywt | Peso corporeo, in chilogrammi |
10.4 Righe
I verbi più importanti che operano sulle righe di un dataset sono filter()
, che seleziona le righe da includere senza modificarne l’ordine, e arrange()
, che cambia l’ordine delle righe senza alterare la selezione delle righe presenti.
msleep |>
dplyr::filter(sleep_total < 4) |>
arrange(sleep_total)
#> # A tibble: 9 × 11
#> name genus vore order conservation
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Giraffe Giraffa herbi Artiodactyla cd
#> 2 Pilot whale Globicephalus carni Cetacea cd
#> 3 Horse Equus herbi Perissodactyla domesticated
#> 4 Roe deer Capreolus herbi Artiodactyla lc
#> 5 Donkey Equus herbi Perissodactyla domesticated
#> 6 African elephant Loxodonta herbi Proboscidea vu
#> 7 Caspian seal Phoca carni Carnivora vu
#> 8 Sheep Ovis herbi Artiodactyla domesticated
#> 9 Asian elephant Elephas herbi Proboscidea en
#> sleep_total sleep_rem sleep_cycle awake brainwt bodywt
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1.9 0.4 NA 22.1 NA 900.
#> 2 2.7 0.1 NA 21.4 NA 800
#> 3 2.9 0.6 1 21.1 0.655 521
#> 4 3 NA NA 21 0.0982 14.8
#> 5 3.1 0.4 NA 20.9 0.419 187
#> 6 3.3 NA NA 20.7 5.71 6654
#> 7 3.5 0.4 NA 20.5 NA 86
#> 8 3.8 0.6 NA 20.2 0.175 55.5
#> 9 3.9 NA NA 20.1 4.60 2547
Possiamo usare filter()
speficicano più di una condizione logica.
msleep |>
dplyr::filter((sleep_total < 4 & bodywt > 100) | brainwt > 1) |>
arrange(sleep_total)
#> # A tibble: 7 × 11
#> name genus vore order conservation
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Giraffe Giraffa herbi Artiodactyla cd
#> 2 Pilot whale Globicephalus carni Cetacea cd
#> 3 Horse Equus herbi Perissodactyla domesticated
#> 4 Donkey Equus herbi Perissodactyla domesticated
#> 5 African elephant Loxodonta herbi Proboscidea vu
#> 6 Asian elephant Elephas herbi Proboscidea en
#> 7 Human Homo omni Primates <NA>
#> sleep_total sleep_rem sleep_cycle awake brainwt bodywt
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1.9 0.4 NA 22.1 NA 900.
#> 2 2.7 0.1 NA 21.4 NA 800
#> 3 2.9 0.6 1 21.1 0.655 521
#> 4 3.1 0.4 NA 20.9 0.419 187
#> 5 3.3 NA NA 20.7 5.71 6654
#> 6 3.9 NA NA 20.1 4.60 2547
#> 7 8 1.9 1.5 16 1.32 62
10.5 Colonne
Esistono quattro verbi principali che modificano le colonne di un dataset senza cambiare le righe:
-
relocate()
cambia la posizione delle colonne; -
rename()
modifica i nomi delle colonne; -
select()
seleziona le colonne da includere o escludere; -
mutate()
crea nuove colonne a partire da quelle esistenti.
msleep2 <- msleep |>
mutate(
rem_prop = sleep_rem / sleep_total * 100
) |>
dplyr::select(name, vore, rem_prop, sleep_total) |>
arrange(desc(rem_prop))
glimpse(msleep2)
#> Rows: 83
#> Columns: 4
#> $ name <chr> "European hedgehog", "Thick-tailed opposum", "Giant ar…
#> $ vore <chr> "omni", "carni", "insecti", "omni", "carni", "omni", "…
#> $ rem_prop <dbl> 34.65, 34.02, 33.70, 29.21, 28.71, 27.22, 26.37, 26.21…
#> $ sleep_total <dbl> 10.1, 19.4, 18.1, 8.9, 10.1, 18.0, 9.1, 10.3, 12.5, 8.…
In questo esempio, utilizziamo mutate()
per creare una nuova colonna rem_prop
che rappresenta la percentuale di sonno REM sul totale del sonno. Successivamente, select()
viene utilizzato per scegliere solo alcune colonne del dataset, e infine desc(rem_prop)
ordina i valori di rem_prop
in ordine decrescente, dal valore maggiore a quello minore.
Per cambiare il nome di una colonna possiamo usare rename()
. Inoltre, possiamo cambiare l’ordine delle variabili con relocate()
.
msleep2 |>
rename(rem_perc = rem_prop) |>
relocate(rem_perc, .before = name)
#> # A tibble: 83 × 4
#> rem_perc name vore sleep_total
#> <dbl> <chr> <chr> <dbl>
#> 1 34.7 European hedgehog omni 10.1
#> 2 34.0 Thick-tailed opposum carni 19.4
#> 3 33.7 Giant armadillo insecti 18.1
#> 4 29.2 Tree shrew omni 8.9
#> 5 28.7 Dog carni 10.1
#> 6 27.2 North American Opossum omni 18
#> 7 26.4 Pig omni 9.1
#> 8 26.2 Desert hedgehog <NA> 10.3
#> 9 25.6 Domestic cat carni 12.5
#> 10 25 Eastern american mole insecti 8.4
#> # ℹ 73 more rows
10.6 Gruppi
Il verbo group_by()
viene utilizzato per suddividere un dataset in gruppi, in base a una o più variabili, che siano rilevanti per l’analisi. Questo permette di eseguire operazioni di sintesi su ciascun gruppo separatamente, ottenendo informazioni aggregate.
Ad esempio, nel codice seguente:
msleep |>
group_by(order) |>
summarise(
avg_sleep = mean(sleep_total),
min_sleep = min(sleep_total),
max_sleep = max(sleep_total),
total = n()
) |>
arrange(desc(avg_sleep))
#> # A tibble: 19 × 5
#> order avg_sleep min_sleep max_sleep total
#> <chr> <dbl> <dbl> <dbl> <int>
#> 1 Chiroptera 19.8 19.7 19.9 2
#> 2 Didelphimorphia 18.7 18 19.4 2
#> 3 Cingulata 17.8 17.4 18.1 2
#> 4 Afrosoricida 15.6 15.6 15.6 1
#> 5 Pilosa 14.4 14.4 14.4 1
#> 6 Rodentia 12.5 7 16.6 22
#> 7 Diprotodontia 12.4 11.1 13.7 2
#> 8 Soricomorpha 11.1 8.4 14.9 5
#> 9 Primates 10.5 8 17 12
#> 10 Erinaceomorpha 10.2 10.1 10.3 2
#> 11 Carnivora 10.1 3.5 15.8 12
#> 12 Scandentia 8.9 8.9 8.9 1
#> 13 Monotremata 8.6 8.6 8.6 1
#> 14 Lagomorpha 8.4 8.4 8.4 1
#> 15 Hyracoidea 5.67 5.3 6.3 3
#> 16 Artiodactyla 4.52 1.9 9.1 6
#> 17 Cetacea 4.5 2.7 5.6 3
#> 18 Proboscidea 3.6 3.3 3.9 2
#> 19 Perissodactyla 3.47 2.9 4.4 3
group_by(order)
suddivide il datasetmsleep
in gruppi, ciascuno corrispondente a un valore distinto della variabileorder
.-
Successivamente,
summarise()
calcola diverse statistiche per ogni gruppo:-
avg_sleep
è la media del totale del sonno (sleep_total
) all’interno di ciascun gruppo. -
min_sleep
è il valore minimo disleep_total
in ogni gruppo. -
max_sleep
è il valore massimo disleep_total
in ogni gruppo. -
total
è il numero di osservazioni (o righe) per ciascun gruppo, calcolato con la funzionen()
.
-
Infine,
arrange(desc(avg_sleep))
ordina i risultati in ordine decrescente in base alla media del sonno totale (avg_sleep
), mostrando prima i gruppi con la media di sonno più alta.
Questo tipo di approccio è utile quando si vuole analizzare come cambiano le caratteristiche dei dati a seconda dei gruppi specifici, fornendo una visione più dettagliata e utile.
10.7 Considerazioni Conclusive
Il data wrangling è una delle fasi più importanti in qualsiasi pipeline di analisi dei dati. In questo capitolo abbiamo introdotto l’uso del pacchetto tidyverse
di R per la manipolazione dei dati e il suo utilizzo in scenari di base. Tuttavia, il tidyverse è un ecosistema ampio e qui abbiamo trattato solo gli elementi fondamentali. Per approfondire, si consiglia di consultare ulteriori risorse come quelle disponibili sul sito web del tidyverse e il libro R for Data Science (2e), di cui esiste anche una traduzione italiana.
10.8 Esercizi
Informazioni sull’Ambiente di Sviluppo
sessionInfo()
#> R version 4.5.0 (2025-04-11)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.5
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
#>
#> locale:
#> [1] C/UTF-8/C/C/C/C
#>
#> time zone: Europe/Rome
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] missForest_1.5 mice_3.17.0 thematic_0.1.6 MetBrewer_0.2.0
#> [5] ggokabeito_0.1.0 see_0.11.0 gridExtra_2.3 patchwork_1.3.0
#> [9] bayesplot_1.12.0 psych_2.5.3 scales_1.4.0 markdown_2.0
#> [13] knitr_1.50 lubridate_1.9.4 forcats_1.0.0 stringr_1.5.1
#> [17] dplyr_1.1.4 purrr_1.0.4 readr_2.1.5 tidyr_1.3.1
#> [21] tibble_3.2.1 ggplot2_3.5.2 tidyverse_2.0.0 rio_1.2.3
#> [25] here_1.0.1
#>
#> loaded via a namespace (and not attached):
#> [1] tidyselect_1.2.1 farver_2.1.2 fastmap_1.2.0
#> [4] pacman_0.5.1 digest_0.6.37 rpart_4.1.24
#> [7] timechange_0.3.0 lifecycle_1.0.4 survival_3.8-3
#> [10] magrittr_2.0.3 compiler_4.5.0 rngtools_1.5.2
#> [13] rlang_1.1.6 tools_4.5.0 utf8_1.2.5
#> [16] doRNG_1.8.6.2 htmlwidgets_1.6.4 mnormt_2.1.1
#> [19] RColorBrewer_1.1-3 withr_3.0.2 itertools_0.1-3
#> [22] nnet_7.3-20 grid_4.5.0 jomo_2.7-6
#> [25] iterators_1.0.14 MASS_7.3-65 cli_3.6.5
#> [28] rmarkdown_2.29 reformulas_0.4.1 generics_0.1.4
#> [31] rstudioapi_0.17.1 tzdb_0.5.0 minqa_1.2.8
#> [34] splines_4.5.0 parallel_4.5.0 vctrs_0.6.5
#> [37] boot_1.3-31 glmnet_4.1-8 Matrix_1.7-3
#> [40] jsonlite_2.0.0 hms_1.1.3 mitml_0.4-5
#> [43] foreach_1.5.2 glue_1.8.0 nloptr_2.2.1
#> [46] pan_1.9 codetools_0.2-20 stringi_1.8.7
#> [49] shape_1.4.6.1 gtable_0.3.6 lme4_1.1-37
#> [52] pillar_1.10.2 htmltools_0.5.8.1 randomForest_4.7-1.2
#> [55] R6_2.6.1 Rdpack_2.6.4 rprojroot_2.0.4
#> [58] evaluate_1.0.3 lattice_0.22-7 rbibutils_2.3
#> [61] backports_1.5.0 broom_1.0.8 Rcpp_1.0.14
#> [64] nlme_3.1-168 xfun_0.52 pkgconfig_2.0.3