Trazar una línea de regresión por partes

10

¿Hay alguna manera de trazar la línea de regresión de un modelo por partes como este, que no sea usar linespara trazar cada segmento por separado o usar geom_smooth(aes(group=Ind), method="lm", fill=FALSE)?

m.sqft <- mean(sqft)
model <- lm(price~sqft+I((sqft-m.sqft)*Ind))
# sqft, price: continuous variables, Ind: if sqft>mean(sqft) then 1 else 0

plot(sqft,price)
abline(reg = model)
Warning message:
In abline(reg = model) :
  only using the first two of 3regression coefficients

Gracias.

George Dontas
fuente

Respuestas:

6

La única forma en que sé cómo hacer esto fácilmente es predecir a partir del modelo a través del rango sqfty trazar las predicciones. No hay una forma general con ablineo similar. También puede echar un vistazo al paquete segmentado que se ajustará a estos modelos y le proporcionará la infraestructura de trazado.

Hacer esto a través de predicciones y gráficos básicos. Primero, algunos datos ficticios:

set.seed(1)
sqft <- runif(100)
sqft <- ifelse((tmp <- sqft > mean(sqft)), 1, 0) + rnorm(100, sd = 0.5)
price <- 2 + 2.5 * sqft
price <- ifelse(tmp, price, 0) + rnorm(100, sd = 0.6)
DF <- data.frame(sqft = sqft, price = price,
                 Ind = ifelse(sqft > mean(sqft), 1, 0))
rm(price, sqft)
plot(price ~ sqft, data = DF)

Adaptarse al modelo:

mod <- lm(price~sqft+I((sqft-mean(sqft))*Ind), data = DF)

Genere algunos datos para predecir y predecir:

m.sqft <- with(DF, mean(sqft))
pDF <- with(DF, data.frame(sqft = seq(min(sqft), max(sqft), length = 200)))
pDF <- within(pDF, Ind <- ifelse(sqft > m.sqft, 1, 0))
pDF <- within(pDF, price <- predict(mod, newdata = pDF))

Trace las líneas de regresión:

ylim <- range(pDF$price, DF$price)
xlim <- range(pDF$sqft, DF$sqft)
plot(price ~ sqft, data = DF, ylim = ylim, xlim = xlim)
lines(price ~ sqft, data = pDF, subset = Ind > 0, col = "red", lwd = 2)
lines(price ~ sqft, data = pDF, subset = Ind < 1, col = "red", lwd = 2)

Puede codificar esto en una función simple: solo necesita los pasos en los dos fragmentos de código anteriores, que puede usar en lugar de abline:

myabline <- function(model, data, ...) {
    m.sqft <- with(data, mean(sqft))
    pDF <- with(data, data.frame(sqft = seq(min(sqft), max(sqft),
                                            length = 200)))
    pDF <- within(pDF, Ind <- ifelse(sqft > m.sqft, 1, 0))
    pDF <- within(pDF, price <- predict(mod, newdata = pDF))
    lines(price ~ sqft, data = pDF, subset = Ind > 0, ...)
    lines(price ~ sqft, data = pDF, subset = Ind < 1, ...)
    invisible(model)
}

Entonces:

ylim <- range(pDF$price, DF$price)
xlim <- range(pDF$sqft, DF$sqft)
plot(price ~ sqft, data = DF, ylim = ylim, xlim = xlim)
myabline(mod, DF, col = "red", lwd = 2)

A través del paquete segmentado

require(segmented)
mod2 <- lm(price ~ sqft, data = DF)
mod.s <- segmented(mod2, seg.Z = ~ sqft, psi = 0.5,
                   control = seg.control(stop.if.error = FALSE))
plot(price ~ sqft, data = DF)
plot(mod.s, add = TRUE)
lines(mod.s, col = "red")

Con estos datos no se estima el punto de interrupción mean(sqft), pero los métodos ploty linesen ese paquete pueden ayudarlo a implementar algo más genérico que myablinehacer este trabajo directamente desde el lm()modelo ajustado .

Editar: si desea segmentar para estimar la ubicación del punto de interrupción, configure el 'psi'argumento en NA:

mod.s <- segmented(mod2, seg.Z = ~ sqft, psi = NA,
                   control = seg.control(stop.if.error = FALSE))

Luego segmentedintentará K = 10cuantiles de sqft, con Kser establecido seg.control()y que por defecto es 10. Mira ?seg.controlpara más.

Gavin Simpson
fuente
@Gavin (+1) Respuesta mucho más completa que la mía; Simplemente me gusta.
chl
@Gavin La sección "Vía el paquete segmentado" no funcionó para mis datos. Obtuve un "Sin punto de corte estimado" después de ejecutar el segmentedcomando.
George Dontas
@ gd047: Disculpas, hubo un error en el código que mostré. Debe proporcionar un argumento seq.Zcon una fórmula unilateral de las variables que tienen una relación segmentada con la respuesta. Edité mi respuesta para incluir seq.Z = ~ sqfty agregué una nota sobre cómo segmentedelegir valores psipara usted.
Gavin Simpson
@ gd047 Me gustaría eliminar mi respuesta ya que esta aborda su pregunta original de una manera mejor. ¿Te importaría aceptar este en lugar del mío?
chl
@chl Por supuesto, aunque sigo recibiendo un error: Error en if (modelo) objF modelo <- mf: la condición tiene una longitud> 1 y solo se usará el primer elementomodel<mf:argumentisnotinterpretableaslogicalInaddition:Warningmessage:Inif(model)objF
George Dontas el