Sobremuestreando x (y no y)

Construyo unos datos (artificiales, para conocer la verdad):

n <- 10000
x1 <- rnorm(n)
x2 <- rnorm(n)
probs <- -2 + x1 + x2
probs <- 1 / (1 + exp(-probs))
y <- sapply(probs, function(p) rbinom(1, 1, p))
dat <- data.frame(y = y, x1 = x1, x2 = x2)

Construyo un modelo de clasificación (logístico, que hoy no hace falta inventar, aunque podría ser cualquier otro):

summary(glm(y ~ x1 + x2, data = dat, family = binomial))
#Call:
#glm(formula = y ~ x1 + x2, family = binomial, data = dat)
#
#Deviance Residuals:
#    Min       1Q   Median       3Q      Max
#-2.2547  -0.5967  -0.3632  -0.1753   3.3528
#
#Coefficients:
#            Estimate Std. Error z value Pr(>|z|)
#(Intercept) -2.05753    0.03812  -53.97   <2e-16 ***
#x1           1.01918    0.03386   30.10   <2e-16 ***
#x2           1.00629    0.03405   29.55   <2e-16 ***
#---
#Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
#(Dispersion parameter for binomial family taken to be 1)
#
#    Null deviance: 9485.2  on 9999  degrees of freedom
#Residual deviance: 7373.4  on 9997  degrees of freedom
#AIC: 7379.4
#
#Number of Fisher Scoring iterations: 5

Correcto.

Sobremuestreo. Por construcción, hay más casos 1 que 0. Así que igualo las clases y reajusto:

tmp <- split(dat, dat$y)
tmp[["0"]] <- tmp[["0"]][sample(1:nrow(tmp[["0"]]), nrow(tmp[["1"]])),]
dat_oversampling <- do.call(rbind, tmp)
summary(glm(y ~ x1 + x2, data = dat_oversampling, family = binomial))
#Call:
#glm(formula = y ~ x1 + x2, family = binomial, data = dat_oversampling)
#
#Deviance Residuals:
#     Min        1Q    Median        3Q       Max
#-2.82905  -0.86448   0.03408   0.84398   2.87510
#
#Coefficients:
#            Estimate Std. Error z value Pr(>|z|)
#(Intercept) -0.54650    0.04441  -12.31   <2e-16 ***
#x1           1.02262    0.04613   22.17   <2e-16 ***
#x2           1.00801    0.04597   21.93   <2e-16 ***
#---
#Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
#(Dispersion parameter for binomial family taken to be 1)
#
#    Null deviance: 5043.3  on 3637  degrees of freedom
#Residual deviance: 3801.0  on 3635  degrees of freedom
#AIC: 3807
#
#Number of Fisher Scoring iterations: 4:

Aparece un sesgo. Estamos incrementando la probabilidad de 1. En la regresión logística eso se manifiesta (únicamente, de hecho), en el término independiente. Hay mil maneras de reajustar ese tipo de modelos, pero no voy a entrar en eso hoy.

Lo que ocurre es que hay otra manera de muestrear: usando una variable muy correlacionada con y pero que no sea y. P.e., una variable muy predictiva en el modelo. Existen muchas variantes de la cosa, pero aquí utilizaré la variante más simple conceptualmente:

dat$split <- dat$x1 > .5
tmp <- split(dat, dat$split)
tmp[["FALSE"]] <- tmp[["FALSE"]][sample(1:nrow(tmp[["FALSE"]]), nrow(tmp[["TRUE"]])),]
dat_oversampling <- do.call(rbind, tmp)

Así las cosas,

summary(glm(y ~ x1 + x2, data = dat_oversampling, family = binomial))
#Call:
#glm(formula = y ~ x1 + x2, family = binomial, data = dat_oversampling)
#
#Deviance Residuals:
#    Min       1Q   Median       3Q      Max
#-2.2712  -0.6609  -0.3946  -0.1306   3.1221
#
#Coefficients:
#            Estimate Std. Error z value Pr(>|z|)
#(Intercept) -2.04067    0.05100  -40.01   <2e-16 ***
#x1           1.01137    0.04175   24.22   <2e-16 ***
#x2           1.03267    0.04078   25.32   <2e-16 ***
#---
#Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
#(Dispersion parameter for binomial family taken to be 1)
#
#    Null deviance: 6635.7  on 6175  degrees of freedom
#Residual deviance: 5140.9  on 6173  degrees of freedom
#AIC: 5146.9
#
#Number of Fisher Scoring iterations: 5

¡Sin sesgo!

Nota: obviamente, todo lo anterior aplica si realmente tienes que sobremuestrear.

Otra nota: aquí he tratado el asunto del sesgo y únicamente el sesgo. Nada se ha dicho sobre lo que le puede ocurrir a la varianza por usar menos observaciones.