¿Cómo ajustar una curva suave a mis datos en R?

87

Estoy tratando de dibujar una curva suave R. Tengo los siguientes datos simples de juguetes:

> x
 [1]  1  2  3  4  5  6  7  8  9 10
> y
 [1]  2  4  6  8  7 12 14 16 18 20

Ahora, cuando lo trazo con un comando estándar, se ve irregular y nervioso, por supuesto:

> plot(x,y, type='l', lwd=2, col='red')

¿Cómo puedo suavizar la curva para que los 3 bordes se redondeen utilizando valores estimados? Sé que hay muchos métodos para ajustar una curva suave, pero no estoy seguro de cuál sería el más apropiado para este tipo de curva y cómo lo escribirías R.

Franco
fuente
3
¡Depende completamente de cuáles son sus datos y por qué los está suavizando! ¿Los datos cuentan? Densidades? ¿Mediciones? ¿Qué tipo de error de medición puede haber? ¿Qué historia está tratando de contar a sus lectores con su gráfico? Todos estos problemas afectan si debe suavizar sus datos y cómo debe hacerlo.
Harlan
Estos son datos medidos. En los valores de x 1, 2, 3, ..., 10, algún sistema cometió 2, 4, 6, ..., 20 errores. Estas coordenadas probablemente no deberían ser cambiadas por el algoritmo de ajuste. Pero quiero simular los errores (y) en los valores x faltantes, por ejemplo, en los datos, f (4) = 8 yf (5) = 7, por lo que presumiblemente f (4.5) es algo entre 7 y 8, usando algún polinomio u otro suavizado.
Frank
2
En ese caso, con un solo punto de datos para cada valor de x, no suavizaría en absoluto. Solo tendría puntos grandes para mis puntos de datos medidos, con líneas finas que los conectan. Cualquier otra cosa sugiere al espectador que usted sabe más sobre sus datos que usted.
Harlan
Puede que tengas razón para este ejemplo. Sin embargo, es bueno saber cómo hacerlo y es posible que desee usarlo en otros datos más adelante, por ejemplo, tiene sentido si tiene miles de puntos de datos muy puntiagudos que suben y bajan, pero hay una tendencia general , por ejemplo, yendo hacia arriba como aquí: plot (seq (1,100) + runif (100, 0,10), type = 'l').
Frank
Aquí hay una buena manera, stats.stackexchange.com/a/278666/134555
Belter

Respuestas:

104

Me gusta loess()mucho suavizar:

x <- 1:10
y <- c(2,4,6,8,7,12,14,16,18,20)
lo <- loess(y~x)
plot(x,y)
lines(predict(lo), col='red', lwd=2)

El libro MASS de Venables y Ripley tiene una sección completa sobre suavizado que también cubre splines y polinomios, pero loess()es el favorito de todos.

Dirk Eddelbuettel
fuente
¿Cómo se aplica a estos datos? No sé cómo porque espera una fórmula. ¡Gracias!
Frank
7
Como les mostré en el ejemplo cuando if xy yson variables visibles. Si son columnas de un data.frame nombrado foo, agrega una data=fooopción a la loess(y ~ x. data=foo)llamada, al igual que en casi todas las demás funciones de modelado en R.
Dirk Eddelbuettel
4
también me gusta supsmu()como un suavizador
listo para usar
4
¿Cómo funcionaría eso si x es un parámetro de fecha? Si lo intento con una tabla de datos que asigna una fecha a un número (usando lo <- loess(count~day, data=logins_per_day) ) obtengo esto:Error: NA/NaN/Inf in foreign function call (arg 2) In addition: Warning message: NAs introduced by coercion
Wichert Akkerman
1
@Wichert Akkerman Parece que la mayoría de las funciones de R. odian el formato de fecha. Normalmente hago algo como new $ date = as.numeric (new $ date, as.Date ("2015-01-01"), units = "days") (como se describe en stat.ethz.ch/pipermail/r- help / 2008-May / 162719.html )
reducción de la actividad
58

Quizás smooth.spline es una opción. Puede establecer un parámetro de suavizado (normalmente entre 0 y 1) aquí

smoothingSpline = smooth.spline(x, y, spar=0.35)
plot(x,y)
lines(smoothingSpline)

también puede utilizar predecir en objetos smooth.spline. La función viene con la base R, consulte? Smooth.spline para obtener más detalles.

Karsten W.
fuente
27

Para que sea REALMENTE suave ...

x <- 1:10
y <- c(2,4,6,8,7,8,14,16,18,20)
lo <- loess(y~x)
plot(x,y)
xl <- seq(min(x),max(x), (max(x) - min(x))/1000)
lines(xl, predict(lo,xl), col='red', lwd=2)

Este estilo interpola muchos puntos extra y le da una curva que es muy suave. También parece ser el enfoque que adopta ggplot. Si el nivel estándar de suavidad está bien, puede usarlo.

scatter.smooth(x, y)
Juan
fuente
25

la función qplot () en el paquete ggplot2 es muy simple de usar y proporciona una solución elegante que incluye bandas de confianza. Por ejemplo,

qplot(x,y, geom='smooth', span =0.5)

produce ingrese la descripción de la imagen aquí

Minador
fuente
No para eludir la pregunta, pero creo que el informe de los valores R ^ 2 (o pseudo R ^ 2) para un ajuste suavizado es dudoso. Un más suave se ajustará necesariamente más a los datos a medida que disminuya el ancho de banda.
Underminer
Esto puede ayudar: stackoverflow.com/questions/7549694/…
Underminer
Hmm, finalmente no pude ejecutar su código en R 3.3.1. Lo instalé ggplot2correctamente pero no se puede ejecutar qplotporque no puede encontrar la función en Debian 8.5.
Léo Léopold Hertz 준영
13

LOESS es un muy buen enfoque, como dijo Dirk.

Otra opción es usar splines Bezier, que en algunos casos pueden funcionar mejor que LOESS si no tiene muchos puntos de datos.

Aquí encontrará un ejemplo: http://rosettacode.org/wiki/Cubic_bezier_curves#R

# x, y: the x and y coordinates of the hull points
# n: the number of points in the curve.
bezierCurve <- function(x, y, n=10)
    {
    outx <- NULL
    outy <- NULL

    i <- 1
    for (t in seq(0, 1, length.out=n))
        {
        b <- bez(x, y, t)
        outx[i] <- b$x
        outy[i] <- b$y

        i <- i+1
        }

    return (list(x=outx, y=outy))
    }

bez <- function(x, y, t)
    {
    outx <- 0
    outy <- 0
    n <- length(x)-1
    for (i in 0:n)
        {
        outx <- outx + choose(n, i)*((1-t)^(n-i))*t^i*x[i+1]
        outy <- outy + choose(n, i)*((1-t)^(n-i))*t^i*y[i+1]
        }

    return (list(x=outx, y=outy))
    }

# Example usage
x <- c(4,6,4,5,6,7)
y <- 1:6
plot(x, y, "o", pch=20)
points(bezierCurve(x,y,20), type="l", col="red")
nico
fuente
11

Las otras respuestas son todos buenos enfoques. Sin embargo, hay algunas otras opciones en R que no se han mencionado, incluidas lowessy approx, que pueden brindar mejores ajustes o un rendimiento más rápido.

Las ventajas se demuestran más fácilmente con un conjunto de datos alternativo:

sigmoid <- function(x)
{
  y<-1/(1+exp(-.15*(x-100)))
  return(y)
}

dat<-data.frame(x=rnorm(5000)*30+100)
dat$y<-as.numeric(as.logical(round(sigmoid(dat$x)+rnorm(5000)*.3,0)))

Aquí están los datos superpuestos con la curva sigmoidea que los generó:

Datos

Este tipo de datos es común cuando se observa un comportamiento binario entre una población. Por ejemplo, esto podría ser un gráfico de si un cliente compró algo (un 1/0 binario en el eje y) versus la cantidad de tiempo que pasó en el sitio (eje x).

Se utilizan una gran cantidad de puntos para demostrar mejor las diferencias de rendimiento de estas funciones.

Smooth, spliney smooth.splinetodos producen un galimatías en un conjunto de datos como este con cualquier conjunto de parámetros que haya probado, tal vez debido a su tendencia a mapear en todos los puntos, lo que no funciona para datos ruidosos.

Las loess, lowessy approxlas funciones de todos los resultados utilizables producen, aunque apenas para approx. Este es el código para cada uno que usa parámetros ligeramente optimizados:

loessFit <- loess(y~x, dat, span = 0.6)
loessFit <- data.frame(x=loessFit$x,y=loessFit$fitted)
loessFit <- loessFit[order(loessFit$x),]

approxFit <- approx(dat,n = 15)

lowessFit <-data.frame(lowess(dat,f = .6,iter=1))

Y los resultados:

plot(dat,col='gray')
curve(sigmoid,0,200,add=TRUE,col='blue',)
lines(lowessFit,col='red')
lines(loessFit,col='green')
lines(approxFit,col='purple')
legend(150,.6,
       legend=c("Sigmoid","Loess","Lowess",'Approx'),
       lty=c(1,1),
       lwd=c(2.5,2.5),col=c("blue","green","red","purple"))

Encaja

Como puede ver, lowessproduce un ajuste casi perfecto a la curva generadora original. Loessestá cerca, pero experimenta una extraña desviación en ambas colas.

Aunque su conjunto de datos será muy diferente, he descubierto que otros conjuntos de datos funcionan de manera similar, con ambos loessy lowesscapaces de producir buenos resultados. Las diferencias se vuelven más significativas cuando observa los puntos de referencia:

> microbenchmark::microbenchmark(loess(y~x, dat, span = 0.6),approx(dat,n = 20),lowess(dat,f = .6,iter=1),times=20)
Unit: milliseconds
                           expr        min         lq       mean     median        uq        max neval cld
  loess(y ~ x, dat, span = 0.6) 153.034810 154.450750 156.794257 156.004357 159.23183 163.117746    20   c
            approx(dat, n = 20)   1.297685   1.346773   1.689133   1.441823   1.86018   4.281735    20 a  
 lowess(dat, f = 0.6, iter = 1)   9.637583  10.085613  11.270911  11.350722  12.33046  12.495343    20  b 

Loesses extremadamente lento, tarda 100 veces más que approx. Lowessproduce mejores resultados que approx, sin dejar de correr bastante rápido (15 veces más rápido que loess).

Loess También se empantana cada vez más a medida que aumenta el número de puntos, volviéndose inutilizables alrededor de 50.000.

EDITAR: La investigación adicional muestra que loessofrece mejores ajustes para ciertos conjuntos de datos. Si se trata de un conjunto de datos pequeño o el rendimiento no es una consideración, pruebe ambas funciones y compare los resultados.

Craig
fuente
8

En ggplot2 puedes suavizar de varias formas, por ejemplo:

library(ggplot2)
ggplot(mtcars, aes(wt, mpg)) + geom_point() +
  geom_smooth(method = "gam", formula = y ~ poly(x, 2)) 
ggplot(mtcars, aes(wt, mpg)) + geom_point() +
  geom_smooth(method = "loess", span = 0.3, se = FALSE) 

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

jsb
fuente
¿Es posible utilizar este geom_smooth para otros procesos?
Ben
2

No vi este método mostrado, así que si alguien más está buscando hacer esto, descubrí que la documentación de ggplot sugería una técnica para usar el gammétodo que producía resultados similares a los que se usaban loesscuando se trabajaba con conjuntos de datos pequeños.

library(ggplot2)
x <- 1:10
y <- c(2,4,6,8,7,8,14,16,18,20)

df <- data.frame(x,y)
r <- ggplot(df, aes(x = x, y = y)) + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs"))+geom_point()
r

Primero con el método loess y la fórmula automática Segundo con el método gam con la fórmula sugerida

Adam Bunn
fuente