Parece que você também está procurando uma resposta do ponto de vista preditivo, então montei uma breve demonstração de duas abordagens em R
- Agrupando uma variável em fatores de tamanho igual.
- Splines cúbicos naturais.
Abaixo, eu forneci o código para uma função que comparará os dois métodos automaticamente para qualquer função de sinal verdadeiro
test_cuts_vs_splines <- function(signal, N, noise,
range=c(0, 1),
max_parameters=50,
seed=154)
Essa função criará conjuntos de dados de treinamento e teste ruidosos a partir de um determinado sinal e ajustará uma série de regressões lineares aos dados de treinamento de dois tipos
- O
cuts
modelo inclui preditores binados, formados pela segmentação do intervalo de dados em intervalos semiabertos de tamanho igual e, em seguida, criando preditores binários indicando a qual intervalo cada ponto de treinamento pertence.
- O
splines
modelo inclui uma expansão da base da spline cúbica natural, com nós igualmente espaçados em toda a faixa do preditor.
Os argumentos são
signal
: Uma função de uma variável representando a verdade a ser estimada.
N
: O número de amostras a serem incluídas nos dados de treinamento e teste.
noise
: O amplo ruído gaussiano aleatório a ser adicionado ao sinal de treinamento e teste.
range
: O intervalo dos x
dados de treinamento e teste , dados gerados uniformemente dentro desse intervalo.
max_paramters
: O número máximo de parâmetros a serem estimados em um modelo. Esse é o número máximo de segmentos no cuts
modelo e o número máximo de nós no splines
modelo.
Observe que o número de parâmetros estimados no splines
modelo é igual ao número de nós, portanto, os dois modelos são comparados de maneira justa.
O objeto de retorno da função possui alguns componentes
signal_plot
: Um gráfico da função de sinal.
data_plot
: Um gráfico de dispersão dos dados de treinamento e teste.
errors_comparison_plot
: Um gráfico que mostra a evolução da soma da taxa de erro ao quadrado de ambos os modelos em uma faixa do número de parâmetros estimados.
Vou demonstrar com duas funções de sinal. A primeira é uma onda sinusal com uma tendência linear crescente sobreposta
true_signal_sin <- function(x) {
x + 1.5*sin(3*2*pi*x)
}
obj <- test_cuts_vs_splines(true_signal_sin, 250, 1)
Aqui está como as taxas de erro evoluem
O segundo exemplo é uma função maluca que eu mantenho por aí apenas para esse tipo de coisa, plote e veja
true_signal_weird <- function(x) {
x*x*x*(x-1) + 2*(1/(1+exp(-.5*(x-.5)))) - 3.5*(x > .2)*(x < .5)*(x - .2)*(x - .5)
}
obj <- test_cuts_vs_splines(true_signal_weird, 250, .05)
E por diversão, aqui está uma função linear chata
obj <- test_cuts_vs_splines(function(x) {x}, 250, .2)
Você pode ver isso:
- As splines oferecem um desempenho geral melhor no teste geral quando a complexidade do modelo é ajustada adequadamente para ambos.
- As splines oferecem desempenho ideal de teste com muito menos parâmetros estimados .
- No geral, o desempenho dos splines é muito mais estável, pois o número de parâmetros estimados varia.
Portanto, splines sempre devem ser preferidos do ponto de vista preditivo.
Código
Aqui está o código que eu usei para produzir essas comparações. Coloquei tudo em uma função para que você possa testá-lo com suas próprias funções de sinal. Você precisará importar as bibliotecas ggplot2
e splines
R.
test_cuts_vs_splines <- function(signal, N, noise,
range=c(0, 1),
max_parameters=50,
seed=154) {
if(max_parameters < 8) {
stop("Please pass max_parameters >= 8, otherwise the plots look kinda bad.")
}
out_obj <- list()
set.seed(seed)
x_train <- runif(N, range[1], range[2])
x_test <- runif(N, range[1], range[2])
y_train <- signal(x_train) + rnorm(N, 0, noise)
y_test <- signal(x_test) + rnorm(N, 0, noise)
# A plot of the true signals
df <- data.frame(
x = seq(range[1], range[2], length.out = 100)
)
df$y <- signal(df$x)
out_obj$signal_plot <- ggplot(data = df) +
geom_line(aes(x = x, y = y)) +
labs(title = "True Signal")
# A plot of the training and testing data
df <- data.frame(
x = c(x_train, x_test),
y = c(y_train, y_test),
id = c(rep("train", N), rep("test", N))
)
out_obj$data_plot <- ggplot(data = df) +
geom_point(aes(x=x, y=y)) +
facet_wrap(~ id) +
labs(title = "Training and Testing Data")
#----- lm with various groupings -------------
models_with_groupings <- list()
train_errors_cuts <- rep(NULL, length(models_with_groupings))
test_errors_cuts <- rep(NULL, length(models_with_groupings))
for (n_groups in 3:max_parameters) {
cut_points <- seq(range[1], range[2], length.out = n_groups + 1)
x_train_factor <- cut(x_train, cut_points)
factor_train_data <- data.frame(x = x_train_factor, y = y_train)
models_with_groupings[[n_groups]] <- lm(y ~ x, data = factor_train_data)
# Training error rate
train_preds <- predict(models_with_groupings[[n_groups]], factor_train_data)
soses <- (1/N) * sum( (y_train - train_preds)**2)
train_errors_cuts[n_groups - 2] <- soses
# Testing error rate
x_test_factor <- cut(x_test, cut_points)
factor_test_data <- data.frame(x = x_test_factor, y = y_test)
test_preds <- predict(models_with_groupings[[n_groups]], factor_test_data)
soses <- (1/N) * sum( (y_test - test_preds)**2)
test_errors_cuts[n_groups - 2] <- soses
}
# We are overfitting
error_df_cuts <- data.frame(
x = rep(3:max_parameters, 2),
e = c(train_errors_cuts, test_errors_cuts),
id = c(rep("train", length(train_errors_cuts)),
rep("test", length(test_errors_cuts))),
type = "cuts"
)
out_obj$errors_cuts_plot <- ggplot(data = error_df_cuts) +
geom_line(aes(x = x, y = e)) +
facet_wrap(~ id) +
labs(title = "Error Rates with Grouping Transformations",
x = ("Number of Estimated Parameters"),
y = ("Average Squared Error"))
#----- lm with natural splines -------------
models_with_splines <- list()
train_errors_splines <- rep(NULL, length(models_with_groupings))
test_errors_splines <- rep(NULL, length(models_with_groupings))
for (deg_freedom in 3:max_parameters) {
knots <- seq(range[1], range[2], length.out = deg_freedom + 1)[2:deg_freedom]
train_data <- data.frame(x = x_train, y = y_train)
models_with_splines[[deg_freedom]] <- lm(y ~ ns(x, knots=knots), data = train_data)
# Training error rate
train_preds <- predict(models_with_splines[[deg_freedom]], train_data)
soses <- (1/N) * sum( (y_train - train_preds)**2)
train_errors_splines[deg_freedom - 2] <- soses
# Testing error rate
test_data <- data.frame(x = x_test, y = y_test)
test_preds <- predict(models_with_splines[[deg_freedom]], test_data)
soses <- (1/N) * sum( (y_test - test_preds)**2)
test_errors_splines[deg_freedom - 2] <- soses
}
error_df_splines <- data.frame(
x = rep(3:max_parameters, 2),
e = c(train_errors_splines, test_errors_splines),
id = c(rep("train", length(train_errors_splines)),
rep("test", length(test_errors_splines))),
type = "splines"
)
out_obj$errors_splines_plot <- ggplot(data = error_df_splines) +
geom_line(aes(x = x, y = e)) +
facet_wrap(~ id) +
labs(title = "Error Rates with Natural Cubic Spline Transformations",
x = ("Number of Estimated Parameters"),
y = ("Average Squared Error"))
error_df <- rbind(error_df_cuts, error_df_splines)
out_obj$error_df <- error_df
# The training error for the first cut model is always an outlier, and
# messes up the y range of the plots.
y_lower_bound <- min(c(train_errors_cuts, train_errors_splines))
y_upper_bound = train_errors_cuts[2]
out_obj$errors_comparison_plot <- ggplot(data = error_df) +
geom_line(aes(x = x, y = e)) +
facet_wrap(~ id*type) +
scale_y_continuous(limits = c(y_lower_bound, y_upper_bound)) +
labs(
title = ("Binning vs. Natural Splines"),
x = ("Number of Estimated Parameters"),
y = ("Average Squared Error"))
out_obj
}