L’azienda Neonatal Health Solutions desidera 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:
Miglioramento delle previsioni cliniche.
Ottimizzazione delle risorse ospedaliere.
Prevenzione e identificazione dei fattori di rischio.
Valutazione delle pratiche ospedaliere.
Supporto alla pianificazione strategica.
Per costruire il modello predittivo, abbiamo raccolto dati su
2500 neonati provenienti da tre ospedali. Le variabili raccolte
includono:
Età della madre: Misura dell’età in anni.
Numero di gravidanze: Quante gravidanze ha avuto la madre.
Fumo materno: Un indicatore binario (0=non fumatrice, 1=fumatrice).
Durata della gravidanza: Numero di settimane di gestazione.
Peso del neonato: Peso alla nascita in grammi.
Lunghezza e diametro del cranio: Lunghezza del neonato e diametro craniale, misurabili anche durante la gravidanza tramite ecografie.
Tipo di parto: Naturale o cesareo.
Ospedale di nascita: Ospedale 1, 2 o 3.
Sesso del neonato: Maschio (M) o femmina (F).
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.
Caricamento del dataset “neonati.csv” in un dataframe R denominato df e visualizzazione dell’intestazione in formato tabella per verificarne il caricamento.
# Caricamento del dataset
df <- read.csv("neonati.csv", sep = ",", stringsAsFactors = FALSE)
# Visualizzazione dell'intestazione
knitr::kable(
head(df),
caption = "Prime 6 osservazioni del dataset neonati"
)
| 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 |
Esaminazione della struttura del dataframe per identificare: il tipo di ciascuna variabile, la presenza di eventuali anomalie o codifiche non appropriate e di valori mancanti o incoerenti al fine di assicurare la qualità dei dati utilizzati.
str(df)
## 'data.frame': 2500 obs. of 10 variables:
## $ Anni.madre : int 26 21 34 28 20 32 26 25 22 23 ...
## $ N.gravidanze: int 0 2 3 1 0 0 1 0 1 0 ...
## $ Fumatrici : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Gestazione : int 42 39 38 41 38 40 39 40 40 41 ...
## $ Peso : int 3380 3150 3640 3690 3700 3200 3100 3580 3670 3700 ...
## $ Lunghezza : int 490 490 500 515 480 495 480 510 500 510 ...
## $ Cranio : int 325 345 375 365 335 340 345 349 335 362 ...
## $ Tipo.parto : chr "Nat" "Nat" "Nat" "Nat" ...
## $ Ospedale : chr "osp3" "osp1" "osp2" "osp2" ...
## $ Sesso : chr "M" "F" "M" "M" ...
Il dataset contiene 2500 osservazioni.
colSums(is.na(df))
## Anni.madre N.gravidanze Fumatrici Gestazione Peso Lunghezza
## 0 0 0 0 0 0
## Cranio Tipo.parto Ospedale Sesso
## 0 0 0 0
Il dataset non contiene valori nulli.
df[duplicated(df), ]
## [1] Anni.madre N.gravidanze Fumatrici Gestazione Peso
## [6] Lunghezza Cranio Tipo.parto Ospedale Sesso
## <0 rows> (or 0-length row.names)
Il dataset non contiene valori duplicati.
Caricamento delle librerie necessarie per condurre l’analisi.
library(dplyr)
library(kableExtra)
library(tidyr)
library(corrplot)
library(car)
library(broom)
library(ggplot2)
library(plotly)
library(MASS)
library(lmtest)
library(gridExtra)
Imputazione dei valori anomali di Anni.madre:
# Summary prima dell'imputazione
summary(df$Anni.madre)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 25.00 28.00 28.16 32.00 46.00
Il summary() della variabile Anni.madre
rivela la presenza di valori pari a 0 e 1, chiaramente non plausibili
per l’età di una madre. Questi valori vengono imputati prima
dell’analisi inferenziale con la media della variabile
calcolata escludendoli, in modo che tutte le statistiche e i modelli
successivi operino su dati coerenti.
# Identificazione valori anomali
cat("Valori anomali di Anni.madre (<=1):",
df$Anni.madre[df$Anni.madre <= 1], "\n")
## Valori anomali di Anni.madre (<=1): 1 0
# Calcolo della media (escludendo i valori anomali)
media_anni <- mean(df$Anni.madre[df$Anni.madre > 1])
# Imputazione
df$Anni.madre[df$Anni.madre <= 1] <- round(media_anni, 0)
cat("Media utilizzata per l'imputazione:", round(media_anni, 1), "anni\n")
## Media utilizzata per l'imputazione: 28.2 anni
# Summary dopo l'imputazione
summary(df$Anni.madre)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 13.00 25.00 28.00 28.19 32.00 46.00
2.1 Analisi Preliminare: 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:
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.
# Statistiche descrittive per le variabili numeriche
knitr::kable(
summary(df[, c("Anni.madre", "N.gravidanze", "Gestazione", "Peso", "Lunghezza", "Cranio")]),
caption = "Statistiche descrittive per le variabili numeriche"
)
| Anni.madre | N.gravidanze | Gestazione | Peso | Lunghezza | Cranio | |
|---|---|---|---|---|---|---|
| Min. :13.00 | Min. : 0.0000 | Min. :25.00 | Min. : 830 | Min. :310.0 | Min. :235 | |
| 1st Qu.:25.00 | 1st Qu.: 0.0000 | 1st Qu.:38.00 | 1st Qu.:2990 | 1st Qu.:480.0 | 1st Qu.:330 | |
| Median :28.00 | Median : 1.0000 | Median :39.00 | Median :3300 | Median :500.0 | Median :340 | |
| Mean :28.19 | Mean : 0.9812 | Mean :38.98 | Mean :3284 | Mean :494.7 | Mean :340 | |
| 3rd Qu.:32.00 | 3rd Qu.: 1.0000 | 3rd Qu.:40.00 | 3rd Qu.:3620 | 3rd Qu.:510.0 | 3rd Qu.:350 | |
| Max. :46.00 | Max. :12.0000 | Max. :43.00 | Max. :4930 | Max. :565.0 | Max. :390 |
# Distribuzioni grafiche
df_hist <- df[, c("Peso", "Gestazione", "Lunghezza", "Cranio", "Anni.madre", "N.gravidanze")] %>%
pivot_longer(cols = everything(), names_to = "Variabile", values_to = "Valore") %>%
mutate(Variabile = factor(Variabile,
levels = c("Peso", "Gestazione", "Lunghezza", "Cranio", "Anni.madre", "N.gravidanze"),
labels = c("Distribuzione del peso", "Settimane di gestazione", "Lunghezza",
"Diametro craniale", "Età della madre", "Numero gravidanze")))
ggplot(df_hist, aes(x = Valore)) +
geom_histogram(fill = "#1f78b4", color = "white", bins = 20) +
facet_wrap(~ Variabile, scales = "free", ncol = 3) +
labs(title = "Distribuzioni grafiche delle variabili numeriche",
x = "", y = "Frequenza") +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
strip.text = element_text(face = "plain"),
legend.position = "none"
)
La tabella seguente riassume frequenze assolute e proporzioni per le quattro variabili categoriche del dataset.
# Frequenze e proporzioni per le variabili categoriche
cat_vars <- list(
data.frame(Variabile = "Fumatrici", Categoria = names(table(df$Fumatrici)), Frequenza = as.integer(table(df$Fumatrici))),
data.frame(Variabile = "Sesso", Categoria = names(table(df$Sesso)), Frequenza = as.integer(table(df$Sesso))),
data.frame(Variabile = "Tipo.parto", Categoria = names(table(df$Tipo.parto)), Frequenza = as.integer(table(df$Tipo.parto))),
data.frame(Variabile = "Ospedale", Categoria = names(table(df$Ospedale)), Frequenza = as.integer(table(df$Ospedale)))
)
cat_df <- bind_rows(cat_vars)
cat_df$Proporzione <- round(cat_df$Frequenza / nrow(df), 4)
kable(cat_df,
caption = "Frequenze e proporzioni per le variabili categoriche",
col.names = c("Variabile", "Categoria", "Frequenza", "Proporzione")) |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE) |>
collapse_rows(columns = 1, valign = "top")
| Variabile | Categoria | Frequenza | Proporzione |
|---|---|---|---|
| Fumatrici | 0 | 2396 | 0.9584 |
| 1 | 104 | 0.0416 | |
| Sesso | F | 1256 | 0.5024 |
| M | 1244 | 0.4976 | |
| Tipo.parto | Ces | 728 | 0.2912 |
| Nat | 1772 | 0.7088 | |
| Ospedale | osp1 | 816 | 0.3264 |
| osp2 | 849 | 0.3396 | |
| osp3 | 835 | 0.3340 |
Si osserva come il campione sia bilanciato per sesso (circa 50/50) e distribuito in modo uniforme tra i tre ospedali (circa 33% ciascuno). I parti naturali rappresentano la netta maggioranza (70.9%) rispetto ai cesarei (29.1%), mentre le madri fumatrici costituiscono solo il 4.2% del campione.
l grafico seguente mostra la distribuzione delle frequenze assolute dei parti cesarei e naturali nei tre ospedali.
# Barplot tipo parto per ospedale (ggplot2)
tab_bar <- as.data.frame(table(df$Ospedale, df$Tipo.parto))
colnames(tab_bar) <- c("Ospedale", "Tipo_parto", "Frequenza")
ggplot(tab_bar, aes(x = Ospedale, y = Frequenza, fill = Tipo_parto)) +
geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
geom_text(aes(label = Frequenza),
position = position_dodge(width = 0.8),
vjust = -0.3,
size = 3) +
scale_fill_manual(values = c(
"Ces" = "#33a02c",
"Nat" = "#1f78b4"
)) +
labs(
title = "Distribuzione del tipo di parto per ospedale",
x = "Ospedale",
y = "Frequenza",
fill = "Tipo di parto"
) +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
legend.position = "right"
)
Si può osservare come le proporzioni siano sostanzialmente omogenee tra le strutture: i parti cesarei oscillano tra 232 e 254, mentre quelli naturali tra 574 e 603, confermando visivamente l’assenza di associazione tra ospedale e tipo di parto che verrà poi verificata formalmente con il test chi-quadrato.
l grafico seguente confronta la distribuzione del peso neonatale tra madri fumatrici e non fumatrici tramite curve di densità sovrapposte.
# Density plot fumatrici vs non fumatrici
ggplot(df, aes(x = Peso, fill = factor(Fumatrici, labels = c("Non fumatrice", "Fumatrice")))) +
geom_density(alpha = 0.5) +
scale_fill_manual(values = c("Non fumatrice" = "#66c2a5", "Fumatrice" = "#fc8d62")) +
labs(title = "Distribuzione del peso: fumatrici vs non fumatrici",
x = "Peso (g)", y = "Densità", fill = "") +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
legend.position = "right")
Si può osservare come la curva delle fumatrici risulti leggermente spostata verso sinistra rispetto a quella delle non fumatrici, suggerendo un peso tendenzialmente inferiore nei neonati di madri fumatrici. Questo effetto verrà poi quantificato nel modello di regressione.
Vengono ora identificati gli outlier tramite il metodo IQR. La tabella seguente riporta, per ogni variabile numerica, le soglie IQR, il numero di outlier e la percentuale sul totale.
# Funzione per identificare outlier con metodo IQR
find_outliers_iqr <- function(x) {
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
lower <- Q1 - 1.5 * IQR_val
upper <- Q3 + 1.5 * IQR_val
list(idx = which(x < lower | x > upper),
lower = lower, upper = upper, Q1 = Q1, Q3 = Q3, IQR = IQR_val)
}
# Variabili numeriche da analizzare
vars_num <- c("Peso", "Lunghezza", "Cranio", "Gestazione", "Anni.madre")
# Creazione tabella riassuntiva
out_table <- lapply(vars_num, function(v) {
res <- find_outliers_iqr(df[[v]])
data.frame(
Variabile = v,
Q1 = round(res$Q1, 2),
Q3 = round(res$Q3, 2),
IQR = round(res$IQR, 2),
Soglia_inf = round(res$lower, 2),
Soglia_sup = round(res$upper, 2),
N_outlier = length(res$idx),
Perc_outlier = paste0(round(length(res$idx) / nrow(df) * 100, 2), "%")
)
}) |> bind_rows()
# Tabella formattata
out_table |>
kable("html", caption = "Outlier individuati con metodo IQR — dettaglio per variabile") |>
kable_styling(full_width = TRUE, bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE) |>
column_spec(9, width = "25em")
| Variabile | Q1 | Q3 | IQR | Soglia_inf | Soglia_sup | N_outlier | Perc_outlier | |
|---|---|---|---|---|---|---|---|---|
| 25%…1 | Peso | 2990 | 3620 | 630 | 2045.0 | 4565.0 | 69 | 2.76% |
| 25%…2 | Lunghezza | 480 | 510 | 30 | 435.0 | 555.0 | 59 | 2.36% |
| 25%…3 | Cranio | 330 | 350 | 20 | 300.0 | 380.0 | 48 | 1.92% |
| 25%…4 | Gestazione | 38 | 40 | 2 | 35.0 | 43.0 | 67 | 2.68% |
| 25%…5 | Anni.madre | 25 | 32 | 7 | 14.5 | 42.5 | 11 | 0.44% |
# Boxplot outliers (ggplot2)
df_long <- df[, vars_num] %>%
pivot_longer(cols = everything(), names_to = "Variabile", values_to = "Valore")
ggplot(df_long, aes(x = Variabile, y = Valore)) +
geom_boxplot(fill = "lightblue", width = 0.4,
outlier.colour = "red", outlier.shape = 16, outlier.size = 2) +
facet_wrap(~ Variabile, scales = "free", ncol = 3) +
labs(title = "Boxplot delle variabili numeriche") +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.text = element_text(face = "bold")
)
Gli outlier identificati vengono mantenuti nel dataset in questa fase. Eventuali osservazioni influenti verranno studiate nella diagnostica del modello (Sezione 2.3).
Si può notare quindi come le variabili numeriche del dataset neonatale mostrano distribuzioni complessivamente regolari e coerenti con dati clinici reali, pur presentando un numero non trascurabile di outlier (soprattutto per peso, lunghezza, cranio e settimane di gestazione) che riflettono la naturale variabilità biologica e la presenza di casi estremi come prematurità o neonati macrosomici. L’assenza di valori mancanti o duplicati conferma inoltre una buona qualità del dataset, rendendolo idoneo per le successive analisi inferenziali e per la costruzione del modello predittivo del peso neonatale.
In seguito a quanto emerso dall’analisi preliminare possiamo saggiare
l’ipotesi che in alcuni ospedali si fanno più parti cesarei utlizzando
il test di chi-quadrato. Considerate le due variabili categoriche:
Tipo.parto (Nat / Ces) e Ospedale (Osp1 / osp2 / osp3), l’obiettivo è
verificare se la frequenza dei parti cesarei dipende dall’ospedale.
L’ipotesi nulla \(H_0\) è che la
proporzione di parti cesarei è la stessa nei tre ospedali e quindi non
c’è associazione tra ospedale e tipo di parto.
L’ipotesi
alternativa \(H_1\) è che la
proporzione di parti cesarei è diversa in almeno un ospedale e quindi
esiste un’associazione tra ospedale e tipo di parto.
# Tabella di contingenza
tab <- table(df$Ospedale, df$Tipo.parto)
tab_df <- as.data.frame.matrix(tab)
# Test chi-quadrato
chisq.test(tab)
##
## Pearson's Chi-squared test
##
## data: tab
## X-squared = 1.0972, df = 2, p-value = 0.5778
# Proporzioni per ospedale
prop_tab <- round(prop.table(tab, margin = 1), 3)
# Creazione di un data frame
combined_df <- data.frame(
Cesareo_freq = tab[, "Ces"],
Naturale_freq = tab[, "Nat"],
Cesareo_prop = prop_tab[, "Ces"],
Naturale_prop = prop_tab[, "Nat"]
)
# Tabella
kable(combined_df,
caption = "Frequenze e proporzioni per tipo di parto e ospedale",
col.names = c("Ospedale",
"Cesareo (n)", "Naturale (n)",
"Cesareo (%)", "Naturale (%)")) |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Ospedale | Cesareo (n) | Naturale (n) | Cesareo (%) | Naturale (%) |
|---|---|---|---|---|
| osp1 | 242 | 574 | 0.297 | 0.703 |
| osp2 | 254 | 595 | 0.299 | 0.701 |
| osp3 | 232 | 603 | 0.278 | 0.722 |
In base ai risultati del test del chi-quadrato, in particolare al
p-value = 0.5778 > 0.05, non si rifiuta l’ipotesi nulla H_0 e quindi
non ci sono differenze significative tra gli ospedali.
Saggiamo l’ipotesi successiva, ovvero che la media del peso e della lunghezza di questo campione di neonati sono significativamente uguali a quelle della popolazione, utilizzando il Test-t per singolo campione e assumendo (da linee guida cliniche) che il peso medio della popolazione sia pari a 3300 g e che la lunghezza media della popolazione sia pari a 500 mm. L’ipotesi nulla H_0 prevede che il peso campione sia uguale al peso della popolazione e che la lunghezza campione sia uguale alla lunghezza della popolazione.
# Valori medi della popolazione
mu_peso <- 3300
mu_lunghezza <- 500
# Test t per un campione
t_peso <- t.test(df$Peso, mu = mu_peso)
t_lung <- t.test(df$Lunghezza, mu = mu_lunghezza)
t_peso
##
## One Sample t-test
##
## data: df$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
t_lung
##
## One Sample t-test
##
## data: df$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
# Creazione tabella
summary_df <- data.frame(
Variabile = c("Peso", "Lunghezza"),
Media_campione = c(mean(df$Peso), mean(df$Lunghezza)),
Media_popolazione = c(mu_peso, mu_lunghezza),
Differenza = c(mean(df$Peso) - mu_peso,
mean(df$Lunghezza) - mu_lunghezza),
T_value = c(t_peso$statistic, t_lung$statistic),
P_value = c(t_peso$p.value, t_lung$p.value),
Decisione = c(
ifelse(t_peso$p.value < 0.05, "Rifiuto H0", "Non rifiuto H0"),
ifelse(t_lung$p.value < 0.05, "Rifiuto H0", "Non rifiuto H0")
)
)
# Tabella finale
kable(summary_df,
digits = 4,
caption = "Risultati dei test t per il confronto con le medie della popolazione") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Variabile | Media_campione | Media_popolazione | Differenza | T_value | P_value | Decisione |
|---|---|---|---|---|---|---|
| Peso | 3284.081 | 3300 | -15.9192 | -1.5160 | 0.1296 | Non rifiuto H0 |
| Lunghezza | 494.692 | 500 | -5.3080 | -10.0841 | 0.0000 | Rifiuto H0 |
In base ai risultati ottenuti dal t-test è possibile concludere che per la media del peso dei neonati non si rifiuta l’ipotesi nulla, con un p-value = 0.1296 > 0.05 e quindi la media del campione non differisce significativamente da quella della popolazione. Invece per la lunghezza media del neonati l’ipotesi nulla viene rifutata, con un p-value = 0 < 0.05 e quindi la media del campione è significativamente diversa da quella della popolazione.
La terza e ultima ipotesi da saggiare dice che le misure antropometriche (Peso, Lunghezza, Cranio) sono significativamente diverse tra i due sessi. Si utilizza anche in questo caso un Test-t per campioni indipendenti. La formulazione delle ipotesi ne prevede una nulla H_0: La media della variabile è uguale nei due sessi e una alternativa H_1: La media della variabile è diversa nei due sessi.
# Test t per campioni indipendenti
t_peso <- t.test(Peso ~ Sesso, data = df)
t_lung <- t.test(Lunghezza ~ Sesso, data = df)
t_cranio <- t.test(Cranio ~ Sesso, data = df)
t_peso
##
## 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_lung
##
## 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_cranio
##
## 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
summary_sex <- data.frame(
Variabile = c("Peso", "Lunghezza", "Cranio"),
Media_M = c(mean(df$Peso[df$Sesso=="M"]),
mean(df$Lunghezza[df$Sesso=="M"]),
mean(df$Cranio[df$Sesso=="M"])),
Media_F = c(mean(df$Peso[df$Sesso=="F"]),
mean(df$Lunghezza[df$Sesso=="F"]),
mean(df$Cranio[df$Sesso=="F"])),
T_value = c(t_peso$statistic,
t_lung$statistic,
t_cranio$statistic),
P_value = c(t_peso$p.value,
t_lung$p.value,
t_cranio$p.value),
Decisione = c(
ifelse(t_peso$p.value < 0.05, "Rifiuto H0", "Non rifiuto H0"),
ifelse(t_lung$p.value < 0.05, "Rifiuto H0", "Non rifiuto H0"),
ifelse(t_cranio$p.value < 0.05, "Rifiuto H0", "Non rifiuto H0")
)
)
kable(summary_sex,
digits = 4,
caption = "Confronto delle misure antropometriche tra i due sessi (t-test)") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Variabile | Media_M | Media_F | T_value | P_value | Decisione |
|---|---|---|---|---|---|
| Peso | 3408.2154 | 3161.1322 | -12.1061 | 0 | Rifiuto H0 |
| Lunghezza | 499.6672 | 489.7643 | -9.5820 | 0 | Rifiuto H0 |
| Cranio | 342.4486 | 337.6330 | -7.4102 | 0 | Rifiuto H0 |
A supporto dei t-test effettuati tra i sessi vengono rappresentati i risultati tramite violin plot con boxplot sovrapposti, che permettono di visualizzare sia la distribuzione complessiva dei dati sia i principali indicatori di posizione.
# Boxplot confronto M vs F con violin + jitter
p1 <- ggplot(df, aes(x = Sesso, y = Peso, fill = Sesso)) +
geom_violin(alpha = 0.3, width = 0.9, trim = FALSE) +
geom_boxplot(width = 0.15, alpha = 0.7, outlier.colour = "red", outlier.size = 1.5) +
scale_color_viridis_d(option = "H")+
labs(title = "Peso per sesso", y = "Peso (g)", x = "") +
theme_minimal(base_size = 10) +
theme(plot.title = element_text(face = "bold"), legend.position = "none")
p2 <- ggplot(df, aes(x = Sesso, y = Lunghezza, fill = Sesso)) +
geom_violin(alpha = 0.3, width = 0.9, trim = FALSE) +
geom_boxplot(width = 0.15, alpha = 0.7, outlier.colour = "red", outlier.size = 1.5) +
scale_color_viridis_d(option = "H")+
labs(title = "Lunghezza per sesso", y = "Lunghezza (mm)", x = "") +
theme_minimal(base_size = 10) +
theme(plot.title = element_text(face = "bold"), legend.position = "none")
p3 <- ggplot(df, aes(x = Sesso, y = Cranio, fill = Sesso)) +
geom_violin(alpha = 0.3, width = 0.9, trim = FALSE) +
geom_boxplot(width = 0.15, alpha = 0.7, outlier.colour = "red", outlier.size = 1.5) +
scale_color_viridis_d(option = "H") +
labs(title = "Cranio per sesso", y = "Cranio (mm)", x = "") +
theme_minimal(base_size = 10) +
theme(plot.title = element_text(face = "bold"), legend.position = "none")
grid.arrange(p1, p2, p3, ncol = 3)
Si può osservare come per tutte e tre le misure antropometriche — peso, lunghezza e diametro cranico — la distribuzione dei maschi risulti sistematicamente spostata verso valori più alti rispetto a quella delle femmine, con mediane visibilmente differenti. La forma dei violin plot evidenzia inoltre una maggiore concentrazione attorno ai valori centrali per entrambi i sessi, con code più pronunciate verso i valori estremi inferiori (neonati prematuri o sottopeso).
In seguito all’analisi condotta è emerso che l’ipotesi nulla viene rifiutata in tutti e tre i casi essendo il p-value < 0.05 per tutte e tre le misure. Quindi le misure antropometriche sono significativamente diverse tra maschi e femmine. In particolare i maschi hanno un peso medio (differenza media di circa 247 g), una lunghezza media e una circonferenza cranica media significativamente maggiori rispetto alle femmine.
2.2 Creazione del modello di regressione: 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.
Il processo di modellazione, come prima cosa, richiede la
predisposizione di un modello completo ma solo con variabili che hanno
senso logico in ottica predittiva. Per questo veongono escluse a priori
le vairiabili Tipo.parto e
Ospedale:
Tipo.parto viene deciso al momento del parto (spesso in emergenza); non è una variabile nota in anticipo e quindi non ha valore predittivo ex-ante.
Ospedale è una variabile logistica che non ha alcuna influenza biologica sul peso del neonato. Inoltre il test chi-quadrato (p = 0.578) ha confermato che la distribuzione dei parti non differisce tra ospedali.
Calcolo della correlazione tra le variabili e visualizzazione della matrice al fine di identificare la presenza di eventuali forti correlazioni. Si considerano a tal fine le variabili numeriche (Anni.madre, N.gravidanze, Gestazione, Peso, Lunghezza, Cranio).
# Selezione variabili numeriche
vars_num <- df[, c("Anni.madre", "N.gravidanze", "Gestazione",
"Peso", "Lunghezza", "Cranio")]
# Matrice di correlazione
cor_mat <- round(cor(vars_num), 3)
# Tabella kable
kable(cor_mat,
caption = "Matrice di correlazione tra le variabili numeriche",
col.names = colnames(cor_mat)) |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Anni.madre | N.gravidanze | Gestazione | Peso | Lunghezza | Cranio | |
|---|---|---|---|---|---|---|
| Anni.madre | 1.000 | 0.383 | -0.135 | -0.024 | -0.065 | 0.016 |
| N.gravidanze | 0.383 | 1.000 | -0.101 | 0.002 | -0.060 | 0.039 |
| Gestazione | -0.135 | -0.101 | 1.000 | 0.592 | 0.619 | 0.461 |
| Peso | -0.024 | 0.002 | 0.592 | 1.000 | 0.796 | 0.705 |
| Lunghezza | -0.065 | -0.060 | 0.619 | 0.796 | 1.000 | 0.603 |
| Cranio | 0.016 | 0.039 | 0.461 | 0.705 | 0.603 | 1.000 |
Per analizzare le relazioni lineari tra le variabili numeriche si calcola la matrice di correlazione di Pearson, rappresentata graficamente tramite una heatmap con i coefficienti sovrapposti.
corrplot(cor_mat, method = "color", type = "upper",
addCoef.col = "black", tl.col = "black", number.cex = 0.8)
Dalla matrice emergono correlazioni forti e positive tra le misure antropometriche: Peso–Lunghezza (0.80), Peso–Cranio (0.70) e Lunghezza–Cranio (0.60). La Gestazione mostra correlazioni moderate con Peso (0.59), Lunghezza (0.62) e Cranio (0.46), confermando che una maggiore durata della gravidanza si associa a una crescita fetale più avanzata. Le variabili materne (Anni.madre e N.gravidanze) presentano invece correlazioni molto deboli o nulle con le misure neonatali, suggerendo un impatto trascurabile. L’unica correlazione apprezzabile tra variabili materne è Anni.madre–N.gravidanze (0.38), fisiologicamente attesa. Nel complesso, non emergono valori superiori a 0.80 tali da indicare problemi di multicollinearità per il modello di regressione.
Per visualizzare nel dettaglio la forma delle relazioni bivariate tra le variabili numeriche si produce una scatterplot matrix, con scatter plot e smoother loess nel triangolo superiore e coefficienti di correlazione nel triangolo inferiore.
panel.cor <- function(x, y, digits = 2, prefix = "", cex.cor, ...) {
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0, 1, 0, 1))
r <- 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, col = "red")
}
vars_num <- df[, c("Anni.madre", "N.gravidanze", "Gestazione",
"Peso", "Lunghezza", "Cranio")]
pairs(vars_num,
upper.panel = panel.smooth,
lower.panel = panel.cor,
main = "Scatterplot matrix")
La scatterplot matrix conferma visivamente quanto evidenziato dalla matrice di correlazione. Le coppie Peso–Lunghezza, Peso–Cranio e Lunghezza–Cranio mostrano nuvole di punti con trend ascendente ben definito e smoother lineari, coerenti con le correlazioni elevate. Per Gestazione–Peso e Gestazione–Lunghezza si osserva una dispersione crescente con possibile lieve curvatura, che sarà indagata nella fase di modellazione tramite effetti quadratici. Le variabili materne (Anni.madre, N.gravidanze) non mostrano trend apprezzabili con le misure neonatali, confermando il loro scarso potere predittivo sul peso alla nascita.
Queste osservazioni confermano la coerenza fisiologica del dataset. Nel complesso, non emergono problemi di multicollinearità tali da compromettere la costruzione del modello di regressione multipla.
Al fine di verificare la normalità della variabile Peso si effettua uno Shapiro-Wilk test.
shapiro.test(df$Peso)
##
## Shapiro-Wilk normality test
##
## data: df$Peso
## W = 0.97066, p-value < 2.2e-16
Finora abbiamo riscontrato una distribuzione del peso asimmetrica, la presenza di outlier e una coda sinistra più lunga (ovvero con valori molto bassi). E’ quindi corretto aspettarsi come esito del test un p-value < 0.05 e il risultato lo conferma. Si rifiuta quindi l’ipotesi nulla H_0 per cui il peso sarebbe normalmente distribuito: il peso non è normalmente distribuito. Questo, tuttavia, non è un problema per la regressione lineare, perché la regressione richiede la normalità dei residui, non della variabile dipendente e con n = 2500, il teorema del limite centrale rende il modello robusto.
Creazione del modello di regressione lineare multipla con le sole variabili che hanno senso predittivo: Gestazione, Lunghezza, Cranio, Anni.madre, N.gravidanze, Fumatrici, Sesso.
mod_full <- lm(Peso ~ Gestazione + Lunghezza + Cranio +
Anni.madre + N.gravidanze + Fumatrici + Sesso,
data = df)
summary(mod_full)
##
## Call:
## lm(formula = Peso ~ Gestazione + Lunghezza + Cranio + Anni.madre +
## N.gravidanze + Fumatrici + Sesso, data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1160.62 -181.17 -15.91 163.47 2631.35
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6711.5440 141.2543 -47.514 < 2e-16 ***
## Gestazione 32.8936 3.8259 8.598 < 2e-16 ***
## Lunghezza 10.2348 0.3009 34.010 < 2e-16 ***
## Cranio 10.5192 0.4268 24.644 < 2e-16 ***
## Anni.madre 0.8772 1.1487 0.764 0.4452
## N.gravidanze 11.4029 4.6745 2.439 0.0148 *
## Fumatrici -30.2865 27.5981 -1.097 0.2726
## SessoM 78.0898 11.2042 6.970 4.05e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.6 on 2492 degrees of freedom
## Multiple R-squared: 0.7272, Adjusted R-squared: 0.7264
## F-statistic: 949 on 7 and 2492 DF, p-value: < 2.2e-16
tab_mod <- tidy(mod_full)
kable(tab_mod,
digits = 4,
caption = "Coefficienti del modello completo (senza Tipo.parto e Ospedale)") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | -6711.5440 | 141.2543 | -47.5139 | 0.0000 |
| Gestazione | 32.8936 | 3.8259 | 8.5976 | 0.0000 |
| Lunghezza | 10.2348 | 0.3009 | 34.0103 | 0.0000 |
| Cranio | 10.5192 | 0.4268 | 24.6440 | 0.0000 |
| Anni.madre | 0.8772 | 1.1487 | 0.7636 | 0.4452 |
| N.gravidanze | 11.4029 | 4.6745 | 2.4394 | 0.0148 |
| Fumatrici | -30.2865 | 27.5981 | -1.0974 | 0.2726 |
| SessoM | 78.0898 | 11.2042 | 6.9697 | 0.0000 |
Una volta creato il modello completo calcoliamo il VIF per la verifica della multicollinearità:
# Calcolo del VIF
vif_values <- vif(mod_full)
# Conversione in data frame
vif_df <- as.data.frame(vif_values)
# Tabella kable
kable(vif_df,
digits = 4,
caption = "Valori del VIF per la verifica della multicollinearità") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| vif_values | |
|---|---|
| Gestazione | 1.6937 |
| Lunghezza | 2.0787 |
| Cranio | 1.6289 |
| Anni.madre | 1.1892 |
| N.gravidanze | 1.1874 |
| Fumatrici | 1.0067 |
| Sesso | 1.0404 |
Da questi risultati emerge che tutti i VIF sono ben al di sotto della soglia critica di 5-10: non ci sono problemi di multicollinearità.
2.2 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.
Selezione del modello tramite AIC (stepwise), usando il metodo stepAIC, che prova aggiunte e rimozioni di variabili per minimizzare l’AIC.
mod_step_AIC <- stepAIC(mod_full,
direction = "both",
trace = FALSE)
summary(mod_step_AIC)
##
## Call:
## lm(formula = Peso ~ Gestazione + Lunghezza + Cranio + N.gravidanze +
## Sesso, data = df)
##
## 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 ***
## 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 ***
## N.gravidanze 12.4750 4.3396 2.875 0.00408 **
## 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
Secondo AIC, queste variabili aggiungono un piccolo miglioramento predittivo, sufficiente a giustificare la loro presenza.
Selezione del modello tramite BIC. Questo modello penalizza di più i modelli complessi, quindi tende a selezionare modelli più parsimoniosi.
mod_step_BIC <- stepAIC(mod_full,
direction = "both",
k = log(nrow(df)),
trace = FALSE)
summary(mod_step_BIC)
##
## Call:
## lm(formula = Peso ~ Gestazione + Lunghezza + Cranio + N.gravidanze +
## Sesso, data = df)
##
## 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 ***
## 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 ***
## N.gravidanze 12.4750 4.3396 2.875 0.00408 **
## 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
Secondo BIC, queste variabili non migliorano abbastanza il modello da giustificare la loro inclusione.
#Variabili selezionate da AIC e BIC
vars_AIC <- names(coef(mod_step_AIC))[-1] # escludi intercetta
vars_BIC <- names(coef(mod_step_BIC))[-1]
sel_df <- data.frame(
Variabile = unique(c(vars_AIC, vars_BIC))
) %>%
mutate(
AIC = ifelse(Variabile %in% vars_AIC, "✔", ""),
BIC = ifelse(Variabile %in% vars_BIC, "✔", "")
)
kable(sel_df,
caption = "Variabili selezionate dai criteri AIC e BIC",
col.names = c("Variabile", "Selezionata da AIC", "Selezionata da BIC")) |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Variabile | Selezionata da AIC | Selezionata da BIC |
|---|---|---|
| Gestazione | ✔ | ✔ |
| Lunghezza | ✔ | ✔ |
| Cranio | ✔ | ✔ |
| N.gravidanze | ✔ | ✔ |
| SessoM | ✔ | ✔ |
Entrambi i modelli confermano che le misure antropometriche (Lunghezza, Cranio) e la durata della gestazione sono i principali predittori del peso neonatale, con un contributo significativo anche del sesso del neonato.
2.3 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.
Prima di saggiare effetti quadratici o di interazione, si esamina
graficamente la relazione tra ciascun predittore e il peso, con smoother
loess, per individuare eventuali non-linearità. Si saggiano solo gli
effetti che si ipotizzano dai grafici, non tutti quanti.
vars_plot <- c("Gestazione", "Lunghezza", "Cranio", "N.gravidanze", "Anni.madre")
par(mfrow = c(2, 3))
for (v in vars_plot) {
plot(df[[v]], df$Peso,
xlab = v, ylab = "Peso (g)",
main = paste("Peso vs", v),
pch = 16, col = rgb(0, 0, 1, 0.2), cex = 0.6)
lines(lowess(df[[v]], df$Peso), col = "red", lwd = 2)
}
par(mfrow = c(1, 1))
Osservando i grafici si nota una curvatura nel rapporto tra
Gestazione e Peso e tra Lunghezza e Peso. Per Cranio, N.gravidanze e
Anni.madre la relazione appare sostanzialmente lineare (o assente). Si
saggia pertanto l’effetto quadratico solo per Gestazione e Lunghezza.
Secondo il principio di marginalità, quando si include un termine
quadratico I(x^2), si mantiene sempre anche il
corrispondente effetto principale x. Lo stesso vale per le
interazioni: se si include A:B, si mantengono sia
A sia B.
# Modello con effetti quadratici (solo dove suggerito dai grafici)
# Si parte dalle variabili del modello AIC/BIC e si aggiungono i termini quadratici
mod_quad <- lm(Peso ~ Gestazione + I(Gestazione^2) +
Lunghezza + I(Lunghezza^2) +
Cranio + N.gravidanze + Fumatrici + Sesso,
data = df)
summary(mod_quad)
##
## Call:
## lm(formula = Peso ~ Gestazione + I(Gestazione^2) + Lunghezza +
## I(Lunghezza^2) + Cranio + N.gravidanze + Fumatrici + Sesso,
## data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1191.75 -179.83 -13.33 165.58 1402.42
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.379e+03 9.056e+02 -2.627 0.008663 **
## Gestazione 3.370e+02 6.270e+01 5.374 8.40e-08 ***
## I(Gestazione^2) -3.877e+00 8.245e-01 -4.703 2.71e-06 ***
## Lunghezza -3.210e+01 4.038e+00 -7.950 2.80e-15 ***
## I(Lunghezza^2) 4.364e-02 4.141e-03 10.539 < 2e-16 ***
## Cranio 1.044e+01 4.192e-01 24.910 < 2e-16 ***
## N.gravidanze 1.468e+01 4.252e+00 3.451 0.000568 ***
## Fumatrici -2.456e+01 2.699e+01 -0.910 0.362981
## SessoM 7.277e+01 1.100e+01 6.616 4.49e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 268.5 on 2491 degrees of freedom
## Multiple R-squared: 0.7393, Adjusted R-squared: 0.7385
## F-statistic: 883.1 on 8 and 2491 DF, p-value: < 2.2e-16
Si verificano anche possibili interazioni suggerite dal contesto
clinico:
Gestazione e Fumatrici: il fumo potrebbe avere un effetto diverso a diverse età gestazionali.
Gestazione e Sesso: la crescita potrebbe differire tra i sessi nelle ultime settimane.
mod_quad_int <- lm(Peso ~ Gestazione + I(Gestazione^2) +
Lunghezza + I(Lunghezza^2) +
Cranio + N.gravidanze + Fumatrici + Sesso +
Gestazione:Fumatrici + Gestazione:Sesso,
data = df)
summary(mod_quad_int)
##
## Call:
## lm(formula = Peso ~ Gestazione + I(Gestazione^2) + Lunghezza +
## I(Lunghezza^2) + Cranio + N.gravidanze + Fumatrici + Sesso +
## Gestazione:Fumatrici + Gestazione:Sesso, data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1188.10 -181.69 -13.02 164.20 1403.40
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.398e+03 9.130e+02 -2.627 0.008677 **
## Gestazione 3.387e+02 6.362e+01 5.324 1.10e-07 ***
## I(Gestazione^2) -3.922e+00 8.431e-01 -4.653 3.45e-06 ***
## Lunghezza -3.200e+01 4.041e+00 -7.919 3.57e-15 ***
## I(Lunghezza^2) 4.354e-02 4.145e-03 10.505 < 2e-16 ***
## Cranio 1.044e+01 4.193e-01 24.887 < 2e-16 ***
## N.gravidanze 1.468e+01 4.253e+00 3.451 0.000568 ***
## Fumatrici 6.784e+02 7.449e+02 0.911 0.362481
## SessoM -1.176e+02 2.353e+02 -0.500 0.617286
## Gestazione:Fumatrici -1.792e+01 1.896e+01 -0.945 0.344549
## Gestazione:SessoM 4.894e+00 6.029e+00 0.812 0.417031
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 268.5 on 2489 degrees of freedom
## Multiple R-squared: 0.7395, Adjusted R-squared: 0.7384
## F-statistic: 706.5 on 10 and 2489 DF, p-value: < 2.2e-16
Di seguito si conforntano i modelli ottenuti:
comp_df <- data.frame(
Modello = c("Completo lineare", "AIC", "BIC", "Quadratico", "Quadratico + Interazioni"),
AIC = c(AIC(mod_full), AIC(mod_step_AIC), AIC(mod_step_BIC), AIC(mod_quad), AIC(mod_quad_int)),
BIC = c(BIC(mod_full), BIC(mod_step_AIC), BIC(mod_step_BIC), BIC(mod_quad), BIC(mod_quad_int)),
R2 = c(glance(mod_full)$r.squared, glance(mod_step_AIC)$r.squared,
glance(mod_step_BIC)$r.squared, glance(mod_quad)$r.squared,
glance(mod_quad_int)$r.squared),
R2_adj = c(glance(mod_full)$adj.r.squared, glance(mod_step_AIC)$adj.r.squared,
glance(mod_step_BIC)$adj.r.squared, glance(mod_quad)$adj.r.squared,
glance(mod_quad_int)$adj.r.squared),
RMSE = c(glance(mod_full)$sigma, glance(mod_step_AIC)$sigma,
glance(mod_step_BIC)$sigma, glance(mod_quad)$sigma,
glance(mod_quad_int)$sigma)
)
kable(comp_df,
digits = 3,
caption = "Confronto tra modelli (AIC, BIC, R², R² adj, RMSE)") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Modello | AIC | BIC | R2 | R2_adj | RMSE |
|---|---|---|---|---|---|
| Completo lineare | 35181.52 | 35233.94 | 0.727 | 0.726 | 274.615 |
| AIC | 35179.33 | 35220.10 | 0.727 | 0.726 | 274.604 |
| BIC | 35179.33 | 35220.10 | 0.727 | 0.726 | 274.604 |
| Quadratico | 35069.90 | 35128.14 | 0.739 | 0.738 | 268.498 |
| Quadratico + Interazioni | 35072.32 | 35142.21 | 0.739 | 0.738 | 268.521 |
Da questa tabella emerge il modello migliore sulla base del trade-off tra AIC, BIC, R² aggiustato e RMSE. Se le interazioni non risultano significative, si seleziona il modello quadratico senza interazioni.
# Selezione del modello finale
# (aggiornare se mod_quad_int risulta significativamente migliore)
mod_finale <- mod_quad
Lo step successivo del processo di modellazione prevede, una volta individuato il modello finale ovvero quello più parsimonioso, un commento rigoroso di tutte le sue stime.
tab_finale <- tidy(mod_finale)
kable(tab_finale,
digits = 4,
caption = "Coefficienti del modello finale") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | -2379.0609 | 905.5721 | -2.6271 | 0.0087 |
| Gestazione | 336.9969 | 62.7042 | 5.3744 | 0.0000 |
| I(Gestazione^2) | -3.8773 | 0.8245 | -4.7026 | 0.0000 |
| Lunghezza | -32.0986 | 4.0375 | -7.9501 | 0.0000 |
| I(Lunghezza^2) | 0.0436 | 0.0041 | 10.5394 | 0.0000 |
| Cranio | 10.4425 | 0.4192 | 24.9105 | 0.0000 |
| N.gravidanze | 14.6751 | 4.2525 | 3.4509 | 0.0006 |
| Fumatrici | -24.5584 | 26.9912 | -0.9099 | 0.3630 |
| SessoM | 72.7731 | 10.9991 | 6.6163 | 0.0000 |
cat("R² =", round(glance(mod_finale)$r.squared, 4), "\n")
## R² = 0.7393
cat("R² adj =", round(glance(mod_finale)$adj.r.squared, 4), "\n")
## R² adj = 0.7385
cat("RMSE =", round(glance(mod_finale)$sigma, 2), "grammi\n")
## RMSE = 268.5 grammi
Vengono quindi interpretati i coefficienti con particolare attenzione
ai punti seguenti:
Gestazione + I(Gestazione²): Effetto non lineare — il peso aumenta con le settimane di gestazione, ma con rendimenti decrescenti (o crescenti) a seconda del segno del termine quadratico. L’effetto principale è mantenuto in accordo con il principio di marginalità.
Lunghezza + I(Lunghezza²): Effetto non lineare — la lunghezza ha un impatto positivo sul peso, la curvatura cattura il fatto che a lunghezze molto elevate l’incremento marginale di peso si modifica. Anche qui l’effetto principale è mantenuto.
Cranio: Effetto lineare positivo — un cranio più grande è associato a un peso maggiore.
N.gravidanze: Effetto della parità — madri con più gravidanze tendono ad avere neonati leggermente più pesanti.
Fumatrici: Effetto negativo — il fumo materno riduce il peso neonatale.
Sesso (M): I maschi tendono a pesare di più rispetto alle femmine.
Il modello spiega circa il 74% della variabilità del peso neonatale e l’errore medio di previsione è di circa 268 grammi. I termini quadratici per gestazione e lunghezza sono risultati altamente significativi, indicando relazioni non strettamente lineari tra queste variabili e il peso alla nascita.
A questo punto dell’analisi si effettua una diagnostica dei residui e, poichè si è ottenuto il modello finale, si valuta la sua capacità predittiva e il rispetto delle assunzioni.
Si analizzano i residui per verificare che: la varianza sia costante (omoschedasticità), i residui siano distribuiti normalmente, non ci siano pattern sistematici, non ci siano osservazioni influenti che distorcono il modello.
par(mfrow = c(2, 2))
plot(mod_finale)
par(mfrow = c(1, 1))
I grafici ottenuti si interpretano come segue:
Residuals vs Fitted: Verifica linearità e omoschedasticità. La dispersione dovrebbe essere casuale e uniforme attorno allo 0.
Normal Q-Q: Verifica normalità dei residui. I punti dovrebbero seguire la retta diagonale.
Scale-Location: Verifica omoschedasticità. La linea rossa dovrebbe essere approssimativamente orizzontale.
Residuals vs Leverage: Identifica osservazioni influenti (Cook’s distance).
residui <- residuals(mod_finale)
par(mfrow = c(1, 2))
hist(residui, breaks = 40, col = "#1f78b4",
main = "Distribuzione dei residui", xlab = "Residui")
abline(v = 0, col = "red", lwd = 2)
qqnorm(residui, main = "QQ-Plot dei residui")
qqline(residui, col = "red", lwd = 2)
par(mfrow = c(1, 1))
Si eseguono i test formali di Breusch-Pagan, Durbin-Watson e Shapiro-Wilk sui residui e si riassumono i risultati ottenuti in una tabella.
# Test di Breusch-Pagan per l'omoschedasticità
bp_test <- bptest(mod_finale)
bp_test
##
## studentized Breusch-Pagan test
##
## data: mod_finale
## BP = 98.19, df = 8, p-value < 2.2e-16
# Test di Durbin-Watson per l'autocorrelazione
dw_test <- dwtest(mod_finale)
dw_test
##
## Durbin-Watson test
##
## data: mod_finale
## DW = 1.9499, p-value = 0.1052
## alternative hypothesis: true autocorrelation is greater than 0
# Test di Shapiro-Wilk sui residui
sw_test <- shapiro.test(residui)
sw_test
##
## Shapiro-Wilk normality test
##
## data: residui
## W = 0.98911, p-value = 7.151e-13
diag_summary <- data.frame(
Test = c("Breusch-Pagan (omoschedasticità)",
"Durbin-Watson (autocorrelazione)",
"Shapiro-Wilk (normalità residui)"),
Statistica = c(bp_test$statistic, dw_test$statistic, sw_test$statistic),
P_value = c(bp_test$p.value, dw_test$p.value, sw_test$p.value),
Decisione = c(
ifelse(bp_test$p.value < 0.05, "Eteroschedasticità", "Omoschedasticità OK"),
ifelse(dw_test$p.value < 0.05, "Autocorrelazione presente", "No autocorrelazione"),
ifelse(sw_test$p.value < 0.05, "Non normalità", "Normalità OK")
)
)
kable(diag_summary,
digits = 4,
caption = "Riepilogo test diagnostici sui residui") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Test | Statistica | P_value | Decisione | |
|---|---|---|---|---|
| BP | Breusch-Pagan (omoschedasticità) | 98.1900 | 0.0000 | Eteroschedasticità |
| DW | Durbin-Watson (autocorrelazione) | 1.9499 | 0.1052 | No autocorrelazione |
| W | Shapiro-Wilk (normalità residui) | 0.9891 | 0.0000 | Non normalità |
Essendo i test formali molto sensibili, possono rifiutare H_0 anche per deviazioni minime dalla distribuzione teorica. È importante quindi valutare i risultati congiuntamente ai grafici diagnostici. Con n = 2500 il teorema del limite centrale garantisce comunque la robustezza dell’inferenza sui coefficienti.
Si passa allo studio degli outlier e alle osservazioni influenti:
Cook’s Distance:
cooks_d <- cooks.distance(mod_finale)
soglia_cook <- 4 / nrow(df)
plot(cooks_d, type = "h", main = "Cook's Distance",
ylab = "Cook's Distance", xlab = "Osservazione",
col = ifelse(cooks_d > soglia_cook, "red", "grey"))
abline(h = soglia_cook, col = "red", lty = 2)
legend("topright", legend = paste("Soglia =", round(soglia_cook, 5)),
col = "red", lty = 2)
influenti_cook <- which(cooks_d > soglia_cook)
cat("Numero di osservazioni con Cook's D > soglia:", length(influenti_cook), "\n")
## Numero di osservazioni con Cook's D > soglia: 131
DFFITS:
dffits_val <- dffits(mod_finale)
p <- length(coef(mod_finale))
n <- nrow(df)
soglia_dffits <- 2 * sqrt(p / n)
cat("Soglia DFFITS:", round(soglia_dffits, 4), "\n")
## Soglia DFFITS: 0.12
influenti_dffits <- which(abs(dffits_val) > soglia_dffits)
cat("Osservazioni con |DFFITS| > soglia:", length(influenti_dffits), "\n")
## Osservazioni con |DFFITS| > soglia: 131
Leverage (hat values):
hat_val <- hatvalues(mod_finale)
soglia_hat <- 2 * p / n
plot(hat_val, type = "h", main = "Leverage (hat values)",
ylab = "Leverage", xlab = "Osservazione",
col = ifelse(hat_val > soglia_hat, "red", "grey"))
abline(h = soglia_hat, col = "red", lty = 2)
influenti_hat <- which(hat_val > soglia_hat)
cat("Osservazioni con leverage > soglia:", length(influenti_hat), "\n")
## Osservazioni con leverage > soglia: 213
Tabella riepilogativa osservazioni influenti:
# Osservazioni flaggate da almeno 2 criteri su 3
flag_cook <- cooks_d > soglia_cook
flag_dffits <- abs(dffits_val) > soglia_dffits
flag_hat <- hat_val > soglia_hat
flag_sum <- flag_cook + flag_dffits + flag_hat
idx_multi <- which(flag_sum >= 2)
if (length(idx_multi) > 0) {
infl_df <- data.frame(
Osservazione = idx_multi,
Cook_D = round(cooks_d[idx_multi], 5),
DFFITS = round(dffits_val[idx_multi], 4),
Leverage = round(hat_val[idx_multi], 4),
Peso = df$Peso[idx_multi],
Gestazione = df$Gestazione[idx_multi],
Lunghezza = df$Lunghezza[idx_multi]
)
kable(infl_df,
caption = "Osservazioni influenti (flaggate da almeno 2 criteri su 3)") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
} else {
cat("Nessuna osservazione flaggata da almeno 2 criteri su 3.\n")
}
| Osservazione | Cook_D | DFFITS | Leverage | Peso | Gestazione | Lunghezza | |
|---|---|---|---|---|---|---|---|
| 99 | 99 | 0.00304 | -0.1655 | 0.0106 | 3350 | 40 | 520 |
| 101 | 101 | 0.00324 | -0.1709 | 0.0173 | 1370 | 34 | 390 |
| 119 | 119 | 0.00952 | -0.2935 | 0.0062 | 3410 | 40 | 550 |
| 130 | 130 | 0.00224 | 0.1422 | 0.0018 | 4240 | 39 | 485 |
| 134 | 134 | 0.00294 | 0.1627 | 0.0078 | 3950 | 37 | 500 |
| 140 | 140 | 0.00460 | 0.2035 | 0.0125 | 4420 | 41 | 530 |
| 146 | 146 | 0.00174 | 0.1252 | 0.0023 | 3820 | 40 | 500 |
| 155 | 155 | 0.02847 | 0.5081 | 0.0128 | 3610 | 36 | 410 |
| 161 | 161 | 0.00421 | -0.1946 | 0.0233 | 3760 | 42 | 540 |
| 220 | 220 | 0.00255 | 0.1516 | 0.0077 | 3520 | 40 | 445 |
| 295 | 295 | 0.00323 | -0.1708 | 0.0042 | 1850 | 40 | 460 |
| 304 | 304 | 0.00163 | -0.1212 | 0.0088 | 2060 | 37 | 420 |
| 310 | 310 | 0.07921 | -0.8453 | 0.0926 | 1560 | 28 | 420 |
| 312 | 312 | 0.00325 | -0.1709 | 0.0386 | 1280 | 32 | 360 |
| 322 | 322 | 0.00227 | -0.1431 | 0.0059 | 1750 | 37 | 430 |
| 329 | 329 | 0.00246 | 0.1489 | 0.0040 | 4560 | 40 | 540 |
| 335 | 335 | 0.00201 | 0.1344 | 0.0112 | 3150 | 38 | 465 |
| 375 | 375 | 0.00237 | 0.1461 | 0.0037 | 4270 | 38 | 510 |
| 378 | 378 | 0.00706 | 0.2522 | 0.0586 | 1285 | 28 | 400 |
| 383 | 383 | 0.00234 | -0.1452 | 0.0077 | 3600 | 39 | 550 |
| 390 | 390 | 0.00309 | 0.1671 | 0.0029 | 3700 | 40 | 470 |
| 391 | 391 | 0.00252 | -0.1507 | 0.0112 | 3400 | 40 | 525 |
| 413 | 413 | 0.00228 | -0.1433 | 0.0106 | 2590 | 40 | 480 |
| 424 | 424 | 0.00200 | 0.1342 | 0.0113 | 3820 | 41 | 500 |
| 442 | 442 | 0.00260 | -0.1530 | 0.0165 | 2430 | 38 | 460 |
| 471 | 471 | 0.00599 | -0.2323 | 0.0101 | 3680 | 40 | 560 |
| 472 | 472 | 0.00169 | 0.1235 | 0.0025 | 3990 | 41 | 495 |
| 473 | 473 | 0.00271 | -0.1563 | 0.0118 | 3090 | 41 | 495 |
| 478 | 478 | 0.00251 | 0.1504 | 0.0058 | 4310 | 42 | 505 |
| 492 | 492 | 0.00346 | -0.1764 | 0.0205 | 1410 | 33 | 380 |
| 516 | 516 | 0.00320 | 0.1698 | 0.0135 | 3520 | 38 | 470 |
| 567 | 567 | 0.00382 | 0.1854 | 0.0106 | 3280 | 38 | 475 |
| 582 | 582 | 0.00440 | -0.1991 | 0.0123 | 2220 | 35 | 470 |
| 615 | 615 | 0.00189 | -0.1306 | 0.0047 | 2100 | 35 | 440 |
| 616 | 616 | 0.00366 | -0.1817 | 0.0047 | 3540 | 42 | 540 |
| 656 | 656 | 0.00225 | -0.1423 | 0.0062 | 2320 | 41 | 450 |
| 657 | 657 | 0.00217 | -0.1399 | 0.0056 | 2970 | 41 | 520 |
| 684 | 684 | 0.00427 | -0.1962 | 0.0089 | 3000 | 39 | 475 |
| 729 | 729 | 0.00264 | -0.1543 | 0.0096 | 3900 | 39 | 555 |
| 750 | 750 | 0.00182 | -0.1279 | 0.0129 | 1450 | 35 | 405 |
| 791 | 791 | 0.00272 | 0.1569 | 0.0019 | 4440 | 41 | 510 |
| 828 | 828 | 0.00292 | 0.1621 | 0.0073 | 4200 | 40 | 510 |
| 890 | 890 | 0.00173 | 0.1248 | 0.0023 | 3660 | 39 | 490 |
| 906 | 906 | 0.00161 | 0.1203 | 0.0046 | 3790 | 42 | 500 |
| 991 | 991 | 0.00308 | -0.1666 | 0.0074 | 4050 | 40 | 550 |
| 1008 | 1008 | 0.00165 | -0.1220 | 0.0057 | 2400 | 36 | 470 |
| 1036 | 1036 | 0.00196 | 0.1329 | 0.0017 | 4330 | 40 | 500 |
| 1109 | 1109 | 0.00180 | -0.1275 | 0.0044 | 3740 | 41 | 545 |
| 1181 | 1181 | 0.00235 | 0.1456 | 0.0061 | 4070 | 36 | 500 |
| 1194 | 1194 | 0.00192 | -0.1316 | 0.0037 | 2160 | 39 | 450 |
| 1215 | 1215 | 0.00330 | -0.1726 | 0.0057 | 3630 | 40 | 550 |
| 1230 | 1230 | 0.00378 | 0.1846 | 0.0042 | 4010 | 41 | 470 |
| 1253 | 1253 | 0.00171 | -0.1239 | 0.0042 | 3450 | 39 | 523 |
| 1268 | 1268 | 0.00363 | 0.1811 | 0.0026 | 3790 | 40 | 460 |
| 1273 | 1273 | 0.00230 | -0.1440 | 0.0121 | 2040 | 33 | 480 |
| 1293 | 1293 | 0.00958 | 0.2944 | 0.0063 | 4600 | 38 | 485 |
| 1306 | 1306 | 0.00593 | 0.2322 | 0.0022 | 4900 | 41 | 510 |
| 1323 | 1323 | 0.00505 | -0.2134 | 0.0085 | 3660 | 39 | 555 |
| 1341 | 1341 | 0.00249 | 0.1498 | 0.0039 | 3780 | 36 | 480 |
| 1357 | 1357 | 0.00286 | 0.1606 | 0.0130 | 2340 | 32 | 445 |
| 1385 | 1385 | 0.00465 | 0.2046 | 0.0405 | 1620 | 29 | 410 |
| 1395 | 1395 | 0.00440 | 0.1994 | 0.0054 | 3790 | 39 | 505 |
| 1398 | 1398 | 0.00192 | 0.1314 | 0.0109 | 3180 | 40 | 470 |
| 1399 | 1399 | 0.00608 | -0.2348 | 0.0028 | 2560 | 38 | 525 |
| 1402 | 1402 | 0.00186 | -0.1293 | 0.0049 | 2660 | 39 | 460 |
| 1426 | 1426 | 0.00428 | -0.1964 | 0.0133 | 2250 | 39 | 460 |
| 1428 | 1428 | 0.01875 | -0.4112 | 0.0295 | 1280 | 36 | 385 |
| 1429 | 1429 | 0.04106 | -0.6088 | 0.0446 | 1280 | 29 | 390 |
| 1433 | 1433 | 0.00373 | 0.1834 | 0.0047 | 4810 | 41 | 530 |
| 1450 | 1450 | 0.00553 | 0.2232 | 0.0155 | 3730 | 41 | 480 |
| 1472 | 1472 | 0.00190 | 0.1307 | 0.0044 | 3720 | 37 | 480 |
| 1505 | 1505 | 0.00330 | -0.1724 | 0.0135 | 2860 | 39 | 490 |
| 1525 | 1525 | 0.00319 | -0.1696 | 0.0105 | 2920 | 39 | 490 |
| 1541 | 1541 | 0.00360 | 0.1802 | 0.0035 | 4540 | 38 | 530 |
| 1551 | 1551 | 1.32448 | 3.4773 | 0.2474 | 4370 | 38 | 315 |
| 1553 | 1553 | 0.01181 | 0.3267 | 0.0088 | 4520 | 35 | 520 |
| 1556 | 1556 | 0.00200 | -0.1344 | 0.0062 | 2420 | 41 | 490 |
| 1583 | 1583 | 0.00287 | 0.1607 | 0.0127 | 4220 | 40 | 510 |
| 1588 | 1588 | 0.00260 | -0.1532 | 0.0049 | 3100 | 42 | 510 |
| 1593 | 1593 | 0.00537 | -0.2201 | 0.0075 | 1500 | 35 | 420 |
| 1619 | 1619 | 0.01674 | -0.3882 | 0.0638 | 990 | 31 | 340 |
| 1626 | 1626 | 0.00198 | -0.1335 | 0.0112 | 3030 | 38 | 500 |
| 1635 | 1635 | 0.00424 | 0.1956 | 0.0041 | 3430 | 39 | 445 |
| 1639 | 1639 | 0.00258 | 0.1525 | 0.0078 | 4760 | 40 | 550 |
| 1644 | 1644 | 0.00175 | -0.1257 | 0.0052 | 3850 | 41 | 550 |
| 1691 | 1691 | 0.00241 | -0.1472 | 0.0113 | 3220 | 41 | 500 |
| 1694 | 1694 | 0.00450 | 0.2018 | 0.0022 | 3850 | 36 | 460 |
| 1701 | 1701 | 0.00317 | -0.1690 | 0.0188 | 1430 | 32 | 380 |
| 1712 | 1712 | 0.00545 | 0.2217 | 0.0073 | 3800 | 39 | 520 |
| 1718 | 1718 | 0.00391 | -0.1877 | 0.0086 | 2660 | 42 | 500 |
| 1743 | 1743 | 0.00202 | -0.1349 | 0.0050 | 1950 | 38 | 445 |
| 1780 | 1780 | 0.04858 | 0.6615 | 0.1300 | 900 | 25 | 325 |
| 1838 | 1838 | 0.00206 | 0.1363 | 0.0023 | 4580 | 40 | 515 |
| 1856 | 1856 | 0.00325 | 0.1712 | 0.0047 | 3110 | 36 | 465 |
| 1868 | 1868 | 0.00480 | -0.2080 | 0.0055 | 3470 | 40 | 525 |
| 1893 | 1893 | 0.00347 | 0.1768 | 0.0060 | 3030 | 34 | 470 |
| 1915 | 1915 | 0.00163 | -0.1212 | 0.0031 | 2480 | 38 | 500 |
| 1920 | 1920 | 0.02230 | 0.4489 | 0.0178 | 4930 | 39 | 550 |
| 1937 | 1937 | 0.00161 | -0.1206 | 0.0023 | 2940 | 41 | 500 |
| 1962 | 1962 | 0.00459 | 0.2036 | 0.0063 | 4370 | 38 | 530 |
| 1963 | 1963 | 0.00293 | 0.1625 | 0.0054 | 4700 | 42 | 540 |
| 1971 | 1971 | 0.00262 | 0.1535 | 0.0124 | 3530 | 38 | 495 |
| 2003 | 2003 | 0.00265 | -0.1544 | 0.0112 | 2740 | 40 | 500 |
| 2016 | 2016 | 0.00183 | -0.1282 | 0.0162 | 3870 | 42 | 530 |
| 2023 | 2023 | 0.00189 | 0.1307 | 0.0012 | 4650 | 39 | 510 |
| 2040 | 2040 | 0.00676 | 0.2467 | 0.0189 | 3240 | 38 | 410 |
| 2046 | 2046 | 0.00180 | 0.1273 | 0.0150 | 4300 | 40 | 530 |
| 2049 | 2049 | 0.00271 | 0.1563 | 0.0106 | 4180 | 40 | 520 |
| 2076 | 2076 | 0.00290 | 0.1618 | 0.0042 | 4720 | 40 | 540 |
| 2086 | 2086 | 0.00347 | -0.1768 | 0.0133 | 3250 | 40 | 500 |
| 2087 | 2087 | 0.00188 | 0.1301 | 0.0083 | 4140 | 43 | 520 |
| 2110 | 2110 | 0.00243 | -0.1478 | 0.0110 | 2910 | 41 | 490 |
| 2115 | 2115 | 0.01506 | -0.3686 | 0.0223 | 1890 | 32 | 500 |
| 2120 | 2120 | 0.01611 | 0.3809 | 0.0697 | 1140 | 27 | 370 |
| 2132 | 2132 | 0.00178 | 0.1266 | 0.0034 | 3710 | 36 | 480 |
| 2135 | 2135 | 0.00189 | 0.1306 | 0.0024 | 3950 | 39 | 480 |
| 2175 | 2175 | 0.00613 | 0.2349 | 0.0625 | 930 | 28 | 355 |
| 2195 | 2195 | 0.00193 | 0.1321 | 0.0015 | 3980 | 40 | 480 |
| 2219 | 2219 | 0.00169 | -0.1237 | 0.0014 | 2500 | 39 | 490 |
| 2221 | 2221 | 0.00471 | -0.2059 | 0.0218 | 2950 | 39 | 495 |
| 2225 | 2225 | 0.00842 | 0.2760 | 0.0062 | 3140 | 35 | 465 |
| 2245 | 2245 | 0.00205 | 0.1358 | 0.0140 | 4050 | 37 | 530 |
| 2287 | 2287 | 0.00186 | 0.1297 | 0.0031 | 3640 | 40 | 470 |
| 2315 | 2315 | 0.00437 | -0.1988 | 0.0037 | 2800 | 42 | 520 |
| 2317 | 2317 | 0.00209 | -0.1372 | 0.0081 | 2530 | 38 | 460 |
| 2337 | 2337 | 0.00230 | 0.1437 | 0.0145 | 3440 | 37 | 460 |
| 2392 | 2392 | 0.00206 | 0.1364 | 0.0033 | 4720 | 40 | 540 |
| 2412 | 2412 | 0.00433 | 0.1975 | 0.0105 | 4030 | 39 | 510 |
| 2422 | 2422 | 0.00590 | -0.2305 | 0.0217 | 3090 | 40 | 485 |
| 2452 | 2452 | 0.04212 | 0.6160 | 0.0938 | 930 | 26 | 345 |
| 2471 | 2471 | 0.00362 | -0.1805 | 0.0214 | 2880 | 38 | 470 |
Considerazioni: Con 2500 osservazioni è normale avere alcuni punti con valori superiori alle soglie. Le osservazioni influenti non vengono rimosse automaticamente: si valuta caso per caso se sono errori di registrazione o valori clinicamente plausibili (es. neonati molto prematuri o macrosomici, già identificati come outlier nelle analisi preliminari). In assenza di errori evidenti, vengono mantenute nel modello. Nessuno di questi punti compromette la stabilità del modello, come confermato dai valori di R² e dalla diagnostica dei residui.
Una volta validato il modello, lo useremo per fare previsioni
pratiche. Invece di una singola previsione puntuale, si costruisce una
tabella con diversi scenari clinicamente rilevanti, variando settimane
di gestazione e condizione di fumatrice/non fumatrice.
Previsione multi-scenario:
media_lunghezza <- mean(df$Lunghezza)
media_cranio <- mean(df$Cranio)
scenari <- expand.grid(
Gestazione = c(36, 37, 38, 39, 40, 41),
Fumatrici = c(0, 1),
Lunghezza = media_lunghezza,
Cranio = media_cranio,
N.gravidanze = 2,
Sesso = "F"
)
pred_scenari <- predict(mod_finale, newdata = scenari, interval = "prediction")
scenari_result <- cbind(scenari[, c("Gestazione", "Fumatrici")],
round(pred_scenari, 1))
scenari_result$Fumatrici <- factor(scenari_result$Fumatrici,
labels = c("No", "Sì"))
kable(scenari_result,
col.names = c("Gestazione (sett.)", "Fumatrice", "Peso stimato (g)",
"IC inf (g)", "IC sup (g)"),
caption = "Previsione del peso neonatale per diversi scenari (femmina, 2ª gravidanza, misure medie)") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover")) |>
column_spec(1, bold = TRUE)
| Gestazione (sett.) | Fumatrice | Peso stimato (g) | IC inf (g) | IC sup (g) |
|---|---|---|---|---|
| 36 | No | 3109.2 | 2581.9 | 3636.5 |
| 37 | No | 3163.2 | 2636.2 | 3690.2 |
| 38 | No | 3209.4 | 2682.5 | 3736.3 |
| 39 | No | 3247.8 | 2721.0 | 3774.6 |
| 40 | No | 3278.5 | 2751.7 | 3805.4 |
| 41 | No | 3301.5 | 2774.3 | 3828.6 |
| 36 | Sì | 3084.7 | 2554.9 | 3614.5 |
| 37 | Sì | 3138.6 | 2609.1 | 3668.1 |
| 38 | Sì | 3184.8 | 2655.5 | 3714.1 |
| 39 | Sì | 3223.3 | 2694.1 | 3752.5 |
| 40 | Sì | 3254.0 | 2724.7 | 3783.2 |
| 41 | Sì | 3276.9 | 2747.4 | 3806.4 |
Grafico: Peso previsto vs Gestazione con banda di confidenza:
# Griglia fine per il grafico
grid_pred <- expand.grid(
Gestazione = seq(min(df$Gestazione), max(df$Gestazione), by = 0.5),
Fumatrici = c(0, 1),
Lunghezza = media_lunghezza,
Cranio = media_cranio,
N.gravidanze = 2,
Sesso = "F"
)
pred_grid <- predict(mod_finale, newdata = grid_pred, interval = "confidence")
grid_pred <- cbind(grid_pred, pred_grid)
grid_pred$Fumo <- factor(grid_pred$Fumatrici, labels = c("Non fumatrice", "Fumatrice"))
ggplot(grid_pred, aes(x = Gestazione, y = fit, color = Fumo, fill = Fumo)) +
geom_line(linewidth = 1) +
geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.2, color = NA) +
labs(title = "Peso previsto vs Settimane di gestazione",
subtitle = "Banda di confidenza al 95% — Femmina, 2ª gravidanza, misure medie",
x = "Settimane di gestazione",
y = "Peso previsto (g)",
color = "", fill = "") +
scale_color_manual(values = c("Non fumatrice" = "#66c2a5", "Fumatrice" = "#fc8d62")) +
scale_fill_manual(values = c("Non fumatrice" = "#66c2a5", "Fumatrice" = "#fc8d62")) +
theme_minimal(base_size = 13)
Esempio di previsione puntuale:
Si calcolano le medie di lunghezza e cranio e si crea il nuovo caso da prevedere effettuando una previsione con intervallo di previsione. Stimiamo il peso di una neonata considerando una madre non fumatrice alla terza gravidanza che partorirà alla 39esima settimana:
nuovo_neonato <- data.frame(
Gestazione = 39,
Lunghezza = media_lunghezza,
Cranio = media_cranio,
N.gravidanze = 3,
Fumatrici = 0,
Sesso = "F"
)
pred_punto <- predict(mod_finale, newdata = nuovo_neonato, interval = "prediction")
pred_df <- data.frame(
Stima = round(pred_punto[1], 2),
IC_lower = round(pred_punto[2], 2),
IC_upper = round(pred_punto[3], 2)
)
kable(pred_df,
col.names = c("Peso stimato (g)", "IC inf (g)", "IC sup (g)"),
caption = "Previsione puntuale: neonata, 39 sett., 3ª gravidanza, non fumatrice, misure medie") |>
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover"))
| Peso stimato (g) | IC inf (g) | IC sup (g) |
|---|---|---|
| 3262.51 | 2735.5 | 3789.53 |
Questa previsione rappresenta un’applicazione pratica del modello e dimostra la sua capacità di fornire stime clinicamente plausibili e coerenti con la fisiologia neonatale.
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.
Si usa plotly per generare un grafico 3D, interattivo e ruotabile, che mostra l’effetto combinato di Gestazione × Fumo sul Peso previsto, utilizzando il modello quadratico.
Questo tipo di grafico è molto utile perché visualizza l’effetto principale della gestazione, mostra la differenza tra fumatrici e non fumatrici, evidenzia eventuali divergenze nelle pendenze (interazioni) e comunica in modo intuitivo l’impatto del fumo sul peso neonatale.
# Sequenza di settimane di gestazione
gest_seq <- seq(min(df$Gestazione), max(df$Gestazione), length.out = 50)
# Data frame per fumatrici e non fumatrici
df_3d <- expand.grid(
Gestazione = gest_seq,
Fumatrici = c(0, 1)
)
# Aggiunta delle altre variabili ai valori medi
df_3d$Lunghezza <- mean(df$Lunghezza)
df_3d$Cranio <- mean(df$Cranio)
df_3d$N.gravidanze <- mean(df$N.gravidanze)
df_3d$Sesso <- "F"
# Predizione del modello
df_3d$Peso_pred <- predict(mod_finale, newdata = df_3d)
# Conversione del fumo in etichette
df_3d$Fumo_label <- factor(df_3d$Fumatrici, labels = c("Non fumatrici", "Fumatrici"))
# Grafico 3D
plot_ly(
df_3d,
x = ~Gestazione,
y = ~Fumo_label,
z = ~Peso_pred,
type = "scatter3d",
mode = "markers",
color = ~Fumo_label,
colors = c("blue", "red"),
marker = list(size = 4)
) %>%
layout(
title = "Effetto combinato di Gestazione e Fumo sul Peso previsto",
scene = list(
xaxis = list(title = "Settimane di gestazione"),
yaxis = list(title = "Fumo materno"),
zaxis = list(title = "Peso previsto (g)")
)
)
Si può interpretare il grafico dicendo che la curva ascendente lungo
l’asse della gestazione mostra l’aumento del peso previsto con
l’avanzare delle settimane.
Le due “nuvole” di punti (fumatrici vs
non fumatrici) sono separate verticalmente, quindi le fumatrici hanno un
peso previsto inferiore a parità di gestazione.
La distanza tra le
due superfici indica l’effetto del fumo: se il modello avesse
un’interazione forte, le due superfici avrebbero pendenze diverse; nel
tuo caso l’effetto è moderato, quindi le superfici sono quasi
parallele.
In conclusione, per visualizzare l’effetto combinato delle settimane di gestazione e del fumo materno sul peso previsto, è stato prodotto un grafico 3D interattivo. Il grafico mostra due superfici distinte: una per le madri fumatrici e una per le non fumatrici. In entrambi i gruppi il peso previsto aumenta con la gestazione, ma la superficie relativa alle fumatrici risulta sistematicamente più bassa, indicando un effetto negativo del fumo sul peso neonatale. Questa rappresentazione consente di cogliere in modo immediato sia l’effetto principale della gestazione sia la differenza tra i due gruppi.
Si genera un secondo grafico 3D, sempre usando plotly, per visualizzare la relazione tra: Gestazione, Lunghezza e Peso previsto. Tale grafico permette di visualizzare i punti simulati dal modello, la superficie di regressione stimata e l’effetto combinato di due variabili chiave sul peso previsto.
# Griglia di valori per le due variabili
gest_seq <- seq(min(df$Gestazione), max(df$Gestazione), length.out = 40)
lung_seq <- seq(min(df$Lunghezza), max(df$Lunghezza), length.out = 40)
grid <- expand.grid(
Gestazione = gest_seq,
Lunghezza = lung_seq,
Cranio = mean(df$Cranio),
N.gravidanze = mean(df$N.gravidanze),
Fumatrici = 0,
Sesso = "F"
)
# Predizioni sulla griglia
grid$Peso_pred <- predict(mod_finale, newdata = grid)
# Grafico 3D
plot_ly(
x = grid$Gestazione,
y = grid$Lunghezza,
z = grid$Peso_pred,
type = "scatter3d",
mode = "markers",
marker = list(size = 3, color = grid$Peso_pred, colorscale = "Viridis")
) %>%
layout(
title = "Scatter plot 3D del peso previsto",
scene = list(
xaxis = list(title = "Settimane di gestazione"),
yaxis = list(title = "Lunghezza (mm)"),
zaxis = list(title = "Peso previsto (g)")
)
)
La sua interpretazione consente di notare, osservando la superficie, come gestazione e lunghezza influenzano il peso previsto. La forma leggermente curva riflette i termini quadratici del modello, mentre le sue zone più alte corrispondono a neonati più maturi e più lunghi.
In conclusione, si può dire che per visualizzare l’effetto combinato delle variabili più rilevanti, è stato prodotto uno scatter plot 3D che mostra la relazione tra settimane di gestazione, lunghezza del neonato e peso previsto dal modello quadratico. La superficie risultante evidenzia un incremento non lineare del peso con l’aumentare della gestazione e della lunghezza, confermando la presenza di effetti quadratici e l’adeguatezza del modello nel catturare la fisiologia della crescita fetale.
Il modello quadratico finale spiega circa il 74% della variabilità del peso neonatale. Le variabili più rilevanti sono la durata della gestazione e le misure antropometriche (lunghezza e cranio), con effetti non lineari per gestazione e lunghezza. Il fumo materno ha un effetto negativo significativo. Le variabili Tipo.parto e Ospedale sono state escluse a priori in quanto prive di senso predittivo (il tipo di parto si decide al momento, l’ospedale non influenza il peso — confermato dal chi-quadro p = 0.578). Gli effetti quadratici sono stati saggiati solo dove i grafici loess suggerivano curvatura, e i relativi effetti principali sono stati mantenuti in accordo con il principio di marginalità. La diagnostica completa sui residui (Breusch-Pagan, Durbin-Watson, Shapiro-Wilk, QQ-plot) e lo studio delle osservazioni influenti (Cook’s distance, DFFITS, leverage) confermano la robustezza del modello. Le previsioni multi-scenario dimostrano l’applicabilità clinica del modello per supportare la pianificazione perinatale.