Verbi a due tabelle {dplyr}

Author

Domenico Vistocco

Prepariamo l’ambiente di lavoro

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.

# package utilizzati ------------------------------------------------------
library(readxl)
library(tidyverse)
── 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-Excel dati_gioco_azzardo-provincia-napoli.xlsx che contiene i seguenti fogli di lavoro:

excel_sheets("dati_gioco_azzardo-provincia-napoli.xlsx")
excel_sheets(paste0(here::here(), "/data/", "dati_gioco_azzardo-provincia-napoli.xlsx"))

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:

comuni_alcune_cols
# A tibble: 10 × 3
   denominazione     giocate_pro_capite giocate_totali_mln
   <chr>                          <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4 
 2 MUGNANO DI NAPOLI               3092             108.  
 3 POMPEI                          2864              72.2 
 4 CERCOLA                         2701              48.6 
 5 CASANDRINO                      2482              35.1 
 6 OTTAVIANO                       2475              58.7 
 7 COMIZIANO                       2469               4.43
 8 CAPRI                           2429              17.4 
 9 SORRENTO                        2411              39.6 
10 SANT'AGNELLO                    2172              19.8 
comuni_altre_cols
# A tibble: 10 × 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

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:

inner_join(comuni_alcune_cols, comuni_altre_cols)
Joining with `by = join_by(denominazione)`
# A tibble: 10 × 5
   denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
   <chr>                          <dbl>              <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4               28.2 
 2 MUGNANO DI NAPOLI               3092             108.                85.2 
 3 POMPEI                          2864              72.2               56.8 
 4 CERCOLA                         2701              48.6               37.4 
 5 CASANDRINO                      2482              35.1               NA   
 6 OTTAVIANO                       2475              58.7               46.4 
 7 COMIZIANO                       2469               4.43               3.14
 8 CAPRI                           2429              17.4               14.3 
 9 SORRENTO                        2411              39.6               29.8 
10 SANT'AGNELLO                    2172              19.8               15.9 
# ℹ 1 more variable: spese_correnti_mln <dbl>

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.

comuni_alcune_cols
# A tibble: 10 × 3
   denominazione     giocate_pro_capite giocate_totali_mln
   <chr>                          <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4 
 2 MUGNANO DI NAPOLI               3092             108.  
 3 POMPEI                          2864              72.2 
 4 CERCOLA                         2701              48.6 
 5 CASANDRINO                      2482              35.1 
 6 OTTAVIANO                       2475              58.7 
 7 COMIZIANO                       2469               4.43
 8 CAPRI                           2429              17.4 
 9 SORRENTO                        2411              39.6 
10 SANT'AGNELLO                    2172              19.8 
comuni_altre_cols_bis
# 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:

inner_join(comuni_alcune_cols, comuni_altre_cols_bis,
           by = join_by(denominazione == comune))
# A tibble: 10 × 5
   denominazione     giocate_pro_capite giocate_totali_mln vincite spese
   <chr>                          <dbl>              <dbl>   <dbl> <dbl>
 1 SAN VITALIANO                   5322              34.4    28.2   3.24
 2 MUGNANO DI NAPOLI               3092             108.     85.2  15.2 
 3 POMPEI                          2864              72.2    56.8  19.2 
 4 CERCOLA                         2701              48.6    37.4   9.45
 5 CASANDRINO                      2482              35.1    27.3  NA   
 6 OTTAVIANO                       2475              58.7    46.4  11.4 
 7 COMIZIANO                       2469               4.43    3.14  1.11
 8 CAPRI                           2429              17.4    14.3  14.1 
 9 SORRENTO                        2411              39.6    29.8  27.0 
10 SANT'AGNELLO                    2172              19.8    15.9   6.67

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).

comuni_altre_cols
# A tibble: 10 × 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
comuni_altre_cols_bis
# 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:

inner_join(comuni_altre_cols, comuni_altre_cols_bis,
           by = join_by(denominazione == comune))
# A tibble: 10 × 5
   denominazione     vincite_totali_mln spese_correnti_mln vincite spese
   <chr>                          <dbl>              <dbl>   <dbl> <dbl>
 1 SAN VITALIANO                  28.2                3.24   28.2   3.24
 2 MUGNANO DI NAPOLI              85.2               15.2    85.2  15.2 
 3 POMPEI                         56.8               19.2    56.8  19.2 
 4 CERCOLA                        37.4                9.45   37.4   9.45
 5 CASANDRINO                     NA                  7.03   27.3  NA   
 6 OTTAVIANO                      46.4               11.4    46.4  11.4 
 7 COMIZIANO                       3.14               1.11    3.14  1.11
 8 CAPRI                          14.3               14.1    14.3  14.1 
 9 SORRENTO                       29.8               27.0    29.8  27.0 
10 SANT'AGNELLO                   15.9                6.67   15.9   6.67

Una possibile soluzione è quella di specificare nell’argomento by le tre colonne come chiavi di incrocio tra le due tabelle:

inner_join(comuni_altre_cols, comuni_altre_cols_bis,
           by = join_by(denominazione == comune,
                        vincite_totali_mln == vincite,
                        spese_correnti_mln == spese))
# A tibble: 9 × 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 OTTAVIANO                      46.4               11.4 
6 COMIZIANO                       3.14               1.11
7 CAPRI                          14.3               14.1 
8 SORRENTO                       29.8               27.0 
9 SANT'AGNELLO                   15.9                6.67

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.

comuni_alcuni
# A tibble: 8 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322              34.4               28.2 
2 MUGNANO DI NAPOLI               3092             108.                85.2 
3 POMPEI                          2864              72.2               56.8 
4 CERCOLA                         2701              48.6               37.4 
5 OTTAVIANO                       2475              58.7               46.4 
6 COMIZIANO                       2469               4.43               3.14
7 SORRENTO                        2411              39.6               29.8 
8 SANT'AGNELLO                    2172              19.8               15.9 
# ℹ 1 more variable: spese_correnti_mln <dbl>
comuni_altri
# A tibble: 4 × 5
  denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                      <dbl>              <dbl>              <dbl>
1 CASANDRINO                  2482              35.1               27.3 
2 OTTAVIANO                   2475              58.7               46.4 
3 COMIZIANO                   2469               4.43               3.14
4 CAPRI                       2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

In questo caso l’operazione di inner_join conserva nella tabella di output solo i dati relativi ai due comuni che sono comuni alle due tabelle:

inner_join(comuni_alcuni, comuni_altri)
Joining with `by = join_by(denominazione, giocate_pro_capite,
giocate_totali_mln, vincite_totali_mln, spese_correnti_mln)`
# A tibble: 2 × 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
# ℹ 1 more variable: spese_correnti_mln <dbl>

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)`
# A tibble: 10 × 5
   denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
   <chr>                          <dbl>              <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4               28.2 
 2 MUGNANO DI NAPOLI               3092             108.                85.2 
 3 POMPEI                          2864              72.2               56.8 
 4 CERCOLA                         2701              48.6               37.4 
 5 OTTAVIANO                       2475              58.7               46.4 
 6 COMIZIANO                       2469               4.43               3.14
 7 SORRENTO                        2411              39.6               29.8 
 8 SANT'AGNELLO                    2172              19.8               15.9 
 9 CASANDRINO                      2482              35.1               27.3 
10 CAPRI                           2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

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:

comuni_altre_cols
# A tibble: 10 × 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
comuni_altre_cols_bis
# 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:

full_join(comuni_altre_cols, comuni_altre_cols_bis,
          by = join_by(denominazione == comune))
# A tibble: 10 × 5
   denominazione     vincite_totali_mln spese_correnti_mln vincite spese
   <chr>                          <dbl>              <dbl>   <dbl> <dbl>
 1 SAN VITALIANO                  28.2                3.24   28.2   3.24
 2 MUGNANO DI NAPOLI              85.2               15.2    85.2  15.2 
 3 POMPEI                         56.8               19.2    56.8  19.2 
 4 CERCOLA                        37.4                9.45   37.4   9.45
 5 CASANDRINO                     NA                  7.03   27.3  NA   
 6 OTTAVIANO                      46.4               11.4    46.4  11.4 
 7 COMIZIANO                       3.14               1.11    3.14  1.11
 8 CAPRI                          14.3               14.1    14.3  14.1 
 9 SORRENTO                       29.8               27.0    29.8  27.0 
10 SANT'AGNELLO                   15.9                6.67   15.9   6.67

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.

full_join(comuni_altre_cols, comuni_altre_cols_bis,
           by = join_by(denominazione == comune,
                        vincite_totali_mln == vincite,
                        spese_correnti_mln == spese))
# 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):

full_join(comuni_altre_cols, comuni_altre_cols_bis,
          by = join_by(denominazione == comune,
                       vincite_totali_mln == vincite,
                       spese_correnti_mln == spese)) |>
  group_by(denominazione) |>
  summarise(vincite_totali_mln = sum(vincite_totali_mln, na.rm = TRUE),
            spese_correnti_mln = sum(spese_correnti_mln, na.rm = TRUE))
# A tibble: 10 × 3
   denominazione     vincite_totali_mln spese_correnti_mln
   <chr>                          <dbl>              <dbl>
 1 CAPRI                          14.3               14.1 
 2 CASANDRINO                     27.3                7.03
 3 CERCOLA                        37.4                9.45
 4 COMIZIANO                       3.14               1.11
 5 MUGNANO DI NAPOLI              85.2               15.2 
 6 OTTAVIANO                      46.4               11.4 
 7 POMPEI                         56.8               19.2 
 8 SAN VITALIANO                  28.2                3.24
 9 SANT'AGNELLO                   15.9                6.67
10 SORRENTO                       29.8               27.0 

{left}{right}_join: un join “laterale”

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:

comuni_alcuni_bis
# A tibble: 8 × 3
  denominazione     giocate_pro_capite giocate_totali_mln
  <chr>                          <dbl>              <dbl>
1 SAN VITALIANO                   5322              34.4 
2 MUGNANO DI NAPOLI               3092             108.  
3 POMPEI                          2864              72.2 
4 CERCOLA                         2701              48.6 
5 OTTAVIANO                       2475              58.7 
6 COMIZIANO                       2469               4.43
7 SORRENTO                        2411              39.6 
8 SANT'AGNELLO                    2172              19.8 
comuni_altri_bis
# A tibble: 4 × 3
  denominazione vincite_totali_mln spese_correnti_mln
  <chr>                      <dbl>              <dbl>
1 CASANDRINO                 27.3                7.03
2 OTTAVIANO                  46.4               11.4 
3 COMIZIANO                   3.14               1.11
4 CAPRI                      14.3               14.1 

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:

semi_join(comuni_alcuni_bis, comuni_altri_bis)
Joining with `by = join_by(denominazione)`
# A tibble: 2 × 3
  denominazione giocate_pro_capite giocate_totali_mln
  <chr>                      <dbl>              <dbl>
1 OTTAVIANO                   2475              58.7 
2 COMIZIANO                   2469               4.43

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:

semi_join(comuni_altri_bis, comuni_alcuni_bis)
Joining with `by = join_by(denominazione)`
# A tibble: 2 × 3
  denominazione vincite_totali_mln spese_correnti_mln
  <chr>                      <dbl>              <dbl>
1 OTTAVIANO                  46.4               11.4 
2 COMIZIANO                   3.14               1.11

anti_join: il “non join”

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.

comuni_alcuni
# A tibble: 8 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322              34.4               28.2 
2 MUGNANO DI NAPOLI               3092             108.                85.2 
3 POMPEI                          2864              72.2               56.8 
4 CERCOLA                         2701              48.6               37.4 
5 OTTAVIANO                       2475              58.7               46.4 
6 COMIZIANO                       2469               4.43               3.14
7 SORRENTO                        2411              39.6               29.8 
8 SANT'AGNELLO                    2172              19.8               15.9 
# ℹ 1 more variable: spese_correnti_mln <dbl>
comuni_altri
# A tibble: 4 × 5
  denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                      <dbl>              <dbl>              <dbl>
1 CASANDRINO                  2482              35.1               27.3 
2 OTTAVIANO                   2475              58.7               46.4 
3 COMIZIANO                   2469               4.43               3.14
4 CAPRI                       2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

L’operazione di anti_join restituisce in output solo i sei record della tabella di sinistra che non sono presenti anche in quella di destra:

anti_join(comuni_alcuni, comuni_altri)
Joining with `by = join_by(denominazione, giocate_pro_capite,
giocate_totali_mln, vincite_totali_mln, spese_correnti_mln)`
# A tibble: 6 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322               34.4               28.2
2 MUGNANO DI NAPOLI               3092              108.                85.2
3 POMPEI                          2864               72.2               56.8
4 CERCOLA                         2701               48.6               37.4
5 SORRENTO                        2411               39.6               29.8
6 SANT'AGNELLO                    2172               19.8               15.9
# ℹ 1 more variable: spese_correnti_mln <dbl>

Invertendo gli argomenti in input, possiamo invece ottenere solo i due record della seconda tabella che non sono presenti anche nella prima:

anti_join(comuni_altri, comuni_alcuni)
Joining with `by = join_by(denominazione, giocate_pro_capite,
giocate_totali_mln, vincite_totali_mln, spese_correnti_mln)`
# A tibble: 2 × 5
  denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                      <dbl>              <dbl>              <dbl>
1 CASANDRINO                  2482               35.1               27.3
2 CAPRI                       2429               17.4               14.3
# ℹ 1 more variable: spese_correnti_mln <dbl>

Esempi di operazioni su insiemi

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:

comuni_alcuni
# A tibble: 8 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322              34.4               28.2 
2 MUGNANO DI NAPOLI               3092             108.                85.2 
3 POMPEI                          2864              72.2               56.8 
4 CERCOLA                         2701              48.6               37.4 
5 OTTAVIANO                       2475              58.7               46.4 
6 COMIZIANO                       2469               4.43               3.14
7 SORRENTO                        2411              39.6               29.8 
8 SANT'AGNELLO                    2172              19.8               15.9 
# ℹ 1 more variable: spese_correnti_mln <dbl>
comuni_altri
# A tibble: 4 × 5
  denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                      <dbl>              <dbl>              <dbl>
1 CASANDRINO                  2482              35.1               27.3 
2 OTTAVIANO                   2475              58.7               46.4 
3 COMIZIANO                   2469               4.43               3.14
4 CAPRI                       2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

La funzione intersect equivale all’operazione di intersezione tra due insiemi, restituendo quindi solo i record comuni alle due tabelle:

intersect(comuni_alcuni, comuni_altri)
# A tibble: 2 × 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
# ℹ 1 more variable: spese_correnti_mln <dbl>

L’operazione di unione si ottiene invece sfruttando la funzione union:

union(comuni_alcuni, comuni_altri)
# A tibble: 10 × 5
   denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
   <chr>                          <dbl>              <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4               28.2 
 2 MUGNANO DI NAPOLI               3092             108.                85.2 
 3 POMPEI                          2864              72.2               56.8 
 4 CERCOLA                         2701              48.6               37.4 
 5 OTTAVIANO                       2475              58.7               46.4 
 6 COMIZIANO                       2469               4.43               3.14
 7 SORRENTO                        2411              39.6               29.8 
 8 SANT'AGNELLO                    2172              19.8               15.9 
 9 CASANDRINO                      2482              35.1               27.3 
10 CAPRI                           2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

La variante union_all restituisce tutte le righe nella prima o nella seconda tabella includendo anche i duplicati:

union_all(comuni_alcuni, comuni_altri)
# A tibble: 12 × 5
   denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
   <chr>                          <dbl>              <dbl>              <dbl>
 1 SAN VITALIANO                   5322              34.4               28.2 
 2 MUGNANO DI NAPOLI               3092             108.                85.2 
 3 POMPEI                          2864              72.2               56.8 
 4 CERCOLA                         2701              48.6               37.4 
 5 OTTAVIANO                       2475              58.7               46.4 
 6 COMIZIANO                       2469               4.43               3.14
 7 SORRENTO                        2411              39.6               29.8 
 8 SANT'AGNELLO                    2172              19.8               15.9 
 9 CASANDRINO                      2482              35.1               27.3 
10 OTTAVIANO                       2475              58.7               46.4 
11 COMIZIANO                       2469               4.43               3.14
12 CAPRI                           2429              17.4               14.3 
# ℹ 1 more variable: spese_correnti_mln <dbl>

La funzione set_diff restituisce invece tutte le righe che sono nella prima tabella che non sono anche nella seconda:

setdiff(comuni_alcuni, comuni_altri)
# A tibble: 6 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322               34.4               28.2
2 MUGNANO DI NAPOLI               3092              108.                85.2
3 POMPEI                          2864               72.2               56.8
4 CERCOLA                         2701               48.6               37.4
5 SORRENTO                        2411               39.6               29.8
6 SANT'AGNELLO                    2172               19.8               15.9
# ℹ 1 more variable: spese_correnti_mln <dbl>

Invertendo quindi gli argomenti si ottiene un risultato diverso:

setdiff(comuni_altri, comuni_alcuni)
# A tibble: 2 × 5
  denominazione giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                      <dbl>              <dbl>              <dbl>
1 CASANDRINO                  2482               35.1               27.3
2 CAPRI                       2429               17.4               14.3
# ℹ 1 more variable: spese_correnti_mln <dbl>

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:

symdiff(comuni_alcuni, comuni_altri)
# A tibble: 8 × 5
  denominazione     giocate_pro_capite giocate_totali_mln vincite_totali_mln
  <chr>                          <dbl>              <dbl>              <dbl>
1 SAN VITALIANO                   5322               34.4               28.2
2 MUGNANO DI NAPOLI               3092              108.                85.2
3 POMPEI                          2864               72.2               56.8
4 CERCOLA                         2701               48.6               37.4
5 SORRENTO                        2411               39.6               29.8
6 SANT'AGNELLO                    2172               19.8               15.9
7 CASANDRINO                      2482               35.1               27.3
8 CAPRI                           2429               17.4               14.3
# ℹ 1 more variable: spese_correnti_mln <dbl>

La funzione setequal può invece essere utilizzata per controllare se le due tabelle hanno o meno gli stessi record:

setequal(comuni_alcuni, comuni_altri)
[1] FALSE

Il seguente esempio permette di apprezzare che il controllo è fatto ignorando l’ordine delle righe:

setequal(arrange(comuni_alcuni, denominazione),
         arrange(comuni_alcuni, giocate_pro_capite))
[1] TRUE