(In fondi risposta al feedback del Tutor)
DATASET: Per costruire il modello predittivo, abbiamo raccolto dati su 2500 neonati provenienti da tre ospedali.
OBIETTIVO: 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
knitr::opts_chunk$set(echo = TRUE)
# Carico Dataset
dataset <- read.csv("neonati.csv")
# Anni.madre con un minimo di zero non è plausibile.
dati <- subset(dataset, Anni.madre > 12)
n <- nrow(dati)
attach(dati)
#Carico Librerie
library(lmtest)
library(TeachingDemos)
library(car)
library(knitr)
library(visreg)
library(ggplot2)
# consigliati da ChatGPT per fare tabelle con summary in HTML
library(broom)
library(knitr)
Nella prima fase, esploreremo le variabili attraverso un’analisi descrittiva per comprenderne la distribuzione e identificare eventuali outlier o anomalie.
# Funzione analisi Variabili Continue
DescribeVar <- function(var,LabelVar="Variabile") {
cat("\n", LabelVar, "\n", rep("-", nchar(LabelVar)), "\n", sep="")
cat("Numero di NA:", sum(is.na(var)), "\n")
# outlier IQR
Q1 <- quantile(var, 0.25, na.rm=TRUE)
Q3 <- quantile(var, 0.75, na.rm=TRUE)
med <- median(var, na.rm=TRUE)
IQR_val <- Q3 - Q1
outlier_idx <- which(var < Q1 - 1.5*IQR_val | var > Q3 + 1.5*IQR_val)
cat("Outlier (IQR):", length(outlier_idx), "su", sum(!is.na(var)), "(", round(100*length(outlier_idx)/sum(!is.na(var)), 2), "% )\n")
# grafici
oldpar <- par(no.readonly = TRUE)
on.exit(par(oldpar), add = TRUE)
par(mfrow=c(1,2))
hist(var, breaks=30, main=paste("Istogramma:", LabelVar), xlab=LabelVar)
abline(v=c(Q1, med, Q3), col="red", lwd=2, lty=2)
boxplot(var, main=paste("Boxplot:", LabelVar), ylab=LabelVar)
}
# Funzione analisi Variabili categoriali
DescribeCat <- function(var,LabelVar="Variabile") {
print(knitr::kable(table(var), caption = LabelVar))
}
# Variabili Continue
DescribeVar(Peso,LabelVar="Peso (g)")
##
## Peso (g)
## --------
## Numero di NA: 0
## Outlier (IQR): 69 su 2498 ( 2.76 % )
DescribeVar(Lunghezza,LabelVar="Lunghezza (mm)")
##
## Lunghezza (mm)
## --------------
## Numero di NA: 0
## Outlier (IQR): 59 su 2498 ( 2.36 % )
DescribeVar(Cranio,LabelVar="Diametro Cranio (mm)")
##
## Diametro Cranio (mm)
## --------------------
## Numero di NA: 0
## Outlier (IQR): 48 su 2498 ( 1.92 % )
DescribeVar(Anni.madre,LabelVar="Anni madre (anni)")
##
## Anni madre (anni)
## -----------------
## Numero di NA: 0
## Outlier (IQR): 11 su 2498 ( 0.44 % )
# Variabili Categoriali
Fumatrici_2 <- factor(Fumatrici,levels = c(0, 1),labels = c("No", "Sì"))
Tipo.parto_2 <- factor(Tipo.parto,levels = c("Nat", "Ces"),labels = c("Naturale", "Cesareo"))
DescribeCat(Fumatrici_2,LabelVar="Fumatrici")
##
##
## Table: Fumatrici
##
## |var | Freq|
## |:---|----:|
## |No | 2394|
## |Sì | 104|
DescribeCat(Tipo.parto_2,LabelVar="Tipo di parto")
##
##
## Table: Tipo di parto
##
## |var | Freq|
## |:--------|----:|
## |Naturale | 1770|
## |Cesareo | 728|
DescribeCat(Ospedale,LabelVar="Ospedale")
##
##
## Table: Ospedale
##
## |var | Freq|
## |:----|----:|
## |osp1 | 816|
## |osp2 | 848|
## |osp3 | 834|
DescribeCat(Sesso,LabelVar="Sesso")
##
##
## Table: Sesso
##
## |var | Freq|
## |:---|----:|
## |F | 1255|
## |M | 1243|
DescribeCat(N.gravidanze,LabelVar="Numero gravidanze")
##
##
## Table: Numero gravidanze
##
## |var | Freq|
## |:---|----:|
## |0 | 1095|
## |1 | 817|
## |2 | 340|
## |3 | 150|
## |4 | 48|
## |5 | 21|
## |6 | 11|
## |7 | 1|
## |8 | 8|
## |9 | 2|
## |10 | 3|
## |11 | 1|
## |12 | 1|
DescribeCat(Gestazione,LabelVar="Gestazione")
##
##
## Table: Gestazione
##
## |var | Freq|
## |:---|----:|
## |25 | 1|
## |26 | 1|
## |27 | 2|
## |28 | 4|
## |29 | 3|
## |30 | 5|
## |31 | 8|
## |32 | 9|
## |33 | 18|
## |34 | 16|
## |35 | 33|
## |36 | 62|
## |37 | 192|
## |38 | 437|
## |39 | 580|
## |40 | 741|
## |41 | 328|
## |42 | 56|
## |43 | 2|
Inoltre si saggeranno le seguenti ipotesi con i test adatti:
in alcuni ospedali si fanno più parti cesarei
La media del peso e della lunghezza di questo campione di neonati sono significativamente uguali a quelle della popolazione
Le misure antropometriche sono significativamente diverse tra i due sessi
# 1. ----------- test -> in alcuni ospedali si fanno più parti cesarei?
# creare una tabella incrociata tipo di parto vs Ospedale e creare una matrice dei valori attesi
tab <- table(Tipo.parto_2, Ospedale)
test.indipendenza <- chisq.test(tab)
kable(round(test.indipendenza$expected, 2), caption = "Frequenze attese (test Chi-quadro)")
| osp1 | osp2 | osp3 | |
|---|---|---|---|
| Naturale | 578.19 | 600.86 | 590.94 |
| Cesareo | 237.81 | 247.14 | 243.06 |
kable(data.frame(statistic = round(unname(test.indipendenza$statistic), 2),df = unname(test.indipendenza$parameter),p_value = signif(test.indipendenza$p.value, 3)), caption = "Risultato test Chi-quadro (Pearson)")
| statistic | df | p_value |
|---|---|---|
| 1.08 | 2 | 0.582 |
CONCLUSIONE 2.1.1: Il test Chi2 Pearson ci dice che non si può statisticamente rifiutare l’ipotesi di indipendenza (pvalue>0.05) suggerendo che la distribuzione di frequenze di parti naturali/cesarei non differisce in modo significativo tra i tre ospedali.
——– 2. test -> quanto media peso/lunghezza significativamente uguali a quelli della pop. mondiale? Ho trovato questo studio come riferimento https://www.scielo.br/j/spmj/a/5HBjGPShrJ4nYpPgQRDKt7m/?format=pdf&lang=en (Table 1) peso medio pop mondiale: mean= 3215.4 g sd= 488.5 g lughezza media pop mondiale: mean= 493 mm sd= 22 mm
——-Test t, H0: Peso = Peso mondiale ——–
BW_mondiale <- rnorm(10000000,mean=3215.4,sd=488.5)
plot(density(Peso),col=1,lwd=2,xlab="Peso alla nascita (g)", main="Confronto densità peso alla nascita")
lines(density(BW_mondiale),col=4,lwd=2)
legend("topright",legend=c("Pop. campione","Pop. mondiale"),col=c(1, 4),lwd=2,bty="n")
tt_peso <- t.test(Peso, mu=3215.4)
kable(data.frame(mean_sample = round(mean(Peso), 2),mu0 = 3215.4,t = round(unname(tt_peso$statistic), 2),df=round(unname(tt_peso$parameter), 0),p_value = signif(tt_peso$p.value, 3)), caption = "t-test Peso vs riferimento mondiale")
| mean_sample | mu0 | t | df | p_value |
|---|---|---|---|---|
| 3284.18 | 3215.4 | 6.55 | 2497 | 0 |
——-Test t, H0: Lunghezza = Lunghezza mondiale ——–
BL_mondiale <- rnorm(10000000,mean=493,sd=22)
plot(density(Lunghezza),col=1,lwd=2, xlab = "Lunghezza (mm)",main = "Confronto densità lunghezza alla nascita")
lines(density(BL_mondiale),col =4,lwd=2)
legend("topright",legend =c("Pop. campione","Pop. mondiale"),col=c(1, 4),lwd=2,bty="n")
tt_lunghezza <- t.test(Lunghezza, mu=493)
kable(data.frame(mean_sample = round(mean(Lunghezza), 2),mu0 = 493,t = round(unname(tt_lunghezza$statistic), 2),df = round(unname(tt_lunghezza$parameter), 0),p_value = signif(tt_lunghezza$p.value, 3)), caption = "t-test Lunghezza vs riferimento mondiale")
| mean_sample | mu0 | t | df | p_value |
|---|---|---|---|---|
| 494.7 | 493 | 3.22 | 2497 | 0.0013 |
CONCLUSIONE 2.1.2: I t-test indicano che le medie di Peso e Lunghezza osservate nel campione differiscono in modo statisticamente significativo dai rispettivi valori di riferimento riportati nello studio utilizzato (p-value<0.05), almeno entro l’incertezza campionaria del nostro campione.
#—— 3. misure antropometriche diverse tra i due sessi? ———
#oldpar <- par(no.readonly = TRUE)
#on.exit(par(oldpar), add = TRUE)
# t-test Peso, Lunghezza e Cranio
tt_peso <- t.test(Peso ~ Sesso, alternative = "two.sided", conf.level = 0.95)
tt_lung <- t.test(Lunghezza ~ Sesso, alternative = "two.sided", conf.level = 0.95)
tt_cranio <- t.test(Cranio ~ Sesso, alternative = "two.sided", conf.level = 0.95)
tt_tab <- data.frame(Variabile = c("Peso (g)", "Lunghezza (mm)", "Cranio (mm)"),
t_statistic = round(c(tt_peso$statistic,tt_lung$statistic,tt_cranio$statistic), 3),
df = round(c(tt_peso$parameter,tt_lung$parameter,tt_cranio$parameter), 1),
p_value = signif(c(tt_peso$p.value,tt_lung$p.value,tt_cranio$p.value), 3),
CI_low = round(c(tt_peso$conf.int[1],tt_lung$conf.int[1],tt_cranio$conf.int[1]), 2),
CI_high = round(c(tt_peso$conf.int[2],tt_lung$conf.int[2],tt_cranio$conf.int[2]), 2),check.names = FALSE)
kable(tt_tab,caption = "Confronto tra Maschi e Femmine (t-test a due campioni)")
| Variabile | t_statistic | df | p_value | CI_low | CI_high |
|---|---|---|---|---|---|
| Peso (g) | -12.115 | 2488.7 | 0 | -287.48 | -207.38 |
| Lunghezza (mm) | -9.582 | 2457.3 | 0 | -11.94 | -7.88 |
| Cranio (mm) | -7.437 | 2489.4 | 0 | -6.11 | -3.56 |
par(mfrow=c(1,3))
boxplot(Peso ~ Sesso)
boxplot(Lunghezza ~ Sesso)
boxplot(Cranio ~ Sesso)
CONCLUSIONE 2.1.3: In tutti i casi i t test restituiscono
p-value<0.05, così da rifiutare H0 (uguaglianza delle medie tra
maschi e femmine). In media, Peso, Lunghezza e Diametro del cranio
risultano maggiori nei neonati di sesso maschile rispetto a quelli di
sesso femminile.
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.
# Voglio solo valori numerici ed escludo variabili non generalizzabili per previsione Peso, come ospedale e tipo.parto
dati_mod <- subset(dati, select = -c(Ospedale, Tipo.parto))
dati_num <- dati_mod[,sapply(dati_mod, is.numeric)]
# covarianza =sum((Peso-mean(Peso))*(Gestazione-mean(Gestazione))/(n-1)
covarianza_PesoGest <- cov(Peso,Gestazione)
#coefficente di correlazione lineare di Pearson
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)}
par(mfrow=c(1,1))
pairs(dati_num,upper.panel = panel.smooth,lower.panel = panel.cor)
kable(round(cor(dati_num),2))
| Anni.madre | N.gravidanze | Fumatrici | Gestazione | Peso | Lunghezza | Cranio | |
|---|---|---|---|---|---|---|---|
| Anni.madre | 1.00 | 0.38 | 0.01 | -0.13 | -0.02 | -0.06 | 0.02 |
| N.gravidanze | 0.38 | 1.00 | 0.05 | -0.10 | 0.00 | -0.06 | 0.04 |
| Fumatrici | 0.01 | 0.05 | 1.00 | 0.03 | -0.02 | -0.02 | -0.01 |
| Gestazione | -0.13 | -0.10 | 0.03 | 1.00 | 0.59 | 0.62 | 0.46 |
| Peso | -0.02 | 0.00 | -0.02 | 0.59 | 1.00 | 0.80 | 0.70 |
| Lunghezza | -0.06 | -0.06 | -0.02 | 0.62 | 0.80 | 1.00 | 0.60 |
| Cranio | 0.02 | 0.04 | -0.01 | 0.46 | 0.70 | 0.60 | 1.00 |
Dalla matrice di correlazione noto delle correlazioni positive tra peso e gestazione, lunghezza e diametro del cranio, ed una lieve correlazione positiva tra anni della madre e numero di gravidanze, che però è abbastanza ovvia come correlazione e non inerente al nostro scopo. Per il resto non abbiamo correlazioni evidenti.
################### modello lineare completo ######################
mod_all <- lm(Peso ~., data=dati_mod)
kable(tidy(mod_all, conf.int = TRUE),digits = 3,caption = "Modello Lineare con tutti i parametri")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6712.240 | 141.334 | -47.492 | 0.000 | -6989.385 | -6435.096 |
| Anni.madre | 0.880 | 1.149 | 0.766 | 0.444 | -1.373 | 3.134 |
| N.gravidanze | 11.379 | 4.677 | 2.433 | 0.015 | 2.208 | 20.549 |
| Fumatrici | -30.396 | 27.608 | -1.101 | 0.271 | -84.533 | 23.741 |
| Gestazione | 32.947 | 3.829 | 8.605 | 0.000 | 25.439 | 40.455 |
| Lunghezza | 10.232 | 0.301 | 33.979 | 0.000 | 9.641 | 10.822 |
| Cranio | 10.520 | 0.427 | 24.633 | 0.000 | 9.682 | 11.357 |
| SessoM | 78.079 | 11.213 | 6.963 | 0.000 | 56.091 | 100.067 |
kable(glance(mod_all),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.727 | 0.726 | 274.707 | 948.29 | 0 | 7 | -17568.53 | 35155.07 | 35207.48 | 187905214 | 2490 | 2498 |
################## (A) rimuovo solo Anni.madre (Pr(>|t|): 0.444) #################
mod1 <- update(mod_all,~.-Anni.madre)
kable(tidy(mod1, conf.int = TRUE),digits = 3,caption = "Modello iniziale (escludendo Anni.madre)")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6682.264 | 135.798 | -49.207 | 0.000 | -6948.553 | -6415.975 |
| N.gravidanze | 12.700 | 4.347 | 2.921 | 0.004 | 4.176 | 21.224 |
| Fumatrici | -30.573 | 27.605 | -1.108 | 0.268 | -84.703 | 23.558 |
| Gestazione | 32.644 | 3.808 | 8.573 | 0.000 | 25.177 | 40.111 |
| Lunghezza | 10.231 | 0.301 | 33.979 | 0.000 | 9.640 | 10.821 |
| Cranio | 10.537 | 0.426 | 24.707 | 0.000 | 9.700 | 11.373 |
| SessoM | 78.160 | 11.212 | 6.971 | 0.000 | 56.174 | 100.145 |
kable(glance(mod1),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.727 | 0.726 | 274.684 | 1106.424 | 0 | 6 | -17568.83 | 35153.66 | 35200.24 | 187949505 | 2491 | 2498 |
################## (B) rimuovo solo Fumatrici (Pr(>|t|): 0.26818) ##################
mod2 <- update(mod_all,~.-Fumatrici)
kable(tidy(mod2, conf.int = TRUE),digits = 3,caption = "Modello iniziale (escludendo Fumatrici)")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6712.065 | 141.340 | -47.489 | 0.000 | -6989.221 | -6434.910 |
| Anni.madre | 0.891 | 1.149 | 0.775 | 0.438 | -1.362 | 3.144 |
| N.gravidanze | 11.120 | 4.671 | 2.381 | 0.017 | 1.961 | 20.280 |
| Gestazione | 32.691 | 3.822 | 8.554 | 0.000 | 25.197 | 40.186 |
| Lunghezza | 10.246 | 0.301 | 34.058 | 0.000 | 9.656 | 10.836 |
| Cranio | 10.524 | 0.427 | 24.642 | 0.000 | 9.687 | 11.361 |
| SessoM | 77.900 | 11.212 | 6.948 | 0.000 | 55.913 | 99.887 |
kable(glance(mod2),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.727 | 0.726 | 274.719 | 1106.042 | 0 | 6 | -17569.14 | 35154.28 | 35200.87 | 187996688 | 2491 | 2498 |
################## (C) rimuovo sia Fumatrici che Anni.madre ##################
mod3 <- update(mod1,~.-Fumatrici)
kable(tidy(mod3, conf.int = TRUE),digits = 3,caption = "Modello iniziale (escludendo sia Anni.madre che Fumatrici)")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6681.725 | 135.804 | -49.201 | 0.000 | -6948.025 | -6415.426 |
| N.gravidanze | 12.455 | 4.342 | 2.869 | 0.004 | 3.942 | 20.969 |
| Gestazione | 32.383 | 3.801 | 8.520 | 0.000 | 24.930 | 39.836 |
| Lunghezza | 10.245 | 0.301 | 34.059 | 0.000 | 9.656 | 10.835 |
| Cranio | 10.541 | 0.426 | 24.717 | 0.000 | 9.705 | 11.377 |
| SessoM | 77.981 | 11.211 | 6.956 | 0.000 | 55.997 | 99.965 |
kable(glance(mod3),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.727 | 0.726 | 274.697 | 1327.343 | 0 | 5 | -17569.44 | 35152.89 | 35193.65 | 188042054 | 2492 | 2498 |
### (D) modello ulteriormente ridotto escludendo N.gravidanze per valutare un modello più semplice ###
mod4 <- update(mod3,~.-N.gravidanze)
kable(tidy(mod4, conf.int = TRUE),digits = 3,caption = "Modello iniziale (escludendo sia Anni.madre che Fumatrici che N.gravidanze)")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6651.673 | 135.595 | -49.055 | 0 | -6917.564 | -6385.782 |
| Gestazione | 31.326 | 3.788 | 8.269 | 0 | 23.897 | 38.755 |
| Lunghezza | 10.202 | 0.301 | 33.909 | 0 | 9.612 | 10.792 |
| Cranio | 10.671 | 0.425 | 25.126 | 0 | 9.838 | 11.503 |
| SessoM | 79.103 | 11.220 | 7.050 | 0 | 57.100 | 101.105 |
kable(glance(mod4),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.726 | 0.726 | 275.095 | 1652.329 | 0 | 4 | -17573.56 | 35159.12 | 35194.06 | 188663107 | 2493 | 2498 |
# In tutti i casi Adjusted R-squared non cambia significativamente (~0.7265)
################### 2.3) Selezione del Modello Ottimale ##################
kable(BIC(mod_all,mod1,mod2,mod3,mod4))
| df | BIC | |
|---|---|---|
| mod_all | 9 | 35207.48 |
| mod1 | 8 | 35200.24 |
| mod2 | 8 | 35200.87 |
| mod3 | 7 | 35193.65 |
| mod4 | 6 | 35194.06 |
kable(AIC(mod_all,mod1,mod2,mod3,mod4))
| df | AIC | |
|---|---|---|
| mod_all | 9 | 35155.07 |
| mod1 | 8 | 35153.66 |
| mod2 | 8 | 35154.28 |
| mod3 | 7 | 35152.89 |
| mod4 | 6 | 35159.12 |
kable(anova(mod_all,mod1,mod2,mod3,mod4))
| Res.Df | RSS | Df | Sum of Sq | F | Pr(>F) |
|---|---|---|---|---|---|
| 2490 | 187905214 | NA | NA | NA | NA |
| 2491 | 187949505 | -1 | -44291.73 | 0.5869257 | 0.4436830 |
| 2491 | 187996688 | 0 | -47182.11 | NA | NA |
| 2492 | 188042054 | -1 | -45366.21 | 0.6011640 | 0.4382079 |
| 2493 | 188663107 | -1 | -621053.31 | 8.2298022 | 0.0041555 |
Il confronto tra modelli tramite il BIC (in accordo con ANOVA e AIC) mostra una preferenza per il modello più semplice, che esclude i parametri Fumatrici e Anni.madre, con una differenza di oltre 6 rispetto ai modelli alternativi, indicando che è il modello migliore da considerare secondo il principio del rasoio di Occam.
# Selezione automatica delle covarianze tramite procedura stepwise con criterio BIC
stepwise.mod_best<-MASS::stepAIC(mod_all,direction="both",k=log(n))
Start: AIC=28110.64 Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione + Lunghezza + Cranio + Sesso
Df Sum of Sq RSS AIC
Step: AIC=28103.4 Peso ~ N.gravidanze + Fumatrici + Gestazione + Lunghezza + Cranio + Sesso
Df Sum of Sq RSS AIC
Step: AIC=28096.81 Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Sesso
Df Sum of Sq RSS AIC
# Output del modello selezionato (formula finale e stime)
kable(tidy(stepwise.mod_best, conf.int = TRUE),digits = 3,caption = "BEST MODEL")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -6681.725 | 135.804 | -49.201 | 0.000 | -6948.025 | -6415.426 |
| N.gravidanze | 12.455 | 4.342 | 2.869 | 0.004 | 3.942 | 20.969 |
| Gestazione | 32.383 | 3.801 | 8.520 | 0.000 | 24.930 | 39.836 |
| Lunghezza | 10.245 | 0.301 | 34.059 | 0.000 | 9.656 | 10.835 |
| Cranio | 10.541 | 0.426 | 24.717 | 0.000 | 9.705 | 11.377 |
| SessoM | 77.981 | 11.211 | 6.956 | 0.000 | 55.997 | 99.965 |
kable(glance(stepwise.mod_best),digits = 3,caption = "Statistiche globali del BEST MODEL")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.727 | 0.726 | 274.697 | 1327.343 | 0 | 5 | -17569.44 | 35152.89 | 35193.65 | 188042054 | 2492 | 2498 |
Per completezza, verifico se semplici estensioni del modello lineare (usando termini quadratici o interazioni) producano un miglioramento sostanziale (in accordo con il principio di marginalità). Nella scelta finale, seppur mi baso sul confronto tramite BIC, cerco di dare priorità alla semplicità del modello (rasoio di Occam) e all’interpretabilità inferenziale.
# Modello con termini quadratici
mod_quad <- update(stepwise.mod_best, ~ . + I(Gestazione^2) + I(Lunghezza^2))
kable(tidy(mod_quad, conf.int = TRUE),digits = 3,caption = "Modello con termini quadratici per Gestazione e Lunghezza")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -2359.379 | 905.751 | -2.605 | 0.009 | -4135.481 | -583.278 |
| N.gravidanze | 14.466 | 4.249 | 3.405 | 0.001 | 6.134 | 22.798 |
| Gestazione | 336.175 | 62.739 | 5.358 | 0.000 | 213.149 | 459.201 |
| Lunghezza | -32.123 | 4.039 | -7.953 | 0.000 | -40.043 | -24.203 |
| Cranio | 10.447 | 0.419 | 24.909 | 0.000 | 9.625 | 11.270 |
| SessoM | 72.599 | 11.007 | 6.596 | 0.000 | 51.015 | 94.183 |
| I(Gestazione^2) | -3.868 | 0.825 | -4.689 | 0.000 | -5.486 | -2.251 |
| I(Lunghezza^2) | 0.044 | 0.004 | 10.545 | 0.000 | 0.036 | 0.052 |
kable(glance(mod_quad),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.739 | 0.739 | 268.587 | 1008.397 | 0 | 7 | -17512.25 | 35042.5 | 35094.91 | 179625530 | 2490 | 2498 |
# Modello con una sola interazione
mod_int <- update(stepwise.mod_best, ~ . + Cranio * Lunghezza)
kable(tidy(mod_int, conf.int = TRUE),digits = 3,caption = "Modello con termini interazione Cranio * Lunghezza")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| (Intercept) | -1803.011 | 1018.128 | -1.771 | 0.077 | -3799.476 | 193.454 |
| N.gravidanze | 12.933 | 4.323 | 2.991 | 0.003 | 4.455 | 21.410 |
| Gestazione | 38.149 | 3.967 | 9.616 | 0.000 | 30.370 | 45.929 |
| Lunghezza | -0.306 | 2.203 | -0.139 | 0.890 | -4.626 | 4.014 |
| Cranio | -4.755 | 3.192 | -1.490 | 0.136 | -11.014 | 1.505 |
| SessoM | 73.240 | 11.204 | 6.537 | 0.000 | 51.270 | 95.210 |
| Lunghezza:Cranio | 0.032 | 0.007 | 4.835 | 0.000 | 0.019 | 0.044 |
kable(glance(mod_int),digits = 3,caption = "Statistiche globali del modello")
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.73 | 0.729 | 273.472 | 1119.946 | 0 | 6 | -17557.78 | 35131.56 | 35178.14 | 186293990 | 2491 | 2498 |
# Confronto BIC: modello lineare vs estensioni
kable(BIC(stepwise.mod_best, mod_quad, mod_int))
| df | BIC | |
|---|---|---|
| stepwise.mod_best | 7 | 35193.65 |
| mod_quad | 9 | 35094.91 |
| mod_int | 8 | 35178.14 |
COMMENTI ALL’ACCETTAZIONE O MENO DELL’AUMENTO DELLA COMPLESSITÀ
confronto con BIC: il confronto tramite BIC indica una preferenza per il modello con termini quadratici “mod_quad.” Quindi, dal punto di vista statistico, una lieve curvatura può descrivere meglio alcune regioni dei dati.
miglioramento del fit: rispetto a stepwise.mod_best, c’è un incremento di Adjusted R-squared di 0.012 ed una riduzione del Residual standard error è di circa 6 g. Questo è un miglioramento contenuto rispetto alla scala “Peso”.
complessità dell’interpretazione: un altro punto importante riguarda la perdita di immediatezza nell’interpretazione dei coefficienti principali. Infatti, in presenza dei termini quadratici, i coefficienti lineari di Gestazione e Lunghezza cambiano segno: questo non significa che “sparisce” l’effetto, ma che l’effetto non è più costante e va interpretato come variabile al variare di Gestazione e Lunghezza. In pratica il modello diventa meno leggibile e più complicato da spiegare.
confronto con i grafici: visivamente le relazioni appaiono monotone e senza punti di flesso evidenti nei grafici in “pairs”. Questo non nega che una lieve curvatura possa aiutare il fit (come infatti suggerisce il BIC), ma indica che la non-linearità non è così marcata da rendere indispensabile un modello complesso per raccontare i risultati.
Conclusione e scelta finale: dato che l’obiettivo principale del progetto è interpretare in modo semplice e comunicabile i fattori associati alla variabile risposta “Peso”, mantengo come best model quello lineare “stepwise.mod_best”, privilegiando semplicità (rasoio di Occam) e interpretabilità inferenziale rispetto a un miglioramento di fit contenuto rispetto alla scala del “Peso” e ottenuto al prezzo di una maggiore complessità interpretativa.
Una volta ottenuto il modello finale, valuteremo la sua capacità predittiva utilizzando metriche come R2 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.
par(mfrow=c(2,2))
plot(stepwise.mod_best)
- Residuals vs Fitted: la nube dei punti è complessivamente centrata
intorno a 0, ma si nota una leggera struttura per valori bassi dei
fitted (possibile non-linearità locale o sottogruppo di
osservazioni).
- Q–Q Residuals: i punti sono abbastanza allineati nella parte centrale,
mentre nelle code (soprattutto superiore) si osservano deviazioni
evidenti dalla normalità, coerenti con la presenza di residui estremi
(e.g., 1551, 155, 1306).
- Scale-Location: la dispersione non è perfettamente costante lungo i
fitted (andamento non del tutto piatto), suggerendo eteroschedasticità,
coerentemente con il test di Breusch–Pagan che rifiuta H0.
- Residuals vs Leverage: è presente almeno un’osservazione con residuo
standardizzato molto alto e leverage relativamente elevato (1551),
associata a Cook’s distance molto grande (vicina alla curva di
riferimento 1). È quindi un candidato “influente” e va verificato.
#Usiamo un metodo numerico per indagare valori di Leverage e Outliers
#Leverage (hat-values)
par(mfrow=c(1,1))
lev <- hatvalues(stepwise.mod_best)
plot(lev, ylab="Leverage (hat-values)",main="hat values")
p <- length(coef(stepwise.mod_best))
soglia <- 2*p/n
abline(h=soglia, col=2)
lev_oltresoglia <- lev[lev>soglia]
# Vediamo che percentuali di valori sono sopra la soglia
infl <- which(lev > soglia)
nlev <- length(infl)
perclev <- round(100*nlev/n, 1)
cat("% Leverage sopra la soglia =", perclev, "%\n")
## % Leverage sopra la soglia = 6.1 %
cat("Num Leverage sopra la soglia =", nlev, "\n")
## Num Leverage sopra la soglia = 152
# Nota: una quota ~6% sopra soglia indica presenza di punti con leverage relativamente alto, che non implica influenza sulle stime.
# Outlier nei residui
plot(rstudent(stepwise.mod_best),main="Outlier nei residui ")
abline(h=c(-2,2), col=2)
outls <- outlierTest(stepwise.mod_best)
# Estrazione della matrice
outls_mat <- outls$rstudent
outls_tab <- as.data.frame(round(outls_mat, 3))
kable(outls_tab, caption = "Outlier nei residui")
| round(outls_mat, 3) | |
|---|---|
| 1551 | 10.046 |
| 155 | 5.025 |
| 1306 | 4.825 |
# 3 outliers: 1306, 155, 1551 (estremamente anomalo con rstudent~10)
#distanza di cook
cookD <- cooks.distance(stepwise.mod_best)
plot(cookD,main = paste("Max CookDistance =", round(max(cookD), 3)),ylab="Cook Distance")
# Un valore massimo ~0.83 è elevato (vicino alla curva di riferimento 1 nel grafico diagnostico), quindi l'osservazione corrispondente merita verifica.
# Test diagnostici sui residui
bptest(stepwise.mod_best)
##
## studentized Breusch-Pagan test
##
## data: stepwise.mod_best
## BP = 90.297, df = 5, p-value < 2.2e-16
# bptest: rifiuto H0 -> evidenza di eteroschedasticità (varianza non costante)
dwtest(stepwise.mod_best)
##
## Durbin-Watson test
##
## data: stepwise.mod_best
## DW = 1.9532, p-value = 0.1209
## alternative hypothesis: true autocorrelation is greater than 0
# dwtest: non rifiuto H0 -> nessuna evidenza di autocorrelazione positiva dei residui
shapiro.test(residuals(stepwise.mod_best))
##
## Shapiro-Wilk normality test
##
## data: residuals(stepwise.mod_best)
## W = 0.97414, p-value < 2.2e-16
# shapiro.test: rifiuto H0 -> residui formalmente non normali con test molto sensibile a n grande
# Check visivo (supporto, non decisionale)
plot(density(residuals(stepwise.mod_best)), main="Densità dei residui")
# Deviazioni soprattutto nelle code, coerentemente con il Q-Q plot.
# CASO ANOMALO VISUALIZZATO in Peso~4370 g Lunghezza~315 mm
plot(Peso, Lunghezza, pch=20, xlab="Peso (g)", ylab="Lunghezza (mm)", main="Peso vs Lunghezza")
# in basso a destra
x_out <- 4370
y_out <- 315
symbols(x_out, y_out,circles = 100,inches = FALSE,add = TRUE,fg = "red",lwd = 2)
text(x_out, y_out, labels = "Leverage", pos = 3, col = "red")
Commento finale: il modello appare complessivamente adeguato. Dalla
diagnostica emerge evidenza di eteroschedasticità e deviazioni dalla
normalità soprattutto nelle code, coerenti con pochi residui estremi. Le
stime beta dei coefficienti restano una sintesi utile delle relazioni
principali, ma i risultati inferenziali (p-value e intervalli di
confidenza) vanno interpretati con cautela. Inoltre, alcune osservazioni
(in particolare 1551) mostrano residui estremi e potenziale influenza
(Cook’s distance elevata), per cui è opportuno verificarne la
plausibilità e valutare l’impatto sulle stime (per esempio si potrebbe
confrontare il modello con e senza questi punti).
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.
# Dall'esempio ora capisco che probabilmente il numero di gravidanze è un parametro importante per il fit.
L39 <- median(Lunghezza[Gestazione == 39])
C39 <- median(Cranio[Gestazione == 39])
#Predizione: N.gravidanze=3,Sesso=F,Gestazione=39,Lunghezza=500 mm,Cranio=340 mm
caso_test <- data.frame(N.gravidanze=3,Sesso="F",Gestazione=39,Lunghezza=L39,Cranio=C39)
#PESO (g):
predict(stepwise.mod_best,newdata=caso_test,interval="prediction", level=0.95)
## fit lwr upr
## 1 3325.219 2786.036 3864.402
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.
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the visreg package.
## Please report the issue at <https://github.com/pbreheny/visreg/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the ggplot2 package.
## Please report the issue at <https://github.com/tidyverse/ggplot2/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Nel complesso, il modello fornisce una descrizione interpretabile e statisticamente solida dei principali fattori associati al Peso dei neonati alla nascita, per il campione (statisticamente scarso rispetto quello mondiale) analizzato.
Ciao Andrea! Ok usare la programmazione, ma nell’analisi descrittiva non serve fare alcune cose un tanto al chilo. Ad esempio, saggiare la normalità per ogni variabile.
Ho ridotto l’analisi descrittiva finalizzata all’identificazione di outlier o anomalie
potevi usare solo il t-test (lo z test è didattico)
Fatto
tipo di parto e ospedale potevano essere escluse a priori “logicamente” dal modello in quanto non hanno molto senso in ottica previsionale
Ok grazie. Sinceramente non ho escluso a priori queste variabili in quanto l’ospedale potrebbe essere correlato ad una diversa locazione delle nascite, che presumo influirebbe sulle caratteristiche fisiche del bambino, mentre il tipo di parto potrebbe ci sta, in quanto, anche nel caso mostrasse una qualche correlazione potrebbe essere indiretta, e direttamente correlata alla gestazione.
considerare l’effetto quadratico (o d’interazione) senza considerare uno o più effetti principali è una questione molto spinosa su cui la gente ci si scanna nei forum. In ottica di statistica pura e non ML/DL, direi che ci si rifà a questo: https://en.wikipedia.org/wiki/Principle_of_marginality
Chiarissimo grazie per l’intervento. Ho preso in considerazione tale effetto di marginalizzazione.
pertanto il modello scelto non andrebbe bene.
Chiaro.
Il processo di selezione del modello è comunque molto confusinario e può essere snellito di molto. Se provi così tanti modelli e così tanti regressori ce ne saranno alcuni significativi solo per effetto del caso ma senza effetto reale per poter fare inferenza. RASOIO DI OCCAM!
Ho scelto il modello lineare semplice. La capacità di individuare modelli più complessi richiede probabilmente un campione statisticamente molto più ampio.
Mancano i commenti alle stime (coefficienti beta) in termini di “incremento medio di Y al variare di X”
Commenti inseriti solo per il best fit
si chiamano “code” della distribuzione, non “ali”
Ok. Mi sono fatto confondere dal termine in inglesi wings :)
Qualità dell’analisi L’analisi non è male ma è presentata male. Migliora la leggibilità, commenta meglio i passaggi e sopratuttto smeplifica il processo di modellazione. Meno modelli e spiegati più chiaramente.
Hai ragione, sono stato molto superficiale considerando che faccio questo master nel tempo libero in cui non lavoro. Mi sono concentrato sul comprendere la teoria piuttosto che l’esposizione. Questa volta ho riguardato il testo, spero sia chiaro.
Qualità del documento Ci sono dei print di console grezzi che non stanno benissimo… sempre meglio inserire i dati almeno in una tabella semplice per sistemare l’output, tipo kable()
Ho riguardato l’output HTML. Ho visto che summary non andava bene, e mi sono fatto consigliare da chatgpt come poterlo printare meglio in HTML. Per il resto, quando possibile, ho usato kable() come consigliato.
i messaggi e i warnings della console, come per l’importazione di librerie e altro, si possono anche omettere per fare tutto più pulito
Fatto. Ho preferito mettere {r setup, include=TRUE, message=FALSE, warning=FALSE}
Arrotonda a 2 cifre decimali, o più solo quando serve
Fatto. I p-values sono stati approssimati a 3 cifre decimali.