---
title: "Regressione I (notebook 6)"
author: "Dario Trevisan"
date: "29/10/2025"
output:
  html_document:
    toc: true
    toc_depth: '3'
    df_print: paged
  html_notebook:
    toc: true
    toc_depth: 3
    toc_float: true
    theme: readable
    df_print: paged
    download_handler: true
subtitle: "Statistica II - 750AA"
---




```{r setup, include=FALSE}
#knitr::opts_chunk$set(warning = FALSE, message = FALSE, echo=FALSE) 
```


# Introduzione

In questo notebook esploreremo diverse tecniche di regressione: $k$-NN per la regressione, regressione lineare semplice, regressione polinomiale e un esempio di regressione (apparentemente) non lineare. Utilizzeremo vari dataset per illustrare i concetti.

Carichiamo delle librerie che saranno utili.

```{r}
library(caret) 
library(FNN) # pacchetto con knn per regressione (anche per classificazione)
library(ggplot2)
library(MASS)     # Per dataset reali
```

# Dataset

Come al solito consideriamo tre datasets, uno generato e due reali (uno precaricato). 

## Dataset generato

Nel primo esempio generiamo un data frame con due colonne, legate da una relazione di associazione lineare cui aggiungiamo del rumore.

```{r}
set.seed(42)

# scegliamo coefficiente angolare e intercetta
a = 3
b = 2
n <- 100
x <- rt(n, df=6)
y <- sin(x) + 0.1*rt(n, df=4)

df_generated <- data.frame(x, y)

# Visualizza il dataset generato
ggplot(df_generated, aes(x = x, y = y)) +
  geom_point() +
  ggtitle("Dataset Generato")
```

## Dataset precaricato (Boston Housing)

Il dataset Boston dal pacchetto ``MASS`` riguarda i prezzi delle case in 506 quartieri di Boston e include varie caratteristiche delle abitazioni.

```{r}

df_boston <- Boston

# Visualizza il dataset Boston
head(df_boston)
```


Selezioniamo solo alcune colonne per la visualizzazione (esplorate anche le altre features).

```{r}

# Visualizza il dataset
ggplot(Boston, aes(x = lstat, y = medv, colour=rm)) +
  geom_point() +
  ggtitle("Boston Housing Dataset")


```

È già evidente una correlazione negativa tra `lstat` (stato della popolazione nel quartiere) e `medv` (valore mediano delle case occupate da proprietari).

## Dataset Reale (Ricoveri Careggi 2012)

Usiamo i dati reali raccolti nel 2012 circa i ricoveri presso l'ospedale Careggi di Firenze scaricabili dal sito [OpenToscano](https://dati.toscana.it/dataset/ricoverati-ammessi-dimessi-e-permanenza-in-reparti-ordinari-aou-careggi-anno-2012).


```{r}
df_careggi <- read.csv("datasets/ricoverati-ammessi-dimessi-e-permanenza-in-reparti-ordinari-per-area-di-attivita.csv")

head(df_careggi)

#usiamo ggpairs per un grafico che combina nuvola di punti, densità stimata e correlogramma (in alternativa usare solo il comando plot)

library(GGally)
ggpairs(df_careggi[2:4])


```
Vediamo una perfetta correlazione tra Ammessi e Dimessi, mentre una correlazione positiva tra Ammessi (o Dimessi) e giorni accumulati di degenza presso il reparto.



# KNN per la Regressione

Per la regressione usiamo inizialmente il modello $k$-NN che si basa sulla media dei fattori di uscita dei $k$ punti più vicini a un punto test in termini di distanza euclidea delle caratteristiche di ingresso. Possiamo usare comandi da due pacchetti: ``knn.reg()`` da ``FNN`` (che funziona grossomodo come ``knn`` del pacchetto ``class``) oppure  ``train`` specificando il metodo ``knn`` e ``predict`` di ``caret``.


## Dataset generato

```{r}
library(FNN)

# scegliamo intanto un valore di k a piacere

k <- 5

knn_generated <- knn.reg(train = as.matrix(df_generated$x), test = as.matrix(x), y = df_generated$y, k = k)

df_generated_pred <- data.frame(x = x, y = knn_generated$pred)

ggplot() +
  geom_point(data = df_generated, aes(x = x, y = y)) +
  geom_line(data = df_generated_pred, aes(x = x, y = y), colour='red')+
  ggtitle(paste("Regressione KNN sul dataset generato con k =", k))
```


Per l'*allenamento* del modello (ossia la scelta del parametro $k$) conviene usare il pacchetto caret.


```{r}

# Suddivisione dei dati
train_index <- createDataPartition(df_generated$y, p = 0.7, list = FALSE)
train_set <- df_generated[train_index, ]
test_set <- df_generated[-train_index, ]

# Allenamento del modello KNN
knn_model <- train(y ~ x, data = train_set, method = "knn", tuneGrid = data.frame("k"= 1:50))
# per specificare i valori di k dare l'opzione tuneGrid=data.frame("k"=...)

plot(knn_model)
print(knn_model)
```
Per la previsione usiamo la funzione ``predict`` specificando modello e test set.

```{r}
# Previsione sul test set
knn_pred <- predict(knn_model, test_set)
 
# Calcolo dell'errore
knn_rmse <- sqrt(mean((knn_pred - test_set$y)^2))
print(paste("RMSE KNN:", knn_rmse))
```

```{r}

# plot della previsione vs fattori di uscita effettivi

df_test_pred <- data.frame(x = test_set$x, y_true = test_set$y, y_pred = knn_pred)
ggplot(df_test_pred, aes(x = x)) +
  geom_point(aes(y = y_true), colour = "black", shape=1) +
  geom_point(aes(y = y_pred), colour = "red", shape=2) +
  ggtitle("Previsioni KNN vs Valori Reali sul Test Set") +
  ylab("Valore di uscita") +
  xlab("Caratteristica di ingresso")
```

Possiamo anche considerare altri indicatori di performance sul test set (confrontiamoli con quelli della cross validation usata per determinare $k$).

```{r}

# calcola il MAE sul test set

knn_mae <- mean(abs(knn_pred - test_set$y))

# calcola il coefficiente di determinazione R^2 sul test set

ss_total <- sum((test_set$y - mean(test_set$y))^2)
ss_res <- sum((knn_pred - test_set$y)^2)
knn_r2 <- 1 - (ss_res / ss_total)

```

L`erore medio assoluto (MAE) vale `r knn_mae` e il coefficiente di determinazione $R^2$ vale `r knn_r2` (calcolati sul test set).

## Dataset Boston

Passiamo ora al dataset sulle case dei quartieri di Boston. Esso contiene molte caratteristiche, quindi possiamo valutare di effettuare una riduzione dimensionale (ad esempio tramite PCA). Ricordate anche che con i metodi basati sulla distanza è consigliato standardizzare le caratteristiche di input (non è necessario standardizzare quelle di output).

Usiamo la caratteristica ``medv`` (valore della casa mediano nel quartiere) come caratteristica di uscita.

```{r}
# usiamo il pacchetto caret per knn sul dataset boston

# Suddivisione dei dati
set.seed(42)
train_index <- createDataPartition(df_boston$medv, p = 0.7, list = FALSE)
train_set <- df_boston[train_index, ]
test_set <- df_boston[-train_index, ]

# Allenamento del modello KNN specifichiamo di centrare e scalare i dati
knn_model_boston <- train(medv ~ ., data = train_set,   preProcess = c("center", "scale"), method = "knn", tuneGrid = data.frame("k"= 1:20))

plot(knn_model_boston)
print(knn_model_boston)

```

Possiamo ora usare il modello per prevedere il valore sui quartieri di test.

```{r}

# Previsione sul test set
knn_pred_boston <- predict(knn_model_boston, test_set)
# Calcolo dell'errore di test
knn_rmse_boston <- sqrt(mean((knn_pred_boston - test_set$medv)^2))
print(paste("RMSE KNN Boston:", knn_rmse_boston))
# Coefficiente R^2 di test
ss_total_boston <- sum((test_set$medv - mean(test_set$medv))^2)
ss_res_boston <- sum((knn_pred_boston - test_set$medv)^2)
knn_r2_boston <- 1 - (ss_res_boston / ss_total_boston)
print(paste("R^2 KNN Boston:", knn_r2_boston))
```

**Esercizio:** ripetere l'analisi selezionando solamente alcune caratteristiche del dataset. Come cambia il RMSE? come cambia il coefficiente di determinazione $R^2$? Come cambia la performance del metodo senza standardizzare i dati?



Confrontiamo l'errore di training con l'errore di test al variare di $k$.

```{r}
# calcola train e test error al variare di k
train_error_boston <- numeric(20)
for (k in 1:20){
  # calcola le previsioni sul train set
  knn_pred_train <- predict(train(medv ~ ., data = train_set,   preProcess = c("center", "scale"), method = "knn", tuneGrid = data.frame("k"= k)), train_set)
  # calcola il RMSE
  train_error_boston[k] <- sqrt(mean((knn_pred_train - train_set$medv)^2))
}

# plot dei risultati della cross-validation
df_error_boston <- data.frame(k = 1:20, train_error = train_error_boston, test_error = knn_model_boston$results$RMSE)
ggplot(df_error_boston, aes(x = k)) +
  geom_line(aes(y = train_error, colour = "Training Error")) +
  geom_point(aes(y = train_error, colour = "Training Error"), shape=1) +
  geom_line(aes(y = test_error, colour = "Test Error")) +
  geom_point(aes(y = test_error, colour = "Test Error"), shape=1) +
  ylab("RMSE") +
  ggtitle("Training e Test Error al variare di k (Boston Housing)") +
  scale_colour_manual("", 
                      breaks = c("Training Error", "Test Error"),
                      values = c("Training Error"="violet", "Test Error"="blue"))+
  theme_minimal()
```

## Dataset Careggi

Consideriamo infine il dataset dei ricoveri al Careggi. L'obiettivo della regressione è prevedere il numero di Giorni Accumulati in funzione del numero di Ammessi (o Dimessi).

```{r}
# Suddivisione dei dati
set.seed(42)
train_index <- createDataPartition(df_careggi$GiorniAccumulati, p = 0.7, list = FALSE)
train_set <- df_careggi[train_index, ]
test_set <- df_careggi[-train_index, ]

# Allenamento del modello KNN
knn_model_careggi <- train(GiorniAccumulati ~ Ammessi, data = train_set, method = "knn", tuneGrid = data.frame("k"= 1:40))
plot(knn_model_careggi)
print(knn_model_careggi)

```

Il modello con $k=$ ``rknn_model_careggi$bestTune$k`` minimizza l'errore di cross-validation. Tuttavia il coefficiente $R^2=$ ``r knn_model_careggi$results$Rsquared[knn_model_careggi$results$k == knn_model_careggi$bestTune$k]`` indica che il modello spiega solo una piccola parte della varianza.


# Regressione Lineare

Passiamo ora alla regressione lineare semplice (una variabile di input). Il comando di base è ``lm()``, ma anche caret può essere usato per l'allenamento del modello.

## Dataset generato

Per il dataset generato possiamo aspettarci un'ottima aderenza al modello sottostante.

```{r}
# divisione train e test set
train_index <- createDataPartition(df_generated$y, p = 0.7, list = FALSE)
train_set <- df_generated[train_index, ]
test_set <- df_generated[-train_index, ]

# Allenamento del modello di regressione lineare semplice
lm_model_generated <- lm(y ~ ., data = train_set)

summary(lm_model_generated)
```
Dalla teoria sappiamo che il coefficiente angolare è proporzionale alla correlazione. Verifichiamolo numericamente.

```{r}

lambda <- cor(train_set$y, train_set$x)*sd(train_set$y)/sd(train_set$x)


print(paste("Coefficiente angolare stimato:", coef(lm_model_generated)[2]))
print(paste("Coefficiente angolare teorico:", lambda))
```



Possiamo effettuare ora una previsione sul test set e calcolare l'errore (RMSE).

```{r}

# Previsione sul test set
lm_pred_generated <- predict(lm_model_generated, test_set)


# Calcolo dell'errore
lm_rmse_generated <- sqrt(mean((lm_pred_generated - test_set$y)^2))
print(paste("RMSE Regressione Lineare (generato):", lm_rmse_generated))
```

Visualizziamo anche la retta di regressione.

```{r}
ggplot(df_generated, aes(x = x, y = y)) +
  geom_point() +
geom_abline(slope=lm_model_generated$coefficients[2], intercept=lm_model_generated$coefficients[1], col='red') +  ggtitle("Regressione Lineare Semplice sul Dataset Generato")
```

Come possiamo aggiungere dell'informazione riguardo all'*incertezza* della previsione? Vedremo la teoria nella prossima lezione, per il momento usiamo il comando ``predict`` con l'opzione ``interval="prediction"``.

```{r}
# Intervalli di previsione
lm_pred_conf <- predict(lm_model_generated, test_set, interval="prediction", level=0.95)

# Aggiungi delle barre di previsione al plot
df_test_pred_conf <- data.frame(x = test_set$x, y_true = test_set$y, y_pred = lm_pred_conf[,1], lwr = lm_pred_conf[,2], upr = lm_pred_conf[,3])
ggplot(df_test_pred_conf, aes(x = x)) +
  geom_point(aes(y = y_true), colour = "blue", shape=1) +
  geom_line(aes(y = y_pred), colour = "red") +
  geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.2) +
  ggtitle("Regressione Lineare con Intervalli di previsione") +
  ylab("Valore di uscita") +
  xlab("Caratteristica di ingresso")
```



## Dataset Boston housing


Consideriamo il dataset Boston Housing e la relazione tra `lstat` (percentuale della popolazione a basso reddito) e `medv` (valore mediano delle case occupate da proprietari). Usiamo stavolta il pacchetto caret.

```{r linear-regression}
# Allenamento del modello di regressione lineare semplice
set.seed(42)
train_index <- createDataPartition(Boston$medv, p = 0.7, list = FALSE)
train_set <- Boston[train_index, ]
test_set <- Boston[-train_index, ]

# usiamo caret
lm_model_boston <- train(medv ~ lstat, data = train_set, method = "lm")

print(lm_model_boston)
```

Confrontiamo i parametri trovati con i due metodi.

```{r}

summary(lm(medv ~ lstat, data = train_set))

lm_model_boston$finalModel

```

Notiamo che il coefficiente $R^2$ è peggiore di quello trovato con $k$-NN (questo è dovuto al bias del modello lineare oltre al fatto che stiamo usando una sola caratteristica: **provare ad applicare $k$-NN con il solo fattore di ingresso lstat**). Confrontiamo quindi l'errore di train con l'errore di test.

```{r}
# calcolar RMSE e R**2 sul test set
train_pred_boston <- predict(lm_model_boston, train_set)
train_rmse_boston <- sqrt(mean((train_pred_boston - train_set$medv)^2))
ss_total_train_boston <- sum((train_set$medv - mean(train_set$medv))^2)
ss_res_train_boston <- sum((train_pred_boston - train_set$medv)^2)
train_r2_boston <- 1 - (ss_res_train_boston / ss_total_train_boston)
test_pred_boston <- predict(lm_model_boston, test_set)
test_rmse_boston <- sqrt(mean((test_pred_boston - test_set$medv)^2))
ss_total_test_boston <- sum((test_set$medv - mean(test_set$medv))^2)
ss_res_test_boston <- sum((test_pred_boston - test_set$medv)^2)
test_r2_boston <- 1 - (ss_res_test_boston / ss_total_test_boston)
print(paste("RMSE Train Boston:", train_rmse_boston))
print(paste("R^2 Train Boston:", train_r2_boston))
print(paste("RMSE Test Boston:", test_rmse_boston))
print(paste("R^2 Test Boston:", test_r2_boston))
```


Procediamo comunque con la previsione sul test set e plottiamo la retta di regressione.

```{r}
# Previsione sul test set
lm_pred <- predict(lm_model_boston, test_set)

# Calcolo dell'errore
lm_rmse <- sqrt(mean((lm_pred - test_set$medv)^2))
print(paste("RMSE Regressione Lineare:", lm_rmse))

# Visualizza la regressione
ggplot(Boston, aes(x = lstat, y = medv)) +
  geom_point() +
  geom_abline(slope=lm_model_boston$finalModel$coefficients[2], intercept = lm_model_boston$finalModel$coefficients[1], col="blue")+
  ggtitle("Regressione Lineare Semplice")
```

Aggiungiamo l'intervallo di previsione (confrontare con intervallo di confidenza). 

```{r}

lm_model_boston = lm(medv ~ lstat, data = train_set)

# Intervalli di previsione
lm_pred_conf_boston <- predict(lm_model_boston, test_set, interval="prediction", level=0.95)
# Aggiungi delle barre di previsione al plot
df_test_pred_conf_boston <- data.frame(x = test_set$lstat, y_true = test_set$medv, y_pred = lm_pred_conf_boston[,1], lwr = lm_pred_conf_boston[,2], upr = lm_pred_conf_boston[,3])
ggplot(df_test_pred_conf_boston, aes(x = x)) +
  geom_point(aes(y = y_true), colour = "blue", shape=1) +
  geom_line(aes(y = y_pred), colour = "red") +
  geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.2) +
  ggtitle("Regressione Lineare Boston con Intervalli di previsione") +
  ylab("Valore di uscita") +
  xlab("Caratteristica di ingresso")
```

### Regressione polinomiale

Proviamo ad aumentare la complessità del modello mantenendo una singola caratteristica (lstat) ma usando una funzione polinomiale. Consideriamo per confronto il dataset generato (per cui non dovrebbe essere necessiario).

```{r}
# Allenamento del modello di regressione polinomiale con grado 2
poly_model_generated <- lm(y ~ x + I(x^2), data = df_generated)
summary(poly_model_generated)
```
Una alternativa tecnicamente migliore è di usare il comando ``poly()`` specificando il grado del modello polinomiale. Attenzione però che i coefficienti *non* sono direttamente quelli del polinomio naturale (il comando usa combinazioni lineari dei polinomi per migliorare la precisione).

```{r}
poly_model_generated <- lm(y ~ poly(x, degree =10), data = df_generated)
summary(poly_model_generated)

```
Vediamo che in entrambi i casi il coefficiente del termine di grado $0$ non è significativo (quindi può essere rimosso). Vedremo meglio la teoria di questo test la prossima lezione. Per il momento applichiamo la stessa strategia al datset Boston.

```{r polynomial-regression}
# Allenamento del modello di regressione polinomiale con grado 2
poly_model <- lm(medv ~ poly(lstat, degree = 10), data = train_set)

summary(poly_model)

```
Vediamo che il coefficiente $R^2$ è migliorato (ma attenzione all'*overfitting*).

```{r}
# Previsione sul test set
poly_pred <- predict(poly_model, test_set)

# Calcolo dell'errore
poly_rmse <- sqrt(mean((poly_pred - test_set$medv)^2))
print(paste("RMSE Regressione Polinomiale:", poly_rmse))

# Visualizza la regressione polinomiale
ggplot(df_boston, aes(x = lstat, y = medv)) +
  geom_point() +
  geom_line(data=test_set, aes(x = lstat, y = predict(poly_model, newdata = test_set), col="red" ))+
 # geom_smooth(method = "lm", formula = y ~ poly(x, 2), col = "red") +
  ggtitle("Regressione Polinomiale")
```

Studiamo al variare del grado l'errore di training (usiamo qui $R^2$ e cerchiamo un punto di *gomito*).

```{r}
set.seed(42)

train_r2 <- numeric(10)

for (grado in 1:10){
  # allenamento del modello polinomiale usando lm()
  poly_model_cv <- glm(medv ~ poly(lstat, degree =  grado ), data = train_set)
  
  # calcolo R^2 di train
  train_pred <- predict(poly_model_cv, train_set)
  ss_total_train <- sum((train_set$medv - mean(train_set$medv))^2)
  ss_res_train <- sum((train_pred - train_set$medv)^2)
  train_r2[grado] <- 1 - (ss_res_train / ss_total_train)
}

# plot
df_r2 <- data.frame(grado = 1:10, train_r2 = train_r2)
ggplot(df_r2, aes(x = grado, y = train_r2)) +
  geom_line() +
  geom_point() +
  ylab("R^2 di Train") +
  xlab("Grado del Polinomio") +
  ggtitle("R^2 di Train al variare del grado del polinomio")

```

### Regressione esponenziale

Possiamo usare i modelli lineari anche per dipendenze più complesse, ad esempio possiamo cercare una relazione del tipo $y \approx exp(ax+b)$: basta infatti passare al logaritmo e diventa una regressione lineare semplice $\log(y) \approx ax+b$.

```{r}
# Allenamento del modello di regressione esponenziale
exp_model <- lm(log(medv) ~ lstat, data = train_set)

summary(exp_model)
```


```{r}
# Previsione sul test set
exp_pred_log <- predict(exp_model, test_set)
exp_pred <- exp(exp_pred_log)
# Calcolo dell'errore
exp_rmse <- sqrt(mean((exp_pred - test_set$medv)^2))
print(paste("RMSE Regressione Esponenziale:", exp_rmse))
# Visualizza la regressione esponenziale
ggplot() +
  geom_point(data = test_set, aes(x = lstat, y = medv), color = "blue") +
  geom_line(data = test_set, aes(x = lstat, y = exp(predict(exp_model, newdata = test_set))), color = "red") +
  labs(title = "Regressione Esponenziale", x = "lstat", y = "medv") +
  theme_minimal()

```

## Dataset Careggi


Confrontiamo infine la performance di una regressione lineare semplice, polinomiale e infine esponenziale sul dataset dell'ospedale Careggi.


```{r}
# regressione lineare

set.seed(42)
train_index <- createDataPartition(df_careggi$GiorniAccumulati, p = 0.8, list = FALSE)
train_set <- df_careggi[train_index, ]
test_set <- df_careggi[-train_index, ]


linear_model_careggi <- lm(GiorniAccumulati ~ Ammessi, data = train_set)
  
summary(linear_model_careggi)
linear_pred_careggi <- predict(linear_model_careggi, test_set)
linear_rmse_values <- sqrt(mean((poly_pred_careggi - test_set$GiorniAccumulati)^2))
```

Consideriamo ora una regressione quadratica (polinomiale di grado $2$).

```{r}

poly_model_careggi <- lm(GiorniAccumulati ~ poly(Ammessi, degree = 2), data = train_set)
summary(poly_model_careggi)
poly_pred_careggi <- predict(poly_model_careggi, test_set)
poly_rmse_values <- sqrt(mean((poly_pred_careggi - test_set$GiorniAccumulati)^2))
``` 
Vediamo che il termine di secondo grado non è significativo. Possiamo limitarci al caso lineare.  Consideriamo infine una relazione del tipo $Giorni \approx Visite ^\alpha$, dove $\alpha$ è da determinare. Per questo possiamo passare al logaritmo ambo i membri e diventa di nuovo una regressione lineare semplice.

```{r}
log_model_careggi <- lm(log(GiorniAccumulati) ~ log(Ammessi), data = train_set)
summary(log_model_careggi)
log_pred_careggi_log <- predict(log_model_careggi, test_set)
log_pred_careggi <- exp(log_pred_careggi_log)
log_rmse_values <- sqrt(mean((log_pred_careggi - test_set$GiorniAccumulati)^2))
```
Il coefficiente del logaritmo è $0.99$, quindi l'ipotesi di relazione lineare è ulteriormente confermata.

Confrontiamo infine i RMSE.

```{r}
print(paste("RMSE Regressione Lineare (Careggi):", linear_rmse_values))
print(paste("RMSE Regressione Polinomiale (Careggi):", poly_rmse_values)) 
print(paste("RMSE Regressione Esponenziale (Careggi):", log_rmse_values)) 
```

Plottiamo i tre modelli.

```{r}

ggplot() +
  geom_point(data = df_careggi, aes(x = Ammessi, y = GiorniAccumulati), color = "blue") +
  geom_smooth(data = train_set, aes(x = Ammessi, y = GiorniAccumulati), method = "lm", formula = y ~ x, color = "red", se = FALSE) +
  geom_smooth(data = train_set, aes(x = Ammessi, y = GiorniAccumulati), method = "lm", formula = y ~ poly(x, 2), color = "green", se = FALSE) +
  geom_line(data = test_set, aes(x = Ammessi, y = exp(predict(log_model_careggi, newdata = test_set))), color = "purple") +
  labs(title = "Confronto Modelli di Regressione (Careggi)", x = "Ammessi", y = "Giorni Accumulati") +
  theme_minimal() +
  scale_color_manual(name = "Modello", values = c("Lineare" = "red", "Polinomiale" = "green", "Esponenziale" = "purple"))
```



# Esercizi



1. Utilizza il dataset `mtcars` per prevedere il consumo di carburante (`mpg`) in base al peso dell'auto (`wt`). Valuta le performance del modello $k$-NN al variare del valore di $k$.

2. Utilizza il dataset `iris` per prevedere la lunghezza del petalo (`Petal.Length`) in base alla lunghezza del sepalo (`Sepal.Length`). Calcola e interpreta il coefficiente di determinazione \( R^2 \).

3. Genera un dataset con una relazione quadratica (ad esempio, \( y = x^2 + \text{rumore} \)). Applica una regressione polinomiale di secondo grado e confronta il risultato con una regressione lineare semplice.

4. Crea un dataset in cui la variabile dipendente cresce esponenzialmente rispetto a una variabile indipendente. Applica una regressione esponenziale e confronta il RMSE con quello di una regressione lineare.

5. Utilizza il dataset `Iris` per prevedere la lunghezza del petalo come funzione della lunghezza del sepalo elevata ad un esponente $\alpha$ da determinare tramite regressione lineare.

6. Utilizza il dataset `airquality` per prevedere la temperatura (`Temp`) in base all'ozono (`Ozone`). Sperimenta con KNN e regressione lineare semplice e confronta gli errori di previsione.

7. Per un modello KNN costruito con un dataset generato, calcola l'errore medio assoluto (MAE) e il root mean squared error (RMSE) e discuti le loro implicazioni.

8. Utilizza il dataset `diamonds` per prevedere il prezzo (`price`) in base alle caratteristiche del diamante (`carat`, `cut`, `color`). Prova a usare KNN e regressione polinomiale e discuti come la scelta delle caratteristiche influisce sulle performance.

9. Costruisci un modello di regressione lineare semplice utilizzando il dataset `ChickWeight` per prevedere il peso (`weight`) in base all'età (`Time`). Visualizza i risultati e gli intervalli di previsione.



