1 Importazione dei Dati

Nella pratica statistica i dati raramente vengono creati direttamente in R: più spesso provengono da file esterni (.csv, .txt, .dat, .xlsx, …).

⚠️ Prima di importare qualsiasi file è fondamentale impostare correttamente la working directory, ovvero la cartella in cui R cerca i file per default.

1.1 Working Directory

getwd()      # mostra la working directory corrente
setwd("")    # imposta una nuova working directory (inserire il percorso tra virgolette)

💡 In RStudio è possibile impostare la working directory anche da menu:

  • Session → Set Working Directory
  • Files → More → Set As Working Directory.
  • Tasto destro sul file R → Set Working Directory

1.2 read.table() e read.csv()

Le funzioni principali per importare dati tabulari sono read.table() e read.csv(). La seconda è un caso speciale della prima, ottimizzata per file con separatore virgola.

# Sintassi generale
read.table("<percorso/file>", <argomenti>)
read.csv("<percorso/file>",   <argomenti>)

I principali argomenti comuni:

Argomento Significato Default read.csv
header la prima riga contiene i nomi delle variabili TRUE
sep separatore di colonna ","
dec separatore decimale "."
na.strings stringa da interpretare come NA "NA"
stringsAsFactors converte automaticamente i character in factor FALSE (R ≥ 4.0)
row.names colonna da usare come nomi delle righe NULL
# File CSV con separatore virgola (default)
Food <- read.csv("importazione dataset/food_coded1.csv")
str(Food)

# Equivalente a 
Food <- read.csv("importazione dataset/food_coded1.csv", header = TRUE, sep = ";")
str(Food)

# Nota: alcuni file File CSV presentano come separatore punto e virgola (comune nei file italiani)
# Vediamo cosa succede in questo caso 
Food <- read.csv("importazione dataset/food_coded1.csv", header = TRUE, sep = ";")
str(Food)

# File .txt senza intestazione
cigarette <- read.table("importazione dataset/cigarette.txt", header = FALSE)
str(cigarette)

# File .dat con header e conversione automatica character → factor
cipolle <- read.table("importazione dataset/cipolle.dat", header = TRUE, stringsAsFactors = TRUE)
str(cipolle)

# File .data con valori mancanti codificati come "?"
macchine <- read.csv("importazione dataset/macchine/macchine.data", header = FALSE, na.strings = "?")
str(macchine)

# File con separatore decimale virgola (comune in Europa)
windmill <- read.table("importazione dataset/windmill.txt", dec = ",", header = TRUE)
str(windmill)

💡 La lettura dei file .txt e .dat sopra è stata fatta mediante read.table(). Tuttavia read.csv() può essere utile allo scopo ma dobbiamo considerare come separatore lo spazio vuoto (sep = ""), dato che il separatore è preimpostato a sep = ",".

# File cigarette letto con read.csv
cigarette <- read.csv("importazione dataset/cigarette.txt",
                      header = FALSE, sep = "")
str(cigarette)

# File cipolle letto con read.csv
cipolle <- read.csv("importazione dataset/cipolle.dat",
                    header = T, stringsAsFactors = T, 
                    sep = "")
str(cipolle)

💡 Per file .xlsx si può usare il pacchetto openxlsx:

# install.packages("openxlsx")
library(openxlsx)
dati <- read.xlsx("<percorso/file>.xlsx")

💡 Per altri formati (SAS, SPSS, Stata, …) è disponibile il pacchetto foreign, che offre funzioni come read.spss(), read.dta(), ecc.

1.2.1 Colonna indice indesiderata

Quando un file CSV è stato salvato con write.csv() senza specificare row.names = FALSE, R aggiunge automaticamente una colonna di indici (di solito chiamata X). Per evitare il problema:

# In lettura: usa la prima colonna come nomi riga (non entra nel data frame)
dati <- read.csv("file.csv", row.names = 1)

# Oppure: rimuovi la colonna dopo la lettura
dati <- dati[, -1]
dati$X <- NULL

1.3 Esplorazione Iniziale del Dataset

Dopo l’importazione è buona pratica ispezionare il dataset prima di qualsiasi analisi. Usiamo il dataset Food come esempio: contiene le risposte di studenti universitari americani su abitudini alimentari e stile di vita.

💡 Il dataset è disponibile su Kaggle.

Food <- read.csv("importazione dataset/food_coded1.csv", header = TRUE, sep = ",")

dim(Food)     # numero di righe e colonne
#> [1] 125  48
str(Food)     # struttura: tipo di ogni variabile
#> 'data.frame':    125 obs. of  48 variables:
#>  $ GPA                       : num  2.4 3.65 3.3 3.2 3.5 ...
#>  $ Gender                    : int  2 1 1 1 1 1 2 1 1 1 ...
#>  $ breakfast                 : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ calories_chicken          : int  430 610 720 430 720 610 610 720 430 430 ...
#>  $ calories_day              : int  NA 3 4 3 2 3 3 3 NA 3 ...
#>  $ calories_scone            : int  315 420 420 420 420 980 420 420 420 315 ...
#>  $ coffee                    : int  1 2 2 2 2 2 2 1 1 2 ...
#>  $ comfort_food_reasons_coded: int  9 1 1 2 1 4 1 1 2 1 ...
#>  $ cook                      : int  2 3 1 2 1 3 2 3 3 3 ...
#>  $ cuisine                   : int  NA 1 3 2 2 NA 1 1 1 1 ...
#>  $ diet_current_coded        : int  1 2 3 2 2 2 3 1 1 1 ...
#>  $ drink                     : int  1 2 1 2 2 2 1 2 1 1 ...
#>  $ eating_changes_coded      : int  1 1 1 1 3 1 2 2 2 1 ...
#>  $ eating_changes_coded1     : int  1 2 3 3 4 3 5 5 8 3 ...
#>  $ eating_out                : int  3 2 2 2 2 1 2 2 5 3 ...
#>  $ employment                : int  3 2 3 3 2 3 3 2 2 3 ...
#>  $ ethnic_food               : int  1 4 5 5 4 4 5 2 5 5 ...
#>  $ exercise                  : int  1 1 2 3 1 2 1 2 NA 1 ...
#>  $ father_education          : int  5 2 2 2 4 1 4 3 5 5 ...
#>  $ fav_cuisine_coded         : int  3 1 1 3 1 6 4 5 1 1 ...
#>  $ fav_food                  : int  1 1 3 1 3 3 1 1 3 1 ...
#>  $ fries                     : int  2 1 1 2 1 1 1 1 1 1 ...
#>  $ fruit_day                 : int  5 4 5 4 4 2 4 5 4 5 ...
#>  $ grade_level               : int  2 4 3 4 4 2 4 2 1 1 ...
#>  $ greek_food                : int  5 4 5 5 4 2 5 3 5 5 ...
#>  $ healthy_feeling           : int  2 5 6 7 6 4 4 3 7 3 ...
#>  $ ideal_diet_coded          : int  8 3 6 2 2 2 2 2 6 2 ...
#>  $ income                    : int  5 4 6 6 6 1 4 5 5 4 ...
#>  $ indian_food               : int  5 4 5 5 2 5 5 1 5 4 ...
#>  $ italian_food              : int  5 4 5 5 5 5 5 3 5 5 ...
#>  $ life_rewarding            : int  1 1 7 2 1 4 8 3 8 3 ...
#>  $ marital_status            : int  1 2 2 2 1 2 1 1 2 2 ...
#>  $ mother_education          : int  1 4 2 4 5 1 4 2 5 5 ...
#>  $ nutritional_check         : int  5 4 4 2 3 1 4 4 2 5 ...
#>  $ on_off_campus             : int  1 1 2 1 1 1 2 1 1 1 ...
#>  $ parents_cook              : int  1 1 1 1 1 2 2 1 2 3 ...
#>  $ pay_meal_out              : int  2 4 3 2 4 5 2 5 3 3 ...
#>  $ persian_food              : int  5 4 5 5 2 5 5 1 5 4 ...
#>  $ self_perception_weight    : int  3 3 6 5 4 5 4 3 4 3 ...
#>  $ soup                      : int  1 1 1 1 1 1 1 1 2 1 ...
#>  $ sports                    : int  1 1 2 2 1 2 1 2 2 1 ...
#>  $ thai_food                 : int  1 2 5 5 4 4 5 1 5 4 ...
#>  $ tortilla_calories         : int  1165 725 1165 725 940 940 940 725 725 580 ...
#>  $ turkey_calories           : int  345 690 500 690 500 345 690 500 345 345 ...
#>  $ veggies_day               : int  5 4 5 3 4 1 4 4 3 5 ...
#>  $ vitamins                  : int  1 2 1 1 2 2 1 2 2 1 ...
#>  $ waffle_calories           : int  1315 900 900 1315 760 1315 1315 1315 760 900 ...
#>  $ weight                    : int  187 155 NA NA 190 190 180 137 180 125 ...
head(Food)    # prime 6 righe
length(Food)  # numero di colonne (= ncol)
#> [1] 48

2 Operazioni su Data Frame

Le operazioni su Data Frame viste in precedenza (usando R base e dplyr) si applicano nello stesso modo per data frame importati esternamente.

📌 Esercizio Con riferimento al dataset food:

  1. Estraiamo la variabile GPA
  2. Convertiamo Gender in factor
  3. Contiamo quante donne ci sono nel dataset
  4. Otteniamo: (i) GPA e peso dei maschi; (ii)sport ed employment per le sole donne; (iii) genere dei soggetti con sports == 1 e vitamins == 1

Esercizio per casa: Si ottengano gli stessi risultati usando la sintassi dplyr

Food$GPA          # con $
#>   [1] 2.400 3.654 3.300 3.200 3.500 2.250 3.800 3.300 3.300 3.300 3.500 3.904
#>  [13] 3.400 3.600 3.100    NA 4.000 3.600 3.400 2.200 3.300 3.870 3.700 3.700
#>  [25] 3.900 2.800 3.700 3.000 3.200 3.500 4.000 4.000 3.400 2.800 3.650 3.000
#>  [37] 3.700 3.400 3.890 3.000 3.400 2.900 3.600 3.500 3.200 3.605 3.800 2.800
#>  [49] 3.500 3.830 3.600 3.300 3.300 3.292 3.500 3.350 3.800 2.800 3.500 3.700
#>  [61] 3.600    NA 3.900 2.600 3.500 3.200 3.000 3.600 3.200 3.670 3.730 4.000
#>  [73] 3.100 3.790 2.710 3.000 3.700 3.100 3.000 3.900 3.400 3.500 3.700 3.700
#>  [85] 3.830 2.600 3.000 3.200 3.500 3.200 3.680 3.800 3.300 3.200 3.750 3.500
#>  [97] 3.920 3.900 3.900 3.200 3.500 3.400    NA 3.700    NA 3.000 3.000 3.800
#> [109] 3.800 3.400 3.700 2.900 3.900 3.600 2.800 3.300 3.400 3.770 3.630 3.200
#> [121] 3.500 3.000 3.882 3.000 3.900
#Food[, "GPA"]     # per nome
#Food[, 1]         # per indice numerico

str(Food[[1]])    # [[ ]] estrae come vettore (equivalente a $)
#>  num [1:125] 2.4 3.65 3.3 3.2 3.5 ...

Una volta selezionata una colonna, è possibile trattarla come un vettore e applicare tutte le operazioni viste in precedenza.

2.1 subset() — Alternativa Espressiva a R Base

subset() accetta tre argomenti principali: subset(data, subset = <condizione righe>, select = <selezione colonne>).

Il vantaggio principale è che non serve ripetere il nome del data frame nelle condizioni. La notazione : per selezionare un range di colonne per nome (nome_start:nome_end) è disponibile solo in subset() e non nel subsetting standard con [.

# Seleziona fruit_day e veggies_day per i maschi sportivi
Food2 <- Food[Food$Gender == "Male" & Food$sports == 1,
              c("fruit_day", "veggies_day")]

# Equivalente con subset()
Food.subset <- subset(Food,
                      subset = (Gender == "Male" & sports == 1),
                      select  = c(fruit_day, veggies_day))

# Escludere una colonna (indice negativo) con condizione sulle righe
Food[Food$employment == 1 | Food$employment == 2, -48]
# Equivalente
#Food[Food$employment < 3, -48]

subset(Food,
       subset = (employment == 1 | employment == 2),
       select  = -c(weight))
# Nuovo data frame: maschi con reddito > 3, variabili da income a nutritional_check
newdata <- subset(Food,
                  subset = Gender == "Male" & income > 3,
                  select  = c(income:nutritional_check))
str(newdata)
#> 'data.frame':    0 obs. of  7 variables:
#>  $ income           : int 
#>  $ indian_food      : int 
#>  $ italian_food     : int 
#>  $ life_rewarding   : int 
#>  $ marital_status   : int 
#>  $ mother_education : int 
#>  $ nutritional_check: int

💡 subset() è comoda per uso interattivo. In funzioni o pacchetti è preferibile il subsetting esplicito con [, perché subset() usa la non-standard evaluation e può dare risultati inattesi in contesti programmati.

3 Valori Mancanti (NA)

I valori mancanti sono una realtà comune nei dati reali. R li rappresenta con NA (Not Available). Prima di qualsiasi analisi è fondamentale capire quanti NA sono presenti, dove si trovano e come gestirli.

3.1 Individuare i Valori Mancanti

💡 Su un singolo vettore/colonna

is.na(Food$calories_day)           # vettore logico: TRUE dove c'è NA
#>   [1]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
#>  [13] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#>  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#>  [37] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
#>  [49]  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE
#>  [61] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE
#>  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
#>  [85] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE  TRUE
#>  [97] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
#> [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE
#> [121] FALSE FALSE  TRUE FALSE  TRUE
which(is.na(Food$calories_day))    # posizioni degli NA
#>  [1]   1   9  16  40  48  49  54  60  66  72  83  93  94  96 102 117 118 123 125
sum(is.na(Food$calories_day))      # numero totale di NA
#> [1] 19
mean(is.na(Food$calories_day))     # proporzione di NA
#> [1] 0.152

💡 Su un intero data frame:

colSums(is.na(Food))    # numero di NA per colonna
#>                        GPA                     Gender 
#>                          4                          0 
#>                  breakfast           calories_chicken 
#>                          0                          0 
#>               calories_day             calories_scone 
#>                         19                          1 
#>                     coffee comfort_food_reasons_coded 
#>                          0                         19 
#>                       cook                    cuisine 
#>                          3                         17 
#>         diet_current_coded                      drink 
#>                          0                          2 
#>       eating_changes_coded      eating_changes_coded1 
#>                          0                          0 
#>                 eating_out                 employment 
#>                          0                          9 
#>                ethnic_food                   exercise 
#>                          0                         13 
#>           father_education          fav_cuisine_coded 
#>                          1                          0 
#>                   fav_food                      fries 
#>                          2                          0 
#>                  fruit_day                grade_level 
#>                          0                          0 
#>                 greek_food            healthy_feeling 
#>                          0                          0 
#>           ideal_diet_coded                     income 
#>                          0                          1 
#>                indian_food               italian_food 
#>                          0                          0 
#>             life_rewarding             marital_status 
#>                          1                          1 
#>           mother_education          nutritional_check 
#>                          3                          0 
#>              on_off_campus               parents_cook 
#>                          1                          0 
#>               pay_meal_out               persian_food 
#>                          0                          1 
#>     self_perception_weight                       soup 
#>                          1                          1 
#>                     sports                  thai_food 
#>                          2                          0 
#>          tortilla_calories            turkey_calories 
#>                          1                          0 
#>                veggies_day                   vitamins 
#>                          0                          0 
#>            waffle_calories                     weight 
#>                          0                          4
colMeans(is.na(Food))   # proporzione di NA per colonna
#>                        GPA                     Gender 
#>                      0.032                      0.000 
#>                  breakfast           calories_chicken 
#>                      0.000                      0.000 
#>               calories_day             calories_scone 
#>                      0.152                      0.008 
#>                     coffee comfort_food_reasons_coded 
#>                      0.000                      0.152 
#>                       cook                    cuisine 
#>                      0.024                      0.136 
#>         diet_current_coded                      drink 
#>                      0.000                      0.016 
#>       eating_changes_coded      eating_changes_coded1 
#>                      0.000                      0.000 
#>                 eating_out                 employment 
#>                      0.000                      0.072 
#>                ethnic_food                   exercise 
#>                      0.000                      0.104 
#>           father_education          fav_cuisine_coded 
#>                      0.008                      0.000 
#>                   fav_food                      fries 
#>                      0.016                      0.000 
#>                  fruit_day                grade_level 
#>                      0.000                      0.000 
#>                 greek_food            healthy_feeling 
#>                      0.000                      0.000 
#>           ideal_diet_coded                     income 
#>                      0.000                      0.008 
#>                indian_food               italian_food 
#>                      0.000                      0.000 
#>             life_rewarding             marital_status 
#>                      0.008                      0.008 
#>           mother_education          nutritional_check 
#>                      0.024                      0.000 
#>              on_off_campus               parents_cook 
#>                      0.008                      0.000 
#>               pay_meal_out               persian_food 
#>                      0.000                      0.008 
#>     self_perception_weight                       soup 
#>                      0.008                      0.008 
#>                     sports                  thai_food 
#>                      0.016                      0.000 
#>          tortilla_calories            turkey_calories 
#>                      0.008                      0.000 
#>                veggies_day                   vitamins 
#>                      0.000                      0.000 
#>            waffle_calories                     weight 
#>                      0.000                      0.032
any(is.na(Food))        # c'è almeno un NA nell'intero data frame?
#> [1] TRUE

3.2 Effetto degli NA sui Calcoli

⚠️ Le funzioni statistiche restituiscono NA se il vettore contiene valori mancanti. Usare sempre na.rm = TRUE per ignorarli:

mean(Food$calories_day)              # → NA
#> [1] NA
mean(Food$calories_day, na.rm = TRUE)  # → media calcolata sugli osservati
#> [1] 3.028302

3.3 Rimuovere i Valori Mancanti

💡 Rimuove solo le righe con NA su una variabile o rimuove tutte le righe con almeno un NA (casi completi)

# Rimuove solo le righe con NA su calories_day
Food[!is.na(Food$calories_day), ]
dim(Food[!is.na(Food$calories_day), ])
#> [1] 106  48
# Rimuove tutte le righe con almeno un NA (casi completi)
na.omit(Food)
Food_clean <- Food[complete.cases(Food), ]
dim(Food_clean)
#> [1] 57 48

💡 complete.cases() restituisce un vettore logico: TRUE per le righe senza alcun NA. È equivalente a !is.na() applicato su tutto il data frame contemporaneamente.

⚠️ na.omit() rimuove qualsiasi riga con almeno un NA, anche su variabili irrilevanti per l’analisi. Meglio filtrare solo sulle colonne che interessano quando il dataset ha molte variabili con dati mancanti.

4 Esportazione dei Dati

Una volta manipolato il dataset, si può salvare il risultato in un file esterno o in un file .RData per riutilizzarlo in sessioni future.

4.1 write.table() e write.csv()

# Salva come file .dat (separatore spazio, con row names)
write.table(Food, file = "Food.dat")

# Salva come CSV senza row names (consigliato) e con separatore
write.table(Food, file = "Food.csv", sep = ",", row.names = FALSE)

# Equivalente con write.csv()
write.csv(Food, file = "Food.csv", row.names = FALSE)

💡 Specificare sempre row.names = FALSE per evitare che R scriva una colonna di indici numerici nel file — quella colonna X che poi compare in fase di reimportazione.

4.2 Salvare Oggetti R (.RData)

save() salva uno o più oggetti R in un file binario .RData, preservando tipi, attributi e strutture esattamente come sono in memoria.

# Salva l'oggetto
save(Food, file = "Food.RData")
save(Food, Food_clean, file = "Food2.RData") # Si può salvare più di un oggetto

# Salva l'intera sessione (tutti gli oggetti nell'environment)
save.image(file = "sessione.RData")

# Ricarica gli oggetti salvati
load("Food.RData")

💡 .RData è il formato più efficiente per scambiare dati tra sessioni R: preserva fattori, livelli, attributi e strutture complesse senza perdita di informazione, a differenza del CSV.

4.2.1 Esercizi

📌 Esercizio 2 Carica il dataset feeling da data/feeling.Rdata. Definisci una variabile categoriale da ft_immig_2016 con le seguenti classi:

Classe Intervallo
"strongly unfavorable" 0–Q1
"unfavorable/indifferent" Q1–Q2
"lightly favorable" Q2–Q3
"favorable" Q3–100

dove Qj è il j-esimo quartile. Si usi la funzione quantile().

Aggiungi la nuova variabile al data frame. Usa la funzione cut().

load("importazione dataset/feeling.Rdata")

Q <- quantile(feeling$ft_immig_2016, 
              prob = c(0.25,0.5,0.75), 
              na.rm = T)


feeling$immig_class <- cut(
  feeling$ft_immig_2016,
  breaks = c(0, Q, 100), 
  labels = c("strongly unfavorable", "unfavorable/indifferent",
             "lightly favorable", "favorable"),
  include.lowest = TRUE
)

str(feeling$immig_class)
#>  Factor w/ 4 levels "strongly unfavorable",..: 4 4 3 4 4 4 4 1 4 4 ...
table(feeling$immig_class)
#> 
#>    strongly unfavorable unfavorable/indifferent       lightly favorable 
#>                    1966                    1946                    1885 
#>               favorable 
#>                    1891

⚠️ Quartili

Un quartile è un valore che divide i dati ordinati in quattro parti uguali.

Prima si ordinano i dati dal più piccolo al più grande. Poi si individuano tre valori che li dividono in quattro gruppi.

Questi valori si chiamano:

Q1 (primo quartile): il valore sotto cui si trova circa il 25% dei dati (25% dei dati è minore o uguale a questo valore)

Q2 (secondo quartile): il valore sotto cui si trova il 50% dei dati (è la mediana, 50% dei dati è minore o uguale a questo valore)

Q3 (terzo quartile): il valore sotto cui si trova circa il 75% dei dati (75% dei dati è minore o uguale a questo valore)

📌 Esercizio 3 Rimuovi i valori mancanti dal dataset feeling ed esporta il data frame risultante in un file .csv senza colonna indice.

feeling_clean <- na.omit(feeling)
dim(feeling_clean)
#> [1] 5383   16
write.csv(feeling_clean, file = "feeling_clean.csv", row.names = FALSE)