library(moments)
library(knitr)
library(kableExtra)
library(patchwork)
library(ggplot2)
library(dplyr)
library(glue)
library(tidyr)
library(ggpubr)
library(car)
library(Metrics)
library(broom)
library(lmtest)
dati_neonati <- read.csv("neonati.csv", stringsAsFactors = TRUE)
kable(head(dati_neonati,10))
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 |
26 | 1 | 0 | 39 | 3100 | 480 | 345 | Nat | osp3 | F |
25 | 0 | 0 | 40 | 3580 | 510 | 349 | Nat | osp1 | M |
22 | 1 | 0 | 40 | 3670 | 500 | 335 | Ces | osp2 | F |
23 | 0 | 0 | 41 | 3700 | 510 | 362 | Ces | osp2 | F |
dimensioni <- data.frame(
num_entries = nrow(dati_neonati),
num_variabili = ncol(dati_neonati)
)
kable(dimensioni, col.names = c("num_entries", "num_variabili"))%>%
kable_styling(full_width = FALSE) %>%
column_spec(1, width = "5cm") %>%
column_spec(2, width = "5cm")
num_entries | num_variabili |
---|---|
2500 | 10 |
Il dataset contiene 10 variabili:
L’obiettivo di questa analisi è individuare quali di queste variabili sono più predittive del peso della nascita, con particolare attenzione all’impatto del fumo materno e delle settimane di gestazione, che potrebbero indicare nascite premature.
Per coerenza con le altre variabili qualitative trasformiamo anche Fumatrici in un Factor:
dati_neonati$Fumatrici <- factor(dati_neonati$Fumatrici,
levels = c(0, 1),
labels = c("NF", "F"))
Cerchiamo eventuali dati anomali (errori d’inserimento) nell’età della madre, facciamo un test vedendo se ci sono età della madre al di sotto dei 10 anni e nel caso eliminiamo quella serie di dati:
kable(dati_neonati[dati_neonati$Anni.madre<10, ])
Anni.madre | N.gravidanze | Fumatrici | Gestazione | Peso | Lunghezza | Cranio | Tipo.parto | Ospedale | Sesso | |
---|---|---|---|---|---|---|---|---|---|---|
1152 | 1 | 1 | NF | 41 | 3250 | 490 | 350 | Nat | osp2 | F |
1380 | 0 | 0 | NF | 39 | 3060 | 490 | 330 | Nat | osp3 | M |
dati_neonati <- subset(dati_neonati, Anni.madre > 9)
Abbiamo eliminato dal dataset le due serie di dati anomali
Procediamo con un’analisi descrittiva delle nostre variabile per comprenderne le loro distribuzioni. Procediamo determinando gli indici di posizione, di variabilità e di forma per le nostre variabile: Anni.madre, N.gravidanze, Gestazione, Peso, Lunghezza, Cranio
# Definizione funzione per il calcolo del coefficiente di variazione
CV <- function(x){
return (sd(x)/mean(x))*100
}
# Lista delle colonne da analizzare
columns <- c("Anni.madre", "N.gravidanze", "Gestazione", "Peso", "Lunghezza", "Cranio")
# Creazione di una lista per salvare i risultati
results <- list()
# Ciclo sulle colonne
for (col in columns) {
data <- dati_neonati[[col]]
# Calcola le statistiche base
stats <- round(summary(data),2)
# Calcola ulteriori statistiche
iqr <- round(IQR(data),2)
varianza <- round(var(data),2)
sd <- round(sd(data),2)
cv <- round(CV(data),2)
skew <- round(skewness(data),2)
kurt <- round(kurtosis(data),2)-3
# Combina tutte le statistiche in una riga
stats_row <- c(stats, IQR = iqr, Varianza = varianza, SD = sd, CV = cv, Skewness = skew, Kurtosis = kurt)
results[[col]] <- as.data.frame(t(stats_row))
}
# Combina i risultati in un unico data.frame
results_table <- do.call(rbind, results)
results_table <- cbind(Variable = rownames(results_table), results_table)
rownames(results_table) <- NULL
# Aggiorna i nomi delle colonne per includere le nuove statistiche
colnames(results_table) <- c("Variable", "Min", "1st Qu.", "Median", "Mean", "3rd Qu.", "Max",
"IQR", "Variance", "SD", "CV", "Skewness", "Kurtosis")
kable(results_table)
Variable | Min | 1st Qu. | Median | Mean | 3rd Qu. | Max | IQR | Variance | SD | CV | Skewness | Kurtosis |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Anni.madre | 13 | 25 | 28 | 28.19 | 32 | 46 | 7 | 27.22 | 5.22 | 0.19 | 0.15 | -0.11 |
N.gravidanze | 0 | 0 | 1 | 0.98 | 1 | 12 | 1 | 1.64 | 1.28 | 1.30 | 2.51 | 10.98 |
Gestazione | 25 | 38 | 39 | 38.98 | 40 | 43 | 2 | 3.49 | 1.87 | 0.05 | -2.07 | 8.26 |
Peso | 830 | 2990 | 3300 | 3284.18 | 3620 | 4930 | 630 | 275865.90 | 525.23 | 0.16 | -0.65 | 2.03 |
Lunghezza | 310 | 480 | 500 | 494.70 | 510 | 565 | 30 | 693.21 | 26.33 | 0.05 | -1.51 | 6.48 |
Cranio | 235 | 330 | 340 | 340.03 | 350 | 390 | 20 | 269.93 | 16.43 | 0.05 | -0.79 | 2.94 |
Guardando questa tabella possiamo fare alcune osservazioni iniziali:
Osserviamo queste distribuzioni anche attraverso istogrammi e boxplot:
# Definizione per il calcolo del numero di bin secondo il criterio di Rice
rice_bins <- function(data) {
n <- length(data) # Numero di osservazioni
return(2 * n^(1/3))
}
#Usiamo attach per accedere più facilmente alla variabili, dovendo costruire molteplici grafici
attach(dati_neonati)
# Lista per salvare i grafici
plots <- list()
# Ciclo per generare gli istogrammi
for (col in columns) {
data <- dati_neonati[[col]]
# Calcola il numero di bin e il binwidth
if(!(col %in% c("Anni.madre", "Gestazione","N.gravidanze"))){
bins <- rice_bins(data)
binwidth <- diff(range(data, na.rm = TRUE)) / bins
# Crea l'istogramma
plot <- ggplot(dati_neonati) +
geom_histogram(aes_string(x = col),
binwidth = binwidth,
col = "black",
fill = "lightblue") +
labs(title = paste("HS per", col),
x = col,
y = "Count") +
theme_minimal()
}
else{
plot <- ggplot(dati_neonati) +
geom_bar(aes_string(x = col),
col = "black",
fill = "lightblue") +
labs(title = paste("HS per", col),
x = col,
y = "Count") +
theme_minimal()
}
# Aggiungi il grafico alla lista
plots[[col]] <- plot
}
# Disposizione dei grafici in una griglia 2x3
final_plot <- (plots[[1]] | plots[[2]] | plots[[3]]) /
(plots[[4]] | plots[[5]] | plots[[6]])
# Mostra i grafici
print(final_plot)
plots <- list()
# Ciclo per generare i boxplot
for (col in columns) {
data <- dati_neonati[[col]]
plot <- ggplot(dati_neonati) +
geom_boxplot(aes_string(y = col),
col = "black",
fill = "lightblue") +
labs(title = paste("BP per", col),
y = col) +
theme_minimal()+
theme(axis.text.x = element_blank(), # Rimuove i numeri dall'asse X
axis.ticks.x = element_blank())
# Aggiungi il grafico alla lista
plots[[col]] <- plot
}
# Disposizione dei grafici in una griglia 2x3
final_plot <- (plots[[1]] | plots[[2]] | plots[[3]]) /
(plots[[4]] | plots[[5]] | plots[[6]])
# Mostra i grafici
print(final_plot)
Dai nostri grafici osserviamo la presenza di molti outlier: nella maggior parte dei casi con valori più piccoli (generano distribuzioni con asimmetria negativa) probabilmente legati a parti prematuri o di neonati particolarmente piccoli. Andiamo adesso ad analizzare le variabili qualitative: Fumatrici, Tipo.parto, Ospedale, Sesso per vedere se il nostro dataset è bilanciato rispetto a tutte le modalità oppure no.
colums_qual <- c("Fumatrici", "Tipo.parto", "Ospedale", "Sesso")
for (col in colums_qual) {
# Calcolo delle frequenze relative
tabella <- as.data.frame(prop.table(table(dati_neonati[[col]])) * 100)
# Rinominazione delle colonne
colnames(tabella) <- c(col, "Freq (%)")
# Generazione della tabella con kable
cat(
kable(tabella, format = "html", digits = 2) %>% # `digits = 2` per mostrare solo due decimali
kable_styling(full_width = FALSE) %>%
column_spec(1, width = "5cm") %>%
column_spec(2, width = "5cm"),
sep = "\n"
)
}
Fumatrici | Freq (%) |
---|---|
NF | 95.84 |
F | 4.16 |
Tipo.parto | Freq (%) |
---|---|
Ces | 29.14 |
Nat | 70.86 |
Ospedale | Freq (%) |
---|---|
osp1 | 32.67 |
osp2 | 33.95 |
osp3 | 33.39 |
Sesso | Freq (%) |
---|---|
F | 50.24 |
M | 49.76 |
Possiamo osservare che le modalità di Fumatrici e Tipo.parto non sono bilanciate: le fumatrici nel dataset rappresentano solo il 4% e i parti cesarei circa il 30%. Per quanto riguarda gli ospedali coinvolti e il sesso il dataset risulta bilanciato.
Vogliamo verificare se in alcuni ospedali si eseguono più parti cesarei:
tabella <- dati_neonati %>%
group_by(Ospedale) %>%
summarise(
Num.parti_cesarei = sum(Tipo.parto == 'Ces'),
Num.parti_naturali = sum(Tipo.parto == 'Nat'),
Perc.parti_cesarei = 100*sum(Tipo.parto == 'Ces')/n()
)
kable(tabella, format = "html", digits = 2) %>%
kable_styling(full_width = FALSE)
Ospedale | Num.parti_cesarei | Num.parti_naturali | Perc.parti_cesarei |
---|---|---|---|
osp1 | 242 | 574 | 29.66 |
osp2 | 254 | 594 | 29.95 |
osp3 | 232 | 602 | 27.82 |
# Creare una tabella di contingenza
contingenza <- table(dati_neonati$Ospedale, dati_neonati$Tipo.parto)
# Test chi-quadrato
risultato<-chisq.test(contingenza)
cat(glue("Risultati del Test Chi-Quadrato: \n
- Statistiche del test: {round(risultato$statistic, 2)} \n
- Gradi di libertà: {risultato$parameter} \n
- P-value: {round(risultato$p.value, 2)}"))
Risultati del Test Chi-Quadrato:
Statistiche del test: 1.08
Gradi di libertà: 2
P-value: 0.58
Con un P-value di 0.58 non rifiutiamo l’ipotesi nulla e quindi non c’è evidenza statistica che in un ospedale si eseguano più parti cesarei che in un altro.
Siamo interessati a verifica se il nostro campione è rappresentativo rispetto alla popolazione. Prendiamo i dati dal sito dell’University of Rochester Medical Center: Newborn Measurements
A baby’s birth weight is an important indicator of health. The average weight for full-term babies (born between 37 and 41 weeks gestation) is about 7 pounds (3.2 kg).
Per quanto riguarda il peso abbiamo un valore medio di 3200 grammi per bambini a termine, vediamo con un boxplot se osserviamo una differenza tra bambini nati a termine, prematuri e oltre il termine:
# Creiamo una nuova variabile Categoria_Gestazione con tre categorie
dati_neonati$Categoria_Gestazione <- ifelse(dati_neonati$Gestazione < 37,
"Prematuri (<37 settimane)",
ifelse(dati_neonati$Gestazione > 41,
"Oltre il termine (>41 settimane)",
"A termine (37-41 settimane)"))
# Creiamo il boxplot
ggplot(dati_neonati) +
geom_boxplot(aes(y = Peso, color = Categoria_Gestazione), fill = "lightblue") +
geom_hline(aes(yintercept = 3200, color = "Valore di riferimento (3200g)"), linetype = "dashed", size = 1) +
labs(title = "Boxplot per Peso e Categoria di Gestazione",
y = "Peso (g)",
color = "Legenda") +
scale_color_manual(values = c("Prematuri (<37 settimane)" = "#F8766D",
"A termine (37-41 settimane)" = "#00BA38",
"Oltre il termine (>41 settimane)" = "#619CFF",
"Valore di riferimento (3200 g)" = "black")) +
theme_minimal() +
theme(legend.position = "right")+
theme(axis.text.x = element_blank(), # Rimuove i numeri dall'asse X
axis.ticks.x = element_blank())
Anche dal nostro boxplot osserviamo una differenza tra le distribuzioni del peso nelle tre categorie, verifichiamo se il campione dei neonati a termine è rappresentativo della popolazione:
format_p_value <- function(p) {
if (p < 0.00000001) {
return("< 0.00000001")
} else {
return(format(p, digits = 8))
}
}
mu0 = 3200
peso_neonati_37_41 <- Peso[dati_neonati$Categoria_Gestazione == "A termine (37-41 settimane)"]
result <- t.test(peso_neonati_37_41,
mu = mu0,
conf.level = 0.95,
alternative = "two.sided")
cat(glue("
Risultati del Test t-Student:
- Media osservata: {round(result$estimate, 0)}
- Ipotesi nulla (media attesa): {round(mu0, 2)}
- Statistica t: {round(result$statistic, 2)}
- Gradi di libertà: {round(result$parameter, 2)}
- P-value: {format_p_value(result$p.value)}
- Intervallo di confidenza al 95%: ({round(result$conf.int[1], 0)}, {round(result$conf.int[2], 0)})
"))
Risultati del Test t-Student:
La nostra distribuzione non è compatibile con il valore trovato in letteratura, infatti il valore di riferimento (3200 g) è al di fuori dell’intervallo di confidenza. Svolgiamo passaggi analoghi per la lunghezza. Dalla stessa fonte sappiamo che la lunghezza media di un neonato nato a termine è 50 cm.
ggplot(dati_neonati) +
geom_boxplot(aes(y = Lunghezza, color = Categoria_Gestazione), fill = "lightblue") +
geom_hline(aes(yintercept = 500, color = "Valore di riferimento (3200g)"), linetype = "dashed", size = 1)+
labs(title = "Boxplot per Lunghezza e Categoria di Gestazione",
y = "Lunghezza (mm)",
color = "Legenda") +
scale_color_manual(values = c("Prematuri (<37 settimane)" = "#F8766D",
"A termine (37-41 settimane)" = "#00BA38",
"Oltre il termine (>41 settimane)" = "#619CFF",
"Valore di riferimento (500 mm)" = "black")) +
theme_minimal() +
theme(legend.position = "right")+
theme(axis.text.x = element_blank(), # Rimuove i numeri dall'asse X
axis.ticks.x = element_blank())
Lu0 = 500
Lunghezza_37_41 <- Lunghezza[dati_neonati$Categoria_Gestazione == "A termine (37-41 settimane)"]
result_L <- t.test(Lunghezza_37_41,
mu = Lu0,
conf.level = 0.95,
alternative = "two.sided")
cat(glue("
Risultati del Test t-Student:
- Media osservata: {round(result_L$estimate, 0)}
- Ipotesi nulla (media attesa): {round(Lu0, 0)}
- Statistica t: {round(result_L$statistic, 2)}
- Gradi di libertà: {round(result_L$parameter, 2)}
- P-value: {format_p_value(result$p.value)}
- Intervallo di confidenza al 95%: ({round(result_L$conf.int[1], 0)}, {round(result_L$conf.int[2], 0)})
"))
Risultati del Test t-Student:
Tutti e due i test danno esito negativo per quanto riguarda la rappresentanza del nostro campione rispetto alla popolazione.
Vogliamo adesso valutare se le misure antropometriche (peso, lunghezza e diametro del cranio) mostrano delle differenze tra i due sessi, iniziamo a confrontare i loro boxplot per poi passare a svolgete il t test fra i due gruppi:
columns <- c("Peso", "Lunghezza", "Cranio")
plots <- list()
# Ciclo per generare i boxplot
for (col in columns) {
# Crea il boxplot
plot <- ggplot(dati_neonati) +
geom_boxplot(aes_string(x = "Sesso", y = col, color = "Sesso"),
fill = "lightblue") +
labs(title = paste("Boxplot per", col),
y = col,
x = "Sesso",
color = "Sesso") +
theme_minimal() +
theme(legend.position = "bottom",
axis.text.x = element_text(angle = 45, hjust = 1))
# Aggiungi il grafico alla lista
plots[[col]] <- plot
}
final_plot <- (plots[[1]] | plots[[2]] | plots[[3]])
# Mostra i grafici
print(final_plot)
I boxplot mostrano delle differenze tra le due categorie ma procediamo adesso con il t.test per vedere se c’è una dipendenza dal sesso per quanto riguarda le grandezze antropometiche:
tabella_contingenza <- dati_neonati %>%
group_by(Sesso) %>%
summarise(Peso_medio = mean(Peso),
Lun_media = mean(Lunghezza),
Di_Cr_medio = mean(Cranio))
kable(tabella_contingenza, format = "html", digits = 2) %>%
kable_styling(full_width = FALSE)
Sesso | Peso_medio | Lun_media | Di_Cr_medio |
---|---|---|---|
F | 3161.06 | 489.76 | 337.62 |
M | 3408.50 | 499.67 | 342.46 |
t_peso <- t.test(Peso ~ Sesso, data = dati_neonati)
t_lunghezza <- t.test(Lunghezza ~ Sesso, data = dati_neonati)
t_cranio <- t.test(Cranio ~ Sesso, data = dati_neonati)
# Estrai i risultati principali
risultati <- data.frame(
Variabile = c("Peso", "Lunghezza", "Cranio"),
`T-Statistic` = c(round(t_peso$statistic,2), round(t_lunghezza$statistic,2), round(t_cranio$statistic,2)),
`Gradi di Libertà` = c(round(t_peso$parameter,0), round(t_lunghezza$parameter,0), round(t_cranio$parameter,0)),
`P-Value` = sapply(c(t_peso$p.value, t_lunghezza$p.value, t_cranio$p.value), format_p_value)
)
# Mostra la tabella in un formato HTML con knitr::kable
kable(risultati, format = "html", digits = 4, align = "c") %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Variabile | T.Statistic | Gradi.di.Libertà | P.Value |
---|---|---|---|
Peso | -12.11 | 2489 | < 0.00000001 |
Lunghezza | -9.58 | 2457 | < 0.00000001 |
Cranio | -7.44 | 2489 | < 0.00000001 |
I risultati ottenuti per tutti i test mostrano che possiamo scartare in tutti i casi l’ipotesi nulla, quindi il peso, la lunghezza e il diametro del cranio mostrano una dipendenza dal sesso.
Completato lo studio delle diverse variabili, possiamo passare allo studio per la costruzione del modello che meglio possa descrivere i nostri dati. Abbiamo già visto che la nostra variabile target si presenta con una asimmetria negativa e con una tendenza leptocurtica, eseguiamo il test di Shapiro-Wilk per verificare l’ipotesi di normalità.
dati_neonati <- dati_neonati %>% select(-Categoria_Gestazione) #Eliminiamo la categoria ausiliaria che avevamo aggiunto precedentemente
n <- nrow(Peso)
ggplot(dati_neonati, aes(x = Peso)) +
geom_density(fill = "lightblue", alpha = 0.6) +
labs(title = "Grafico di Densità per Peso",
x = "Peso (g)",
y = "Densità") +
theme_minimal()
# Esegui il test di Shapiro-Wilk
shapiro_result <- shapiro.test(Peso)
# Stampa dei risultati con format.pval
cat(glue::glue("Risultati del Test di Shapiro-Wilk: \n
- W: {round(shapiro_result$statistic, 3)} \n
- P-value: {format_p_value(shapiro_result$p.value)}"))
Risultati del Test di Shapiro-Wilk:
W: 0.971
P-value: < 0.00000001
Dato il risultato del test rifiutiamo l’ipotesi di distribuzione normale, vedremo se questo avrà un impatto sull’analisi dei nostri residui. Studiamo la correlazione tra i regressori tra di loro e in particolare con la variabile target.
dati_neonati_num <- dati_neonati %>% select_if(is.numeric)
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, 1), digits = digits)[1]
txt <- paste0(prefix, txt)
if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
text(0.5, 0.5, txt, cex = 1.5)
}
#correlazioni
pairs(dati_neonati_num,lower.panel=panel.cor, upper.panel=panel.smooth)
Sia dai valori delle correlazioni che dagli scatterplot sembrerebbe che le correlazioni del Peso più importanti siano con Gestazione, Lunghezza e Cranio, si vedono anche dei possibili effetti non di primo grado.
Analizziamo attraverso boxplot la dipendenza del Peso dai regressori qualitativi Fumatrici, Tipo.parto e Ospedale, abbiamo già verificato come ci sia una dipendenza del peso dal sesso.
columns_qual <- c("Fumatrici", "Tipo.parto", "Ospedale")
plots<-list()
for (col in columns_qual){
dati_neonati[[col]] <- as.factor(dati_neonati[[col]])
plot <- ggplot(dati_neonati) +
geom_boxplot(aes_string(x = col, y = Peso, color = col),
fill = "lightblue") +
labs(title = paste("Boxplot Peso per", col),
y = "Peso (g)",
x = col,
color = col) +
theme_minimal() +
theme(legend.position = "right")+
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank())
# Aggiungi il grafico alla lista
plots[[col]] <- plot
}
final_plot <- (plots[[1]]) /
(plots[[2]]) /
(plots[[3]])
# Mostra i grafici
print(final_plot)
I boxplot non mostrano particolari dipendenze ma svolgiamo anche i corrispondenti test statistici:
# Esegui i test
t_fumatrici <- t.test(Peso ~ Fumatrici)
t_tipoparto <- t.test(Peso ~ Tipo.parto)
pairwise_ospedale <- pairwise.t.test(Peso, Ospedale, paired = FALSE,
pool.sd = TRUE,
p.adjust.method = 'bonferroni')
# Estrapola i risultati principali
risultati_test <- data.frame(
Test = c("Peso ~ Fumatrici", "Peso ~ Tipo.parto"),
Statistica = c(
round(t_fumatrici$statistic, 2),
round(t_tipoparto$statistic, 2)
),
P_Value = c(
round(t_fumatrici$p.value,2),
round(t_tipoparto$p.value,2)
)
)
# Visualizza la tabella in RMarkdown
kable(risultati_test, format = "html", digits = 3) %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped")
Test | Statistica | P_Value |
---|---|---|
Peso ~ Fumatrici | 1.04 | 0.30 |
Peso ~ Tipo.parto | -0.14 | 0.89 |
pairwise_results <- as.data.frame(pairwise_ospedale$p.value)
pairwise_results <- tibble::rownames_to_column(pairwise_results, "Ospedale")
kable(pairwise_results, format = "html", digits = 2) %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped")
Ospedale | osp1 | osp2 |
---|---|---|
osp2 | 1.00 | NA |
osp3 | 0.33 | 0.32 |
I test statistici ci dicono che non è possibile scartare l’ipotesi nulla, non ci sono differenze significative nel peso medio tra fumatrici e non, tra i vari tipi di parto e tra i neonati dei tre ospedali.
Fatte queste analisi procediamo con la costruzione del modello di regressione, ricordiamo che la richiesta che ci è stata fatta è di porre un focus particolare sull’impatto del fumo materno (vedremo se il modello confermerà, come ci aspettiamo, l’indipendenza del peso dal fumo) e delle settimane di gestazione.
#Funzione per gestire la stampa formattata dei nostri modelli
print_summary <- function(mod) {
# Estrazione dei coefficienti
coefficients <- summary(mod)$coefficients
# Separare i p-value e formattarli
p_values <- coefficients[, 4]
formatted_p_values <- sapply(p_values, format_p_value)
# Arrotondare le altre quantità della tabella a 2 cifre decimali
coeff_table <- data.frame(
Estimate = round(coefficients[, 1], 2),
Std.Error = round(coefficients[, 2], 2),
t_value = round(coefficients[, 3], 2),
`P-value` = formatted_p_values
)
# Stampare la tabella
print(kable(coeff_table, format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped"))
# Calcolare l'Adjusted R-squared
adj_r_squared <- summary(mod)$adj.r.squared
# Stampare l'Adjusted R-squared con il messaggio
cat("L'Adjusted R-squared per questo modello è:", format(adj_r_squared, digits = 2), "\n")
}
mod1 <- lm(Peso ~ . , data=dati_neonati)
# Calcolare l'Adjusted R-squared
print_summary(mod1)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6735.80 | 141.48 | -47.61 | < 0.00000001 |
Anni.madre | 0.80 | 1.15 | 0.70 | 0.48448612 |
N.gravidanze | 11.38 | 4.67 | 2.44 | 0.01484575 |
FumatriciF | -30.27 | 27.55 | -1.10 | 0.27191274 |
Gestazione | 32.58 | 3.82 | 8.53 | < 0.00000001 |
Lunghezza | 10.29 | 0.30 | 34.21 | < 0.00000001 |
Cranio | 10.47 | 0.43 | 24.57 | < 0.00000001 |
Tipo.partoNat | 29.63 | 12.09 | 2.45 | 0.014315418 |
Ospedaleosp2 | -11.09 | 13.45 | -0.82 | 0.40956172 |
Ospedaleosp3 | 28.25 | 13.51 | 2.09 | 0.03656524 |
SessoM | 77.57 | 11.19 | 6.93 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
In questo modello abbiamo considerato tutte le variabili del nostro dataset originario usando Peso come variabile target. I primi risultati ci dicono che Anni.madre è il regressore meno correlato con la variabile target, proviamo a toglierla tenendo come riferimento il risultato dell’Adjusted R-squared di 0.73.
mod2 <- update(mod1, ~.-Anni.madre)
print_summary(mod2)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6708.62 | 136.02 | -49.32 | < 0.00000001 |
N.gravidanze | 12.58 | 4.34 | 2.90 | 0.0037719398 |
FumatriciF | -30.43 | 27.55 | -1.10 | 0.2694382 |
Gestazione | 32.30 | 3.80 | 8.50 | < 0.00000001 |
Lunghezza | 10.29 | 0.30 | 34.21 | < 0.00000001 |
Cranio | 10.49 | 0.43 | 24.64 | < 0.00000001 |
Tipo.partoNat | 29.67 | 12.09 | 2.45 | 0.014200587 |
Ospedaleosp2 | -10.95 | 13.44 | -0.81 | 0.41541031 |
Ospedaleosp3 | 28.52 | 13.50 | 2.11 | 0.034735333 |
SessoM | 77.65 | 11.18 | 6.94 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
L’Adjusted R-squared non è cambiato, quindi effettivamente la variabile Anni.madre non aggiungeva varianza spiegata al modello stesso, eliminiamo la variabile Ospedale che solo nel caso dell’ospedale 3 mostra un p-value inferiore al 5% ma ancora molto alto.
mod3 <- update(mod2, ~.-Ospedale)
print_summary(mod3)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6708.81 | 136.06 | -49.31 | < 0.00000001 |
N.gravidanze | 12.99 | 4.34 | 2.99 | 0.0028077677 |
FumatriciF | -31.88 | 27.58 | -1.16 | 0.24779972 |
Gestazione | 32.60 | 3.80 | 8.57 | < 0.00000001 |
Lunghezza | 10.27 | 0.30 | 34.10 | < 0.00000001 |
Cranio | 10.50 | 0.43 | 24.64 | < 0.00000001 |
Tipo.partoNat | 30.42 | 12.10 | 2.51 | 0.012014373 |
SessoM | 78.10 | 11.20 | 6.97 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
L’Adjusted R-squared non è cambiato. Il risultato del t.test sul Peso e sulle mamme Fumatrici ci aveva mostrato che non era possibile rifiutare l’ipotesi nulla, cioè l’assenza di correlazione, proviamo, quindi, ad eliminare dal nostro modello questo regressore, anche se è una delle variabili oggetto di studio:
mod4 <- update(mod3, ~.-Fumatrici)
print_summary(mod4)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6708.02 | 136.07 | -49.30 | < 0.00000001 |
N.gravidanze | 12.74 | 4.34 | 2.94 | 0.0033607508 |
Gestazione | 32.33 | 3.80 | 8.51 | < 0.00000001 |
Lunghezza | 10.28 | 0.30 | 34.18 | < 0.00000001 |
Cranio | 10.51 | 0.43 | 24.65 | < 0.00000001 |
Tipo.partoNat | 30.16 | 12.10 | 2.49 | 0.012766914 |
SessoM | 77.92 | 11.20 | 6.96 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
L’Adjusted R-squared è sempre 0.73, quindi concludiamo che dal nostro dataset l’essere fumatrice o meno da parte della madre non ha avuto influenze sul peso del bambino. Proviamo ad eliminare dal nostro modello il regressore Tipo.parto in quando è quello che presenta il p-value più alto, seppur minore del 5%. Riflettendo sul significato di questa variabile non sembra avere un impatto sulla formazione e lo sviluppo prenatale del neonato stesso.
mod5 <- update(mod4, ~.-Tipo.parto)
print_summary(mod5)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6681.73 | 135.80 | -49.20 | < 0.00000001 |
N.gravidanze | 12.46 | 4.34 | 2.87 | 0.0041540727 |
Gestazione | 32.38 | 3.80 | 8.52 | < 0.00000001 |
Lunghezza | 10.25 | 0.30 | 34.06 | < 0.00000001 |
Cranio | 10.54 | 0.43 | 24.72 | < 0.00000001 |
SessoM | 77.98 | 11.21 | 6.96 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
Anche togliendo questa variabile l’adjusted R-squared è rimasto pari a 0.73. Per motivi analoghi proviamo a eliminare anche il regressore N.gravidanze che mostra un p-value molto maggiore degli altri, applicando il principio per cui un modello più semplice permette una maggiore interpretabilità:
mod6 <- update(mod5, ~.-N.gravidanze)
print_summary(mod6)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6651.67 | 135.60 | -49.06 | < 0.00000001 |
Gestazione | 31.33 | 3.79 | 8.27 | < 0.00000001 |
Lunghezza | 10.20 | 0.30 | 33.91 | < 0.00000001 |
Cranio | 10.67 | 0.42 | 25.13 | < 0.00000001 |
SessoM | 79.10 | 11.22 | 7.05 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
Notiamo come l’adjusted R-squared non ha subito modifiche.
Vale la pena testare se ci sono delle collinearità tra le variabile rimaste, calcolando il Variance Inflation Factor che ci darà un risultato minore di 5 in assenza di collinearità:
kable(vif(mod6), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
x | |
---|---|
Gestazione | 1.654101 |
Lunghezza | 2.070582 |
Cranio | 1.606316 |
Sesso | 1.038918 |
Il test mostra che tra i regressori selezionati non ci sono collinearità che possono influenzare in negativo il nostro modello. A partire da queste variabile verifichiamo se il nostro modello dev’essere arricchito con effetti non lineari e interazion tra le variabili.
Verifichiamo una prima ipotesi: dobbiamo introdurre effetti non lineari per i regressosi Gestazione, Lunghezza e Cranio? Gli scatterplot mostrano un possibile effetto quadratico
mod7L <- update(mod6, ~.+I(Lunghezza^2))
print_summary(mod7L)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | 153.60 | 725.07 | 0.21 | 0.83225127 |
Gestazione | 41.22 | 3.86 | 10.67 | < 0.00000001 |
Lunghezza | -19.91 | 3.17 | -6.29 | < 0.00000001 |
Cranio | 10.80 | 0.42 | 25.87 | < 0.00000001 |
SessoM | 71.34 | 11.05 | 6.45 | < 0.00000001 |
I(Lunghezza^2) | 0.03 | 0.00 | 9.55 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.74
mod7C <- update(mod6, ~.+I(Cranio^2))
print_summary(mod7C)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | 74.00 | 1153.56 | 0.06 | 0.94885607 |
Gestazione | 37.78 | 3.92 | 9.64 | < 0.00000001 |
Lunghezza | 10.44 | 0.30 | 34.62 | < 0.00000001 |
Cranio | -31.40 | 7.18 | -4.37 | 1.269182e-05 |
SessoM | 74.28 | 11.18 | 6.65 | < 0.00000001 |
I(Cranio^2) | 0.06 | 0.01 | 5.87 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.73
mod7G <- update(mod6, ~.+I(Gestazione^2))
print_summary(mod7G)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -4640.60 | 899.96 | -5.16 | 2.7142655e-07 |
Gestazione | -80.95 | 49.81 | -1.62 | 0.10429162 |
Lunghezza | 10.31 | 0.30 | 33.89 | < 0.00000001 |
Cranio | 10.77 | 0.43 | 25.25 | < 0.00000001 |
SessoM | 76.91 | 11.25 | 6.83 | < 0.00000001 |
I(Gestazione^2) | 1.50 | 0.66 | 2.26 | 0.023882927 |
L’Adjusted R-squared per questo modello è: 0.73
Per decidere quale di questi tre modelli confrontare col modello 5, eseguiamo anche il test AIC e BIC per selezionare l’effetto più importante:
# Tabella AIC
kable(AIC(mod7C, mod7G, mod7L), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
df | AIC | |
---|---|---|
mod7C | 7 | 35126.81 |
mod7G | 7 | 35156.01 |
mod7L | 7 | 35071.37 |
# Tabella BIC
kable(BIC(mod7C, mod7G, mod7L), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
df | BIC | |
---|---|---|
mod7C | 7 | 35167.58 |
mod7G | 7 | 35196.77 |
mod7L | 7 | 35112.13 |
Per entrambi i test il mod7L dove abbiamo introdotto la lunghezza al quadrato è quello col valore minore, quindi quello da preferire in ancora con i risulati dell’adjusted R-squared, facciamo anche il test ANOVA per la significatività della varianza spiegata rispetto al mod6.
# Esegui ANOVA e salva i risultati
anova_results <- anova(mod7L, mod6)
# Estrai il p-value del confronto tra i modelli
p_value <- anova_results$`Pr(>F)`[2]
formatted_p_value <- format_p_value(p_value)
# Stampa il risultato con un messaggio
cat("Il p-value del confronto tra i modelli è:", formatted_p_value, "\n")
Il p-value del confronto tra i modelli è: < 0.00000001
Anche questo test conferma che il mod7L è in grado di spiegare maggiormente i dati, prima di decidere se sceglieremo o meno il mod7L, verifichiamo se è necessario introdurre l’interazione tra le variabile Lunghezza e Cranio nel nostro modello. Considerando le variabili rimaste queste sono le uniche per cui è ragionevole considerare un effetto combinato:
mod8A <-update(mod7L, ~.+Lunghezza*Cranio)
print_summary(mod8A)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -2299.20 | 1005.07 | -2.29 | 0.022244732 |
Gestazione | 39.14 | 3.90 | 10.04 | < 0.00000001 |
Lunghezza | -20.99 | 3.17 | -6.61 | < 0.00000001 |
Cranio | 27.38 | 4.74 | 5.78 | < 0.00000001 |
SessoM | 73.20 | 11.04 | 6.63 | < 0.00000001 |
I(Lunghezza^2) | 0.04 | 0.00 | 8.98 | < 0.00000001 |
Lunghezza:Cranio | -0.03 | 0.01 | -3.52 | 0.00044696936 |
L’Adjusted R-squared per questo modello è: 0.74
mod8B <- update(mod6, ~.+Lunghezza*Cranio)
print_summary(mod8B)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -1840.74 | 1019.67 | -1.81 | 0.071160332 |
Gestazione | 36.97 | 3.95 | 9.35 | < 0.00000001 |
Lunghezza | -0.20 | 2.21 | -0.09 | 0.92716773 |
Cranio | -4.40 | 3.20 | -1.38 | 0.16820293 |
SessoM | 74.47 | 11.21 | 6.64 | < 0.00000001 |
Lunghezza:Cranio | 0.03 | 0.01 | 4.76 | 2.0464178e-06 |
L’Adjusted R-squared per questo modello è: 0.73
Osserviamo che aver introdotto questa interazione non ha modificato il nostro adjusted R-squared (rimasto a 0.74) quindi decidiamo di scartarla. Selezioniamo il nostro modello tra quelli creati (da mod1 a mod6 più mod7L) attraverso il criterio di Informazione di Akaike (AIC) e quello di Bayes (BIC):
# Tabella AIC
kable(AIC(mod1,mod2,mod3,mod4,mod5,mod6,mod7L), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
df | AIC | |
---|---|---|
mod1 | 12 | 35145.57 |
mod2 | 11 | 35144.06 |
mod3 | 9 | 35149.33 |
mod4 | 8 | 35148.67 |
mod5 | 7 | 35152.89 |
mod6 | 6 | 35159.12 |
mod7L | 7 | 35071.37 |
# Tabella BIC
kable(BIC(mod1,mod2,mod3,mod4,mod5,mod6,mod7L), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
df | BIC | |
---|---|---|
mod1 | 12 | 35215.45 |
mod2 | 11 | 35208.12 |
mod3 | 9 | 35201.73 |
mod4 | 8 | 35195.25 |
mod5 | 7 | 35193.65 |
mod6 | 6 | 35194.06 |
mod7L | 7 | 35112.13 |
Per entrambi i criteri il mod7L è quello che descrive al meglio i nostri dati, ristampiamo il summary per poter commentare i coefficienti dei nostri regressori:
print_summary(mod7L)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | 153.60 | 725.07 | 0.21 | 0.83225127 |
Gestazione | 41.22 | 3.86 | 10.67 | < 0.00000001 |
Lunghezza | -19.91 | 3.17 | -6.29 | < 0.00000001 |
Cranio | 10.80 | 0.42 | 25.87 | < 0.00000001 |
SessoM | 71.34 | 11.05 | 6.45 | < 0.00000001 |
I(Lunghezza^2) | 0.03 | 0.00 | 9.55 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.74
Verifichiamo la presenza o meno di collinearità per il modello scelto:
kable(round(vif(mod7L),2), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
x | |
---|---|
Gestazione | 1.78 |
Lunghezza | 237.72 |
Cranio | 1.61 |
Sesso | 1.04 |
I(Lunghezza^2) | 229.68 |
Il risultato non è preoccupante perché è normale che Lunghezza e Lunghezza al quadrato mostrino una collinearità, mentre le altre grandezze mostrano valori perfettamente nella norma.
Valutiamo la qualità del nostro modello attraverso alcune metrice:
# Valori predetti
predicted <- predict(mod7L, dati_neonati)
# Valori osservati
observed <- dati_neonati$Peso
# RMSE
rmse_value <- rmse(observed, predicted)
cat("RMSE del modello mod7L:", round(rmse_value, 2))
RMSE del modello mod7L: 269.93
Il risultato del RMSE ci di che le nostre predizioni presenterano un’indeterminazione media di più o meno 270 g, circa l’8% rispetto al valore medio del peso del campione.
L’ultimo punto per valutare la qualità del nostro modello è l’analisi dei residui e la determinazione di valori influenti che potrebbero distorcere le nostre previsioni:
par(mfrow=c(2,2))
plot(mod4)
Dopo queste prime considerazioni vediamo più in dettaglio i nostri grafici con l’ausilio anche di test quantitativi:
Verifichiamo le caratteristiche della curva dei residui:
residui <- residuals(mod7L)
ggplot(data.frame(residui = residui), aes(x = residui)) +
geom_density(fill = "blue", alpha = 0.3) +
geom_vline(xintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Distribuzione della densità dei residui",
x = "Residui",
y = "Densità") +
theme_minimal()
shapiro <- shapiro.test(residuals(mod7L))
bp <- bptest(mod7L)
dw <- dwtest(mod7L)
# Creazione di un data frame per la tabella
test_results <- data.frame(
Test = c("Shapiro-Wilk Test", "Breusch-Pagan Test", "Durbin-Watson Test"),
Statistic = round(c(shapiro$statistic, bp$statistic, dw$statistic), 3),
`P-Value` = sapply(c(shapiro$p.value, bp$p.value, dw$p.value), format_p_value)
)
# Stampa della tabella
kable(test_results, format = "html", col.names = c("Test", "Statistic", "P-Value")) %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped")
Test | Statistic | P-Value | |
---|---|---|---|
W | Shapiro-Wilk Test | 0.986 | < 0.00000001 |
BP | Breusch-Pagan Test | 126.080 | < 0.00000001 |
DW | Durbin-Watson Test | 1.951 | 0.10831412 |
I nostri residui non soddisfano l’ipotesi di distribuzione normale (Shapito Test), mostrano eteroschedasticità, non hanno una varianza costante (Breusch-Pagan Test) ma non presentano autocorrelazione (Durbin-Watson test).
Cerchiamo i leverages e gli outliers:
lev <- hatvalues(mod7L)
p =sum(lev)
n=length(Peso)
soglia <- 2 * p / n
lev_data <- data.frame(Index = seq_along(lev), Leverage = lev)
ggplot(lev_data, aes(x = Index, y = Leverage)) +
geom_point(color = "blue", size = 2) +
geom_hline(yintercept = soglia, color = "red", linetype = "dashed") +
labs(title = "Grafico dei valori di leverage",
x = "Indice delle osservazioni",
y = "Valori di leverage") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))
length(lev[lev>soglia])
[1] 132
Tramite il grafico dei leverages e il filtro sulla soglia abbiamo individuato 132 valori leverages, cioè 132 dati che nello spazio dei regressori hanno valori molto distanti da quelli medi. Vediamo come si comportano gli outliers:
resid_student <- rstudent(mod7L)
resid_data <- data.frame(Index = seq_along(resid_student), Residuals = resid_student)
ggplot(resid_data, aes(x = Index, y = Residuals)) +
geom_point(color = "blue", size = 2) + # Punti per i residui studentizzati
geom_hline(yintercept = c(-2, 2), color = "red", linetype = "dashed") + # Linee soglia
labs(title = "Grafico dei residui studentizzati",
x = "Indice delle osservazioni",
y = "Residui studentizzati") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5)) # Centra il titolo
# Eseguire il test degli outlier
outliers <- outlierTest(mod7L)
# Controlla se ci sono risultati
if (!is.null(outliers)) {
# Estrarre i dati rilevanti dall'oggetto outlierTest
outliers_df <- data.frame(
Row = names(outliers$rstudent), # Nomi delle righe (gli indici)
`Bonferroni P` = sapply(outliers$bonf.p, format_p_value) # P-value di Bonferroni
)
# Stampare la tabella formattata senza duplicare i numeri
kable(outliers_df, format = "html", digits = 3, row.names = FALSE) %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped")
} else {
cat("Non sono stati rilevati outlier.")
}
Row | Bonferroni.P |
---|---|
1551 | < 0.00000001 |
1306 | 0.004660181 |
155 | 0.0082057635 |
1399 | 0.044339766 |
1694 | 0.045897464 |
Anche se il grafico dei residui studentizzati mostrerebbe più outliers, tramite l’outlierTest individuiamo quelli che sono statisticamente significativi e sono i seguenti: 1551, 1306, 155, 1399 e 1694 i primi 3 gli avevamo incontrati anche nei 4 scatterplot dei residui. Facciamo un ultimo test: quello della distanza di Cook per verifica se ci sono dati sperimentali che hanno un forte impatto sul nostro modello.
cook <- cooks.distance(mod7L)
plot(cook)
indice_max_cook <- which.max(cook)
indice_originale <- rownames(dati_neonati)[indice_max_cook]
# Estrai la riga corrispondente
riga <- dati_neonati[indice_max_cook, ]
# Crea un messaggio formattato con entrambi gli indici
messaggio <- glue(
"Dati per la posizione corrente {indice_max_cook} (indice originale: {indice_originale}): \n
Gestazione: {riga$Gestazione} \n
Peso: {riga$Peso} \n
Lunghezza: {riga$Lunghezza} \n
Cranio: {riga$Cranio} \n
Sesso: {riga$Sesso}"
)
# Stampa i dati
cat(messaggio)
Dati per la posizione corrente 1549 (indice originale: 1551):
Gestazione: 38
Peso: 4370
Lunghezza: 315
Cranio: 374
Sesso: F
Osserviamo che il dato che occupa la posizione 1549 è il dato 1551 (a causa dei due dati anomali cancellati all’inizio). Utilizzando la piattaforma presente nel sito Valutazione antropometrica neonatale che permette di calcolare i centili di appartenenza scopriamo che il neonato del dato 1551(numerazione del dataset originario) sarebbe al 100 centile per il Peso e allo 0 per la Lunghezza, addirittura non è previsto un Cranio con tale diametro, possiamo quindi concludere che questo dato è stato affetto da qualche errore durante la registrazione quindi può essere eliminato. Riaggiorniamo i parametri del modello una volta eliminata la misura, questo sarà il nostro modello definitivo:
dati_neonati <- dati_neonati[rownames(dati_neonati) != "1551", ]
mod7L <- update(mod7L, data = dati_neonati)
print_summary(mod7L)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -1666.39 | 760.26 | -2.19 | 0.028480687 |
Gestazione | 36.44 | 3.88 | 9.39 | < 0.00000001 |
Lunghezza | -11.37 | 3.35 | -3.39 | 0.00069825441 |
Cranio | 10.30 | 0.42 | 24.58 | < 0.00000001 |
SessoM | 73.58 | 10.94 | 6.72 | < 0.00000001 |
I(Lunghezza^2) | 0.02 | 0.00 | 6.66 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.74
shapiro <- shapiro.test(residuals(mod7L))
bp <- bptest(mod7L)
dw <- dwtest(mod7L)
# Creazione di un data frame per la tabella
test_results <- data.frame(
Test = c("Shapiro-Wilk Test", "Breusch-Pagan Test", "Durbin-Watson Test"),
Statistic = round(c(shapiro$statistic, bp$statistic, dw$statistic), 3),
`P-Value` = sapply(c(shapiro$p.value, bp$p.value, dw$p.value), format_p_value)
)
# Stampa della tabella
kable(test_results, format = "html", col.names = c("Test", "Statistic", "P-Value")) %>%
kable_styling(full_width = FALSE, bootstrap_options = "striped")
Test | Statistic | P-Value | |
---|---|---|---|
W | Shapiro-Wilk Test | 0.990 | < 0.00000001 |
BP | Breusch-Pagan Test | 13.578 | 0.018527103 |
DW | Durbin-Watson Test | 1.952 | 0.11448895 |
Osserviamo che i parametri del nostro modello sono leggermente cambiati pur mantenendo lo stesso adjusted R-squared. L’altro cambiamento è legato al test di Breusch-Pagan che mostra un valore pari al 2% che seppur ancora ci porta ad un rifiuto dell’ipotesi di omoschedasticità, vuol dire che i nostri residui si discostano meno da questa ipotesi.
Visto l’impatto che ha avuto sui residui aver eliminato il datto 1551, per completezza, risvolgiamo una veloce rianalisi del modello, stavolta usando la funzione stepAIC(). Il modello risultante lo confronteremo con il mod7L e usaremo il test BIC per determinare se il nostro modello è ancora adeguato.
mod1B <- lm(Peso ~., data=dati_neonati)
suppress_output <- capture.output(
stepwise.mod <- MASS::stepAIC(mod1B, direction = "both", k = log(n))
)
print_summary(stepwise.mod)
Estimate | Std.Error | t_value | P.value | |
---|---|---|---|---|
(Intercept) | -6683.83 | 133.16 | -50.19 | < 0.00000001 |
N.gravidanze | 13.14 | 4.26 | 3.09 | 0.0020417163 |
Gestazione | 29.63 | 3.74 | 7.93 | < 0.00000001 |
Lunghezza | 10.89 | 0.30 | 36.08 | < 0.00000001 |
Cranio | 9.92 | 0.42 | 23.46 | < 0.00000001 |
SessoM | 78.14 | 10.99 | 7.11 | < 0.00000001 |
L’Adjusted R-squared per questo modello è: 0.74
kable(BIC(mod7L,stepwise.mod), format = "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "condensed"))
df | BIC | |
---|---|---|
mod7L | 7 | 35046.88 |
stepwise.mod | 7 | 35081.40 |
Il risultato del test BIC conferma che il modello mod7L è ancora il più adeguato.
Usiamo il nostro modello per fare una previsione e vedere come si comporta:
nuovi_dati <- data.frame(
Gestazione = 39,
Sesso = 'F',
Lunghezza = mean(dati_neonati$Lunghezza),
Cranio = mean(dati_neonati$Cranio),
`I(Lunghezza^2)` = mean(dati_neonati$Lunghezza)^2
)
previsione <- round(predict(mod7L, newdata = nuovi_dati),0)
cat("La previsione è:", previsione)
La previsione è: 3232
Con questo esempio abbiamo verificato che il nostro modello è in grado di fare una stima del peso dati i parametri richiesi.
Mostreremo alcuni grafici che ci possono permettere di visualizzare la qualità del nostro grafico. Aggiungiamo al nostro dataset la colonna con le previsioni del nostro modello e costruiamo i grafici in funzione delle settimane di gestazione, della lunghezza e del diametro del cranio, oltre ad un grafico che fa il plot delle predizioni vs i dati dei pesi del dataset. Non mostriamo la dipendenza dal peso per non rendere i grafici poco chiari:
dati_neonati$predizione <- predict(mod7L, newdata = dati_neonati)
ggplot(dati_neonati, aes(x = Gestazione)) +
geom_point(aes(y = Peso, color = "Dati reali"), alpha = 0.6, size = 3) + # Dati reali
geom_point(aes(y = predizione, color = "Predizioni"), alpha = 0.6, size = 3) + # Predizioni
labs(title = "Peso in funzione della Gestazione",
x = "Gestazione (settimane)",
y = "Peso (g)") +
theme_minimal() +
theme(legend.position = "right") +
scale_color_manual(values = c("Dati reali" = "blue", "Predizioni" = "red")) # Colori per dati e predizioni
ggplot(dati_neonati, aes(x = Lunghezza)) +
geom_point(aes(y = Peso, color = "Dati reali"), alpha = 0.6, size = 3) + # Dati reali
geom_point(aes(y = predizione, color = "Predizioni"), alpha = 0.6, size = 3) + # Predizioni
labs(title = "Peso in funzione della Lunghezza",
x = "Lunghezza (cm)",
y = "Peso (g)") +
theme_minimal() +
theme(legend.position = "right") +
scale_color_manual(values = c("Dati reali" = "blue", "Predizioni" = "red")) # Colori per dati e predizioni
ggplot(dati_neonati, aes(x = Cranio)) +
geom_point(aes(y = Peso, color = "Dati reali"), alpha = 0.6, size = 3) + # Dati reali
geom_point(aes(y = predizione, color = "Predizioni"), alpha = 0.6, size = 3) + # Predizioni
labs(title = "Peso in funzione del Cranio",
x = "Cranio (cm)",
y = "Peso (g)") +
theme_minimal() +
theme(legend.position = "right") +
scale_color_manual(values = c("Dati reali" = "blue", "Predizioni" = "red")) # Colori per dati e predizioni
ggplot(dati_neonati, aes(x = Peso, y = predizione)) +
geom_point(color = "blue", alpha = 0.6, size = 3) + # Punti dei dati
geom_abline(intercept = 0, slope = 1, color = "red") + # Linea ideale (peso osservato = peso predetto)
labs(title = "Peso osservato vs Peso predetto",
x = "Peso osservato (g)",
y = "Peso predetto (g)") +
theme_minimal()
Come possiamo vedere dai primi 3 grafici c’è un’ottima sovrapposizione tra i dati e le previsioni del nostro modello. Nell’ultimo grafico invece abbiamo creato uno scatterplot con le nostre previsioni in funzione dei valori della variabile Peso, in un modello ideale tutti i punti del grafico dovrebbero trovarsi sulla bisettrice del primo quadrante o comunque distribuirsi equamente attorno ad essa. Da questo grafico vediamo come il nostro modello nelle code tende a sottostimare i pesi dei neonati mentre nella zona centrale ha una buona risposta.
Tramite l’analisi statistica del dataset sul peso dei neonati abbiamo ottenuto molteplci risultati:
Considerazioni sul modello selezionato: