I verbi a due tabelle sono funzioni della libreria dplyr, automaticamente caricata quando si carica tidyverse. Per mostrare il funzionamento di questi verbi carico quest’ultimo package unitamente al package readxl che sfrutto per leggere i dati utilizzati per l’esempio.
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Un’azzardata tabella di esempio
I dati utilizzati per questo esempio sono alcune tabelle ottenute a partire da un insieme di dati relative ai comuni della provincia di Napoli. I dati sono disponibili nel file MS-Exceldati_gioco_azzardo-provincia-napoli.xlsx che contiene i seguenti fogli di lavoro:
Il primo foglio contiene i dati relativi a tutte le province, mentre gli altri fogli contengono estratti di questa tabella che si differenziano per il numero di righe e/o colonne contenute. Per gli esempi mostrati di seguito carico i dati a partire dal secondo foglio in avanti, creando per ognuno un oggetto nel workspace:
# leggo le varie tabelle --------------------------------------------------dieci_comuni <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="primi dieci completi")comuni_alcune_cols <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="primi dieci alcune variabili")comuni_altre_cols <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="primi dieci altre variabili")comuni_altre_cols_bis <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="primi dieci altre variab. due")comuni_alcuni <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="alcuni comuni")comuni_altri <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="altri comuni")comuni_alcuni_bis <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="alcuni comuni parziale")comuni_altri_bis <-read_excel("../data/dati_gioco_azzardo-provincia-napoli.xlsx",sheet ="altri comuni parziale")
La tabella dati di riferimento è quella assegnata all’oggetto dieci_comuni, di seguito riportata:
Si tratta dei dati relativi ai primi dieci comuni (prima colonna) per giocate di azzardo pro-capite in provincia di Napoli (seconda colonna). Le altre due colonne fanno riferimento all’ammontare delle giocate totali, all’ammontare delle vincite totali e al totale delle spese correnti del comuni, che è un indicatore economico utile per avere un’idea della grandezza del fenomeno azzardo. Basti pensare infatti che le spese correnti sono la quota di bilancio di un comune destinato alla gestione quotidiana dell’ente e all’erogazione dei servizi comunali.
Esempi di mutating joins
Per mostrare gli effetti delle varie funzioni di join uso nel seguito il nome di tabella di sinistra e di tabella di destra per fare riferimento, rispettivamente, al primo e al secondo argomento delle funzioni utilizzate. Ogni riferimento politico è pertanto puramente casuale…
inner_join: il join interno
Le seguenti due tabelle contengono i dati relativi a tutti i comuni ma si differenziano per le colonne, avendo come unica colonna in comune la colonna denominazione, che contiene i nomi dei dieci comuni:
L’operazione di inner_join in questo caso si ottiene semplicemente utilizzando le due tabelle come input, poiché la colonna chiave primaria ha lo stesso nome in entrambe le tabelle:
Una modifica alla sintassi è invece necessaria nel caso in cui la(e) colonna(e) che permette l’incrocio tra le due tabelle ha nomi diversi. Facciamo ad esempio riferimento alle tabelle mostrate nelle due schede seguenti: la prima tabella è la stessa dell’esempio precedente, mentre nella seconda i nomi dei comuni sono contenuti nella colonna comune.
# A tibble: 10 × 3
comune vincite spese
<chr> <dbl> <dbl>
1 SAN VITALIANO 28.2 3.24
2 MUGNANO DI NAPOLI 85.2 15.2
3 POMPEI 56.8 19.2
4 CERCOLA 37.4 9.45
5 CASANDRINO 27.3 NA
6 OTTAVIANO 46.4 11.4
7 COMIZIANO 3.14 1.11
8 CAPRI 14.3 14.1
9 SORRENTO 29.8 27.0
10 SANT'AGNELLO 15.9 6.67
In questo caso, per ottenere l’incrocio tra le due tabelle, è necessario specificare l’argomento by indicando i nomi delle colonne che permettono di incrociare le due tabelle:
Questa sintassi può naturalmente essere estesa nel caso in cui ci siano più colonne da utilizzare per l’incrocio delle due tabelle. Le due tabelle mostrate di seguito contengono i dati dei dieci comuni sulle stesse due variabili: in questo caso però i nomi delle tre colonne sono tutti diversi tra le due tabelle. Da notare inoltre (per gli esempi successivi) che in queste tabelle ci sono alcuni valori mancanti (NA).
# A tibble: 10 × 3
comune vincite spese
<chr> <dbl> <dbl>
1 SAN VITALIANO 28.2 3.24
2 MUGNANO DI NAPOLI 85.2 15.2
3 POMPEI 56.8 19.2
4 CERCOLA 37.4 9.45
5 CASANDRINO 27.3 NA
6 OTTAVIANO 46.4 11.4
7 COMIZIANO 3.14 1.11
8 CAPRI 14.3 14.1
9 SORRENTO 29.8 27.0
10 SANT'AGNELLO 15.9 6.67
Se si effettua un join interno specificando la sola colonna comune come chiave di incrocio si ottiene una tabella con tutti i comuni ma con una replica delle altre due colonne contenenti i dati relativi alle vincite e alle spese:
Questo permette di evitare la duplicazione delle colonne ma perdiamo alcuni dati: il join interno conserva nella tabella di output solo i record (righe) in cui i valori sulle colonne di incrocio sono uguali. Se invece siamo interessati a conservare tutti i dati è necessario fare riferimento ad un altro tipo di join, illustrato nella sezione successiva.
full_join: il join pieno
Se siamo interessati a conservare tutti i record delle due tabelle in join possiamo sfruttare il join pieno (full_join). Utilizzo le seguenti due tabelle per mostrare un primo esempio di funzionamento: si tratta di due tabelle che contengono tutti i dati sulle quattro variabili ma relativamente a comuni differenti, su otto comuni la prima e su quattro comuni la seconda.
Sfruttando invece il full_join è possibile costruire una tabella che contiene tutti i dati evitando la ripetizione dei comuni presenti in entrambe le tabelle:
full_join(comuni_alcuni, comuni_altri)
Joining with `by = join_by(denominazione, giocate_pro_capite,
giocate_totali_mln, vincite_totali_mln, spese_correnti_mln)`
Possiamo considerare nuovamente le due tabelle viste in precedenza per mostrare un altro esempio. Le due tabelle facevano riferimento agli stessi dati, ma con nomi di colonne differenti e con la presenza di alcuni valori mancanti relativamente ad uno dei comuni:
# A tibble: 10 × 3
comune vincite spese
<chr> <dbl> <dbl>
1 SAN VITALIANO 28.2 3.24
2 MUGNANO DI NAPOLI 85.2 15.2
3 POMPEI 56.8 19.2
4 CERCOLA 37.4 9.45
5 CASANDRINO 27.3 NA
6 OTTAVIANO 46.4 11.4
7 COMIZIANO 3.14 1.11
8 CAPRI 14.3 14.1
9 SORRENTO 29.8 27.0
10 SANT'AGNELLO 15.9 6.67
Effettuando l’incrocio specificando come chiave di incrocio la sola colonna contenente la denominazione del comune, a differenza del join interno visto in precedenza, è possibile conservare i dati di tutti e dieci i comuni presenti nelle due tabelle:
Le due colonne contenenti gli indicatori numerici sono però ripetuti. Specificando invece le tre colonne come chiavi di incrocio, si ottiene in questo caso una duplicazione dei comuni che sono difformi tra le due tabelle per la presenza di un valore mancante.
# A tibble: 11 × 3
denominazione vincite_totali_mln spese_correnti_mln
<chr> <dbl> <dbl>
1 SAN VITALIANO 28.2 3.24
2 MUGNANO DI NAPOLI 85.2 15.2
3 POMPEI 56.8 19.2
4 CERCOLA 37.4 9.45
5 CASANDRINO NA 7.03
6 OTTAVIANO 46.4 11.4
7 COMIZIANO 3.14 1.11
8 CAPRI 14.3 14.1
9 SORRENTO 29.8 27.0
10 SANT'AGNELLO 15.9 6.67
11 CASANDRINO 27.3 NA
Specificando infatti tutte le colonne come chiavi di incrocio i record sono considerati equivalenti solo se i dati su tutte le colonne sono uguali. In questo caso una possibile soluzione per combinare i due record relativi al comune di Casandrino conservando i valori presenti consiste nell’effettuare il raggruppamento della tabella con i record duplicati in base al comune (funzione group_b) e sommare (summarise con sum) specificando di trascurare i valori mancanti (argomento na_rm):
Il join sinistro (left_join) e quello destro (right_join) permettono di conservare tutti i record di una delle due tabelle recuperando eventuali informazioni utili dall’altra tabella (rispettivamente la tabella destra e quella sinistra). Per mostrarli utilizzo le seguenti due tabelle che si differenziano sia per i comuni contenuti che per le variabili:
Il left_join in questo caso conserva tutti i dati della tabella di sinistra, recuperando le informazioni in cui la chiave è comune dalla tabella di destra, utilizzando NA per i campi in cui non ci sono informazioni utili:
left_join(comuni_alcuni_bis, comuni_altri_bis)
Joining with `by = join_by(denominazione)`
# A tibble: 8 × 5
denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
<chr> <dbl> <dbl> <dbl>
1 SAN VITALIANO 5322 34.4 NA
2 MUGNANO DI NAPOLI 3092 108. NA
3 POMPEI 2864 72.2 NA
4 CERCOLA 2701 48.6 NA
5 OTTAVIANO 2475 58.7 46.4
6 COMIZIANO 2469 4.43 3.14
7 SORRENTO 2411 39.6 NA
8 SANT'AGNELLO 2172 19.8 NA
# ℹ 1 more variable: spese_correnti_mln <dbl>
Il right_join procede allo stesso modo ma conservando tutti i record della tabella di destra:
right_join(comuni_alcuni_bis, comuni_altri_bis)
Joining with `by = join_by(denominazione)`
# A tibble: 4 × 5
denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
<chr> <dbl> <dbl> <dbl>
1 OTTAVIANO 2475 58.7 46.4
2 COMIZIANO 2469 4.43 3.14
3 CASANDRINO NA NA 27.3
4 CAPRI NA NA 14.3
# ℹ 1 more variable: spese_correnti_mln <dbl>
Si tratta di join in cui una delle due tabelle viene utilizzata solo come “tabella di controllo” per decidere quali record mantenere nella tabella finale.
Esempi di filtering joins
I join di filtro sono utilizzati per selezionare record. Il primo (semi_join) può essere considerato una variante di un join interno, da cui si differenzia per le colonne restituite in output, mentre il secondo (anti_join) permette di capire quali record sono presenti in una tabella ma non nell’altra.
semi_join: il join interno parziale
Per mostrare il funzionamento del join parziale (semi_join) faccio riferimento alle stesse due tabelle utilizzate per mostrare gli ultimi due tipi di join:
La tabella di output contiene solo i record in cui la chiave è comune nelle due tabelle ma solo le colonne della tabella di sinistra. Per ottenere lo stesso risultato ma conservando le sole colonne della tabella di destra basta semplicemente invertire gli argomenti in input:
L’anti_join restituisce invece solo i record “non comuni”, ovvero quelli in cui i valori della chiave primaria sono presenti solo nella tabella di sinistra ma non in quella di destra. Per mostrarla riprendiamo le due tabelle contenenti tutti gli indicatori ma su comuni differenti: la prima relativa ad otto comuni e la seconda a quattro comuni, con alcuni record comuni sulle due tabelle.
Le tre funzioni intersect, union (con la variante di union_all) e set_diff permettono di effettuare le operazioni di intersezione, unione e “differenza” nella logica dell’algebra dell’insieme. In questo caso le due tabelle devono avere necessariamente le stesse colonne: stesso numero e stessi nomi. Per mostrarle utilizzo pertanto queste due tabelle che si differenziano solo rispetto alle righe:
La funzione symdiff calcola invece la differenza simmestrica, ovvero restituisce tutte le righe della tabella di sinistra che non sono in quella di destra, e tutte le righe della tabella di destra che non sono in quella di sinistra: