Este relatório investiga a relação entre PIB per capita e expectativa de vida utilizando dados do World Development Indicators (WDI). A literatura descreve um padrão conhecido como curva de Preston: países com renda muito baixa tendem a apresentar grandes ganhos de expectativa de vida à medida que o PIB cresce, enquanto em níveis de renda mais altos os ganhos marginais diminuem. Isso sugere uma relação crescente e fortemente não linear, favorecendo modelos que consigam capturar curvaturas e mudanças de inclinação ao longo do domínio de \(X\).
Nosso objetivo é ajustar um modelo de regressão não linear por meio de expansões de Fourier, uma base de funções senoidais e cossenoidais que oferece flexibilidade para aproximar formas complexas sem impor, a priori, uma estrutura polinomial específica. Em vez de escolher manualmente transformações de \(X\), deixamos que a combinação de harmônicos descreva a forma da curva, controlando a complexidade via número de termos \(m\) e escolhendo esse hiperparâmetro por validação cruzada (com AIC/BIC como apoio).
Perguntas que orientam a análise
Contribuições do relatório
Limitações e escopo
A organização do restante do trabalho segue: (i) descrição dos dados e normalização, (ii) construção da base de Fourier e seleção de \(m\), (iii) ajuste final e avaliação de incerteza, e (iv) diagnóstico e conclusões.
Utilizamos a base World Development Indicators (WDI), que consolida estatísticas socioeconômicas comparáveis entre países. Para este estudo selecionamos duas variáveis:
A amostra é transversal (um registro por país). Não
inferimos causalidade; buscamos caracterizar a
associação entre renda e longevidade. Antes de ajustar modelos,
realizamos limpeza mínima: remoção de ausências (NA
),
checagem de tipos numéricos e padronização de nomes de colunas.
Lembramos que fatores omitidos (educação, saneamento, proteção social)
podem influenciar \(Y\); portanto, os resultados devem ser lidos como
descritivos.
Para garantir reprodutibilidade no seu ambiente, o relatório aponta explicitamente para o arquivo:
D:/Script_R/Aprendizado_Maquina_TrabGrupo/worldDevelopmentIndicators.csv
Esse caminho fixo evita erros de “arquivo não encontrado” quando o
working directory muda. Se o projeto for compartilhado,
recomenda-se manter o .csv
na mesma pasta
do .Rmd
e usar caminho relativo.
# Caminho fixo para o CSV
csv_path <- "D:/Script_R/Aprendizado_Maquina_TrabGrupo/worldDevelopmentIndicators.csv"
if (!file.exists(csv_path)) stop("CSV não encontrado em: ", csv_path)
# Ler dados
wdi <- read.csv(csv_path, stringsAsFactors = FALSE)
# Garantir colunas esperadas
stopifnot(all(c("GDPercapita", "LifeExpectancy") %in% names(wdi)))
# Normalização x ∈ (0,1)
xmin <- min(wdi$GDPercapita, na.rm = TRUE)
xmax <- max(wdi$GDPercapita, na.rm = TRUE)
wdi$x <- (wdi$GDPercapita - xmin) / (xmax - xmin)
wdi$y <- wdi$LifeExpectancy
# Cheque rápido
cat("OK: wdi criado com", nrow(wdi), "linhas.\\n")
## OK: wdi criado com 211 linhas.\n
summary(wdi[, c("GDPercapita","LifeExpectancy")])
## GDPercapita LifeExpectancy
## Min. : 251 Min. :45.33
## 1st Qu.: 1682 1st Qu.:64.06
## Median : 5786 Median :72.49
## Mean : 14150 Mean :70.30
## 3rd Qu.: 16863 3rd Qu.:76.75
## Max. :103858 Max. :83.48
cor(wdi$x, wdi$y)
## [1] 0.5965097
Como a regressão será feita com base de Fourier, normalizamos o preditor para o intervalo \((0,1)\):
\[ x = \frac{X - x_{\min}}{x_{\max} - x_{\min}} \quad \text{com } x \in (0,1). \]
Essa transformação coloca \(X\) numa escala padrão, preserva a ordem entre observações e facilita comparar a escolha de \(m\) entre bases. Em todos os gráficos, mostramos a curva tanto em \(x\) normalizado quanto na escala original do PIB.
O modelo ajustado tem a forma:
\[ \hat{y}(x) = \beta_0 + \sum_{k=1}^{m} \big[a_k \sin(2\pi k x) + b_k \cos(2\pi k x)\big], \]
onde \(m\) representa o número de harmônicos. A escolha de \(m\) é feita por validação cruzada (CV) — para priorizar desempenho preditivo — e com apoio de critérios de informação (AIC/BIC) — para parcimônia.
fourier_basis <- function(x, m) {
n <- length(x)
if (m < 1) return(data.frame())
out <- as.data.frame(matrix(NA_real_, nrow = n, ncol = 2*m))
cn <- character(2*m)
for (k in 1:m) {
out[[2*k-1]] <- sin(2*pi*k*x)
out[[2*k]] <- cos(2*pi*k*x)
cn[2*k-1] <- paste0("sin",k)
cn[2*k] <- paste0("cos",k)
}
names(out) <- cn
out
}
fit_fourier <- function(df, m) {
X <- fourier_basis(df$x, m)
dat <- data.frame(y = df$y, X)
lm(y ~ ., data = dat)
}
cv_fourier <- function(df, m, K=5, seed=123){
set.seed(seed)
n <- nrow(df)
folds <- sample(rep(1:K, length.out=n))
rmse <- numeric(K)
for (k in 1:K) {
train <- df[folds != k,]
test <- df[folds == k,]
model <- fit_fourier(train,m)
preds <- predict(model, newdata=fourier_basis(test$x,m))
rmse[k] <- sqrt(mean((test$y - preds)^2))
}
mean(rmse)
}
Mmax <- 10
results <- data.frame(m=integer(), cv_rmse=numeric(), AIC=numeric(), BIC=numeric())
for (m in 1:Mmax) {
model_m <- fit_fourier(wdi,m)
cv_m <- cv_fourier(wdi,m)
results <- rbind(results, data.frame(m=m, cv_rmse=cv_m, AIC=AIC(model_m), BIC=BIC(model_m)))
}
results
best_m <- results$m[which.min(results$cv_rmse)]
best_m
## [1] 3
final_model <- fit_fourier(wdi, best_m)
summary(final_model)
##
## Call:
## lm(formula = y ~ ., data = dat)
##
## Residuals:
## Min 1Q Median 3Q Max
## -24.0410 -2.5184 0.7698 3.8918 21.6134
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 75.8326 1.0534 71.990 < 2e-16 ***
## sin1 -0.2121 1.6467 -0.129 0.898
## cos1 -7.3859 1.4760 -5.004 1.21e-06 ***
## sin2 1.9198 1.7157 1.119 0.264
## cos2 -4.9157 1.2059 -4.076 6.55e-05 ***
## sin3 4.3068 0.9770 4.408 1.68e-05 ***
## cos3 -0.9528 1.3201 -0.722 0.471
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.446 on 204 degrees of freedom
## Multiple R-squared: 0.508, Adjusted R-squared: 0.4935
## F-statistic: 35.1 on 6 and 204 DF, p-value: < 2.2e-16
grid <- data.frame(x=seq(0,1,length.out=400))
Xgrid <- fourier_basis(grid$x, best_m)
pred <- predict(final_model, newdata=Xgrid, se.fit=TRUE)
grid$yhat <- as.numeric(pred$fit)
grid$lo <- as.numeric(pred$fit - 1.96*pred$se.fit)
grid$hi <- as.numeric(pred$fit + 1.96*pred$se.fit)
grid$gdp <- grid$x*(xmax-xmin)+xmin
# Normalizado
plot(wdi$x, wdi$y, pch=16, xlab="Normalized GDP per capita", ylab="Life Expectancy",
main=paste("Fourier Fit (m =", best_m,")"))
lines(grid$x, grid$yhat, lwd=2)
lines(grid$x, grid$lo, lty=2)
lines(grid$x, grid$hi, lty=2)
# Escala original
plot(wdi$GDPercapita, wdi$y, pch=16, xlab="GDP per capita", ylab="Life Expectancy",
main=paste("Fourier Fit vs GDP (m =", best_m,")"))
lines(grid$gdp, grid$yhat, lwd=2)
lines(grid$gdp, grid$lo, lty=2)
lines(grid$gdp, grid$hi, lty=2)
par(mfrow=c(1,2))
plot(final_model, which=1) # resíduos vs ajustados
plot(final_model, which=2) # QQ-plot
par(mfrow=c(1,1))
plot(cooks.distance(final_model), type="h", ylab="Cook's distance",
main="Influence (Cook's distance)")
abline(h=4/nrow(wdi), lty=2)