L’azienda Neonatal Health Solutions vuole sviluppare un modello predittivo del peso del neonato alla nascita a partire da informazioni cliniche raccolte su un campione di 2500 neonati provenienti da tre ospedali. L’obiettivo è identificare le variabili più predittive e fornire stime utili a supportare la gestione clinica e l’allocazione delle risorse ospedaliere.
Prima di fare inferenza sul dataset e costruire il modello analizzo
la struttura del dataset composto dalle seguenti features: -
Anni.madre — età della madre in anni
- N.gravidanze — numero di gravidanze
pregresse della madre
- Fumatrici — indicatore binario del fumo
materno (0 = non fumatrice, 1 = fumatrice)
- Gestazione — durata della gravidanza in
settimane
- Peso — peso del neonato alla nascita, in
grammi
- Lunghezza — lunghezza del neonato in
millimetri (misurabile anche in fase prenatale tramite ecografia)
- Cranio — diametro craniale del neonato
in millimetri (anch’esso misurabile via ecografia)
- Tipo.parto — tipologia del parto
(Nat = naturale, Ces = cesareo)
- Ospedale — ospedale di nascita
(osp1, osp2, osp3)
- Sesso — sesso del neonato
(M = maschio, F = femmina)
L’obiettivo principale è identificare quali variabili siano più predittive del peso alla nascita, con un focus particolare su:
glimpse(neonati)
## Rows: 2,500
## Columns: 10
## $ Anni.madre <dbl> 26, 21, 34, 28, 20, 32, 26, 25, 22, 23, 29, 21, 36, 24, 3…
## $ N.gravidanze <dbl> 0, 2, 3, 1, 0, 0, 1, 0, 1, 0, 2, 2, 5, 0, 3, 2, 2, 3, 0, …
## $ Fumatrici <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ Gestazione <dbl> 42, 39, 38, 41, 38, 40, 39, 40, 40, 41, 38, 40, 38, 40, 3…
## $ Peso <dbl> 3380, 3150, 3640, 3690, 3700, 3200, 3100, 3580, 3670, 370…
## $ Lunghezza <dbl> 490, 490, 500, 515, 480, 495, 480, 510, 500, 510, 480, 51…
## $ Cranio <dbl> 325, 345, 375, 365, 335, 340, 345, 349, 335, 362, 330, 34…
## $ Tipo.parto <chr> "Nat", "Nat", "Nat", "Nat", "Nat", "Nat", "Nat", "Nat", "…
## $ Ospedale <chr> "osp3", "osp1", "osp2", "osp2", "osp3", "osp2", "osp3", "…
## $ Sesso <chr> "M", "F", "M", "M", "F", "F", "F", "M", "F", "F", "M", "F…
summary(neonati)
## Anni.madre N.gravidanze Fumatrici Gestazione
## Min. : 0.00 Min. : 0.0000 Min. :0.0000 Min. :25.00
## 1st Qu.:25.00 1st Qu.: 0.0000 1st Qu.:0.0000 1st Qu.:38.00
## Median :28.00 Median : 1.0000 Median :0.0000 Median :39.00
## Mean :28.16 Mean : 0.9812 Mean :0.0416 Mean :38.98
## 3rd Qu.:32.00 3rd Qu.: 1.0000 3rd Qu.:0.0000 3rd Qu.:40.00
## Max. :46.00 Max. :12.0000 Max. :1.0000 Max. :43.00
## Peso Lunghezza Cranio Tipo.parto Ospedale
## Min. : 830 Min. :310.0 Min. :235 Length :2500 Length :2500
## 1st Qu.:2990 1st Qu.:480.0 1st Qu.:330 N.unique : 2 N.unique : 3
## Median :3300 Median :500.0 Median :340 N.blank : 0 N.blank : 0
## Mean :3284 Mean :494.7 Mean :340 Min.nchar: 3 Min.nchar: 4
## 3rd Qu.:3620 3rd Qu.:510.0 3rd Qu.:350 Max.nchar: 3 Max.nchar: 4
## Max. :4930 Max. :565.0 Max. :390
## Sesso
## Length :2500
## N.unique : 2
## N.blank : 0
## Min.nchar: 1
## Max.nchar: 1
##
Dalla prima esplorazione dei dati i valori da osservare e trattare perchè possibili errori nel dataset riguardano età minima della madre (0, quantomeno sospetto) e 12 gravidanze come valore massimo.
sum(neonati$Anni.madre == 0)
## [1] 1
neonati %>% filter(Anni.madre < 14)
## # A tibble: 3 × 10
## Anni.madre N.gravidanze Fumatrici Gestazione Peso Lunghezza Cranio Tipo.parto
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 13 0 0 38 2760 470 325 Nat
## 2 1 1 0 41 3250 490 350 Nat
## 3 0 0 0 39 3060 490 330 Nat
## # ℹ 2 more variables: Ospedale <chr>, Sesso <chr>
Dei tre casi di gravidanza sotto i 14 anni due sono sicuri errori di battitura( 0 anni e 1 anno), il terzo (13 anni) potrebbe essere plausibile ma visto che non inficia i risultati, avendo gli altri valori non troppo discostati dalla media, lo considero trascurabile come gli altri e rimuovo tutti e tre.
Vista la presenza di donne con numero di gravidanze elevato prima di valutare che siano errori di compilazione controllo che le età siano credibili:
neonati %>%
mutate(ID = row_number()) %>%
filter(N.gravidanze >= 8) %>%
arrange(desc(N.gravidanze)) %>%
select(ID, everything()) %>%
kable(caption = "Osservazioni con N.gravidanze elevato") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| ID | Anni.madre | N.gravidanze | Fumatrici | Gestazione | Peso | Lunghezza | Cranio | Tipo.parto | Ospedale | Sesso |
|---|---|---|---|---|---|---|---|---|---|---|
| 1219 | 38 | 12 | 0 | 39 | 3350 | 490 | 344 | Nat | osp2 | M |
| 1130 | 33 | 11 | 0 | 43 | 3400 | 475 | 360 | Nat | osp1 | M |
| 2221 | 35 | 10 | 0 | 39 | 2950 | 495 | 335 | Nat | osp1 | F |
| 2422 | 33 | 10 | 0 | 40 | 3090 | 485 | 353 | Nat | osp3 | M |
| 2471 | 34 | 10 | 0 | 38 | 2880 | 470 | 345 | Ces | osp2 | M |
| 161 | 35 | 9 | 0 | 42 | 3760 | 540 | 348 | Nat | osp2 | F |
| 1781 | 35 | 9 | 0 | 37 | 3150 | 490 | 335 | Nat | osp2 | M |
| 89 | 36 | 8 | 0 | 39 | 3610 | 500 | 351 | Ces | osp1 | M |
| 204 | 30 | 8 | 0 | 40 | 3850 | 518 | 340 | Nat | osp3 | F |
| 516 | 40 | 8 | 0 | 38 | 3520 | 470 | 341 | Nat | osp3 | M |
| 1450 | 36 | 8 | 0 | 41 | 3730 | 480 | 335 | Nat | osp3 | M |
| 1505 | 30 | 8 | 0 | 39 | 2860 | 490 | 337 | Ces | osp2 | F |
| 1727 | 36 | 8 | 0 | 36 | 2860 | 460 | 334 | Nat | osp2 | F |
| 2086 | 26 | 8 | 0 | 40 | 3250 | 500 | 355 | Nat | osp2 | M |
| 2175 | 37 | 8 | 0 | 28 | 930 | 355 | 235 | Nat | osp1 | F |
Delle 15 osservazioni con almeno 8 gravidanze tutte le età o quasi sono plausibili(fascia 30-38 anni) seppur in contesti socio-culturali particolari. Il caso limite che potrebbe tranquillamente essere errore di data entry è l’id 2086, madre di 26 anni all’ottava gravidanza il che presupporrebbe dall’inizio dell’età fertile una gravidanza ogni circa 15 mesi senza soste. Raro ma senza evidenze contrarie che possano permettere una rimozione dal dataset. Altro caso particolare l’id 2175, madre con gravidanza con gestazione minima del dataset (28 settimane) e valori fisici del bambino ovviamente compatibili con una nascita prematura.
Sulla base di quanto riportato applico le modifiche al dataset:
ID con il numero di riga
originale, per poter fare riferimento a singole osservazioni nel
commento dei risultati successivi.Tipo.parto,
Ospedale, Sesso, Fumatrici) in
factor, tipo nativo di R per le variabili qualitative,
necessario per la corretta gestione nei test inferenziali e nei modelli
di regressione.neonati <- neonati %>%
mutate(ID = row_number()) %>%
filter(Anni.madre >= 14) %>%
mutate(
Tipo.parto = factor(Tipo.parto),
Ospedale = factor(Ospedale),
Sesso = factor(Sesso),
Fumatrici = factor(Fumatrici, levels = c(0, 1), labels = c("No", "Sì"))
)
nrow(neonati)
## [1] 2497
Il dataset pulito risulta composto ora da 2497 osservazioni.
glimpse(neonati)
## Rows: 2,497
## Columns: 11
## $ Anni.madre <dbl> 26, 21, 34, 28, 20, 32, 26, 25, 22, 23, 29, 21, 36, 24, 3…
## $ N.gravidanze <dbl> 0, 2, 3, 1, 0, 0, 1, 0, 1, 0, 2, 2, 5, 0, 3, 2, 2, 3, 0, …
## $ Fumatrici <fct> No, No, No, No, No, No, No, No, No, No, No, No, No, No, N…
## $ Gestazione <dbl> 42, 39, 38, 41, 38, 40, 39, 40, 40, 41, 38, 40, 38, 40, 3…
## $ Peso <dbl> 3380, 3150, 3640, 3690, 3700, 3200, 3100, 3580, 3670, 370…
## $ Lunghezza <dbl> 490, 490, 500, 515, 480, 495, 480, 510, 500, 510, 480, 51…
## $ Cranio <dbl> 325, 345, 375, 365, 335, 340, 345, 349, 335, 362, 330, 34…
## $ Tipo.parto <fct> Nat, Nat, Nat, Nat, Nat, Nat, Nat, Nat, Ces, Ces, Ces, Na…
## $ Ospedale <fct> osp3, osp1, osp2, osp2, osp3, osp2, osp3, osp1, osp2, osp…
## $ Sesso <fct> M, F, M, M, F, F, F, M, F, F, M, F, F, F, M, M, M, M, F, …
## $ ID <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
Prima di procedere con i test inferenziali, esploro le caratteristiche del dataset pulito.
describe(neonati[, c("Anni.madre", "N.gravidanze", "Gestazione",
"Peso", "Lunghezza", "Cranio")]) %>%
kable(digits = 2, caption = "Statistiche descrittive delle variabili numeriche") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
scroll_box(width = "100%")
| vars | n | mean | sd | median | trimmed | mad | min | max | range | skew | kurtosis | se | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Anni.madre | 1 | 2497 | 28.19 | 5.21 | 28 | 28.11 | 4.45 | 14 | 46 | 32 | 0.16 | -0.12 | 0.10 |
| N.gravidanze | 2 | 2497 | 0.98 | 1.28 | 1 | 0.75 | 1.48 | 0 | 12 | 12 | 2.51 | 10.97 | 0.03 |
| Gestazione | 3 | 2497 | 38.98 | 1.87 | 39 | 39.18 | 1.48 | 25 | 43 | 18 | -2.06 | 8.25 | 0.04 |
| Peso | 4 | 2497 | 3284.39 | 525.23 | 3300 | 3303.32 | 459.61 | 830 | 4930 | 4100 | -0.65 | 2.03 | 10.51 |
| Lunghezza | 5 | 2497 | 494.71 | 26.33 | 500 | 496.47 | 22.24 | 310 | 565 | 255 | -1.51 | 6.48 | 0.53 |
| Cranio | 6 | 2497 | 340.04 | 16.43 | 340 | 340.69 | 14.83 | 235 | 390 | 155 | -0.79 | 2.94 | 0.33 |
# Tipo di parto
table(neonati$Tipo.parto) %>%
as.data.frame() %>%
rename(Tipo = Var1, Frequenza = Freq) %>%
mutate(Percentuale = round(Frequenza / sum(Frequenza) * 100, 2)) %>%
kable(caption = "Distribuzione del tipo di parto") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Tipo | Frequenza | Percentuale |
|---|---|---|
| Ces | 728 | 29.15 |
| Nat | 1769 | 70.85 |
# Ospedale
table(neonati$Ospedale) %>%
as.data.frame() %>%
rename(Ospedale = Var1, Frequenza = Freq) %>%
mutate(Percentuale = round(Frequenza / sum(Frequenza) * 100, 2)) %>%
kable(caption = "Distribuzione delle nascite per ospedale") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Ospedale | Frequenza | Percentuale |
|---|---|---|
| osp1 | 816 | 32.68 |
| osp2 | 847 | 33.92 |
| osp3 | 834 | 33.40 |
# Sesso
table(neonati$Sesso) %>%
as.data.frame() %>%
rename(Sesso = Var1, Frequenza = Freq) %>%
mutate(Percentuale = round(Frequenza / sum(Frequenza) * 100, 2)) %>%
kable(caption = "Distribuzione per sesso") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Sesso | Frequenza | Percentuale |
|---|---|---|
| F | 1254 | 50.22 |
| M | 1243 | 49.78 |
# Madri fumatrici
table(neonati$Fumatrici) %>%
as.data.frame() %>%
rename(Fumatrice = Var1, Frequenza = Freq) %>%
mutate(Percentuale = round(Frequenza / sum(Frequenza) * 100, 2)) %>%
kable(caption = "Distribuzione del fumo materno") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Fumatrice | Frequenza | Percentuale |
|---|---|---|
| No | 2393 | 95.84 |
| Sì | 104 | 4.16 |
Le due variabili centrali del progetto sono Peso (variabile risposta del modello di regressione) e Gestazione (predittore principale, indicatore di nascite premature). Verifico graficamente la distribuzione.
ggplot(neonati, aes(x = Peso)) +
geom_histogram(bins = 40, fill = "steelblue", color = "white") +
labs(title = "Distribuzione del peso del neonato",
x = "Peso (grammi)",
y = "Frequenza") +
theme_minimal()
ggplot(neonati, aes(x = Gestazione)) +
geom_histogram(bins = 20, fill = "darkorange", color = "white") +
labs(title = "Distribuzione della settimana di gestazione",
x = "Settimane di gestazione",
y = "Frequenza") +
theme_minimal()
Distribuzione del Peso: la variabile risposta del modello mostra una forma approssimativamente gaussiana, simmetrica e centrata attorno ai 3300 grammi. È presente una leggera coda sinistra dovuta ai casi di prematuri estremi (es. ID 2175 identificato in fase di pulizia). Questa forma è favorevole come premessa al metodo: la regressione lineare assume che ad essere normali siano i residui e una variabile risposta già approssimativamente gaussiana costituisce un buon punto di partenza.
Distribuzione della Gestazione: a differenza del Peso, la durata della gestazione mostra una distribuzione fortemente asimmetrica a sinistra, con un picco modale a 40 settimane e una coda di parti prematuri (25-37 settimane). Questa asimmetria è plausibile con i casi clinici reali e non costituisce un problema per il modello di regressione, che come detto richiede normalità dei residui ma non dei predittori.
Voglio verificare se la proporzione di parti cesarei differisce significativamente tra i tre ospedali del campione, oppure se le differenze osservate sono attribuibili alla variabilità campionaria.
tabella_parti <- table(neonati$Tipo.parto, neonati$Ospedale)
addmargins(tabella_parti, FUN = list(Totale = sum), quiet = TRUE) %>%
kable(caption = "Frequenze osservate: tipo di parto per ospedale") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
row_spec(3, bold = TRUE, background = "#e8e8e8") %>%
column_spec(5, bold = TRUE, background = "#e8e8e8")
| osp1 | osp2 | osp3 | Totale | |
|---|---|---|---|---|
| Ces | 242 | 254 | 232 | 728 |
| Nat | 574 | 593 | 602 | 1769 |
| Totale | 816 | 847 | 834 | 2497 |
Per facilitare la lettura, calcolo anche le percentuali di cesarei per ospedale:
prop.table(tabella_parti, margin = 2) %>%
round(3) %>%
addmargins(margin = 1,FUN = list(Totale = sum), quiet = TRUE) %>%
kable(caption = "Proporzioni di tipo di parto per ospedale") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
row_spec(3, bold = TRUE, background = "#e8e8e8")
| osp1 | osp2 | osp3 | |
|---|---|---|---|
| Ces | 0.297 | 0.3 | 0.278 |
| Nat | 0.703 | 0.7 | 0.722 |
| Totale | 1.000 | 1.0 | 1.000 |
Il parametro margin = 2 calcola le proporzioni
per colonna (ospedale): ogni colonna somma a 1, e leggo
direttamente la percentuale di cesarei e naturali per ciascun
ospedale.
test_chi <- chisq.test(tabella_parti)
test_chi
##
## Pearson's Chi-squared test
##
## data: tabella_parti
## X-squared = 1.1062, df = 2, p-value = 0.5752
Verifico le frequenze attese (utile sia per controllare la validità del test che per capire dove cadono le maggiori discrepanze):
test_chi$expected %>%
round(2) %>%
addmargins(FUN = list(Totale = sum), quiet = TRUE) %>%
kable(caption = "Frequenze attese sotto l'ipotesi di indipendenza") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
row_spec(3, bold = TRUE, background = "#e8e8e8") %>%
column_spec(5, bold = TRUE, background = "#e8e8e8")
| osp1 | osp2 | osp3 | Totale | |
|---|---|---|---|---|
| Ces | 237.9 | 246.94 | 243.15 | 727.99 |
| Nat | 578.1 | 600.06 | 590.85 | 1769.01 |
| Totale | 816.0 | 847.00 | 834.00 | 2497.00 |
Il test del chi quadrato ha prodotto i seguenti risultati:
Con un p-value pari a 0.575, ampiamente superiore alla soglia convenzionale di 0.05, non rifiuto l’ipotesi nulla di indipendenza. I dati non forniscono evidenza statistica sufficiente per affermare che la proporzione di parti cesarei differisca tra i tre ospedali del campione.
Il range di percentuale dei parti cesarei nei tre ospedali è ristretto (27,8-30 %) e compatibile con la variabilità campionaria attesa sotto l’ipotesi di indipendenza tra tipo di parto e ospedale, pertanto la scelta su come far nascere i bambini segue criteri omogenei.
Verifico se le medie campionarie di Peso e Lunghezza sono compatibili con i valori medi della popolazione di riferimento.
Da fonti come ISTAT e OMS, i valori medi di riferimento per neonati a termine sono approssimativamente:
Non conoscendo la σ di peso e lunghezza della popolazione italiana il test da adottare è il t, con n=2497(campione grande) per il Teorema del Limite Centrale so che la distribuzione campionaria sarà praticamente normale, anche se la popolazione non lo fosse.
data.frame(
Statistica = c("n", "Media", "Deviazione standard", "Errore standard"),
Valore = c(
nrow(neonati),
round(mean(neonati$Peso), 2),
round(sd(neonati$Peso), 2),
round(sd(neonati$Peso)/sqrt(nrow(neonati)), 2)
)
) %>%
kable(caption = "Statistiche descrittive del Peso") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Statistica | Valore |
|---|---|
| n | 2497.00 |
| Media | 3284.39 |
| Deviazione standard | 525.23 |
| Errore standard | 10.51 |
test_peso <- t.test(neonati$Peso, mu = 3300)
test_peso
##
## One Sample t-test
##
## data: neonati$Peso
## t = -1.4847, df = 2496, p-value = 0.1377
## alternative hypothesis: true mean is not equal to 3300
## 95 percent confidence interval:
## 3263.783 3305.005
## sample estimates:
## mean of x
## 3284.394
Con p-value = 0.1377 > 0.05 non rifiuto l’ipotesi nulla, la media del campione non è statisticamente diversa dal valore di riferimento(3300 g), i grammi di differenza rientrano nella variabilità campionaria. Il valore della media della popolazione(3300 gr) rientra, seppur di poco, nell’ intervallo di confidenza[3263.783 ; 3305.005] a conferma della precedente conclusione.
data.frame(
Statistica = c("n", "Media", "Deviazione standard", "Errore standard"),
Valore = c(
nrow(neonati),
round(mean(neonati$Lunghezza), 2),
round(sd(neonati$Lunghezza), 2),
round(sd(neonati$Lunghezza)/sqrt(nrow(neonati)), 2)
)
) %>%
kable(caption = "Statistiche descrittive della Lunghezza") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Statistica | Valore |
|---|---|
| n | 2497.00 |
| Media | 494.71 |
| Deviazione standard | 26.33 |
| Errore standard | 0.53 |
test_lunghezza <- t.test(neonati$Lunghezza, mu = 500)
test_lunghezza
##
## One Sample t-test
##
## data: neonati$Lunghezza
## t = -10.048, df = 2496, p-value < 0.00000000000000022
## alternative hypothesis: true mean is not equal to 500
## 95 percent confidence interval:
## 493.6724 495.7389
## sample estimates:
## mean of x
## 494.7056
Il p value risulta virtualmente nullo pertanto rifiuto l’ipotesi nulla con ampia evidenza, l’intervallo di confidenza[493.6724;495.7389] non contiene 500 pertanto c’è una differenza statistica significativa. In questo caso occorre però distinguere tra significatività statistica e rilevanza pratica, seppur sia da scartare l’ipotesi nulla relativamente alla lunghezza dei neonati, i 5 mm di differenza media rilevati (circa 1% della media nazionale) non hanno una valenza clinica e sono trascurabili. La causa può essere dovuta a metodologie di misurazione differenti.
Peso e lunghezza hanno mostrato nel campione differenze in percentuale dalla popolazione molto simili e piccole (0,5% peso e 1% lunghezza). Il fatto che vengano statisticamente trattati diversamente dipende dall’errore standard delle due variabili, nel caso della lunghezza dei neonati essendo molto piccolo (0,53) fa sì che la differenza trovata sia statisticamente estrema (t = -10.048), in altre parole i dati sono più concentrati [493.6724 ; 495.7389] e la differenza risalta di più.
Il campione può quindi ritenersi “rappresentativo della popolazione” anche se dal punto di vista statistico solo nel caso del peso si può accettare l’ipotesi nulla. Il test per la lunghezza come detto è significativo (p < 10⁻¹⁶, IC esclude 500), ma la differenza assoluta è di 5 mm — entro la precisione delle misurazioni cliniche standard. Ai fini operativi il campione può considerarsi buono, in attesa che eventuali approfondimenti metodologici possano spiegare l’origine della discrepanza.
In questo caso voglio verificare se le misure( Peso, Lunghezza, Cranio) dei neonati maschi differiscono significativamente da quelle delle neonate femmine. In questo caso il test da applicare sarà a due campioni indipendenti (media vs altra media). I due campioni sono indipendenti dato che un neonato o nasce maschio o nasce femmina, quindi non si misura lo stesso due volte.
Per ciascuna delle tre variabili antropometriche(Peso, Lunghezza, Cranio) testo:
neonati %>%
group_by(Sesso) %>%
summarise(
n = n(),
media_peso = round(mean(Peso), 2),
sd_peso = round(sd(Peso), 2),
media_lunghezza = round(mean(Lunghezza), 2),
sd_lunghezza = round(sd(Lunghezza), 2),
media_cranio = round(mean(Cranio), 2),
sd_cranio = round(sd(Cranio), 2)
) %>%
kable(caption = "Statistiche antropometriche per sesso") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Sesso | n | media_peso | sd_peso | media_lunghezza | sd_lunghezza | media_cranio | sd_cranio |
|---|---|---|---|---|---|---|---|
| F | 1254 | 3161.38 | 526.6 | 489.78 | 27.55 | 337.63 | 16.74 |
| M | 1243 | 3408.50 | 493.9 | 499.67 | 24.05 | 342.46 | 15.75 |
test_peso_sesso <- t.test(Peso ~ Sesso, data = neonati)
test_peso_sesso
##
## Welch Two Sample t-test
##
## data: Peso by Sesso
## t = -12.096, df = 2487.4, p-value < 0.00000000000000022
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -287.1763 -207.0525
## sample estimates:
## mean in group F mean in group M
## 3161.381 3408.496
test_lung_sesso <- t.test(Lunghezza ~ Sesso, data = neonati)
test_lung_sesso
##
## Welch Two Sample t-test
##
## data: Lunghezza by Sesso
## t = -9.5638, df = 2455.7, p-value < 0.00000000000000022
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -11.923919 -7.866232
## sample estimates:
## mean in group F mean in group M
## 489.7799 499.6750
test_cranio_sesso <- t.test(Cranio ~ Sesso, data = neonati)
test_cranio_sesso
##
## Welch Two Sample t-test
##
## data: Cranio by Sesso
## t = -7.4188, df = 2488.1, p-value = 0.0000000000001612
## alternative hypothesis: true difference in means between group F and group M is not equal to 0
## 95 percent confidence interval:
## -6.100823 -3.549966
## sample estimates:
## mean in group F mean in group M
## 337.6332 342.4586
Per visualizzare graficamente le differenze, utilizzo i boxplot per ciascuna delle tre misure:
neonati %>%
pivot_longer(cols = c(Peso, Lunghezza, Cranio),
names_to = "Misura",
values_to = "Valore") %>%
ggplot(aes(x = Sesso, y = Valore)) +
geom_boxplot(alpha = 0.7) +
facet_wrap(~ Misura, scales = "free_y") +
labs(title = "Distribuzione delle misure antropometriche per sesso",
x = "Sesso", y = "Valore") +
theme_minimal()
I tre test t per campioni indipendenti hanno prodotto risultati coerenti: in tutte le tre misure antropometriche, le medie tra maschi e femmine differiscono in modo statisticamente significativo (tutti p-value < 10⁻¹³ quindi virtualmente nulli, gli IC escludono lo zero). I maschi presentano sistematicamente valori medi superiori alle femmine:
Questa gerarchia è coerente con la letteratura biologica e pediatrica: la differenza di peso tra i due sessi alla nascita è un fenomeno ampiamente documentato.
A differenza dell’Ipotesi 2, in cui le differenze statistiche risultavano clinicamente trascurabili, in questo caso la differenza di peso ha effettiva rilevanza clinica: i 247 grammi rappresentano circa l’8% del peso medio neonatale e quasi mezza deviazione standard. Le differenze di lunghezza (1 cm) e cranio (0.5 cm), pur statisticamente certe, sono di entità più contenuta ma comunque biologicamente rilevanti.
Implicazione per il modello di regressione: la
variabile Sesso rappresenta un predittore importante del
peso del neonato e in quanto tale va inclusa nel modello di regressione
che andrò a costruire.
Dopo aver analizzato le relazioni singole e verificato le ipotesi
formulate, procedo alla costruzione del modello statistico per la
previsione del Peso neonatale. L’obiettivo è identificare
tra le variabili i predittori più significativi e quantificare il loro
impatto.
Nel modello inizialmente inserisco tutte le variabili , comprese quelle categoriche opportunamente convertite in fattori nella fase di pulizia.
modello_completo <- lm(Peso ~ Anni.madre + N.gravidanze + Fumatrici +
Gestazione + Lunghezza + Cranio + Tipo.parto + Ospedale + Sesso,
data = neonati)
summary(modello_completo)
##
## Call:
## lm(formula = Peso ~ Anni.madre + N.gravidanze + Fumatrici + Gestazione +
## Lunghezza + Cranio + Tipo.parto + Ospedale + Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1123.22 -181.60 -14.71 161.14 2611.88
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6735.6079 141.5732 -47.577 < 0.0000000000000002 ***
## Anni.madre 0.7989 1.1489 0.695 0.4869
## N.gravidanze 11.3829 4.6697 2.438 0.0149 *
## FumatriciSì -30.2796 27.5550 -1.099 0.2719
## Gestazione 32.5766 3.8215 8.524 < 0.0000000000000002 ***
## Lunghezza 10.2921 0.3010 34.198 < 0.0000000000000002 ***
## Cranio 10.4721 0.4264 24.562 < 0.0000000000000002 ***
## Tipo.partoNat 29.6402 12.0939 2.451 0.0143 *
## Ospedaleosp2 -11.0767 13.4539 -0.823 0.4104
## Ospedaleosp3 28.2506 13.5082 2.091 0.0366 *
## SessoM 77.5650 11.1900 6.932 0.00000000000528 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.1 on 2486 degrees of freedom
## Multiple R-squared: 0.7288, Adjusted R-squared: 0.7277
## F-statistic: 668 on 10 and 2486 DF, p-value: < 0.00000000000000022
Il primo risultato è il multiple R-squared=0.7288 : significa che le
variabili inserite riescono a spiegare quasi il 73% dei motivi per cui
il peso di un neonato varia. Il p-value: < 0.00000000000000022
conferma che il modello è statisticamente solidissimo e i risultati non
dipendono dal caso.
Residual standard error: 274.1 significa che il modello nel prevedere il
peso di un neonato sbaglia 274.1 grammi in difetto o eccesso. Su un peso
medio di 3,3 kg un valore trascurabile dal punto di vista clinico.
Guardando nel complesso la tabella nella colonna Pr(>|t|) si trovano i p-value di ogni feature e il numero di asterischi determina se la variabile può considerarsi predittiva della colonna target,il peso del neonato in questo caso.
Per quanto riguarda le variabili predittive trovo, in ordine di significatività: -le variabili “antropometriche”che ovviamente determinano il peso, maggiore è il diametro craniale, la lunghezza e il periodo di gestazione e più peserà il neonato -il sesso come visto nell’ipotesi 3 ha un valore “Estimate” di 77.5650, significa che,a parità di tutte le altre variabili, essere maschio determina mediamente un aumento di 78 grammi di peso. Questo valore risulta inferiore a quello rilevato nel test t dell’ipotesi 3: questo perchè la regressione ‘depura’ l’effetto del sesso da quello delle variabili correlate(i maschi hanno lunghezza e diametro del cranio maggiore) e restituisce il contributo netto.
Un risultato che potrebbe sorprendere è che il fumo materno (p = 0.272) non risulti tra i predittori significativi, nonostante sia una delle variabili di interesse indicate dal progetto. Le cause plausibili sono due:
Analogamente, l’età materna (p = 0.487) e l’ospedale di nascita (significativo solo marginalmente per osp3) sembrano apportare scarso potere predittivo diretto. Queste variabili rischiano di introdurre rumore statistico senza migliorare sostanzialmente il modello.
Per individuare il modello più parsimonioso, nel rispetto del principio di parsimonia, procedo con una selezione algoritmica tramite metodo stepwise. Confronterò due criteri di selezione richiesti dal progetto: l’AIC (Akaike), che penalizza la complessità con un fattore 2 per parametro, e il BIC (Bayes), più severo, che penalizza con un fattore ln(n) per parametro e tende quindi a selezionare modelli più piccoli.
Come anticipato applicherò la procedura stepwise con due criteri diversi:
# AIC
modello_aic <- step(modello_completo, direction = "both", trace = FALSE)
# BIC
modello_bic <- step(modello_completo, direction = "both",
k = log(nrow(neonati)), trace = FALSE)
summary(modello_aic)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Tipo.parto + Ospedale + Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1113.06 -181.96 -16.63 161.11 2619.53
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6707.7610 136.0686 -49.297 < 0.0000000000000002 ***
## N.gravidanze 12.3312 4.3357 2.844 0.00449 **
## Gestazione 32.0391 3.7932 8.446 < 0.0000000000000002 ***
## Lunghezza 10.3056 0.3007 34.277 < 0.0000000000000002 ***
## Cranio 10.4918 0.4258 24.642 < 0.0000000000000002 ***
## Tipo.partoNat 29.4198 12.0908 2.433 0.01503 *
## Ospedaleosp2 -10.8685 13.4512 -0.808 0.41917
## Ospedaleosp3 28.7918 13.4996 2.133 0.03304 *
## SessoM 77.4519 11.1878 6.923 0.00000000000561 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.1 on 2488 degrees of freedom
## Multiple R-squared: 0.7286, Adjusted R-squared: 0.7277
## F-statistic: 834.9 on 8 and 2488 DF, p-value: < 0.00000000000000022
summary(modello_bic)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1149.3 -181.0 -15.6 163.7 2639.0
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6681.4708 135.8505 -49.183 < 0.0000000000000002 ***
## N.gravidanze 12.4483 4.3429 2.866 0.00419 **
## Gestazione 32.3833 3.8016 8.518 < 0.0000000000000002 ***
## Lunghezza 10.2452 0.3009 34.050 < 0.0000000000000002 ***
## Cranio 10.5406 0.4266 24.710 < 0.0000000000000002 ***
## SessoM 77.9613 11.2147 6.952 0.00000000000459 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.8 on 2491 degrees of freedom
## Multiple R-squared: 0.7269, Adjusted R-squared: 0.7264
## F-statistic: 1326 on 5 and 2491 DF, p-value: < 0.00000000000000022
data.frame(
Criterio = c("AIC", "BIC"),
Num_predittori = c(length(coef(modello_aic)) - 1,
length(coef(modello_bic)) - 1),
AIC = c(round(AIC(modello_aic), 2), round(AIC(modello_bic), 2)),
BIC = c(round(BIC(modello_aic), 2), round(BIC(modello_bic), 2)),
R2_adj = c(round(summary(modello_aic)$adj.r.squared, 4),
round(summary(modello_bic)$adj.r.squared, 4))
) %>%
kable(caption = "Confronto tra modello AIC e modello BIC") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Criterio | Num_predittori | AIC | BIC | R2_adj |
|---|---|---|---|---|
| AIC | 8 | 35130.22 | 35188.45 | 0.7277 |
| BIC | 5 | 35139.81 | 35180.57 | 0.7264 |
I due criteri hanno selezionato modelli diversi:
Tipo.parto e Ospedale.Tipo.parto e Ospedale.N.B. nel conteggio delle variabili va considerato che ogni categorica apporta k-1 colonne dummy dove k è il numero di modalità assunte
Entrambi concordano nell’eliminare Anni.madre e
Fumatrici, le variabili meno significative del modello
completo.
Il confronto mostra che la capacità predittiva dei due modelli è praticamente identica: l’R² aggiustato passa da 0.7277 (AIC) a 0.7264 (BIC), una differenza dello 0.13% del tutto trascurabile. A fronte di questa equivalenza predittiva, il modello BIC utilizza 3 variabili in meno.
Scelgo il modello BIC come modello finale, per le seguenti ragioni:
Ospedale dal modello è quindi
coerente.Il modello finale è dunque:
\[\text{Peso} = \beta_0 + \beta_1 \text{N.gravidanze} + \beta_2 \text{Gestazione} + \beta_3 \text{Lunghezza} + \beta_4 \text{Cranio} + \beta_5 \text{Sesso} + \varepsilon\]
modello_finale <- modello_bic
summary(modello_finale)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1149.3 -181.0 -15.6 163.7 2639.0
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6681.4708 135.8505 -49.183 < 0.0000000000000002 ***
## N.gravidanze 12.4483 4.3429 2.866 0.00419 **
## Gestazione 32.3833 3.8016 8.518 < 0.0000000000000002 ***
## Lunghezza 10.2452 0.3009 34.050 < 0.0000000000000002 ***
## Cranio 10.5406 0.4266 24.710 < 0.0000000000000002 ***
## SessoM 77.9613 11.2147 6.952 0.00000000000459 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.8 on 2491 degrees of freedom
## Multiple R-squared: 0.7269, Adjusted R-squared: 0.7264
## F-statistic: 1326 on 5 and 2491 DF, p-value: < 0.00000000000000022
Vado ora a valutare l’eventuale presenza di effetti non lineari e interazioni tra le variabili, partendo dal modello finale selezionato.
Dal punto di vista clinico il feto non cresce in maniera lineare durante la gestazione ma nelle ultime settimane tende a crescere piu rapidamente. Verifico quindi se un termine quadratico legato alla Gestazione possa migliorare il modello.
modello_quad <- lm(Peso ~ N.gravidanze + Gestazione + I(Gestazione^2) +
Lunghezza + Cranio + Sesso,
data = neonati)
summary(modello_quad)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + I(Gestazione^2) +
## Lunghezza + Cranio + Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1144.00 -181.62 -12.88 165.89 2661.86
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -4647.6512 898.9178 -5.170 0.0000002523343 ***
## N.gravidanze 12.5444 4.3394 2.891 0.00388 **
## Gestazione -81.1695 49.7584 -1.631 0.10296
## I(Gestazione^2) 1.5160 0.6624 2.289 0.02218 *
## Lunghezza 10.3500 0.3041 34.035 < 0.0000000000000002 ***
## Cranio 10.6374 0.4283 24.837 < 0.0000000000000002 ***
## SessoM 75.7454 11.2469 6.735 0.0000000000203 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.5 on 2490 degrees of freedom
## Multiple R-squared: 0.7275, Adjusted R-squared: 0.7268
## F-statistic: 1108 on 6 and 2490 DF, p-value: < 0.00000000000000022
Confronto il modello con termine quadratico rispetto al modello finale lineare:
anova(modello_finale, modello_quad)
## Analysis of Variance Table
##
## Model 1: Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Sesso
## Model 2: Peso ~ N.gravidanze + Gestazione + I(Gestazione^2) + Lunghezza +
## Cranio + Sesso
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 2491 188041153
## 2 2490 187646385 1 394769 5.2384 0.02218 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Analizzando i risultati appare subito curioso che la variabile Gestazione perda di significatività (p=0.103) e abbia ora Estimate negativa (−81). Questo effetto è dovuto alla compresenza della variabile quadratica Gestazione² che porta alla collinearità che confonde i coefficienti individuali.
Il p-value = 0.022 dice che il termine quadratico migliora il modello anche se marginalmente: R² aggiustato passa da 0.7264 (finale) a 0.7268 (quadratico), un miglioramento dello 0,04%…
Ritorna il dilemma tra lo “statisticamente significativo” e il
“praticamente utile”. Vale la pena complicare il modello introducendo
collinearità e interpretazione più difficile per uno 0,04% di varianza
in più seppur clinicamente sensato?
Scelgo il no come risposta, visto che inoltre il
p-value 0.022 della Gestazione^2 non è così forte ma rileva solo per la
numerosità campionaria.
Altra ipotesi clinica da verificare è il diverso peso della Gestazione tra neonati maschi e femmine nel computo del peso:
modello_inter <- lm(Peso ~ N.gravidanze + Gestazione + Lunghezza +
Cranio + Sesso + Gestazione:Sesso,
data = neonati)
summary(modello_inter)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Sesso + Gestazione:Sesso, data = neonati)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1144.68 -181.31 -15.16 163.42 2637.57
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6572.8113 167.1497 -39.323 < 0.0000000000000002 ***
## N.gravidanze 12.4022 4.3429 2.856 0.00433 **
## Gestazione 29.5188 4.5872 6.435 0.000000000148 ***
## Lunghezza 10.2491 0.3009 34.062 < 0.0000000000000002 ***
## Cranio 10.5419 0.4265 24.715 < 0.0000000000000002 ***
## SessoM -183.8641 234.9464 -0.783 0.43395
## Gestazione:SessoM 6.7094 6.0138 1.116 0.26467
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 274.7 on 2490 degrees of freedom
## Multiple R-squared: 0.727, Adjusted R-squared: 0.7264
## F-statistic: 1105 on 6 and 2490 DF, p-value: < 0.00000000000000022
Confronto con il modello finale:
anova(modello_finale, modello_inter)
## Analysis of Variance Table
##
## Model 1: Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Sesso
## Model 2: Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio + Sesso +
## Gestazione:Sesso
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 2491 188041153
## 2 2490 187947200 1 93953 1.2447 0.2647
Con questa ipotesi ho provato a rispondere alla domanda “l’effetto di una settimana di gestazione sul peso è diverso tra maschi e femmine?” La risposta dei dati, stavolta senza dilemmi interpretativi è no: il p-value 0.265 è proprio alto e il coefficiente di interazione (+6.71) non è significativamente diverso da 0. Da notare che anche in questo caso la collinearità rende il coefficiente di SessoM = - 183
Come richiesto dal progetto, ho valutato due possibili arricchimenti del modello finale:
Effetto non lineare della Gestazione (termine
quadratico): il termine I(Gestazione²) risulta
statisticamente significativo (p = 0.022), e il test ANOVA conferma un
miglioramento dell’adattamento. Tuttavia, l’incremento di capacità
predittiva è trascurabile: l’R² aggiustato passa da 0.7264 a 0.7268
(+0.04%). La significatività è in larga parte attribuibile alla
numerosità campionaria elevata, che rende rilevabili anche effetti di
entità minima. Considerando il principio di parsimonia e l’introduzione
di collinearità tra Gestazione e il suo quadrato, decido di non
includere il termine quadratico nel modello finale.
Interazione Gestazione × Sesso: il termine di interazione non è statisticamente significativo (p = 0.265), e il test ANOVA non rileva alcun miglioramento del modello. L’effetto della gestazione sul peso è dunque sostanzialmente uguale per maschi e femmine (le pendenze sono parallele). L’interazione viene scartata.
In sintesi, nessuno dei due arricchimenti esplorati apporta un beneficio sostanziale. Il modello lineare a cinque predittori selezionato in precedenza si conferma come modello finale, privilegiando semplicità e interpretabilità senza sacrificare capacità predittiva.
Valuto ora la qualità del modello finale sotto tre aspetti: le metriche di capacità predittiva, l’analisi dei residui (verifica delle assunzioni) e l’individuazione di eventuali valori influenti.
# R² e R² aggiustato
r2 <- summary(modello_finale)$r.squared
r2_adj <- summary(modello_finale)$adj.r.squared
# RMSE
rmse <- sqrt(mean(residuals(modello_finale)^2))
# MAE (errore assoluto medio, metrica complementare)
mae <- mean(abs(residuals(modello_finale)))
data.frame(
Metrica = c("R²", "R² aggiustato", "RMSE (g)", "MAE (g)"),
Valore = c(round(r2, 4), round(r2_adj, 4), round(rmse, 2), round(mae, 2))
) %>%
kable(caption = "Metriche di qualità del modello finale") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Metrica | Valore |
|---|---|
| R² | 0.7269 |
| R² aggiustato | 0.7264 |
| RMSE (g) | 274.4200 |
| MAE (g) | 211.0600 |
La regressione lineare si fonda su quattro assunzioni sui residui:
linearità, normalità, omoschedasticità (varianza costante) e
indipendenza.
Verifico ognuna di esse con i quattro grafici diagnostici prodotti da
R:
par(mfrow = c(2, 2))
plot(modello_finale)
par(mfrow = c(1, 1))
Affianco al QQ-plot un test formale. Data la numerosità elevata, utilizzo sia un grafico sia gli indici di forma:
residui <- residuals(modello_finale)
# Istogramma dei residui
ggplot(data.frame(residui), aes(x = residui)) +
geom_histogram(bins = 40, fill = "steelblue", color = "white") +
labs(title = "Distribuzione dei residui del modello",
x = "Residui (grammi)",
y = "Frequenza") +
theme_minimal()
# Indici di forma dei residui
data.frame(
Indice = c("Media", "Deviazione standard", "Skewness", "Curtosi"),
Valore = c(
round(mean(residui), 4),
round(sd(residui), 2),
round(skewness(residui), 4),
round(kurtosis(residui), 4)
)
) %>%
kable(caption = "Indici di forma dei residui") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Indice | Valore |
|---|---|
| Media | 0.0000 |
| Deviazione standard | 274.4800 |
| Skewness | 0.6967 |
| Curtosi | 7.1544 |
Verifico ora la presenza di osservazioni che influenzano in modo sproporzionato le stime del modello, utilizzando la distanza di Cook.
cook <- cooks.distance(modello_finale)
# Soglia convenzionale: 4/n
soglia_cook <- 4 / nrow(neonati)
# Grafico della distanza di Cook
plot(cook, type = "h",
main = "Distanza di Cook per osservazione",
xlab = "Indice osservazione",
ylab = "Distanza di Cook")
abline(h = soglia_cook, col = "red", lty = 2)
cook <- cooks.distance(modello_finale)
soglia_cook <- 4 / nrow(neonati)
# Osservazioni oltre la soglia
n_influenti <- sum(cook > soglia_cook)
n_influenti
## [1] 124
# 5 osservazioni più influenti
neonati %>%
mutate(cook = cook) %>%
arrange(desc(cook)) %>%
head(5) %>%
select(ID, Gestazione, Peso, Lunghezza, Cranio, Sesso, cook) %>%
kable(digits = 4, caption = "Osservazioni più influenti (distanza di Cook)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| ID | Gestazione | Peso | Lunghezza | Cranio | Sesso | cook |
|---|---|---|---|---|---|---|
| 1551 | 38 | 4370 | 315 | 374 | F | 0.8295 |
| 310 | 28 | 1560 | 420 | 379 | F | 0.0675 |
| 1780 | 25 | 900 | 325 | 253 | F | 0.0335 |
| 155 | 36 | 3610 | 410 | 330 | M | 0.0303 |
| 928 | 28 | 830 | 310 | 254 | F | 0.0297 |
Capacità predittiva: il modello finale spiega il 72.7% della variabilità del peso (R² aggiustato = 0.7264), con un errore tipico di previsione (RMSE) di circa 274 grammi. Su un peso medio di 3284 g, l’errore relativo è dell’ordine dell’8%, un risultato soddisfacente per un modello clinico. Il fatto che il MAE (211 g) sia inferiore all’RMSE (274 g) segnala la presenza di alcuni errori di previsione elevati, concentrati sui casi clinici estremi.
Analisi dei residui: i grafici diagnostici mostrano un modello complessivamente adeguato. - La relazione è sostanzialmente lineare (grafico Residuals vs Fitted). - I residui hanno media nulla e sono ben distribuiti nella parte centrale. - Si osserva tuttavia una coda destra pesante nel QQ-plot, confermata dall’istogramma e dagli indici di forma (skewness = 0.70, curtosi = 7.15, ben superiore al valore 3 di una distribuzione normale). - È presente una lieve eteroschedasticità (varianza dei residui leggermente crescente per i valori predetti più alti).
Queste violazioni delle assunzioni di normalità e omoschedasticità sono moderate e localizzate nelle code, dovute ai casi clinici estremi (i neonati prematuri con peso molto basso). Grazie all’elevata numerosità campionaria (n = 2497), il Teorema del Limite Centrale garantisce comunque l’affidabilità delle stime dei coefficienti e dei relativi test.
Valori influenti: l’analisi della distanza di Cook ha rivelato un’osservazione nettamente anomala, l’ID 1551, con un valore (0.83) significativamente diverso da tutte le altre. Esaminando i suoi dati emerge un’incoerenza fisica: a fronte di una gestazione a termine (38 settimane) e di un peso elevato (4370 g), la lunghezza registrata è di soli 315 mm (31.5 cm), valore incompatibile con un neonato di quel peso (atteso ~520 mm).
Si tratta quasi certamente di un errore di data entry sfuggito alla pulizia preliminare, che aveva controllato età materna e numero di gravidanze ma non la coerenza interna tra peso e dimensioni. È importante notare la differenza rispetto agli altri casi influenti (ID 310, 1780, 928), che sono invece prematuri reali (gestazione breve, peso basso, valori coerenti tra loro) con distanze di Cook molto inferiori: questi rappresentano casi clinici autentici e vanno mantenuti.
Alla luce di questa scoperta, nella sezione seguente procedo alla rimozione dell’osservazione 1551 e alla stima del modello sul dataset corretto, dopo aver confermato che sia effettivamente anomala rispetto alle altre di neonati con peso e gestazione simili:
# L'osservazione anomala
neonati %>%
filter(ID == 1551) %>%
select(ID, Gestazione, Peso, Lunghezza, Cranio, Sesso) %>%
kable(caption = "Osservazione ID 1551") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| ID | Gestazione | Peso | Lunghezza | Cranio | Sesso |
|---|---|---|---|---|---|
| 1551 | 38 | 4370 | 315 | 374 | F |
# Confronto con neonati di peso elevato e gestazione a termine
neonati %>%
filter(Peso > 4200, Gestazione >= 37) %>%
arrange(desc(Peso)) %>%
select(ID, Gestazione, Peso, Lunghezza, Cranio, Sesso) %>%
kable(caption = "Neonati con peso > 4200g e gestazione a termine") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
scroll_box(height = "300px")
| ID | Gestazione | Peso | Lunghezza | Cranio | Sesso |
|---|---|---|---|---|---|
| 1920 | 39 | 4930 | 550 | 350 | F |
| 1306 | 41 | 4900 | 510 | 352 | F |
| 1433 | 41 | 4810 | 530 | 364 | M |
| 1639 | 40 | 4760 | 550 | 365 | F |
| 2076 | 40 | 4720 | 540 | 360 | F |
| 2392 | 40 | 4720 | 540 | 355 | M |
| 1963 | 42 | 4700 | 540 | 362 | F |
| 2235 | 41 | 4690 | 540 | 373 | M |
| 126 | 41 | 4680 | 545 | 370 | M |
| 2023 | 39 | 4650 | 510 | 354 | M |
| 1513 | 40 | 4620 | 560 | 367 | M |
| 368 | 41 | 4600 | 540 | 370 | M |
| 1293 | 38 | 4600 | 485 | 380 | M |
| 1838 | 40 | 4580 | 515 | 360 | M |
| 329 | 40 | 4560 | 540 | 340 | M |
| 2129 | 42 | 4550 | 540 | 365 | M |
| 1541 | 38 | 4540 | 530 | 343 | M |
| 1440 | 40 | 4480 | 540 | 375 | M |
| 1775 | 40 | 4480 | 530 | 380 | M |
| 420 | 40 | 4470 | 530 | 370 | F |
| 273 | 40 | 4440 | 540 | 370 | F |
| 791 | 41 | 4440 | 510 | 335 | M |
| 140 | 41 | 4420 | 530 | 362 | F |
| 1113 | 40 | 4420 | 520 | 380 | F |
| 303 | 41 | 4410 | 545 | 360 | M |
| 397 | 40 | 4410 | 540 | 370 | M |
| 428 | 40 | 4400 | 550 | 360 | M |
| 556 | 42 | 4400 | 530 | 370 | M |
| 2391 | 41 | 4400 | 565 | 366 | F |
| 2403 | 41 | 4400 | 535 | 357 | F |
| 1551 | 38 | 4370 | 315 | 374 | F |
| 1962 | 38 | 4370 | 530 | 340 | F |
| 165 | 40 | 4350 | 550 | 360 | M |
| 277 | 41 | 4350 | 530 | 372 | M |
| 1060 | 41 | 4350 | 540 | 360 | M |
| 1321 | 40 | 4340 | 515 | 383 | M |
| 59 | 40 | 4330 | 540 | 355 | M |
| 1036 | 40 | 4330 | 500 | 355 | F |
| 1085 | 39 | 4330 | 535 | 360 | F |
| 1222 | 40 | 4330 | 550 | 372 | M |
| 274 | 39 | 4320 | 530 | 350 | M |
| 513 | 41 | 4320 | 545 | 357 | F |
| 996 | 41 | 4320 | 535 | 367 | M |
| 478 | 42 | 4310 | 505 | 365 | M |
| 1404 | 41 | 4310 | 540 | 370 | F |
| 1745 | 40 | 4310 | 525 | 378 | M |
| 2046 | 40 | 4300 | 530 | 360 | M |
| 65 | 41 | 4290 | 530 | 367 | M |
| 1459 | 41 | 4290 | 525 | 360 | M |
| 2322 | 41 | 4290 | 525 | 360 | M |
| 1942 | 41 | 4280 | 545 | 365 | M |
| 2323 | 41 | 4280 | 530 | 370 | M |
| 375 | 38 | 4270 | 510 | 353 | M |
| 1826 | 41 | 4270 | 550 | 360 | M |
| 129 | 39 | 4260 | 540 | 345 | M |
| 1267 | 39 | 4260 | 545 | 343 | F |
| 1390 | 39 | 4260 | 520 | 367 | F |
| 1573 | 40 | 4260 | 540 | 366 | F |
| 565 | 40 | 4250 | 520 | 386 | F |
| 1283 | 40 | 4250 | 550 | 376 | F |
| 1885 | 40 | 4250 | 530 | 355 | M |
| 2000 | 41 | 4250 | 525 | 366 | M |
| 2343 | 39 | 4250 | 520 | 350 | M |
| 130 | 39 | 4240 | 485 | 352 | M |
| 184 | 41 | 4240 | 520 | 369 | F |
| 666 | 40 | 4240 | 555 | 345 | F |
| 958 | 40 | 4240 | 550 | 354 | F |
| 1491 | 42 | 4230 | 540 | 355 | M |
| 1370 | 41 | 4220 | 545 | 341 | F |
| 1533 | 40 | 4220 | 540 | 354 | M |
| 1583 | 40 | 4220 | 510 | 375 | M |
| 2442 | 41 | 4220 | 545 | 374 | M |
Filtrando tutti i neonati con peso superiore ai 4200 grammi e gestazione quasi a termine le lunghezze trovate oscillano tra i 485 e i 550 mm, mentre l’osservazione 1551 riporta 315 mm, un valore clinicamente non plausibile per un peso di 4370 grammi. Lo considero quindi un errore di data entry e lo rimuovo :
neonati_clean <- neonati %>% filter(ID != 1551)
nrow(neonati_clean)
## [1] 2496
Il dataset corretto contiene ora 2496 osservazioni.
Stimo il modello finale sul dataset corretto:
modello_finale_clean <- lm(Peso ~ N.gravidanze + Gestazione + Lunghezza +
Cranio + Sesso,
data = neonati_clean)
summary(modello_finale_clean)
##
## Call:
## lm(formula = Peso ~ N.gravidanze + Gestazione + Lunghezza + Cranio +
## Sesso, data = neonati_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1165.67 -179.88 -12.43 163.07 1410.65
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6683.6269 133.2063 -50.175 < 0.0000000000000002 ***
## N.gravidanze 13.1390 4.2590 3.085 0.00206 **
## Gestazione 29.6347 3.7376 7.929 0.00000000000000331 ***
## Lunghezza 10.8896 0.3019 36.067 < 0.0000000000000002 ***
## Cranio 9.9190 0.4228 23.459 < 0.0000000000000002 ***
## SessoM 78.1219 10.9964 7.104 0.00000000000157198 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 269.4 on 2490 degrees of freedom
## Multiple R-squared: 0.7371, Adjusted R-squared: 0.7366
## F-statistic: 1396 on 5 and 2490 DF, p-value: < 0.00000000000000022
data.frame(
Modello = c("Con osservazione 1551", "Senza osservazione 1551"),
R2_adj = c(round(summary(modello_finale)$adj.r.squared, 4),
round(summary(modello_finale_clean)$adj.r.squared, 4)),
RMSE = c(round(sqrt(mean(residuals(modello_finale)^2)), 2),
round(sqrt(mean(residuals(modello_finale_clean)^2)), 2)),
Residuo_max = c(round(max(abs(residuals(modello_finale))), 2),
round(max(abs(residuals(modello_finale_clean))), 2))
) %>%
kable(caption = "Confronto del modello prima e dopo la rimozione dell'osservazione anomala") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Modello | R2_adj | RMSE | Residuo_max |
|---|---|---|---|
| Con osservazione 1551 | 0.7264 | 274.42 | 2639.03 |
| Senza osservazione 1551 | 0.7366 | 269.08 | 1410.65 |
Dal primo confronto la rimozione di una sola osservazione: -migliora R² di un punto percentuale -dimezza il residuo massimo che ora vale 1411 g -riduce di 5 g il RMSE (riduzione modesta trattandosi di una media su tutte le osservazioni)
Verifico ora che la rimozione abbia migliorato la diagnostica dei residui.
par(mfrow = c(2, 2))
plot(modello_finale_clean)
par(mfrow = c(1, 1))
residui_clean <- residuals(modello_finale_clean)
data.frame(
Statistica = c("Skewness", "Curtosi"),
Prima = c(round(skewness(residuals(modello_finale)), 4),
round(kurtosis(residuals(modello_finale)), 4)),
Dopo = c(round(skewness(residui_clean), 4),
round(kurtosis(residui_clean), 4))
) %>%
kable(caption = "Forma dei residui prima e dopo la correzione") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Statistica | Prima | Dopo |
|---|---|---|
| Skewness | 0.6967 | 0.3821 |
| Curtosi | 7.1544 | 4.0759 |
La rimozione dell’osservazione anomala ha migliorato sensibilmente la forma dei residui:
I nuovi grafici diagnostici confermano il miglioramento: il QQ-plot mostra residui che seguono la diagonale per quasi tutta l’estensione, con deviazioni minime alle estremità; il grafico Residuals vs Leverage non presenta più punti con distanza di Cook elevata.
Le lievi deviazioni residue dalla normalità (code ancora leggermente più pesanti del caso teorico) sono ora attribuibili ai casi clinici estremi autentici — i neonati prematuri con peso molto basso — che restano correttamente nel dataset in quanto rappresentativi del fenomeno reale. Considerando l’elevata numerosità campionaria, il modello può ritenersi adeguato e robusto per gli scopi predittivi e interpretativi del progetto.
Ora che il modello è pronto, come richiede il progetto, passo alla sua validazione testandolo su un caso specifico dato in input.
Il progetto richiede di stimare il peso di una neonata (Sesso = F), con madre alla terza gravidanza (N.gravidanze = 3) e parto alla 39ª settimana di gestazione.
I valori di lunghezza e cranio, non forniti, verranno sostituiti con le medie campionarie delle neonate femmine.
# valori medi di Lunghezza e Cranio per le femmine
medie_F <- neonati_clean %>%
filter(Sesso == "F") %>%
summarise(
Lunghezza_media = mean(Lunghezza),
Cranio_medio = mean(Cranio)
)
medie_F
## # A tibble: 1 × 2
## Lunghezza_media Cranio_medio
## <dbl> <dbl>
## 1 490. 338.
# Caso da predire
nuovo_caso <- data.frame(
N.gravidanze = 3,
Gestazione = 39,
Lunghezza = medie_F$Lunghezza_media,
Cranio = medie_F$Cranio_medio,
Sesso = factor("F", levels = levels(neonati_clean$Sesso))
)
previsione <- predict(modello_finale_clean, newdata = nuovo_caso,
interval = "prediction", level = 0.95)
previsione
## fit lwr upr
## 1 3195.268 2666.493 3724.043
Il modello stima per questa neonata un peso di circa 3195 grammi.
Per rendere l’analisi più completa confronto diverse predizioni ottenute variando sesso e settimane di gestazione.
scenari <- data.frame(
Descrizione = c("Femmina, 39 sett", "Maschio, 39 sett",
"Femmina, 32 sett (pretermine)", "Femmina, 41 sett (post-termine)"),
N.gravidanze = c(3, 3, 3, 3),
Gestazione = c(39, 39, 32, 41),
Lunghezza = c(medie_F$Lunghezza_media,
mean(neonati_clean$Lunghezza[neonati_clean$Sesso == "M"]),
medie_F$Lunghezza_media, medie_F$Lunghezza_media),
Cranio = c(medie_F$Cranio_medio,
mean(neonati_clean$Cranio[neonati_clean$Sesso == "M"]),
medie_F$Cranio_medio, medie_F$Cranio_medio),
Sesso = factor(c("F", "M", "F", "F"), levels = levels(neonati_clean$Sesso))
)
# Previsioni per tutti gli scenari
pred_scenari <- predict(modello_finale_clean, newdata = scenari,
interval = "prediction", level = 0.95)
# Tabella riassuntiva
scenari %>%
mutate(
Peso_previsto = round(pred_scenari[, "fit"], 0),
IC_inf = round(pred_scenari[, "lwr"], 0),
IC_sup = round(pred_scenari[, "upr"], 0)
) %>%
select(Descrizione, Gestazione, Sesso, Peso_previsto, IC_inf, IC_sup) %>%
kable(caption = "Peso previsto in diversi scenari (intervallo di predizione 95%)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Descrizione | Gestazione | Sesso | Peso_previsto | IC_inf | IC_sup |
|---|---|---|---|---|---|
| Femmina, 39 sett | 39 | F | 3195 | 2666 | 3724 |
| Maschio, 39 sett | 39 | M | 3428 | 2899 | 3957 |
| Femmina, 32 sett (pretermine) | 32 | F | 2988 | 2457 | 3519 |
| Femmina, 41 sett (post-termine) | 41 | F | 3255 | 2725 | 3784 |
Per la neonata richiesta dal progetto (femmina, terza gravidanza, 39 settimane, dimensioni medie), il modello stima un peso di 3195 grammi, con intervallo di predizione al 95% pari a [2666 ; 3724]. Il valore puntuale è clinicamente plausibile e coerente con il peso atteso per una neonata a termine.
L’ampiezza dell’intervallo di predizione (±530 g circa) merita attenzione: riflette la variabilità individuale intrinseca dei neonati. Il modello stima con buona precisione il peso medio di neonati con date caratteristiche, ma il peso del singolo individuo resta soggetto a una variabilità considerevole, dovuta a fattori non inclusi nel modello (genetici, nutrizionali, ambientali). Questo non è un limite del modello ma una caratteristica intrinseca del fenomeno.
Il confronto tra scenari illustra il funzionamento del modello e a tale scopo sono state variate a turno le feature per valutarne il singolo effetto:
Allo stesso modo il terzo esempio aumenta la durata gestazionale(di 2 settimane) che porta a un aumento del peso predetto mantenendo comunque una coerenza clinica.
In sintesi, il modello fornisce previsioni clinicamente sensate e si presta bene a quantificare l’impatto delle singole variabili, mantenendo però la consapevolezza dei limiti predittivi sul singolo caso.
Nella fase finale presento le visualizzazioni più utili a cogliere le relazioni significative tra le variabili del modello.
La Lunghezza è il predittore con l’effetto più forte. Visualizzo quindi la sua relazione con il Peso, distinguendo tra i due sessi.
ggplot(neonati_clean, aes(x = Lunghezza, y = Peso, color = Sesso)) +
geom_point(alpha = 0.3, size = 1) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Peso in funzione della Lunghezza, per sesso",
x = "Lunghezza (mm)",
y = "Peso (g)",
color = "Sesso") +
theme_minimal()
La durata della gestazione è un’altra delle variabili di interesse del progetto. Visualizzo pertanto la relazione con il Peso.
ggplot(neonati_clean, aes(x = Gestazione, y = Peso)) +
geom_point(alpha = 0.3, size = 1, color = "steelblue") +
geom_smooth(method = "lm", color = "darkred", se = TRUE) +
labs(title = "Peso in funzione della settimana di gestazione",
x = "Settimane di gestazione",
y = "Peso (g)") +
theme_minimal()
Questo grafico mostra la capacità predittiva complessiva del modello: confronta i pesi reali con quelli stimati. Più i punti si allineano sulla diagonale, migliore è la previsione.
neonati_clean %>%
mutate(Peso_predetto = predict(modello_finale_clean)) %>%
ggplot(aes(x = Peso_predetto, y = Peso)) +
geom_point(alpha = 0.3, size = 1, color = "steelblue") +
geom_abline(intercept = 0, slope = 1, color = "darkred", linewidth = 1) +
labs(title = "Valori osservati vs valori predetti",
subtitle = "La linea rossa rappresenta la previsione perfetta (osservato = predetto)",
x = "Peso predetto (g)",
y = "Peso osservato (g)") +
theme_minimal()
I tre grafici illustrano in modo complementare il funzionamento del modello:
Peso vs Lunghezza per sesso: emerge la relazione lineare positiva più forte del modello. Le rette di regressione per maschi e femmine sono quasi parallele e molto vicine tra loro, a conferma che — a parità di lunghezza — il sesso incide solo marginalmente (coerente con il coefficiente di +78 g), mentre la lunghezza rappresenta il principale driver del peso. La nuvola di punti compatta attorno alle rette indica una buona capacità esplicativa.
Peso vs Gestazione: la relazione è positiva ma visibilmente più dispersa rispetto alla lunghezza, segno che la gestazione, pur significativa, spiega una quota minore della variabilità del peso. Si nota la forte concentrazione dei parti tra la 38ª e la 41ª settimana e la coda dei nati prima del termine effettivo.
Valori osservati vs predetti: i punti si distribuiscono attorno alla diagonale di previsione perfetta in modo compatto e simmetrico, senza pattern sistematici. Questo conferma visivamente la buona capacità predittiva del modello (R² = 0.74) e l’assenza di distorsioni evidenti lungo l’intero intervallo di pesi.
Nota: le rette tracciate nei primi due grafici rappresentano regressioni semplici sulla singola variabile, utili a visualizzare la direzione e la forza delle relazioni; non corrispondono esattamente ai coefficienti del modello multiplo, che misurano l’effetto di ciascuna variabile al netto delle altre.
L’analisi condotta sul campione di 2496 neonati (dopo la rimozione delle osservazioni errate) ha permesso di definire i fattori associati al peso neonatale e di costruire un modello predittivo affidabile.
Verifica delle ipotesi inferenziali:
Modello predittivo:
Il modello di regressione lineare multipla finale spiega il 73.7% della variabilità del peso (R² aggiustato) attraverso cinque predittori: numero di gravidanze, settimane di gestazione, lunghezza, diametro cranico e sesso. I predittori antropometrici (lunghezza e cranio) e la gestazione risultano i fattori più influenti. L’errore tipico di previsione è di circa 269 grammi.
Due risultati meritano particolare attenzione:
La diagnostica dei residui ha confermato l’adeguatezza del modello, permettendo inoltre di individuare e correggere un errore di data entry (osservazione con peso e lunghezza incompatibili) sfuggito alla pulizia del dataset preliminare. La rimozione di tale osservazione ha migliorato sia la capacità predittiva sia la normalità dei residui, senza alterare la struttura del modello.
Utilizzare il modello come strumento di screening: il modello consente di stimare il peso atteso del neonato a partire dalle misure ecografiche prenatali (lunghezza, diametro cranico) e dai dati gestazionali. Può quindi aiutare nell’identificazione precoce di neonati a rischio di basso peso, utile per la pianificazione dell’assistenza.
Concentrare le misurazioni sui predittori più informativi: lunghezza, diametro cranico e settimane di gestazione sono le variabili che meglio predicono il peso. L’attenzione andrà quindi convogliata nella raccolta di questi dati anche a discapito di quelli meno predittivi.
Migliorare i controlli di qualità sui dati: l’individuazione di un errore di data entry (peso e lunghezza incompatibili) suggerisce il bisogno di introdurre controlli automatici di coerenza al momento dell’inserimento dei dati clinici, per esempio segnalando combinazioni fisicamente non plausibili.
Approfondire l’effetto del fumo con campioni dedicati: data l’importanza clinica del fumo in gravidanza ma la sua bassa numerosità in questo campione, si raccomanda uno studio mirato con una rappresentanza adeguata di fumatrici, per quantificarne correttamente l’impatto.
Interpretare le previsioni con consapevolezza dei limiti: il modello stima accuratamente il peso medio, ma il singolo neonato presenta una variabilità individuale ampia (intervallo di predizione ±530 g). Le stime vanno quindi trattate come tendenza e non come valori certi.
In conclusione, il progetto ha prodotto un modello solido e interpretabile per la previsione del peso neonatale, con applicazioni concrete per il supporto alle decisioni cliniche, pur nei limiti propri di un’analisi che verte su un singolo campione.