As outras respostas são boas abordagens. No entanto, existem algumas outras opções em R que não foram mencionadas, incluindo lowesse approx, que podem fornecer ajustes melhores ou desempenho mais rápido.
As vantagens são demonstradas mais facilmente com um conjunto de dados 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)))
Aqui estão os dados sobrepostos pela curva sigmóide que os gerou:

Esse tipo de dado é comum ao observar um comportamento binário entre uma população. Por exemplo, isso pode ser um gráfico que mostra se um cliente comprou ou não algo (um binário 1/0 no eixo y) versus a quantidade de tempo que ele passou no site (eixo x).
Um grande número de pontos é usado para demonstrar melhor as diferenças de desempenho dessas funções.
Smooth,, splineesmooth.spline todos produzem gibberish em um conjunto de dados como este com qualquer conjunto de parâmetros que eu tentei, talvez devido à sua tendência de mapear para todos os pontos, o que não funciona para dados com ruído.
Os loess, lowesse approxfunções de todos os produzir resultados utilizáveis, embora apenas um pouco para approx. Este é o código para cada um usando parâmetros levemente otimizados:
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))
E os 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"))

Como você pode ver, lowessproduz um ajuste quase perfeito à curva de geração original. Loessestá perto, mas experimenta um estranho desvio em ambas as caudas.
Embora seu conjunto de dados seja muito diferente, descobri que outros conjuntos de dados têm desempenho semelhante, com ambos loesse lowesscapazes de produzir bons resultados. As diferenças se tornam mais significativas quando você olha para os benchmarks:
> 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é extremamente lento, levando 100x mais tempo approx. Lowessproduz melhores resultados do que approx, enquanto ainda é executado com bastante rapidez (15x mais rápido do que loess).
Loess também fica cada vez mais atolado à medida que o número de pontos aumenta, tornando-se inutilizável por volta de 50.000.
EDIT: Pesquisas adicionais mostram que loessoferece melhores ajustes para determinados conjuntos de dados. Se você estiver lidando com um pequeno conjunto de dados ou se o desempenho não for levado em consideração, tente as duas funções e compare os resultados.