Azienda:Neonatal Healt Solutions
Obiettivo:Creare un modello statistico in grado di prevedere con precisione il peso dei neonati alla nascita, basandosi su variabili cliniche raccolte da tre ospedali. Il progetto mira a migliorare la gestione delle gravidanze ad alto rischio, ottimizzare le risorse ospedaliere e garantire migliori risultati per la salute neonatale.
Il progetto si inserisce all’interno di un contesto di crescente attenzione verso la prevenzione delle complicazioni neonatali. La possibilità di prevedere il peso alla nascita dei neonati rappresenta un’opportunità fondamentale per migliorare la pianificazione clinica e ridurre i rischi associati a nascite problematiche, come parti prematuri o neonati con basso peso. Di seguito, i principali benefici che questo progetto porterà all’azienda e al settore sanitario:
Carichiamo tutte le librerie che ci serviranno per l’analisi:
library(moments)
library(dplyr)
library(ggplot2)
library(knitr)
library(lmtest)
library(ggpubr)
library(car)
library(MASS)
Andiamo a costruire tutte le funzioni che utilizzeremo per semplificarci l’analisi
#Creiamo una funzione per calcolare il coefficiente di variazione
cv <- function(x){
return(sd(x)/mean(x)*100)
}
#Creiamo una funzione per gli indici di posizione, variabilità e forma
summary_stats <- function(x) {
c(
min = min(x),
max = max(x),
range = max(x) - min(x),
IQR = IQR(x),
Q = quantile(x, probs = 0.25),
median = median(x),
Q = quantile(x, probs = 0.75),
mean = mean(x),
var = var(x),
sd = sd(x),
cv = cv(x),
skewness = skewness(x),
kurtosis = kurtosis(x) - 3
)
}
#Creiamo una funzione per costruire le distribuzioni di frequenze
distr_freq <- function(col){
ni <- table(col)
fi <- ni/length(col)
return(list(ni = as.numeric(ni),
fi = as.numeric(round(fi, 4)),
categories = names(ni)))
}
#Creiamo una funzione per graficare la matrice di correlazione
panel.cor <- function(x, y, digits = 2, prefix = "", cex.cor, ...)
{
par(usr = c(0, 1, 0, 1))
r <- abs(cor(x, y))
txt <- format(c(r, 0.123456789), digits = digits)[1]
txt <- paste0(prefix, txt)
if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
text(0.5, 0.5, txt, cex = cex.cor * r)
}
Per costruire il modello predittivo, abbiamo raccolto dati su 2500 neonati provenienti da tre ospedali. Le variabili raccolte sono:
L’obiettivo principale è identificare quali di queste variabili sono più predittive del peso alla nascita, con un focus particolare sull’impatto del fumo materno e delle settimane di gestazione, che potrebbero indicare nascite premature.
#Importiamo il dataset
dati <- read.csv("neonati.csv",
stringsAsFactors = T,
sep=",")
n <- nrow(dati)
#Controlliamo se il tutto è andato a buon fine
kable(head(dati))
| Anni.madre | N.gravidanze | Fumatrici | Gestazione | Peso | Lunghezza | Cranio | Tipo.parto | Ospedale | Sesso |
|---|---|---|---|---|---|---|---|---|---|
| 26 | 0 | 0 | 42 | 3380 | 490 | 325 | Nat | osp3 | M |
| 21 | 2 | 0 | 39 | 3150 | 490 | 345 | Nat | osp1 | F |
| 34 | 3 | 0 | 38 | 3640 | 500 | 375 | Nat | osp2 | M |
| 28 | 1 | 0 | 41 | 3690 | 515 | 365 | Nat | osp2 | M |
| 20 | 0 | 0 | 38 | 3700 | 480 | 335 | Nat | osp3 | F |
| 32 | 0 | 0 | 40 | 3200 | 495 | 340 | Nat | osp2 | F |
#Controlliamo quanti valori nulli presenta il dataset
cat("Il dataset presenta", sum(is.na(dati)), "valori nulli")
## Il dataset presenta 0 valori nulli
Nella prima fase, esploreremo le variabili attraverso un’analisi descrittiva per comprenderne la distribuzione e identificare eventuali outlier o anomalie.
Inoltre si saggeranno le seguenti ipotesi con i test adatti:
dati <- dati %>%
mutate(Fumatrici = factor(Fumatrici, levels = 0:1, labels = c("False", "True"), ordered = FALSE))
df_numeric <- dati[sapply(dati, is.numeric)]
tabella_riassuntiva_df <- t(sapply(df_numeric, summary_stats))
tabella_riassuntiva_df <- as.data.frame(tabella_riassuntiva_df)
kable(tabella_riassuntiva_df, caption = "Statistiche Descrittive delle Variabili", digits = 2)
| min | max | range | IQR | Q.25% | median | Q.75% | mean | var | sd | cv | skewness | kurtosis | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Anni.madre | 0 | 46 | 46 | 7 | 25 | 28 | 32 | 28.16 | 27.81 | 5.27 | 18.72 | 0.04 | 0.38 |
| N.gravidanze | 0 | 12 | 12 | 1 | 0 | 1 | 1 | 0.98 | 1.64 | 1.28 | 130.51 | 2.51 | 10.99 |
| Gestazione | 25 | 43 | 18 | 2 | 38 | 39 | 40 | 38.98 | 3.49 | 1.87 | 4.79 | -2.07 | 8.26 |
| Peso | 830 | 4930 | 4100 | 630 | 2990 | 3300 | 3620 | 3284.08 | 275665.68 | 525.04 | 15.99 | -0.65 | 2.03 |
| Lunghezza | 310 | 565 | 255 | 30 | 480 | 500 | 510 | 494.69 | 692.67 | 26.32 | 5.32 | -1.51 | 6.49 |
| Cranio | 235 | 390 | 155 | 20 | 330 | 340 | 350 | 340.03 | 269.79 | 16.43 | 4.83 | -0.79 | 2.95 |
Analizziando gli indici:
In questo caso è utile vedere i boxplot per l’analisi degli outliers per le variabili più “sospette”, ovvero per Anni.madre, N.gravidanze e Peso:
ggplot(data = dati) +
geom_boxplot(aes(y = Anni.madre), fill="lightblue") +
labs(title = "Box plot dell'età della madre",
y = "Anni") +
theme_classic()
Il boxplot della variabile Anni.madre ci permette di vedere e analizzare gli outliers. Possiamo vedere che vi sono valori sopra il 40 considerati outliers, ma potrebbero semplicemente essere dei casi molto rari di madri avanti con l’età, e possiamo analizzare più o meno la stessa cosa in basso, con valori che potremmo considerare come casi di madri molto giovani. Valori “anormali” invece sono quegli outlier uguali a 0 o vicini ad esso.
cat("Il numero di osservazioni considerati errori sono:", sum(dati$Anni.madre <= 10, na.rm = TRUE))
## Il numero di osservazioni considerati errori sono: 2
dati$Anni.madre[dati$Anni.madre <= 10] <- median(dati$Anni.madre[dati$Anni.madre > 10], na.rm = TRUE)
summary(dati$Anni.madre)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 13.00 25.00 28.00 28.19 32.00 46.00
Dato il numero esiguo di tali anomalie, abbiamo optato per un’imputazione basata sulla mediana del dataset, un metodo robusto che mantiene la numerosità del campione e riduce l’impatto di valori estremi.
ggplot(data = dati) +
geom_boxplot(aes(y = N.gravidanze), fill="lightblue") +
labs(title = "Box plot del numero di gravidanze",
y = "Numero di gravidanze") +
scale_y_continuous(breaks = seq(0,15,1))+
theme_classic()
Per quanto riguarda invece il boxplot della variabile N.gravidanze possiamo vedere che la maggior parte delle madri ha avuto tra 0 e 2 gravidanze con una mediana in 1. Si registrano valori outliers che vanno da 3 fino a 12 gravidanze
ggplot(data = dati) +
geom_boxplot(aes(y = Peso), fill="lightblue") +
labs(title = "Box plot del numero di gravidanze") +
theme_classic()
Per analizzare meglio i valori outliers mostrati col boxplot andiamo a creare un istogramma
ggplot(data = dati) +
geom_histogram(aes(x = Peso), fill="lightblue",
color="black",
fill="lightblue") +
labs(title = "Istogramma del Peso dei Neonati",
x = "Peso in grammi",
y = "Frequenza") +
xlim(c(min(dati$Peso, na.rm=TRUE) - 100, max(dati$Peso, na.rm = TRUE) + 100))+
theme_classic()
L’istogramma non mostra valori isolati, quindi probabilmente gli outliers mostrati dal boxplot sono soltanto valori molto rari ma biologicamente possibili e genuini, piuttosto che errori di misurazione o di immissione dati.
var_cat <- c("Fumatrici", "Tipo.parto", "Ospedale", "Sesso")
df_categoric <- dati[var_cat]
risultati_list <- lapply(df_categoric, distr_freq)
lista_righe_df <- list()
for (var_name in names(risultati_list)) {
res <- risultati_list[[var_name]]
num_categories <- length(res$categories)
# Creiamo una riga per ogni categoria
for (i in 1:num_categories) {
lista_righe_df[[length(lista_righe_df) + 1]] <- data.frame(
Variabile = var_name,
Categoria = res$categories[i],
Frequenza_Assoluta = res$ni[i],
Frequenza_Relativa = res$fi[i]
)
}
}
tabella_riassuntiva_df_finale <- do.call(rbind, lista_righe_df)
kable(tabella_riassuntiva_df_finale,
caption = "Distribuzioni di Frequenze delle Variabili Qualitative",
digits = 2,
align = "lcrr")
| Variabile | Categoria | Frequenza_Assoluta | Frequenza_Relativa |
|---|---|---|---|
| Fumatrici | False | 2396 | 0.96 |
| Fumatrici | True | 104 | 0.04 |
| Tipo.parto | Ces | 728 | 0.29 |
| Tipo.parto | Nat | 1772 | 0.71 |
| Ospedale | osp1 | 816 | 0.33 |
| Ospedale | osp2 | 849 | 0.34 |
| Ospedale | osp3 | 835 | 0.33 |
| Sesso | F | 1256 | 0.50 |
| Sesso | M | 1244 | 0.50 |
È utile avere anche un supporto grafico
ggplot(df_categoric)+
geom_bar(aes(x = Fumatrici),
col = "black",
fill = "blue")+
labs(title = "Distribuzioni variabile Fumatrici",
x = "Fumatrici",
y = "Numero di Bambini")
ggplot(df_categoric)+
geom_bar(aes(x = Tipo.parto),
col = "black",
fill = "blue")+
labs(title = "Distribuzione del tipo di parto",
x = "Tipo di parto",
y = "Numero di Bambini")
ggplot(df_categoric)+
geom_bar(aes(x = Ospedale),
col = "black",
fill = "blue")+
labs(title = "Distribuzione dei bambini per ospedale",
y = "Numero di Bambini")
ggplot(df_categoric)+
geom_bar(aes(x = Sesso),
col = "black",
fill = "blue")+
labs(title = "Distribuzione del Sesso dei bambini",
y = "Numero di Bambini")
Questi grafici ci permettono di dire che la maggior parte dei bambini è nata da un parto naturale, che la numerosità dei bambini tra gli ospedali è distribuita equamente e che il numero di maschi e di femmine è più o meno uguale.
Potrebbe essere utile anche capire la distribuzione di bambini maschi e femmine tra gli ospedali.
kable(table(df_categoric$Sesso,df_categoric$Ospedale)/dim(df_categoric)[1])
| osp1 | osp2 | osp3 | |
|---|---|---|---|
| F | 0.1636 | 0.1740 | 0.1648 |
| M | 0.1628 | 0.1656 | 0.1692 |
ggplot(df_categoric)+
geom_bar(aes(x = Ospedale, fill=Sesso),
col = "black",
position = "stack")+
labs(title = "Sesso dei bambini per Ospedale",
y = "Numero di Bambini")
Dalla tabella e dal grafico possiamo vedere che la suddivisione per Sesso tra gli ospedali è molto simile. Questo ci permetterà di fare un’analisi/confronto tra gli ospedali in modo più accurato e semplice.
Infine un’altra informazione utile potrebbe essere il numero di parti cesarei e naturali tra gli ospedali.
ggplot(df_categoric)+
geom_bar(aes(x = Ospedale, fill=Tipo.parto),
col = "black",
position = "dodge")+
labs(title = "Tipo di parto per Ospedale",
y = "Numero di Bambini")
Da questo grafico possiamo vedere che il numero di parti cesarei e naturali è molto simile tra gli ospedali.
In alcuni ospedali si fanno più parti cesarei:
#Creiamo una tabella di contingenza
tabella_parti_ospedale <- table(dati$Tipo.parto, dati$Ospedale)
kable(tabella_parti_ospedale)
| osp1 | osp2 | osp3 | |
|---|---|---|---|
| Ces | 242 | 254 | 232 |
| Nat | 574 | 595 | 603 |
#Eseguiamo il Test Chi-quadro
chi_quadro_result <- chisq.test(tabella_parti_ospedale)
print(chi_quadro_result)
##
## Pearson's Chi-squared test
##
## data: tabella_parti_ospedale
## X-squared = 1.0972, df = 2, p-value = 0.5778
Dal valore del p-value l’unica cosa che possiamo dire è che non possiamo rifiutare l’ipotesi di indipendenza: ovvero non c’è un’associazione tra i tipi di parto e gli ospedali statisticamente significativa. Non possiamo dire, perciò, che in alcuni ospedali si facciano più parti cesareo. Risultato plausibile guardando semplicemente il grafico a barre precedente.
ggpubr::ggballoonplot(data=as.data.frame(tabella_parti_ospedale),
fill="blue")
Possiamo vedere infatti anche dal balloon plot che non vi sono pattern particolari che potrebbero indicare dipedenza.
Per saggiare l’ipotesi che la media del peso dei neonati nel nostro campione sia confrontabile con quella della popolazione generale, abbiamo utilizzato un valore di riferimento di 3300 grammi, comunemente accettato in letteratura. Abbiamo quindi eseguito un t-test per singolo campione per confrontare la media del nostro campione con questo valore ipotizzato.
t.test(dati$Peso, mu=3300, alternative = "two.sided")
##
## One Sample t-test
##
## data: dati$Peso
## t = -1.516, df = 2499, p-value = 0.1296
## alternative hypothesis: true mean is not equal to 3300
## 95 percent confidence interval:
## 3263.490 3304.672
## sample estimates:
## mean of x
## 3284.081
Il valore rientra nell’intervallo di confidenza e infatti il p-value è maggiore di 0.05, quindi non rifiutiamo l’ipotesi di media del peso del campione uguale a quella della popolazione. Facciamo la stessa cosa per la lunghezza prendendo come valore di riferimento 500 mm
t.test(dati$Lunghezza, mu=500, alternative = "two.sided")
##
## One Sample t-test
##
## data: dati$Lunghezza
## t = -10.084, df = 2499, p-value < 2.2e-16
## alternative hypothesis: true mean is not equal to 500
## 95 percent confidence interval:
## 493.6598 495.7242
## sample estimates:
## mean of x
## 494.692
In questo caso il test t per la variabile Lunghezza, con un’ipotesi nulla di una media pari a 500, ha prodotto un p-value estremamente basso e un intervallo di confidenza del 95% per la media [493.66, 495.72] che non include il valore 500. Questo risultato ci permette di rifiutare l’ipotesi nulla e concludere che la media campionaria della lunghezza dei neonati nel nostro campione (494.692) è significativamente inferiore a 500.
Questa discrepanza è ulteriormente supportata dall’analisi della mediana: mentre la media campionaria è di circa 494.7, la mediana della Lunghezza è esattamente 500. Poiché la mediana è un indice di tendenza centrale più robusto alla presenza di valori estremi rispetto alla media, il fatto che la media sia inferiore alla mediana indica una asimmetria a sinistra nella distribuzione della Lunghezza, come già evidenziato da un indice di asimmetria di -1.51. Questo suggerisce la presenza di valori particolarmente bassi (o outlier negativi) che stanno “tirando” la media verso il basso. Questo rinforza la possibilità che la deviazione dalla media della popolazione possa essere attribuibile a registrazioni di valori molto rari o errati nel campione. Questi outliers saranno da attenzionare durante la costruzione del modello.
La terza ipotesi che andremo a saggiare è
Potremmo procedere con un test t e costruire delle ipotesi di questo tipo:
Per effettuare un test t in questo caso per campioni indipendenti (maschi e femmine), vediamo se le variabili in gioco rispettano le assunzioni principali:
Le variabili antropometriche sono Peso, Lunghezza, Cranio e sono variabili quantitative. La variabile Sesso è una variabile qualitativa con due categorie che ci permetterà di definire i due gruppi (indipendenti). Inoltre la numerosità elevata del campione (2500) ci permette di andare relativamente sicuri per l’assunzione di normalità per le variabili Peso, Lunghezza e Cranio grazie al Teorema del Limite Centrale. Infine per l’assunzione di variabilità simile tra le variabili procediamo con il test t di Welch che non assume l’uguaglianza delle varianze ed è robusto anche quando le varianze sono diverse.
attach(dati)
t.test(Peso~Sesso, data=dati)
##
## Welch Two Sample t-test
##
## data: Peso by Sesso
## t = -12.106, df = 2490.7, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -287.1051 -207.0615
## sample estimates:
## mean in group F mean in group M
## 3161.132 3408.215
t.test(Lunghezza~Sesso, data=dati)
##
## Welch Two Sample t-test
##
## data: Lunghezza by Sesso
## t = -9.582, df = 2459.3, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -11.929470 -7.876273
## sample estimates:
## mean in group F mean in group M
## 489.7643 499.6672
t.test(Cranio~Sesso, data=dati)
##
## Welch Two Sample t-test
##
## data: Cranio by Sesso
## t = -7.4102, df = 2491.4, p-value = 1.718e-13
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -6.089912 -3.541270
## sample estimates:
## mean in group F mean in group M
## 337.6330 342.4486
detach(dati)
In questo modo come ci si poteva aspettare, le differenze antropometriche tra i due sessi sono significative, ottenendo infatti per tutti e 3 i test dei p-value molto bassi. Visualizziamo ad esempio le medie tra i due gruppi:
cat("peso medio femmine:", round(mean(dati$Peso[dati$Sesso == "F"]),2), "g",
"\npeso medio maschi:", round(mean(dati$Peso[dati$Sesso == "M"]),2), "g",
"\nlunghezza media femmine:", round(mean(dati$Lunghezza[dati$Sesso == "F"]),2), "mm",
"\nlunghezza media maschi:", round(mean(dati$Lunghezza[dati$Sesso == "M"]),2), "mm",
"\nlunghezza cranio media femmine:", round(mean(dati$Cranio[dati$Sesso == "F"]),2), "mm",
"\nlunghezza cranio media maschi:", round(mean(dati$Cranio[dati$Sesso == "M"]),2), "mm")
## peso medio femmine: 3161.13 g
## peso medio maschi: 3408.22 g
## lunghezza media femmine: 489.76 mm
## lunghezza media maschi: 499.67 mm
## lunghezza cranio media femmine: 337.63 mm
## lunghezza cranio media maschi: 342.45 mm
Verrà sviluppato un modello di regressione lineare multipla che includa tutte le variabili rilevanti. In questo modo, potremo quantificare l’impatto di ciascuna variabile indipendente sul peso del neonato ed eventuali interazioni. Ad esempio, ci aspettiamo che una maggiore durata della gestazione aumenterebbe in media il peso del neonato.
Attraverso tecniche di selezione del modello, come la minimizzazione del criterio di informazione di Akaike (AIC) o di Bayes (BIC), selezioneremo il modello più parsimonioso, eliminando le variabili non significative. Verranno considerati anche modelli con interazioni tra le variabili e possibili effetti non lineari.
Prima di tutto verifichiamo che la variabile risposta sia approssimativamente normale effettuando un test di shapiro-Wilk.
Inoltre ricordiamo che:
skewness(dati$Peso)
## [1] -0.6470308
kurtosis(dati$Peso)
## [1] 5.031532
Vediamo allora se il test di Shapiro Wilk ci fa passare la variabile risposta come un distribuzione normale, o perlomeno non rifiuta l’ipotesi di normalità
shapiro.test(dati$Peso)
##
## Shapiro-Wilk normality test
##
## data: dati$Peso
## W = 0.97066, p-value < 2.2e-16
Si osserva quindi che la variabile Peso presenta una lieve asimmetria negativa (skewness = -0.65) e una curtosi positiva (curtosis = 2.031), indicando una distribuzione più appuntita e con code più pesanti rispetto a una normale. Il test di Shapiro-Wilk su questa variabile ha prodotto un p-value estremamente basso, indicando una differenza statisticamente significativa dalla normalità.
È importante sottolineare che, per avere un modello di regressione lineare di cui possiamo fidarci per fare inferenze statistiche consistenti e valide sulla popolazione, l’assunzione critica non riguarda la normalità della variabile risposta in sé, bensì la normalità dei residui del modello. È pur vero che una variabile risposta con una distribuzione marcatamente non normale può talvolta riflettersi in residui che a loro volta deviano dalla normalità.
La vera verifica delle assunzioni si concentrerà quindi sull’analisi dei residui del modello dopo la sua stima. Faremo successivamente un’analisi approfondita dei residui tramite altri grafici e test.
Fatto questo possiamo andare ad indagare sulle prime ralazioni fra variabili, passando alla visualizzazione della matrice di correlazione che mostra le correlazioni a due a due fra le variabili.
Le variabili esplicative con un’alta correlazione positiva o negativa con la variabile risposta verosimilmente saranno quelle che ci daranno più informazioni su di essa, quelle che riusciranno a spiegare la maggior parte della sua variabilità. Invece i regressori molto correlati tra loro potrebbero portare a problemi di multicollinearità.
pairs(df_numeric, upper.panel = panel.smooth, lower.panel = panel.cor)
Questa griglia non è altro che la trasposizione grafica della matrice di correlazione. Sulla diagonale vengono mostrati i nomi delle variabili presenti nel dataframe sotto la diagonale vengono mostrate le correlazioni per ogni coppia di variabili con il testo di grandezza proporzionale alla correlazione rilevata e infine sopra la diagonale vengono mostrati gli scatterplot associati.
Non abbiamo considerato le variabili qualitative perché il coefficiente di correlazione di Pearson perderebbe di significato, e anche gli scatterplot relativi non avrebbero molto senso. Una soluzione è andare ad analizzare i boxplot:
attach(dati)
par(mfrow=c(1,2))
boxplot(Peso)
boxplot(Peso~Sesso)
Già abbiamo visto precedentemente con un test statistico che la differenza di peso tra maschi e femmine è molto significativa e questi boxplot confermano il tutto. Mi aspetterò quindi per questa variabile un beta di regressione signficativo.
par(mfrow=c(1,2))
boxplot(Peso)
boxplot(Peso~Fumatrici)
Dalle informazioni ricavate da questi boxplot potremmo dire che non vi è una differenza significativa del peso del bambino rispetto alla variabile Fumatrici, quindi in base se la madre fuma o meno, ma è anche vero che si vede una differenza sostanziale nella numerosità del campione per quanto riguarda le due tipologie (visibile anche dai grafici a barre visti precedentemente), infatti le madri che non fumano sono nettamente di più, portando quindi a non avere abbastanza informazioni per dire se effettivamente il fumo possa incidere negativamente sul peso del bambino.
Possiamo fare anche un t test per saggiare l’ipotesi di uguaglianza tra medie per gruppi indipendenti in cui però mi aspetto un risultato in cui non possiamo dire niente a priori sull’ipotesi nulla.
t.test(Peso~Fumatrici)
##
## Welch Two Sample t-test
##
## data: Peso by Fumatrici
## t = 1.034, df = 114.1, p-value = 0.3033
## alternative hypothesis: true difference in means between group False and group True is not equal to 0
## 95 percent confidence interval:
## -45.61354 145.22674
## sample estimates:
## mean in group False mean in group True
## 3286.153 3236.346
Effettivamente otteniamo un p-value di 0.30 e quindi non possiamo rifiutare l’ipotesi nulla ovvero non possiamo andare contro l’uguaglianza tra medie di questi due gruppi. Alla luce di questa osservazione preliminare, ci aspettiamo che, anche nel modello di regressione lineare multipla, il coefficiente di regressione associato alla variabile Fumatrici risulterà probabilmente non statisticamente significativo. Tale previsione sarà comunque confermata e interpretata nel contesto dell’intero, considerando l’influenza e il controllo di tutte le altre variabili incluse.
par(mfrow=c(1,2))
boxplot(Peso)
boxplot(Peso~Tipo.parto)
Per quanto riguarda il tipo di parto il discorso potrebbe essere molto simile a quello della variabile Fumatrici, ma andiamo a saggiare l’ipotesi con un t test.
t.test(Peso~Tipo.parto)
##
## Welch Two Sample t-test
##
## data: Peso by Tipo.parto
## t = -0.12968, df = 1493, p-value = 0.8968
## alternative hypothesis: true difference in means between group Ces and group Nat is not equal to 0
## 95 percent confidence interval:
## -46.27992 40.54037
## sample estimates:
## mean in group Ces mean in group Nat
## 3282.047 3284.916
Effettivamente il p-value è molto grande e non possiamo dire nulla contro l’ipotesi nulla.
par(mfrow=c(1,2))
boxplot(Peso)
boxplot(Peso~Ospedale)
detach(dati)
Dato che le distribuzioni del peso sembrano essere simili tra i tre ospedali, è probabile che la variabile Ospedale non sia un predittore forte o statisticamente significativo del peso del neonato, questo ovviamente come per le altre variabili, andremo a confermarlo o meno con l’analisi dei risultati del modello di regressione.
Ritornando alla matrice di correlazione, questa è uno strumento fondamentale per esplorare la multicollinearità tra i regressori. La multicollinearità si verifica quando due o più variabili indipendenti (regressori) nel modello di regressione sono altamente correlate tra loro.
Nella matrice si evidenziato le seguenti correlazioni bivariate:
Le correlazioni di 0.60 e 0.62 sono considerate moderate-forti, mentre la correlazione di 0.46 è considerata moderata.
Possiamo concludere in definitiva che non ci sono correlazioni estreme, ma il tutto verrà analizzato meglio con il calcolo dei VIF che ci permetterà di confermare con più precisione la presenza o meno di multicollinearità
Andiamo a costruire il primo modello di regressione lineare multipla, andando ad inserire inizialmente tutte le variabili.
attach(dati)
mod1 <- lm(Peso ~., data=dati)
summary(mod1)
##
## Call:
## lm(formula = Peso ~ ., data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1123.3 -181.2 -14.6 160.7 2612.6
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6735.1677 141.3977 -47.633 < 2e-16 ***
## Anni.madre 0.7983 1.1463 0.696 0.4862
## N.gravidanze 11.4118 4.6665 2.445 0.0145 *
## FumatriciTrue -30.1567 27.5396 -1.095 0.2736
## Gestazione 32.5265 3.8179 8.520 < 2e-16 ***
## Lunghezza 10.2951 0.3007 34.237 < 2e-16 ***
## Cranio 10.4725 0.4261 24.580 < 2e-16 ***
## Tipo.partoNat 29.5027 12.0848 2.441 0.0147 *
## Ospedaleosp2 -11.2216 13.4388 -0.835 0.4038
## Ospedaleosp3 28.0984 13.4972 2.082 0.0375 *
## SessoM 77.5473 11.1779 6.938 5.07e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 273.9 on 2489 degrees of freedom
## Multiple R-squared: 0.7289, Adjusted R-squared: 0.7278
## F-statistic: 669.1 on 10 and 2489 DF, p-value: < 2.2e-16
Analisi dei coefficienti di regressione per le variabili numeriche :
Analisi dei coefficienti di regressione per le variabili qualitative:
Passiamo ora alle variabili qualitative, Fumatrici, Tipo.parto, Ospedale e Sesso. R le trasforma in variabili dummy, sceglie una delle modalità come baseline e stima il parametro per l’altra modalità. L’interpretazione delle stime in questo caso rispetto alle variabili numeriche è leggermente diversa perché vanno lette come confronto rispetto alla baseline, quindi si ha:
Il modello mostra un R^2 aggiustato di 0.7278 quindi una variabilità spiegata circa del 73% che non è niente male, ma il modello nel complesso può sicuramente essere migliorato.
Abbiamo osservato quindi che la variabile Anni.madre non risultava statisticamente significativa (p-value = 0.4862), suggerendo un’influenza trascurabile sul Peso del neonato una volta considerati gli altri predittori. Per migliorare la parsimonia del modello, in accordo con il Rasoio di Occam, abbiamo deciso di rimuovere Anni.madre e stimare un nuovo modello (mod2).
mod2 <- update(mod1, ~. -Anni.madre)
summary(mod2)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Fumatrici + Gestazione + Lunghezza +
## Cranio + Tipo.parto + Ospedale + Sesso, data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1113.93 -180.11 -16.36 160.58 2616.96
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6708.1065 135.9394 -49.346 < 2e-16 ***
## N.gravidanze 12.6085 4.3381 2.906 0.00369 **
## FumatriciTrue -30.3092 27.5359 -1.101 0.27113
## Gestazione 32.2501 3.7968 8.494 < 2e-16 ***
## Lunghezza 10.2944 0.3007 34.239 < 2e-16 ***
## Cranio 10.4876 0.4255 24.651 < 2e-16 ***
## Tipo.partoNat 29.5351 12.0834 2.444 0.01458 *
## Ospedaleosp2 -11.0816 13.4359 -0.825 0.40957
## Ospedaleosp3 28.3660 13.4903 2.103 0.03559 *
## SessoM 77.6205 11.1763 6.945 4.81e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 273.9 on 2490 degrees of freedom
## Multiple R-squared: 0.7288, Adjusted R-squared: 0.7278
## F-statistic: 743.6 on 9 and 2490 DF, p-value: < 2.2e-16
Il mod2 ha mostrato un R-quadro aggiustato (0.7278) praticamente invariato rispetto al mod1 (0.7278). Questo risultato, di per sé, suggerirebbe che la rimozione di Anni.madre non comporti una perdita significativa nella capacità esplicativa complessiva del modello, pur semplificandone la struttura.
Un aspetto particolarmente interessante emerso da questo confronto è stato l’aumento della significatività della variabile N.gravidanze, il cui p-value è migliorato da 0.0145 ( * ) nel mod1 a 0.00369 ( ** ) nel mod2. Questo cambiamento è probabilmente attribuibile alla presenza di multicollinearità tra Anni.madre e N.gravidanze. La matrice di correlazione ha infatti evidenziato una correlazione positiva di 0.38 tra queste due variabili. Quando Anni.madre era inclusa, condivideva parte della varianza spiegata con N.gravidanze. Rimuovendo Anni.madre, la varianza precedentemente condivisa è stata ora attribuita principalmente a N.gravidanze, permettendo a quest’ultima di esprimere più chiaramente il suo effetto indipendente sul Peso del neonato e diventando quindi più significativa nel modello.
Nonostante questa osservazione suggerisca una potenziale semplificazione del modello attraverso l’eliminazione di Anni.madre, abbiamo deciso di mantenere questa variabile nel modello finale. La scelta è dettata dalla sua importanza come variabile di controllo. Sebbene il suo effetto diretto sul Peso del neonato possa non sempre raggiungere la significatività statistica in presenza di altre variabili correlate (come N.gravidanze), l’età della madre è un fattore clinicamente e biologicamente rilevante. Includendo Anni.madre, garantiamo che gli effetti degli altri predittori sul Peso siano stimati controllando per l’età materna, riducendo il rischio di bias da variabili omesse e migliorando la validità interna delle nostre stime. Questo approccio ci permette di ottenere una comprensione più robusta delle relazioni, isolando l’impatto delle variabili primarie di interesse da potenziali fattori confondenti.
Un discorso simile potremmo farlo per la variabile Fumatrici.
Come passo successivo potremmo pensare di mettere una relazione quadratica della variabile Gestazione, in quanto questa relazione viene sospettata dal grafico della matrice di correlazione
#Scatter plot Gestazione/Peso
plot(Gestazione, Peso, pch=20)
mod3 <- update(mod1, ~.+I(Gestazione^2))
summary(mod3)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Ospedale + Sesso + I(Gestazione^2),
## data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1148.24 -179.95 -11.61 163.03 2634.03
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -4830.8240 897.2543 -5.384 7.97e-08 ***
## Anni.madre 0.8782 1.1461 0.766 0.4436
## N.gravidanze 11.3755 4.6631 2.439 0.0148 *
## FumatriciTrue -28.9924 27.5249 -1.053 0.2923
## Gestazione -73.9035 49.6668 -1.488 0.1369
## Lunghezza 10.3928 0.3039 34.198 < 2e-16 ***
## Cranio 10.5623 0.4278 24.690 < 2e-16 ***
## Tipo.partoNat 29.0581 12.0778 2.406 0.0162 *
## Ospedaleosp2 -10.4856 13.4334 -0.781 0.4351
## Ospedaleosp3 27.7499 13.4884 2.057 0.0398 *
## SessoM 75.4810 11.2111 6.733 2.06e-11 ***
## I(Gestazione^2) 1.4212 0.6612 2.149 0.0317 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 273.7 on 2488 degrees of freedom
## Multiple R-squared: 0.7294, Adjusted R-squared: 0.7282
## F-statistic: 609.6 on 11 and 2488 DF, p-value: < 2.2e-16
Possiamo vedere che sì la relazione quadratica porta un coefficiente significativo, ma non va ad aumentare l’R^2 in modo significativo, quindi non si ha un miglioramento di performance e inoltre fa perdere di significatività alla variabile lineare corrispondente. Quindi in questi casi si và di Rasoio di Occam, ovvero a parità di performance modelli più semplici con meno parametri sono preferiti a modelli più complessi. Togliamo questa relazione quadratica.
Adesso proviamo a togliere anche la variabile Ospedale che mostra un’influenza non continuativa sulla variabile Peso, con Ospedale2 non significativo e Ospedale3 solo leggermente significativo (p-value = 0.0398)
mod4 <- update(mod1, ~.-Ospedale)
summary(mod4)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Sesso, data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1140.23 -181.78 -14.89 160.22 2630.40
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6737.4886 141.4866 -47.619 < 2e-16 ***
## Anni.madre 0.8647 1.1475 0.754 0.4512
## N.gravidanze 11.7148 4.6712 2.508 0.0122 *
## FumatriciTrue -31.5829 27.5739 -1.145 0.2522
## Gestazione 32.8391 3.8219 8.592 < 2e-16 ***
## Lunghezza 10.2723 0.3010 34.129 < 2e-16 ***
## Cranio 10.4844 0.4266 24.575 < 2e-16 ***
## Tipo.partoNat 30.2562 12.0994 2.501 0.0125 *
## SessoM 78.0338 11.1925 6.972 3.99e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.3 on 2491 degrees of freedom
## Multiple R-squared: 0.7279, Adjusted R-squared: 0.727
## F-statistic: 832.9 on 8 and 2491 DF, p-value: < 2.2e-16
Come si può notare si ha un abbassamento minimo dell’R^2 che però viene altamente compensato con la diminuzione di un parametro. Quindi per il momento potremmo pensare di non inserire la variabile Ospedale nel modello.
Si potrebbe pensare adesso a possibili effetti di interazione tra le variabili, magari controllando se all’aumentare delle settimane di Gestazione vi siano effetti particolari se la madre è fumatrice o meno.
Per esplorare la possibile interazione tra Gestazione e Fumatrici per la variabile Peso, andiamo a costruire uno scatterplot con due linee di regressione separate in base se la madre è fumatrice o non fumatrice
ggplot(dati, aes(x = Gestazione, y = Peso, color = Fumatrici)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
labs(
title = "Peso del Neonato in Funzione della Gestazione, per Stato Fumatore della Madre",
x = "Settimane di Gestazione",
y = "Peso del Neonato (grammi)",
color = "Madre Fum.:"
) +
theme_minimal()
Questa differenza nelle pendenze delle linee è un forte indicatore visivo di un’interazione significativa. Essa suggerisce che l’effetto della Gestazione sul Peso del neonato non è lo stesso per le madri fumatrici e non fumatrici. In altre parole, il tasso di crescita del peso del bambino per ogni settimana aggiuntiva di gestazione potrebbe essere inferiore nelle gravidanze di madri fumatrici rispetto a quelle di madri non fumatrici.
Andiamo a verificare il tutto inserendo questa interazione in un nuovo modello
mod5 <- update(mod4, ~.+Gestazione*Fumatrici)
summary(mod5)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Sesso + Fumatrici:Gestazione,
## data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1139.41 -180.90 -16.33 160.72 2630.52
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6754.6739 142.3172 -47.462 < 2e-16 ***
## Anni.madre 0.8423 1.1476 0.734 0.4631
## N.gravidanze 11.7969 4.6716 2.525 0.0116 *
## FumatriciTrue 811.4019 756.7166 1.072 0.2837
## Gestazione 33.3989 3.8546 8.665 < 2e-16 ***
## Lunghezza 10.2668 0.3010 34.107 < 2e-16 ***
## Cranio 10.4792 0.4266 24.563 < 2e-16 ***
## Tipo.partoNat 30.4532 12.1001 2.517 0.0119 *
## SessoM 78.6317 11.2048 7.018 2.9e-12 ***
## FumatriciTrue:Gestazione -21.4728 19.2626 -1.115 0.2651
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.3 on 2490 degrees of freedom
## Multiple R-squared: 0.728, Adjusted R-squared: 0.727
## F-statistic: 740.6 on 9 and 2490 DF, p-value: < 2.2e-16
Putroppo nonostante il grafico facesse intendere una possibile interazione, questa non sembra significativa. Questo è un classico esempio in cui l’occhio può suggerire qualcosa, ma la statistica formale, a causa delle limitazioni del campione, non ha abbastanza fiducia per dichiararlo “significativo”. È molto probabile che la non significatività dell’interazione, nonostante l’indicazione visiva, dipenda da una bassa numerosità di madri fumatrici nel campione, il che riduce il potere statistico per rilevare tale effetto. In questo scenario quindi si preferisce non mantenere l’effetto di interazione.
Un altro effetto di interazione da poter considerare è Gestazione*N.gravidanze, perché l’effetto della durata della gestazione sul peso potrebbe essere modulato dal numero di gravidanze precedenti (ad esempio, madri con molte gravidanze potrebbero avere dinamiche leggermente diverse).
mod6 <- update(mod4, ~ . + Gestazione*N.gravidanze)
summary(mod6)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Sesso + N.gravidanze:Gestazione,
## data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1139.47 -182.52 -16.33 160.03 2629.20
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6644.4130 163.8466 -40.553 < 2e-16 ***
## Anni.madre 0.8561 1.1475 0.746 0.4557
## N.gravidanze -67.0156 70.0593 -0.957 0.3389
## FumatriciTrue -32.1380 27.5768 -1.165 0.2440
## Gestazione 30.4175 4.3850 6.937 5.10e-12 ***
## Lunghezza 10.2717 0.3010 34.128 < 2e-16 ***
## Cranio 10.4907 0.4266 24.589 < 2e-16 ***
## Tipo.partoNat 30.1793 12.0990 2.494 0.0127 *
## SessoM 77.9590 11.1920 6.966 4.17e-12 ***
## N.gravidanze:Gestazione 2.0297 1.8021 1.126 0.2602
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.3 on 2490 degrees of freedom
## Multiple R-squared: 0.728, Adjusted R-squared: 0.727
## F-statistic: 740.6 on 9 and 2490 DF, p-value: < 2.2e-16
Possiamo vedere che l’R^2 aggiustanto non varia e quindi anche in questo caso non conviene lasciare questo effetto di interazione.
Adesso possiamo fare analisi dei modelli per capire come procedere utilizzando test più formali.
AIC(mod1, mod2, mod3, mod4, mod5, mod6)
## df AIC
## mod1 12 35172.09
## mod2 11 35170.57
## mod3 13 35169.45
## mod4 10 35177.26
## mod5 11 35178.01
## mod6 11 35177.98
BIC(mod1, mod2, mod3, mod4, mod5, mod6)
## df BIC
## mod1 12 35241.97
## mod2 11 35234.64
## mod3 13 35245.16
## mod4 10 35235.50
## mod5 11 35242.07
## mod6 11 35242.05
anova(mod4, mod3)
## Analysis of Variance Table
##
## Model 1: Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione + Lunghezza +
## Cranio + Tipo.parto + Sesso
## Model 2: Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione + Lunghezza +
## Cranio + Tipo.parto + Ospedale + Sesso + I(Gestazione^2)
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 2491 187459103
## 2 2488 186426590 3 1032514 4.5932 0.003275 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
vif(mod3)
## GVIF Df GVIF^(1/(2*Df))
## Anni.madre 1.191462 1 1.091541
## N.gravidanze 1.189267 1 1.090535
## Fumatrici 1.007800 1 1.003892
## Gestazione 287.271649 1 16.949090
## Lunghezza 2.133550 1 1.460668
## Cranio 1.646639 1 1.283215
## Tipo.parto 1.004550 1 1.002272
## Ospedale 1.005731 2 1.001430
## Sesso 1.048359 1 1.023894
## I(Gestazione^2) 280.671253 1 16.753246
Nel processo di selezione del modello, abbiamo valutato attentamente diversi candidati, bilanciando il fit statistico con i principi di parsimonia e interpretabilità.
Il mod3, pur mostrando un leggero vantaggio secondo il criterio AIC e un miglioramento statisticamente significativo del fit complessivo rispetto a modelli più semplici (come mostrato dal test ANOVA), presenta problematiche significative legate alla multicollinearità.
In particolare, l’analisi del Variance Inflation Factor (VIF) per il mod3 ha rivelato valori estremamente elevati per i termini Gestazione (GVIF = 287.27, GVIF^(1/(2 * Df)) = 16.95) e I(Gestazione^2) (GVIF = 280.67, GVIF^(1/(2 * Df)) = 16.75). Valori di VIF superiori a 5 sono generalmente considerati indicativi di una grave multicollinearità. In questo caso, i valori eccezionalmente alti suggeriscono che la relazione quadratica I(Gestazione^2) è fortemente correlata con il termine lineare Gestazione.
Il BIC, che impone una penalità maggiore per la complessità del modello, ha indicato il mod2 come il migliore, seguito molto da vicino dal mod4. Il mod3, pur favorito dall’AIC, ha mostrato un BIC più elevato a causa della sua maggiore complessità.
Un punto fondamentale nella nostra strategia di modellazione è stata la decisione di mantenere la variabile Anni.madre nel modello, nonostante la sua significatività statistica bassa. Anni.madre è stata inclusa come variabile di controllo essenziale.
Sebbene quindi il mod2 abbia mostrato un BIC leggermente inferiore a mod4, mod2 non include Anni.madre. La nostra decisione di mantenere Anni.madre come variabile di controllo essenziale, nonostante la sua non significatività individuale, supera la marginale preferenza del BIC per un modello che omette un predittore teoricamente importante.
Considerando tutti questi fattori, il modello 4 (mod4) è stato selezionato come il nostro modello finale.
Possiamo vedere che cosa consiglia R stesso:
stepwise.mod <- stepAIC(mod1,
direction = "both",
k=log(n))
## Start: AIC=28139.46
## Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione + Lunghezza +
## Cranio + Tipo.parto + Ospedale + Sesso
##
## Df Sum of Sq RSS AIC
## - Anni.madre 1 36392 186809099 28132
## - Fumatrici 1 89979 186862686 28133
## - Ospedale 2 686397 187459103 28133
## - Tipo.parto 1 447233 187219939 28138
## - N.gravidanze 1 448762 187221469 28138
## <none> 186772707 28140
## - Sesso 1 3611588 190384294 28180
## - Gestazione 1 5446558 192219264 28204
## - Cranio 1 45338051 232110758 28675
## - Lunghezza 1 87959790 274732497 29096
##
## Step: AIC=28132.12
## Peso ~ N.gravidanze + Fumatrici + Gestazione + Lunghezza + Cranio +
## Tipo.parto + Ospedale + Sesso
##
## Df Sum of Sq RSS AIC
## - Fumatrici 1 90897 186899996 28126
## - Ospedale 2 692738 187501837 28126
## - Tipo.parto 1 448222 187257321 28130
## <none> 186809099 28132
## - N.gravidanze 1 633756 187442855 28133
## + Anni.madre 1 36392 186772707 28140
## - Sesso 1 3618736 190427835 28172
## - Gestazione 1 5412879 192221978 28196
## - Cranio 1 45588236 232397335 28670
## - Lunghezza 1 87950050 274759149 29089
##
## Step: AIC=28125.51
## Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Tipo.parto +
## Ospedale + Sesso
##
## Df Sum of Sq RSS AIC
## - Ospedale 2 701680 187601677 28119
## - Tipo.parto 1 440684 187340680 28124
## <none> 186899996 28126
## - N.gravidanze 1 610840 187510837 28126
## + Fumatrici 1 90897 186809099 28132
## + Anni.madre 1 37311 186862686 28133
## - Sesso 1 3602797 190502794 28165
## - Gestazione 1 5346781 192246777 28188
## - Cranio 1 45632149 232532146 28664
## - Lunghezza 1 88355030 275255027 29086
##
## Step: AIC=28119.23
## Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Tipo.parto +
## Sesso
##
## Df Sum of Sq RSS AIC
## - Tipo.parto 1 463870 188065546 28118
## <none> 187601677 28119
## - N.gravidanze 1 651066 188252743 28120
## + Ospedale 2 701680 186899996 28126
## + Fumatrici 1 99840 187501837 28126
## + Anni.madre 1 43845 187557831 28126
## - Sesso 1 3649259 191250936 28160
## - Gestazione 1 5444109 193045786 28183
## - Cranio 1 45758101 233359778 28657
## - Lunghezza 1 88054432 275656108 29074
##
## Step: AIC=28117.58
## Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Sesso
##
## Df Sum of Sq RSS AIC
## <none> 188065546 28118
## - N.gravidanze 1 623141 188688687 28118
## + Tipo.parto 1 463870 187601677 28119
## + Ospedale 2 724866 187340680 28124
## + Fumatrici 1 91892 187973654 28124
## + Anni.madre 1 45044 188020502 28125
## - Sesso 1 3655292 191720838 28158
## - Gestazione 1 5464853 193530399 28181
## - Cranio 1 46108583 234174130 28658
## - Lunghezza 1 87632762 275698308 29066
summary(stepwise.mod)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Sesso, data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1149.44 -180.81 -15.58 163.64 2639.72
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6681.1445 135.7229 -49.226 < 2e-16 ***
## N.gravidanze 12.4750 4.3396 2.875 0.00408 **
## Gestazione 32.3321 3.7980 8.513 < 2e-16 ***
## Lunghezza 10.2486 0.3006 34.090 < 2e-16 ***
## Cranio 10.5402 0.4262 24.728 < 2e-16 ***
## SessoM 77.9927 11.2021 6.962 4.26e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.6 on 2494 degrees of freedom
## Multiple R-squared: 0.727, Adjusted R-squared: 0.7265
## F-statistic: 1328 on 5 and 2494 DF, p-value: < 2.2e-16
summary(mod4)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Sesso, data = dati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1140.23 -181.78 -14.89 160.22 2630.40
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6737.4886 141.4866 -47.619 < 2e-16 ***
## Anni.madre 0.8647 1.1475 0.754 0.4512
## N.gravidanze 11.7148 4.6712 2.508 0.0122 *
## FumatriciTrue -31.5829 27.5739 -1.145 0.2522
## Gestazione 32.8391 3.8219 8.592 < 2e-16 ***
## Lunghezza 10.2723 0.3010 34.129 < 2e-16 ***
## Cranio 10.4844 0.4266 24.575 < 2e-16 ***
## Tipo.partoNat 30.2562 12.0994 2.501 0.0125 *
## SessoM 78.0338 11.1925 6.972 3.99e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.3 on 2491 degrees of freedom
## Multiple R-squared: 0.7279, Adjusted R-squared: 0.727
## F-statistic: 832.9 on 8 and 2491 DF, p-value: < 2.2e-16
BIC(mod4, stepwise.mod)
## df BIC
## mod4 10 35235.5
## stepwise.mod 7 35220.1
Il modello generato da stepAIC è estremamente parsimonioso, escludendo le variabili Anni.madre, Fumatrici, Tipo.parto e Ospedale. Tuttavia, la selezione automatica, pur eccellente per la parsimonia statistica, non incorpora considerazioni teoriche o cliniche cruciali. Variabili come Anni.madre, Fumatrici e Tipo.parto sono state mantenute nel nostro mod4 non solo per il loro potenziale impatto statistico (anche se non sempre individualmente significativo), ma soprattutto per la loro importanza come variabili di controllo o per la loro rilevanza clinica/biologica intrinseca. Omettere queste variabili, sebbene possa migliorare leggermente il BIC (o l’AIC) in un contesto puramente statistico, potrebbe portare a un modello che non cattura la piena complessità delle relazioni sottostanti o che è soggetto a bias da variabili omesse. Ad esempio, non includere lo stato di Fumatrici sarebbe clinicamente irresponsabile, nonostante il suo p-value alto, poiché è un noto fattore che influenza il peso neonatale. Il mod_stepwise ha un R-quadro aggiustato di 0.7265, leggermente inferiore al 0.7270 del nostro mod4. La minima differenza nella capacità predittiva è un piccolo prezzo da pagare per la maggiore robustezza teorica e la completezza clinica offerta dal mod4.
Una volta ottenuto il modello finale, valuteremo la sua capacità predittiva utilizzando metriche come R² e il Root Mean Squared Error (RMSE). Un’attenzione particolare sarà rivolta all’analisi dei residui e alla presenza di valori influenti, che potrebbero distorcere le previsioni, indagando su di essi.
Non ci resta che andare ad effettuare l’analisi dei residui del modello. Ricordiamoci che i residui del modello devono essere puliti, quindi devono rispettare le assunzioni di base, ovvero: normalità, omoschedasticità e incorrelazione e ovviamente media di 0. Inoltre dovremo andare a verificare la presenza di particolari valori anomali o particolarmente influenti.
Incominciamo col dividere la finestra grafica in 4 sottofinestre, dopodiché lanciamo la funzione plot inserendo all’interno il modello 4 di regressione, ottenendo 4 grafici:
par(mfrow=c(2,2))
plot(mod4)
ci sono anche metodi numerici per andare a indagare i valori di leva e valori outliers.
#leverage
lev <- hatvalues(mod4)
plot(lev)
p = sum(lev)
soglia = 2* p/n
abline(h=soglia,col=2)
La linea rossa indica la soglia per la leva. La maggior parte delle osservazioni si trova al di sotto di questa soglia, il che è positivo. Ci sono alcune osservazioni che superano leggermente la soglia, ma nessuna sembra essere un punto di leva estremamente anomalo e isolato.
I valori di leva indicano quanto una singola osservazione “tira” la linea di regressione verso di sé nel caso in cui i suoi valori di regressori siano estremi. Il fatto che pochi punti superino la soglia e non in modo drastico suggerisce che non ci sono osservazioni con una leva eccessivamente alta che possano influenzare indebitamente le stime dei coefficienti.
#outliers
plot(rstudent(mod4))
abline(h=c(-2,2), col=2)
Questo grafico mostra i residui studentizzati. I residui studentizzati sono residui standardizzati che tengono conto della leva dell’osservazione, rendendoli più appropriati per la rilevazione di outlier. Le linee rosse a -2 e +2 sono soglie comuni per identificare potenziali outlier.
La maggior parte dei residui studentizzati si trova tra -2 e +2. Tuttavia, ci sono alcune osservazioni che superano significativamente queste soglie.
La presenza di residui studentizzati al di fuori dell’intervallo [-2, 2] indica la presenza di outlier, ovvero osservazioni il cui valore della variabile dipendente è scarsamente previsto dal modello.
In questo caso non si fa altro che un test t con la funzione outliersTest. Questo test identifica l’osservazione con il residuo più estremo e testa se è significativamente diversa dal resto. Poi continua per le successive.
outlierTest(mod4)
## rstudent unadjusted p-value Bonferroni p
## 1551 10.029598 3.0991e-23 7.7477e-20
## 155 4.998218 6.1882e-07 1.5470e-03
## 1306 4.806856 1.6245e-06 4.0612e-03
In conclusione possiamo dire che:
Valori leva: non ci sono problemi significativi di leverage che possano distorcere il modello.
Valori outlier: ci sono outlier significativi (1551, 155, 1306). L’osservazione 1551 è particolarmente estrema.
cook<-cooks.distance(mod2)
plot(cook)
Adesso procediamo con dei test formali per avere un’idea quantitativa sulle assunzioni del modello in aggiunta all’analisi visiva dei grafici:
Breusch-Pagan Test: Questo test verifica l’assunzione di omoschedasticità. L’ipotesi nulla è che la varianza dei residui sia costante (omoschedasticità). L’ipotesi alternativa è che la varianza non sia costante (eteroschedasticità). Basandoci sul grafico Scale-Location, che mostra una linea rossa relativamente piatta, ci aspetteremmo che questo test non sia significativo, indicando che l’omoschedasticità è rispettata. Tuttavia, a volte i test formali possono rilevare piccole violazioni non immediatamente evidenti a occhio.
Durbin-Watson Test: Questo test verifica l’assunzione di indipendenza dei residui. L’ipotesi nulla è che non ci sia autocorrelazione tra i residui.
Shapiro-Wilk Test: Questo test verifica l’assunzione di normalità dei residui. L’ipotesi nulla è che i residui siano distribuiti normalmente. L’ipotesi alternativa è che non siano distribuiti normalmente. Basandoci sul grafico Normal Q-Q, che mostra una chiara deviazione dalla normalità alle code, ci aspettiamo che questo test sia altamente significativo (p-value molto piccolo), indicando una violazione dell’assunzione di normalità.
bptest(mod4)
##
## studentized Breusch-Pagan test
##
## data: mod4
## BP = 92.532, df = 8, p-value < 2.2e-16
lmtest::dwtest(mod4)
##
## Durbin-Watson test
##
## data: mod4
## DW = 1.9527, p-value = 0.1182
## alternative hypothesis: true autocorrelation is greater than 0
shapiro.test(mod4$residuals)
##
## Shapiro-Wilk normality test
##
## data: mod4$residuals
## W = 0.97416, p-value < 2.2e-16
plot(density(residuals(mod4)))
Inoltre questo grafico mostra la stima della densità di probabilità dei residui. È un modo visivo per valutare la forma della loro distribuzione. Dovremmo vedere una campana quasi simmetrica, ma data l’analisi del Q-Q plot, ci aspettiamo che mostri una leggera asimmetria o “code grasse” che confermino la non-normalità.
Test di Breusch-Pagan (bptest(mod4))
Test di Durbin-Watson (lmtest::dwtest(mod4))
Test di Shapiro-Wilk (shapiro.test(mod4$residuals))
Grafico della densità dei residui (plot(density(residuals(mod4))))
Riepilogo delle Assunzioni del mod4:
Linearità: Apparentemente rispettata (dal grafico Residuals vs Fitted).
Indipendenza dei residui: Rispettata (confermato dal Durbin-Watson test).
Normalità dei residui: Violata (confermato dal Shapiro-Wilk test e Q-Q plot).
Omoschedasticità: Violata (confermato dal Breusch-Pagan test, nonostante il grafico Scale-Location sembrasse accettabile).
Assenza di punti influenti: Rispettata (Cook’s distance e analisi di leverage non mostrano punti con eccessiva influenza).
Presenza di Outlier: Confermato (outlierTest).
L’analisi diagnostica dei residui ha rivelato la presenza di outlier, ovvero osservazioni i cui valori della variabile dipendente sono scarsamente predetti dal modello. In particolare, il test outlierTest() ha identificato le osservazioni 1551, 155 e 1306 come outlier statisticamente significativi, con l’osservazione 1551 che presentava un residuo studentizzato eccezionalmente elevato (10.03).
Un’indagine approfondita sull’osservazione 1551 ha rivelato una combinazione di valori biologicamente implausibili per le variabili Lunghezza (315) e Cranio (374), suggerendo un probabile errore di data entry, (forse uno scambio di valori). Data la natura chiaramente errata e l’influenza estrema di questa osservazione, è stato deciso di rimuovere l’osservazione 1551 dal dataset per garantire l’integrità e la validità del modello.
Le osservazioni 155 e 1306, sebbene identificate come outlier statistici, sono state ritenute biologicamente plausibili dopo un’ispezione dei dati. Pertanto, sono state mantenute nel dataset.
obs_da_rimuovere <- 1551
# Crea un nuovo dataset escludendo l'osservazione 1551
dati_filtrati <- dati[-obs_da_rimuovere, ]
# Ricostruiamo il mod4 sul nuovo dataset filtrato
mod4_filtrato <- lm(Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
Lunghezza + Cranio + Tipo.parto + Sesso, data = dati_filtrati)
summary(mod4_filtrato)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Sesso, data = dati_filtrati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1153.27 -181.71 -17.53 161.61 1402.11
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6730.1790 138.7422 -48.509 < 2e-16 ***
## Anni.madre 0.5895 1.1256 0.524 0.60051
## N.gravidanze 12.7894 4.5818 2.791 0.00529 **
## FumatriciTrue -28.5256 27.0404 -1.055 0.29156
## Gestazione 29.9829 3.7585 7.977 2.26e-15 ***
## Lunghezza 10.9157 0.3020 36.140 < 2e-16 ***
## Cranio 9.8706 0.4228 23.346 < 2e-16 ***
## Tipo.partoNat 30.0881 11.8646 2.536 0.01127 *
## SessoM 78.1832 10.9752 7.124 1.37e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 269 on 2490 degrees of freedom
## Multiple R-squared: 0.738, Adjusted R-squared: 0.7372
## F-statistic: 876.7 on 8 and 2490 DF, p-value: < 2.2e-16
par(mfrow=c(2,2))
plot(mod4_filtrato)
bptest(mod4_filtrato)
##
## studentized Breusch-Pagan test
##
## data: mod4_filtrato
## BP = 12.922, df = 8, p-value = 0.1146
lmtest::dwtest(mod4_filtrato)
##
## Durbin-Watson test
##
## data: mod4_filtrato
## DW = 1.954, p-value = 0.1249
## alternative hypothesis: true autocorrelation is greater than 0
shapiro.test(mod4_filtrato$residuals)
##
## Shapiro-Wilk normality test
##
## data: mod4_filtrato$residuals
## W = 0.98889, p-value = 5.021e-13
outlierTest(mod4_filtrato)
## rstudent unadjusted p-value Bonferroni p
## 155 5.260282 1.5609e-07 0.00039008
## 1306 4.918983 9.2669e-07 0.00231580
## 1694 4.322185 1.6056e-05 0.04012500
## 1399 -4.314777 1.6600e-05 0.04148400
detach(dati)
Ora possiamo analizzare i risultati del mod4_filtrato e confrontarli con il mod4 originale.
Summary del mod4_filtrato:
Residuals: Min -1153.27, Max 1402.11. Il valore massimo è sceso drasticamente rispetto al modello precedente (2639.72), a causa della rimozione dell’outlier 1551.
Coefficients: Le stime dei coefficienti sono cambiate solo marginalmente rispetto al modello originale, il che è un buon segno che l’osservazione 1551, pur estrema, non stava distorcendo drammaticamente le stime centrali.
Le significatività sono generalmente simili. Anni.madre (p = 0.60051) e FumatriciFalse (p = 0.29156) rimangono non significative, N.gravidanze (p = 0.00529 ), Gestazione (p = 2.26e-15 ), Lunghezza (p < 2e-16 ), Cranio (p < 2e-16 ), Tipo.partoNat (p = 0.01127 ), SessoM (p = 1.37e-12 ***) rimangono tutte altamente significative.
Residual standard error: 269 on 2490 degrees of freedom. Questo è diminuito rispetto a 274.6 nel modello precedente, indicando che la rimozione dell’outlier ha migliorato la precisione delle stime.
R^2 aggiustato: 0.7372. È leggermente aumentato rispetto a 0.7265 del modello originale, mostrando un miglioramento marginale nel potere esplicativo.
Analisi dei Grafici Diagnostici (plot(mod4_filtrato)):
Residuals vs Fitted: L’aspetto è molto simile al precedente. La linearità è ancora ben supportata. L’omoschedasticità visiva è decente, ma vedremo il test.
Normal Q-Q: Questo grafico mostra un miglioramento significativo rispetto a quello precedente, la coda superiore è meno deviata, e i punti seguono molto più da vicino la linea diagonale. Questo indica che la rimozione dell’osservazione 1551 ha avuto un impatto positivo sulla normalità dei residui. Sebbene non sia perfetta, è notevolmente migliore.
Scale-Location: Anche qui l’aspetto è simile. Sembra visivamente omoschedastico.
Residuals vs Leverage: I punti anomali identificati precedentemente (155, 1306) sono ancora visibili con residui elevati, ma ora non c’è più il punto estremo 1551. Nessun punto supera le linee di Cook’s distance in modo preoccupante.
Test Formali:
Breusch-Pagan Test (bptest(mod4_filtrato)): BP = 12.922, df = 8, p-value = 0.1146. C’è stato un grande miglioramento. Il p-value è ora 0.1146, che è maggiore di 0.05. Questo significa che non rifiutiamo più l’ipotesi nulla di omoschedasticità. La rimozione dell’outlier 1551 ha risolto il problema dell’eteroschedasticità statisticamente significativa.
Durbin-Watson Test (lmtest::dwtest(mod4_filtrato)): DW = 1.954, p-value = 0.1249. Nessun cambiamento significativo. Il p-value rimane ben al di sopra di 0.05, confermando l’assenza di autocorrelazione. L’indipendenza dei residui è pienamente rispettata.
Shapiro-Wilk Test (shapiro.test(mod4_filtrato$residuals)): W = 0.98889, p-value = 5.021e-13. Qua c’è stato un miglioramento, ma ancora significativo. Il p-value è ancora estremamente piccolo, indicando che la normalità è ancora statisticamente violata. Tuttavia, il valore W è più vicino a 1 (rispetto a 0.97416), il che suggerisce una migliore aderenza. Il grafico di densità mostrerà una forma più vicina a una campana. Con un campione così grande (N=2499), quasi ogni minima deviazione dalla normalità perfetta sarà statisticamente significativa. Però dal punto di vista pratico, la distribuzione dei residui è ora molto più vicina alla normalità.
Grafico della densità (plot(density(residuals(mod4_filtrato)))):
plot(density(residuals(mod4_filtrato)))
Il grafico mostra una curva a campana molto più simmetrica e meno “schiacciata” rispetto a prima, confermando il miglioramento della normalità.
Outlier Test (outlierTest(mod4_filtrato)):
Abbiamo ottenuto un miglioramento significativo rispetto al modello precedente.
Linearità e indipendenza sono ben rispettate. L’omoschedasticità è ora rispettata (il test di Breusch-Pagan non è significativo). La normalità dei residui è notevolmente migliorata e, sebbene ancora statisticamente violata (dovuto alla grande dimensione campionaria), è accettabile per l’inferenza pratica data la robustezza del modello lineare con grandi N. I pochi outlier rimanenti sono meno estremi e, essendo plausibili, possono essere mantenuti nel modello, accettando che i residui non saranno perfettamente normali.
Una volta validato il modello, lo useremo per fare previsioni pratiche. Ad esempio, potremo stimare il peso di una neonata considerando una madre alla terza gravidanza che partorirà alla 39esima settimana:
Ora che abbiamo finalizzato il mod4_filtrato e siamo confidenti nelle sue proprietà, possiamo usarlo per fare previsioni.
Per esempio se volessimo stimare il peso di una neonata con le caratteristiche specificate, dobbiamo creare un nuovo dataframe con questi valori e poi usare la funzione predict() in R.
Neonato/a: Femmina (quindi Sesso = “F”)
Madre: Alla terza gravidanza (N.gravidanze = 3)
Gestazione: Partorirà alla 39esima settimana (Gestazione = 39)
Per le altre variabili incluse nel modello (Anni.madre, Fumatrici, Lunghezza, Cranio, Tipo.parto), dobbiamo fornire dei valori. Senza indicazioni specifiche, la pratica comune è usare i valori medi (o mediani) dal dataset, o valori che siano clinicamente/biologicamente plausibili per un neonato a termine.
Assumiamo di usare i valori medi per le variabili quantitative e la moda (categoria più frequente) per le variabili categoriche non specificate.
media_anni_madre <- mean(dati_filtrati$Anni.madre)
media_lunghezza <- mean(dati_filtrati$Lunghezza)
media_cranio <- mean(dati_filtrati$Cranio)
moda_fumatrici <- "False"
moda_tipo_parto <- "Nat"
dati_test <- data.frame(
Anni.madre = media_anni_madre,
N.gravidanze = 3,
Fumatrici = factor(moda_fumatrici, levels = levels(dati_filtrati$Fumatrici)),
Gestazione = 39,
Lunghezza = media_lunghezza,
Cranio = media_cranio,
Tipo.parto = factor(moda_tipo_parto, levels = levels(dati_filtrati$Tipo.parto)),
Sesso = factor("F", levels = levels(dati_filtrati$Sesso))
)
previsione <- predict(mod4_filtrato, newdata = dati_test, interval = "prediction")
print(dati_test)
## Anni.madre N.gravidanze Fumatrici Gestazione Lunghezza Cranio Tipo.parto
## 1 28.18327 3 False 39 494.7639 340.0156 Nat
## Sesso
## 1 F
print(previsione)
## fit lwr upr
## 1 3281.074 2752.991 3809.158
In sintesi, il modello prevede che una neonata con quelle specifiche caratteristiche peserà circa 3.25 kg, con un range di previsione che va da circa 2.72 kg a 3.78 kg.
Infine, utilizzeremo grafici e rappresentazioni visive per comunicare i risultati del modello e mostrare le relazioni più significative tra le variabili. Ad esempio, potremmo visualizzare l’impatto del numero di settimane di gestazione e del fumo sul peso previsto
ggplot(data = dati_filtrati)+
geom_point(aes(x=Gestazione,
y=Peso,
col=Sesso), position = "jitter")+
geom_smooth(aes(x=Gestazione,
y=Peso,
col=Sesso), se=F, method="lm")+
geom_smooth(aes(x=Gestazione,
y=Peso), col="black", se=F, method="lm")
ggplot(data = dati_filtrati)+
geom_point(aes(x=Lunghezza,
y=Peso,
col=Sesso), position = "jitter")+
geom_smooth(aes(x=Lunghezza,
y=Peso,
col=Sesso), se=F, method="lm")+
geom_smooth(aes(x=Lunghezza,
y=Peso), col="black", se=F, method="lm")
ggplot(data = dati_filtrati)+
geom_point(aes(x=Gestazione,
y=Peso,
col=Tipo.parto), position = "jitter")+
geom_smooth(aes(x=Gestazione,
y=Peso,
col=Tipo.parto), se=F, method="lm")+
geom_smooth(aes(x=Gestazione,
y=Peso), col="black", se=F, method="lm")
ggplot(data = dati_filtrati)+
geom_point(aes(x=Lunghezza,
y=Peso,
col=Tipo.parto), position = "jitter")+
geom_smooth(aes(x=Lunghezza,
y=Peso,
col=Tipo.parto), se=F, method="lm")+
geom_smooth(aes(x=Lunghezza,
y=Peso), col="black", se=F, method="lm")
ggplot(data = dati_filtrati)+
geom_point(aes(x=Gestazione,
y=Peso,
col=Fumatrici), position = "jitter")+
geom_smooth(aes(x=Gestazione,
y=Peso,
col=Fumatrici), se=F, method="lm")+
geom_smooth(aes(x=Gestazione,
y=Peso), col="black", se=F, method="lm")
ggplot(data = dati_filtrati)+
geom_point(aes(x=Lunghezza,
y=Peso,
col=Fumatrici), position = "jitter")+
geom_smooth(aes(x=Lunghezza,
y=Peso,
col=Fumatrici), se=F, method="lm")+
geom_smooth(aes(x=Lunghezza,
y=Peso), col="black", se=F, method="lm")