Other answers are all good approaches. However, there are several other options in R that have not been mentioned, including lowess and approx , which can give better features or better performance.
The benefits are easier to demonstrate with an alternative dataset:
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)))
Here is the data superimposed on the sigmoid curve that generated it:

Similar data are common when considering binary behavior among the population. For example, this could be a graph of whether a customer is buying something (binary 1/0 on the Y axis) and the amount of time they spent on the site (x axis).
A large number of points are used to better demonstrate the differences in the performance of these functions.
Smooth , spline and smooth.spline all gibberish in a data set like this with any set of parameters I tried, possibly because of their tendency to map to each point, which doesn't work for noisy data.
The functions loess , lowess and approx all give useful results, although hardly for approx . This is the code for each using slightly optimized parameters:
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))
And the results:
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"))

As you can see, lowess creates an almost perfect fit to the original generation curve. loess is close, but experiences a strange deviation on both tails.
Although your dataset will be completely different, I found that other datasets work similarly, with loess and lowess able to produce good results. The differences become more significant when you look at the tests:
> 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 extremely slow, taking 100x until approx . lowess gives better results than approx , but still works pretty fast (15 times faster than loess).
loess also becoming more and more bogged down as the number of points increases, becoming unusable around 50,000.
EDIT: More research shows that loess provides better tricks for some datasets. If you are dealing with a small set of data or performance, this is not a consideration, try both functions and compare the results.