Preambolo

Riprendiamo il data frame AutoBi del pacchetto insuranceData, considerando alcune delle operazioni svolte nella precedente lezione

  1. Conversione in fattore di ATTORNEY con livelli yes e no
  2. Conversione in fattore di CLMSEX con livelli M e F
  3. Conversione in fattore di MARITAL con livelli married, single, widowed e divorced (o nella versione con widowed e divorced accorpati nella classe previouslymarried)
  4. Creazione della variabile CLMAGEclass, contenente l’età resa come categoriale (livelli 0-15, 16-24, 25-36, 37-50, 51-95)
library(insuranceData)
data("AutoBi")

AutoBi$ATTORNEY <- factor(AutoBi$ATTORNEY)
levels(AutoBi$ATTORNEY) <- c("yes", "no")

AutoBi$CLMSEX <- factor(AutoBi$CLMSEX)
levels(AutoBi$CLMSEX) <- c("M", "F")

AutoBi$MARITAL <- factor(AutoBi$MARITAL)
levels(AutoBi$MARITAL) <- c("married", "single", "previouslymarried", "previouslymarried")


CLMAGEclass <- cut(AutoBi$CLMAGE, breaks = c(-1, 15, 24, 36, 50, 95))
AutoBi <- cbind(AutoBi, CLMAGEclass)

1 Tabelle di Frequenza Semplici

1.1 Frequenze Assolute: table()

La funzione table() conta quante unità del dataset assumono ciascuna modalità di un fattore, restituendo la distribuzione di frequenza assoluta.

Ad esempio, considerando la variabile ATTORNEY.

tabella_att <- table(AutoBi$ATTORNEY)
tabella_att
#> 
#> yes  no 
#> 685 655

L’oggetto restituito è di classe table — di fatto un array nominato — e può essere manipolato come un vettore:

str(tabella_att)
#>  'table' int [1:2(1d)] 685 655
#>  - attr(*, "dimnames")=List of 1
#>   ..$ : chr [1:2] "yes" "no"
tabella_att["yes"]   # accesso per nome della modalità
#> yes 
#> 685
tabella_att[1]       # accesso per posizione
#> yes 
#> 685

Mentre per la variabile MARITAL, si ha

tabella_mar <- table(AutoBi$MARITAL)
tabella_mar
#> 
#>           married            single previouslymarried 
#>               624               650                50

Si noti che il comando summary riporta anche il numero di valori NA presenti

summary(AutoBi$MARITAL)
#>           married            single previouslymarried              NA's 
#>               624               650                50                16

1.2 Frequenze Relative: prop.table()

Le frequenze relative si ottengono dividendo le frequenze assolute di ogni modalità per il totale di unità statistiche.

⚠️ In presenza di valori mancanti (NA), usare length() come denominatore è errato: conta anche gli NA e restituisce frequenze relative che non sommano a 1.

# Metodo errato in presenza di NA
freqrel_errata <- table(AutoBi$MARITAL) / length(AutoBi$MARITAL)
sum(freqrel_errata)   # non fa 1!
#> [1] 0.9880597

La soluzione corretta usa margin.table() per ottenere il totale dei soli casi validi:

totale <- margin.table(table(AutoBi$MARITAL))
totale
#> [1] 1324
freqrel <- table(AutoBi$MARITAL) / totale
freqrel
#> 
#>           married            single previouslymarried 
#>        0.47129909        0.49093656        0.03776435
sum(freqrel)   # ora fa 1
#> [1] 1

Il modo più diretto è usare direttamente prop.table():

prop.table(table(AutoBi$MARITAL))
#> 
#>           married            single previouslymarried 
#>        0.47129909        0.49093656        0.03776435

1.3 Variabili Quantitative: Prima le Classi

⚠️ Non ha senso applicare table() direttamente a una variabile quantitativa continua: si ottengono tante righe quante sono le osservazioni distinte. Occorre prima creare le classi con cut().

# Poco utile: ogni età compare come categoria separata
# table(AutoBi$CLMAGE)

# Molto più leggibile dopo la classificazione
table(CLMAGEclass)
#> CLMAGEclass
#> (-1,15] (15,24] (24,36] (36,50] (50,95] 
#>     144     280     269     299     159
round(prop.table(table(CLMAGEclass)), 3)
#> CLMAGEclass
#> (-1,15] (15,24] (24,36] (36,50] (50,95] 
#>   0.125   0.243   0.234   0.260   0.138

💡 Mediante round(x, k) possiamo aumentare la leggibilità di una informazione (contenuta in x) perchè ci permette di arrotondare alla k-esima cifra decimale

1.4 Livelli Vuoti nei Fattori

Quando si filtra un fattore, i livelli originali rimangono presenti nell’oggetto table, anche se vuoti. Ad esempio, estraiamo il data frame solo_maschi che contiene il genere per i soli individui maschi.

solo_maschi <- AutoBi[AutoBi$CLMSEX == "M", ]$CLMSEX
table(solo_maschi)            # il livello "F" compare con frequenza 0
#> solo_maschi
#>   M   F 
#> 586   0
table(droplevels(solo_maschi))  # droplevels() elimina i livelli vuoti
#> 
#>   M 
#> 586

💡 L’uso di table() su un fattore (anziché su un vettore character) rende espliciti i livelli vuoti. Questo è utile per individuare categorie non rappresentate nei dati filtrati. Con il comando droplevels() si eliminano i livelli vuoti


1.4.1 Esercizio

Esercizio 1
Usando AutoBi:
1. Costruisci la tabella di frequenza assoluta di CLMSEX.
2. Calcola le frequenze relative corrispondenti con prop.table().
3. Verifica che la somma delle frequenze relative sia 1.
4. Costruisci la tabella di frequenza di LOSSclass (usa i seguenti intervalli : (0-0.5]; (0.5-2]; (2-4]; (4-8]; (8-1100]), dopo averla inclusa nel data frame, e calcola le frequenze relative cumulate con cumsum().

# 1.
table(AutoBi$CLMSEX)
#> 
#>   M   F 
#> 586 742
# 2.
prop.table(table(AutoBi$CLMSEX))
#> 
#>         M         F 
#> 0.4412651 0.5587349
# 3.
sum(prop.table(table(AutoBi$CLMSEX)))
#> [1] 1
# 4.
AutoBi$LOSSclass <- cut(AutoBi$LOSS, breaks = c(0, 0.5, 2, 4, 8, 1100))
freq_loss <- prop.table(table(AutoBi$LOSSclass))
cumsum(freq_loss)
#>     (0,0.5]     (0.5,2]       (2,4]       (4,8] (8,1.1e+03] 
#>   0.2149254   0.4567164   0.7522388   0.8977612   1.0000000

2 Tabelle a Doppia Entrata

2.1 Frequenze Assolute Congiunte

Passando due argomenti a table() si ottiene una tabella di contingenza (o tabella a doppia entrata): ogni cella contiene il numero di unità che presentano contemporaneamente una coppia di modalità. Ad esempio la distribuzione congiunta di genere e tutela legale. Dalla tabell sotto vediamo ad esempio che 325 individui sono maschi e rappresntati da un avvocato.

tab1 <- table(AutoBi$CLMSEX, AutoBi$ATTORNEY)
tab1
#>    
#>     yes  no
#>   M 325 261
#>   F 352 390
rownames(tab1)
#> [1] "M" "F"
colnames(tab1)
#> [1] "yes" "no"

💡 Le righe e le colonne corrispondono rispettivamente alle modalità del primo e del secondo argomento di table().

2.2 Distribuzioni Marginali

Sommando per riga o per colonna si ottengono le distribuzioni marginali, cioè le distribuzioni di frequenza di ciascuna variabile presa singolarmente:

margin.table(tab1, 1)   # distribuzione marginale di CLMSEX (righe)
#> 
#>   M   F 
#> 586 742
margin.table(tab1, 2)   # distribuzione marginale di ATTORNEY (colonne)
#> 
#> yes  no 
#> 677 651
margin.table(tab1)      # totale generale
#> [1] 1328

Possiamo verificare che il risultato coincida con quello ottenuto mediante su singola variabile

table(AutoBi$CLMSEX)   
#> 
#>   M   F 
#> 586 742
table(AutoBi$ATTORNEY)  
#> 
#> yes  no 
#> 685 655

3 Frequenze Relative in Tabelle a Doppia Entrata

prop.table() accetta un secondo argomento margin che specifica il tipo di frequenza relativa da calcolare:

Chiamata Risultato Somme
prop.table(tab) Frequenze relative congiunte Tutta la tabella somma a 1
prop.table(tab, 1) Frequenze relative di riga Ogni riga somma a 1
prop.table(tab, 2) Frequenze relative di colonna Ogni colonna somma a 1

Quindi otteniamo le frequenze relative congiunte, l’elemento [1,1] ci dice che il 24.5% degli individui è di genere maschile ed è rappresentato da un avvocato.

# Frequenze relative congiunte
round(prop.table(tab1), 3)
#>    
#>       yes    no
#>   M 0.245 0.197
#>   F 0.265 0.294

Quando invece esiami le frequenze relative di riga (di colonna) stiamo esaminando la distribuzione condizionata. Nell’esempio genere-tutela legale, le frequenze di riga ci danno informazioni della tutela legale per genere. Degli individui di genere femminile, il 47.4% è rappresentato da un legale, mentre il 52.6% non è rappresentato da un avvocato.

# Distribuzione di ATTORNEY condizionata a CLMSEX (lettura per riga)
round(prop.table(tab1, 1), 3)
#>    
#>       yes    no
#>   M 0.555 0.445
#>   F 0.474 0.526

Invece, analizzando le frequenze di colonna valutiamo la distribuzione del genere nei livelli della variabile tutela legale. Quindi di coloro che sono rappresentati da un avvocato, il 55.5% è di genere maschile e il 44.5% è di genere femminile.

# Distribuzione di CLMSEX condizionata ad ATTORNEY (lettura per colonna)
round(prop.table(tab1, 2), 3)
#>    
#>       yes    no
#>   M 0.480 0.401
#>   F 0.520 0.599

4 Associazione tra Variabili

4.1 Il Concetto di Associazione

Quando due variabili (\(X\) e \(Y\)) sono su scala nominale parliamo di analisi dell’associazione (quando sono su scala ordinale si parla di analisi della cograduazione, quando abbiamo due continue si parla di analisi della correlazione).

E’ importante distinguere tra:

  • Simmetrica: le due variabili sono tratta tattate allo stesso livello, senza che una dipenda dall’altra (per rispondere alla domanda se le due variabili sono associate le misute utilizzate daranno lo stesso risultato indipendentemente da quale variabili chiami \(X\) o \(Y\)). Quindi non si assume che una dipenda dall’altra.
  • Asimmetrica: In tal caso si assume che una variabile sia l’esplicativa (variabile indipendente, solitamente \(X\)) e l’atra sia la rispota (variabile dipendente, solitamente \(Y\)). In tal caso scambiare \(X\) con \(Y\) da un risultato diverso. In tal caso l’obiettivo è capire se la conoscenza di \(X\) aumenta la nostra informazione su \(Y\)
Ruolo Denominazione alternativa Significato
Variabile risposta Variabile dipendente La variabile che vogliamo spiegare
Variabile esplicativa Variabile indipendente La variabile la cui conoscenza ci aiuta a spiegare

Svilupperemo il discorso considerando una relazione di tipo asimmetrico.

Una tabella a doppia entrata è utile per indagare se due variabili sono associate. La tabella da esaminare è quella delle frequenze condizionate rispetto alla variabile esplicativa.

  • Se le distribuzioni condizionate di una variabile cambiano al variare delle modalità dell’altra → le due variabili sono associate.
  • Se le distribuzioni condizionate sono tutte simili tra loro (e simili alla distribuzione marginale) → le variabili sono indipendenti.

⚠️ Il confronto va fatto utilizzando le frequenze relative.

⚠️ La parola “simili” lascia aperta la questione di quando le differenze osservate siano sufficientemente grandi da escludere l’indipendenza. Rispondere in modo rigoroso richiede strumenti di statistica inferenziale.

4.2 Esempio: ATTORNEY e LOSS

Ci aspettiamo che il ricorso all’avvocato (ATTORNEY, variabile risposta) dipenda dall’ammontare del danno subito (LOSS, variabile esplicativa).

# Distribuzione marginale di ATTORNEY (riferimento)
tot <- prop.table(table(AutoBi$ATTORNEY))
tot
#> 
#>      yes       no 
#> 0.511194 0.488806
# Tabella congiunta
tabella_al <- table(AutoBi$ATTORNEY, AutoBi$LOSSclass)
tabella_al
#>      
#>       (0,0.5] (0.5,2] (2,4] (4,8] (8,1.1e+03]
#>   yes      49     104   263   147         122
#>   no      239     220   133    48          15
# Frequenze condizionate: ATTORNEY | LOSSclass (colonne)
prof_col <- prop.table(tabella_al, 2)

# Affiancate alla distribuzione marginale di ATTORNEY
cbind(round(prof_col, 3), marginale = round(tot, 3))
#>     (0,0.5] (0.5,2] (2,4] (4,8] (8,1.1e+03] marginale
#> yes    0.17   0.321 0.664 0.754       0.891     0.511
#> no     0.83   0.679 0.336 0.246       0.109     0.489

💡 Interpretazione: la proporzione di “yes” cresce sistematicamente al crescere del danno — dal ~17% per danni sotto i 500$ all’89% per danni superiori agli 8000$. Le due variabili sono chiaramente associate. Si ricorre molto più frequentemente all’avvocato se il danno è maggiore.

Può essere utile, in questa fase esplorativa analizzare le deviazioni dei profili colonna dalla marginale di colonna

dev_prof_col <- prof_col - 
  matrix(rep(tot,ncol(prof_col)),
         nrow(prof_col), ncol(prof_col), 
         byrow = F)
dev_prof_col
#>      
#>          (0,0.5]    (0.5,2]      (2,4]      (4,8] (8,1.1e+03]
#>   yes -0.3410551 -0.1902064  0.1529474  0.2426521   0.3793169
#>   no   0.3410551  0.1902064 -0.1529474 -0.2426521  -0.3793169

Risulta evidente, ancor più chiaramente di prima, che il ricorso all’avvocato varia a seconda dell’ammontare del danno subito. Le differenze più marcate dal profilo medio si riscontrano nelle due classi estreme.

4.3 Esempio: Variabili Quasi Indipendenti

tabella_sa <- table(AutoBi$CLMSEX, CLMAGEclass)
prof_col <- prop.table(tabella_sa, 2)
round(prof_col, 3)
#>    CLMAGEclass
#>     (-1,15] (15,24] (24,36] (36,50] (50,95]
#>   M   0.462   0.396   0.476   0.411   0.459
#>   F   0.538   0.604   0.524   0.589   0.541

💡 Interpretazione: le distribuzioni condizionate del sesso per classe di età non mostrano variazioni sistematiche marcate. Le due variabili appaiono quasi indipendenti.

dev_prof_col <- prof_col - 
  matrix(rep(tot,ncol(prof_col)),
         nrow(prof_col), ncol(prof_col), 
         byrow = F)
dev_prof_col
#>    CLMAGEclass
#>         (-1,15]     (15,24]     (24,36]     (36,50]     (50,95]
#>   M -0.04965557 -0.11551058 -0.03553860 -0.10041962 -0.05207453
#>   F  0.04965557  0.11551058  0.03553860  0.10041962  0.05207453

⚠️ Se si invertisse il ruolo delle variabili si avrebbe una lettura diversa. Tuttavia nel caso vi fosse indipendenza, essa esisterebbe qualunque sia la lettura.


4.3.1 Esercizio

Esercizio 2
Costruisci la tabella congiunta di CLMSEX e MARITAL:
1. Calcola le distribuzioni marginali di entrambe le variabili.
2. Calcola le frequenze condizionate di MARITAL dato CLMSEX (ogni riga somma a 1).
3. Affianca le frequenze condizionate alla distribuzione marginale di MARITAL con rbind().
4. Le due variabili sembrano associate?

tab_sm <- table(AutoBi$CLMSEX, AutoBi$MARITAL)

# 1.
margin.table(tab_sm, 1)   # CLMSEX
#> 
#>   M   F 
#> 580 736
margin.table(tab_sm, 2)   # MARITAL
#> 
#>           married            single previouslymarried 
#>               622               644                50
# 2.
cond <- round(prop.table(tab_sm, 1), 3)
cond
#>    
#>     married single previouslymarried
#>   M   0.467  0.509             0.024
#>   F   0.477  0.474             0.049
# 3.
marginale_marital <- round(prop.table(table(AutoBi$MARITAL)), 3)
rbind(cond, marginale = marginale_marital)
#>           married single previouslymarried
#> M           0.467  0.509             0.024
#> F           0.477  0.474             0.049
#> marginale   0.471  0.491             0.038
# 4. Le distribuzioni condizionate di MARITAL per sesso
#    non differiscono molto dalla marginale → variabili tendenzialmente indipendenti

4.3.2 Esercizio

Esercizio 3 — Donne al volante, pericolo costante?
Esplora se il sesso del richiedente è associato all’ammontare del danno subito.

  1. Crea LOSSclass_q suddividendo LOSS in base ai quartili (quantile()), con include.lowest = TRUE.
  2. Costruisci la tabella congiunta di CLMSEX e LOSSclass_q.
  3. Calcola le frequenze di CLMSEX condizionate a LOSSclass_q (colonne).
  4. Calcola le frequenze di LOSSclass_q condizionate a CLMSEX (righe).
  5. Confronta le distribuzioni condizionate con le rispettive marginali. Le variabili sono associate?
# 1.
table(AutoBi$CLMSEX)
#> 
#>   M   F 
#> 586 742
prop.table(table(AutoBi$CLMSEX))
#> 
#>         M         F 
#> 0.4412651 0.5587349
# 2.
AutoBi$LOSSclass_q <- cut(AutoBi$LOSS,
                           breaks = quantile(AutoBi$LOSS),
                           include.lowest = TRUE)
levels(AutoBi$LOSSclass_q)
#> [1] "[0.005,0.64]"    "(0.64,2.33]"     "(2.33,3.99]"     "(3.99,1.07e+03]"
# 3.
tab_sq <- table(AutoBi$CLMSEX, AutoBi$LOSSclass_q)
tab_sq
#>    
#>     [0.005,0.64] (0.64,2.33] (2.33,3.99] (3.99,1.07e+03]
#>   M          149         140         156             141
#>   F          184         191         176             191
# 4. CLMSEX | LOSSclass_q
round(prop.table(tab_sq, 2), 3)
#>    
#>     [0.005,0.64] (0.64,2.33] (2.33,3.99] (3.99,1.07e+03]
#>   M        0.447       0.423       0.470           0.425
#>   F        0.553       0.577       0.530           0.575
# 5. LOSSclass_q | CLMSEX
round(prop.table(tab_sq, 1), 3)
#>    
#>     [0.005,0.64] (0.64,2.33] (2.33,3.99] (3.99,1.07e+03]
#>   M        0.254       0.239       0.266           0.241
#>   F        0.248       0.257       0.237           0.257
# 6.
marginale_loss <- round(prop.table(table(AutoBi$LOSSclass_q)), 3)
rbind(round(prop.table(tab_sq, 1), 3),
      marginale = marginale_loss)
#>           [0.005,0.64] (0.64,2.33] (2.33,3.99] (3.99,1.07e+03]
#> M                0.254       0.239       0.266           0.241
#> F                0.248       0.257       0.237           0.257
#> marginale        0.251       0.249       0.250           0.250
# Le distribuzioni condizionate di LOSSclass per sesso
# sono molto simili alla marginale → le variabili sembrano indipendenti

5 Tabelle a Tre Entrate

Con tre argomenti, table() restituisce un array tridimensionale: viene mostrata una tabella a due entrate per ogni livello della terza variabile.

tabella3 <- table(AutoBi$ATTORNEY, AutoBi$LOSSclass, AutoBi$CLMSEX)
str(tabella3)   # array 2 × 5 × 2
#>  'table' int [1:2, 1:5, 1:2] 25 103 52 83 127 55 65 14 56 6 ...
#>  - attr(*, "dimnames")=List of 3
#>   ..$ : chr [1:2] "yes" "no"
#>   ..$ : chr [1:5] "(0,0.5]" "(0.5,2]" "(2,4]" "(4,8]" ...
#>   ..$ : chr [1:2] "M" "F"
tabella3        # una tabella 2D per livello di CLMSEX
#> , ,  = M
#> 
#>      
#>       (0,0.5] (0.5,2] (2,4] (4,8] (8,1.1e+03]
#>   yes      25      52   127    65          56
#>   no      103      83    55    14           6
#> 
#> , ,  = F
#> 
#>      
#>       (0,0.5] (0.5,2] (2,4] (4,8] (8,1.1e+03]
#>   yes      24      52   131    81          64
#>   no      133     136    78    34           9
tabella3[2, 3, 1]  # frequenza: no-avvocato, 3a classe di LOSS, sesso M
#> [1] 55

⚠️ La lettura di tabelle a più di tre entrate diventa rapidamente complessa. In pratica si va raramente oltre le tre variabili.

5.1 Un paradosso: Caffè e Cancro al Polmone

I dati seguenti riportano la distribuzione percentuale di uno studio sull’incidenza dell’abitudine a bere caffè nello svilupparsi del cancro al polmone. Le variabili in gioco sono:

Simbolo Variabile
\(C\) abitudine a bere caffè
\(T\) avere il cancro al polmone
\(F\) abitudine al fumo

5.1.1 Dati come Array Tridimensionale

Anche in questo caso i dati a tre variabili si rappresentano naturalmente come un array tridimensionale \(2 \times 2 \times 2\): le prime due dimensioni corrispondono a Cancro e Caffè, la terza all’abitudine al fumo.

# Costruiamo l'array 3D: dim = c(Cancro, Caffe, Fumo)
# Cella [i, j, k] = conteggio con Cancro=i, Caffe=j, Fumo=k

caffe <- array(
  data = c(
    # Fumatori
    41,  6,   # cancro sì: caffè sì, caffè no
     8,  1,   # cancro no: caffè sì, caffè no
    # Non fumatori
     4,  4,   # cancro sì: caffè sì, caffè no
    17, 19    # cancro no: caffè sì, caffè no
  ),
  dim = c(2, 2, 2),
  dimnames = list(
    Cancro = c("sì", "no"),
    Caffe  = c("sì", "no"),
    Fumo   = c("sì", "no")
  )
)

caffe
#> , , Fumo = sì
#> 
#>       Caffe
#> Cancro sì no
#>     sì 41  8
#>     no  6  1
#> 
#> , , Fumo = no
#> 
#>       Caffe
#> Cancro sì no
#>     sì  4 17
#>     no  4 19

5.1.2 Tabella Marginale: Cancro × Caffè

Sommando sulla terza dimensione (abitudine al fumo) otteniamo la tabella marginale:

tab_TC <- apply(caffe, c(1, 2), sum)
tab_TC
#>       Caffe
#> Cancro sì no
#>     sì 45 25
#>     no 10 20
sum(tab_TC)
#> [1] 100
round(prop.table(tab_TC), 2)
#>       Caffe
#> Cancro   sì   no
#>     sì 0.45 0.25
#>     no 0.10 0.20
# Profili colonna: distribuzione di Cancro per abitudine al caffè
round(prop.table(tab_TC, margin = 2), 2)
#>       Caffe
#> Cancro   sì   no
#>     sì 0.82 0.56
#>     no 0.18 0.44

📌 Dalla tabella sembra evidente una forte dipendenza: il 82% dei bevitori di caffè ha il cancro al polmone, contro il 56% dei non bevitori. L’abitudine al caffè sembrerebbe una “causa” del cancro.

5.2 Tabelle Condizionate per Abitudine al Fumo

Controllando per la variabile fumo, il quadro cambia radicalmente:

# Fumatori
caffe[, , "sì"]
#>       Caffe
#> Cancro sì no
#>     sì 41  8
#>     no  6  1
# Non fumatori
caffe[, , "no"]
#>       Caffe
#> Cancro sì no
#>     sì  4 17
#>     no  4 19
# Profili colonna per fumatori
round(prop.table(caffe[, , "sì"],  margin = 2) * 100, 1)
#>       Caffe
#> Cancro   sì   no
#>     sì 87.2 88.9
#>     no 12.8 11.1
# Profili colonna per non fumatori
round(prop.table(caffe[, , "no"], margin = 2) * 100, 1)
#>       Caffe
#> Cancro sì   no
#>     sì 50 47.2
#>     no 50 52.8

📌 Nelle due sottopopolazioni il cancro al polmone e l’abitudine al caffè sono quasi indipendenti: tra i fumatori il cancro colpisce l’87% dei bevitori di caffè e l’89% dei non bevitori; tra i non fumatori il 50% e il 47% rispettivamente. Le differenze sono trascurabili in entrambi i gruppi.

5.3 Perché Avviene?

Vediamo la distribuzione dell’abitudine al fumo per abitudine al caffè

# Distribuzione dell'abitudine al fumo per abitudine al caffè
apply(caffe, c(2, 3), sum)
#>      Fumo
#> Caffe sì no
#>    sì 47  8
#>    no  9 36
round(prop.table(apply(caffe, c(2, 3), sum), margin = 1) * 100, 1)
#>      Fumo
#> Caffe   sì   no
#>    sì 85.5 14.5
#>    no 20.0 80.0

La variabile confondente è l’abitudine al fumo (\(F\)): i bevitori di caffè sono molto più spesso fumatori rispetto ai non bevitori, e il fumo è la vera causa del cancro al polmone. Nella tabella marginale questo effetto si trasferisce artificialmente al caffè.

Il cancro (\(T\)) e il caffè (\(C\)) sono marginalmente dipendenti, ma indipendenti condizionatamente all’abitudine al fumo (\(F\)).

⚠️ Dipendenza non è causalità. Anche in assenza di paradossi come questo, una associazione osservata nei dati non implica che una variabile sia “causa” dell’altra. Può esistere semplicemente un legame statistico mediato da una terza variabile confondente. È la conoscenza del problema — non i soli dati — a permettere di non incorrere in errori interpretativi come quello storico sul caffè e il cancro al polmone.


6 Grafici per Variabili Qualitative: barplot()

Il diagramma a barre è la rappresentazione grafica standard per una variabile qualitativa. Si costruisce applicando barplot() a una tabella di frequenze. Come alternativa si può usare il grafico a torta pie, ma sebbene diffusissimo ha diversi limiti (si veda la Note in help(pie))

Grafico Variabile Funzione principale
Diagramma a barre Qualitativa barplot()
Grafico a torta Qualtitativa pie()

6.1 Barplot Semplice

Continuiamo con il data frame AutoBi di InsuranceData.


Per rappresentare le frequenze della variabile relativa alla tutela legale abbiamo

tabella_att <- table(AutoBi$ATTORNEY)

barplot(tabella_att,
        main = "Ricorso all'avvocato",
        xlab = "Avvocato",
        ylab = "Frequenza assoluta",
        col  = c("darkblue", "darkred"))

Con le frequenze relative l’asse delle ordinate diventa direttamente interpretabile come proporzione:

barplot(prop.table(tabella_att),
        main = "Ricorso all'avvocato",
        xlab = "Avvocato",
        ylab = "Frequenza relativa",
        col  = c("darkblue", "darkred"),
        ylim = c(0, 0.7))

6.2 Barplot Orizzontale

Con horiz = TRUE le barre diventano orizzontali — utile quando le etichette delle modalità sono lunghe:

barplot(sort(prop.table(table(AutoBi$MARITAL)), decreasing = TRUE),
        main  = "Stato civile",
        xlab  = "Frequenza relativa",
        col   = c("darkgreen", "darkblue", "darkred"),
        horiz = TRUE,
        cex.names = 0.45, 
        las   = 1)   # las=1: tutte le etichette orizzontali

💡 Ordinare le barre (sort()) rende il confronto tra modalità più immediato, soprattutto quando le frequenze sono simili.

6.3 Barplot per Tabella a Doppia Entrata

Per una tabella a doppia entrata possiamo visualizzare le frequenze assolute (o relative) congiunte sempre mediante barplot. Con beside = TRUE le barre sono affiancate; con beside = FALSE (default) sono impilate.

tab2 <- table(AutoBi$CLMSEX, AutoBi$ATTORNEY)
barplot(tab2,
        beside  = TRUE,
        main    = "Ricorso all'avvocato per genere",
        xlab    = "Avvocato",
        ylab    = "Frequenza assoluta",
        ylim = c(0,500),
       col  = c("darkblue", "darkred"),
       legend  = rownames(tab2),
        args.legend = list(
          title = "Genere",
          x = "topright",
          bty = "n"
        ))

barplot(prop.table(tab2),
        beside  = TRUE,
        main    = "Ricorso all'avvocato per sesso",
        xlab    = "Avvocato",
        ylab    = "Frequenza relativa",
        ylim = c(0,0.5),
       col  = c("darkblue", "darkred"),
       legend  = rownames(tab2),
        args.legend = list(
          title = "Sesso",
          x = "topright",
          bty = "n"
        ))

Tuttavia il confronto più interessante può essere fatto utilizzando le distribuzioni condizionate, ad esempio consideriamo il barplot con barre impilate per rappresentare la distribuzione del genere per tutela legale.

# Barre impilate con frequenze condizionate di colonna
tab_cond <- prop.table(tab2, 2)

barplot(tab_cond,
        beside  = FALSE,
        main    = "Distribuzione del sesso per avvocato",
        xlab    = "Avvocato",
        ylab    = "Frequenza relativa",
        ylim = c(0, 1.35),
        col  = c("darkblue", "darkred"),
        legend  = rownames(tab_cond),
        args.legend = list(title = "Sesso", bty = "n"))

💡 Il barplot impilato con frequenze condizionate è il complemento grafico della tabella di frequenze condizionate: le colonne di altezza uguale indicherebbero indipendenza tra le variabili.


6.4 Diagramma a Torta

Il diagramma a torta rappresenta la distribuzione di una variabile qualitativa attraverso settori circolari proporzionali alle frequenze relative. Può essere una scelta quando le categorie sono poche e si vuole enfatizzare il contributo di ciascuna al totale.

freq_marital <- prop.table(table(AutoBi$MARITAL))

pie(freq_marital,
    main   = "Stato civile",
    col    = c("darkgreen", "darkblue", "darkred", "darkorange"),
    labels = paste0(names(freq_marital), "\n",
                    round(freq_marital * 100, 1), "%"))

💡 Con paste0() si costruiscono etichette informative che combinano il nome della categoria e la percentuale corrispondente direttamente sul grafico, evitando la necessità di una legenda separata.

⚠️ Il diagramma a torta rende difficile confrontare visivamente settori di dimensione simile — in questi casi il diagramma a barre è generalmente preferibile.

6.4.1 Esercizio

Esercizio 4
Usando il dataset Cars93 (MASS):
1. Costruisci il barplot della variabile Type.
2. Ordina le barre in senso decrescente di frequenza.
3. Costruisci un barplot affiancato che mostri la distribuzione di Type separatamente per Origin (USA vs non-USA).
💡 Usa legend = TRUE nel barplot per aggiungere automaticamente la legenda.

library(MASS)
data("Cars93")

# 1.
barplot(table(Cars93$Type),
        main = "Tipo di auto")

# 2.
barplot(sort(table(Cars93$Type), decreasing = TRUE),
        main = "Tipo di auto (ordinato)")

# 3.
tab_ot <- table(Cars93$Origin, Cars93$Type)
barplot(tab_ot,
        beside  = TRUE,
        main    = "Tipo di auto per origine",
        legend  = TRUE,
        args.legend = list(bty = "n"))

# O in alternativa
tab_to <- table(Cars93$Type, Cars93$Origin)
barplot(tab_to,
        beside  = TRUE,
        main    = "Tipo di auto per origine",
        legend  = TRUE,
        args.legend = list(bty = "n"))


6.5 Cheatsheet

Grafico Funzione Variabili
Barre semplice barplot(table(x)) 1 qualitativa
Barre affiancate barplot(table(x,y), beside=TRUE) 2 qualitative
Barre impilate condizionate barplot(prop.table(table(x,y),2), beside=FALSE) 2 qualitative
# TABELLA DI FREQUENZA SEMPLICE
table(x)                          # frequenze assolute
prop.table(table(x))              # frequenze relative
margin.table(table(x))            # totale validi (senza NA)
cumsum(prop.table(table(x)))      # frequenze relative cumulate
droplevels(x)                     # rimuove livelli vuoti

# TABELLA A DOPPIA ENTRATA
tab <- table(x, y)                # frequenze assolute congiunte
margin.table(tab, margin = 1)              # marginale di x (riga)
margin.table(tab, margin = 2)              # marginale di y (colonna)
prop.table(tab)                   # frequenze relative congiunte
prop.table(tab, margin = 1)                # frequenze condizionate di riga
prop.table(tab, margin = 2)                # frequenze condizionate di colonna

# AFFIANCARE CONDIZIONATE E MARGINALE
cbind(prop.table(tab, 2), marginale = prop.table(table(x)))
rbind(prop.table(tab, 1), marginale = prop.table(table(y)))

# TABELLA A TRE ENTRATE
tab3 <- table(x, y, z)                    # array 3D
tab3[,,k]; tab3[,j,k]; tab3[i,j,k]
apply(tab3, c(1,2), sum) # sommare su terza dimensione

# ── BARPLOT ────────────────────────────────────────────────────────────────
barplot(table(x))                              # frequenze assolute
barplot(sort(table(x), decreasing=TRUE))       # barre ordinate per frequenza
barplot(table(x), horiz=TRUE, las=1)           # in orizzontale con ruotamento etichette

barplot(prop.table(table(x)))                  # frequenze relative

# Doppia entrata
tab <- table(x, y)
# Barplot affiancati
barplot(tab, beside=TRUE, legend=rownames(tab),
        args.legend=list(bty="n", title="Gruppo"))  

# Barplot impilati (per condizionate)
barplot(prop.table(tab, 2), beside=FALSE,        
        legend=rownames(tab))

# Opzioni grafiche comuni
barplot(..., col=c("darkblue","darkred"), main="Titolo",
        xlab="x", ylab="y", ylim=c(0, 0.8))

# Grafico a torta
pie(prop.table(table(x)))

# Arrotondamento
round(x, k)  # Arrotondamento x a k decimali