如何在R中将平滑曲线拟合到我的数据?

100

我正在尝试在R中绘制平滑曲线。我有以下简单的玩具数据:

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

现在,当我使用标准命令绘制时,它看起来很崎岖而且有棱角:

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

我该如何使曲线平滑,从而使用估计值将三个边缘变圆?我知道有许多方法可以拟合平滑曲线,但我不确定哪种方法最适用于这种曲线,以及如何在 R 中编写它。


3
这完全取决于您的数据是什么以及为什么要进行平滑处理!这些数据是计数、密度还是测量值?可能存在什么样的测量误差?您试图通过图表向读者讲述什么故事?所有这些问题都会影响您是否以及如何平滑处理数据。 - Harlan
这些是测量数据。在x值为1、2、3、...、10的情况下,某个系统产生了2、4、6、...、20个错误。这些坐标可能不应该被拟合算法改变。但我想模拟缺失的x值处的误差(y),例如在数据中,f(4)=8和f(5)=7,因此假设f(4.5)在7和8之间,使用一些多项式或其他平滑方法。 - Frank
2
在这种情况下,对于每个x值只有一个数据点,我不会进行平滑处理。我只会使用大点表示测量数据点,并用细线将它们连接起来。其他任何处理方式都会让观察者认为你比实际了解更多关于数据的信息。 - Harlan
你的观点可能是正确的。虽然这个例子中用不到,但了解如何做很好,以后可能会在其他数据上使用,例如当你有成千上万个非常尖锐的数据点,它们有一定的趋势,比如像这样向上:plot(seq(1,100)+runif(100, 0,10), type='l')。 - Frank
这里有一个好方法,https://stats.stackexchange.com/a/278666/134555 - Belter
9个回答

115

我非常喜欢使用loess()来进行数据平滑:

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)

Venables和Ripley的MASS书籍中有一个完整的平滑部分,其中还包括样条和多项式 - 但loess()几乎是每个人最喜欢的。


你如何将它应用到这个数据上?我不太确定,因为它期望一个公式。谢谢! - Frank
7
就像我在示例中向您展示的那样,如果xy是可见变量。如果它们是名为foo的数据框的列,则在loess(y ~ x, data=foo)调用中添加data=foo选项--就像R中几乎所有其他建模函数一样。 - Dirk Eddelbuettel
4
我也喜欢 supsmu() 作为一个开箱即用的平滑函数。 - apeescape
4
如果x是一个日期参数,那么它将如何工作?如果我尝试使用一个将日期映射到数字的数据表(使用lo <- loess(count~day, data=logins_per_day)),我会得到以下错误:另外:警告信息: 转换引入了NAs``` - Wichert Akkerman
1
@Wichert Akkerman 看起来大多数 R 函数都不喜欢日期格式。我通常会像这样做:new$date = as.numeric(new$date, as.Date("2015-01-01"), units="days")(如 https://stat.ethz.ch/pipermail/r-help/2008-May/162719.html 中所述)。 - reducing activity
如果线条大多是垂直的(即单个 x 有许多不同的 y),会怎么样? - drastega

68

或许可以尝试使用smooth.spline函数。你可以在这里设置一个平滑参数(通常在0到1之间)。

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

您还可以在smooth.spline对象上使用predict函数。该函数随base R一起提供,有关详细信息请参见?smooth.spline。


28

为了让它真正变得平滑...

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)

这种样式插值了很多额外的点,从而得到了一个非常平滑的曲线。它似乎也是ggplot采用的方法。如果标准的平滑程度可以接受,那么你可以直接使用。

scatter.smooth(x, y)

26

ggplot2软件包中的qplot()函数非常简单易用,提供了一种优雅的解决方案,包括置信区间带。例如,

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

生产 enter image description here


不是回避问题,但我认为对于平滑拟合的R^2(或伪R^2)值的报告是可疑的。随着带宽的减小,平滑器必然会更接近数据。 - Underminer
1
这可能会有所帮助:https://dev59.com/vmsz5IYBdhLWcg3w9soQ - Underminer
1
嗯,我最终无法在R 3.3.1上运行您的代码。我成功安装了ggplot2,但是无法运行qplot,因为Debian 8.5中找不到该函数。 - Léo Léopold Hertz 준영

15

LOESS是一种非常好的方法,正如Dirk所说。

另一个选择是使用贝塞尔样条曲线,在数据点不多的情况下,可能比LOESS更有效。

在这里您会找到一个例子: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")

13

其他答案都是很好的方法。然而,在R中还有一些其他选项没有被提到,包括lowessapprox,它们可能会给出更好的拟合或更快的性能。

这些优点可以更容易地通过替代数据集来演示:

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)))

这是用Sigmoid曲线生成的数据叠加在一起的结果:

Data

当我们观察人群中的二元行为时,这种数据很常见。例如,这可能是一个客户是否购买某物(y轴上的二元1/0)与他们在网站上花费的时间(x轴)的情况。

大量的点被用来更好地展示这些函数之间的性能差异。

在像这样的数据集上,Smoothsplinesmooth.spline使用任何我尝试过的参数都会产生无意义的结果,也许是因为它们倾向于映射到每个点,而嘈杂的数据不适合这种方法。

loesslowessapprox函数都可以产生可用的结果,尽管对于approx来说勉强而已。下面是每个函数使用轻度优化参数的代码:

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))

结果如下:

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"))

Fits

正如您所看到的,lowess 对原始生成曲线产生了近乎完美的拟合。Loess 接近,但在两端经历了奇怪的偏差。

尽管您的数据集将会非常不同,但我发现其他数据集表现类似,loesslowess 都能够产生良好的结果。当您查看基准测试时,这些差异变得更加显著:

> 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 

Loess非常缓慢,比approx慢100倍。 Lowess产生比approx更好的结果,而仍然运行相当快(比loess快15倍)。

Loess在点数增加时也变得越来越拖沓,在大约50,000个点左右变得无法使用。

编辑:进一步的研究表明,对于某些数据集,loess提供更好的适合度。如果您处理的是小型数据集或性能不是考虑因素,请尝试这两种函数并比较结果。


9

在ggplot2中,你可以通过多种方式进行平滑处理,例如:

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) 

enter image description here enter image description here


这个geom_smooth能否用于进一步的处理? - Ben

3

我没有看到这种方法的展示,所以如果有人想要这样做,我发现ggplot文档提供了一种使用gam方法的技巧,当处理小数据集时,可以产生类似于loess的结果。

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

首先使用黄土法和自动公式 其次使用gam方法和建议的公式


0
另一个选项是使用ggpubr包中的ggscatter函数。通过指定add="loess",您可以获得数据的平滑线。在上面的链接中,您可以找到更多使用此功能的可能性。这里是一个可重现的示例,使用mtcars数据集:
library(ggpubr)
ggscatter(data = mtcars,
          x = "wt",
          y = "mpg",
          add = "loess",
          conf.int = TRUE)
#> `geom_smooth()` using formula 'y ~ x'

使用reprex v2.0.2于2022年8月28日创建


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接