Wie passe ich eine glatte Kurve an meine Daten in R an?

87

Ich versuche eine glatte Kurve zu zeichnen R. Ich habe die folgenden einfachen Spielzeugdaten:

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

Wenn ich es jetzt mit einem Standardbefehl zeichne, sieht es natürlich holprig und nervös aus:

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

Wie kann ich die Kurve glatt machen, damit die 3 Kanten mit geschätzten Werten gerundet werden? Ich weiß, dass es viele Methoden gibt, um eine glatte Kurve anzupassen, aber ich bin mir nicht sicher, welche für diese Art von Kurve am besten geeignet ist und wie Sie sie schreiben würden R.

Frank
quelle
3
Es hängt ganz davon ab, was Ihre Daten sind und warum Sie sie glätten! Zählen die Daten? Dichte? Messungen? Welche Art von Messfehler könnte es geben? Welche Geschichte versuchen Sie Ihren Lesern mit Ihrer Grafik zu erzählen? All diese Probleme wirken sich darauf aus, ob und wie Sie Ihre Daten glätten sollten.
Harlan
Dies sind Messdaten. Bei x-Werten 1, 2, 3, ..., 10 hat ein System 2, 4, 6, ..., 20 Fehler gemacht. Diese Koordinaten sollten wahrscheinlich nicht durch den Anpassungsalgorithmus geändert werden. Aber ich möchte die Fehler (y) bei den fehlenden x-Werten simulieren, zum Beispiel in den Daten, f (4) = 8 und f (5) = 7, also ist f (4.5) vermutlich etwas zwischen 7 und 8, wobei etwas Polynom oder andere Glättung.
Frank
2
In diesem Fall würde ich mit einem einzelnen Datenpunkt für jeden Wert von x überhaupt nicht glätten. Ich hätte nur große Punkte für meine gemessenen Datenpunkte, mit dünnen Linien, die sie verbinden. Alles andere deutet darauf hin, dass Sie mehr über Ihre Daten wissen als Sie.
Harlan
Sie können für dieses Beispiel richtig sein. Es ist gut zu wissen, wie es geht, und ich möchte es vielleicht später für einige andere Daten verwenden, z. B. ist es sinnvoll, wenn Sie Tausende von sehr stacheligen Datenpunkten haben, die auf und ab gehen, aber es gibt einen allgemeinen Trend Beispiel: Gehen Sie wie hier nach oben: plot (seq (1.100) + runif (100, 0,10), type = 'l').
Frank
Hier ist ein guter Weg, stats.stackexchange.com/a/278666/134555
Belter

Antworten:

103

Ich mag loess()viel zum Glätten:

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 und Ripleys MASS-Buch enthält einen ganzen Abschnitt über das Glätten, der auch Splines und Polynome behandelt - aber loess()fast jedermanns Favorit.

Dirk Eddelbuettel
quelle
Wie wenden Sie es auf diese Daten an? Ich bin mir nicht sicher wie, weil es eine Formel erwartet. Vielen Dank!
Frank
6
Wie ich Ihnen im Beispiel gezeigt habe, wenn xund ysind sichtbare Variablen. Wenn es sich um Spalten eines data.frame mit dem Namen handelt foo, fügen Sie data=foodem loess(y ~ x. data=foo)Aufruf eine Option hinzu - genau wie bei fast allen anderen Modellierungsfunktionen in R.
Dirk Eddelbuettel
4
Ich mag es auch supsmu()als Out-of-the-Box-Glätter
Apeescape
4
Wie würde das funktionieren, wenn x ein Datumsparameter ist? Wenn ich es mit einer Datentabelle versuche, die ein Datum einer Zahl zuordnet (mit lo <- loess(count~day, data=logins_per_day) ), erhalte ich Folgendes:Error: NA/NaN/Inf in foreign function call (arg 2) In addition: Warning message: NAs introduced by coercion
Wichert Akkerman
1
@Wichert Akkerman Es scheint, dass das Datumsformat von den meisten R-Funktionen gehasst wird. Normalerweise mache ich so etwas wie new $ date = as.numeric (neues $ date, as.Date ("2015-01-01"), unit = "days") (wie unter stat.ethz.ch/pipermail/r- beschrieben) help / 2008-May / 162719.html )
Reduzierung der Aktivität
57

Möglicherweise ist glatt.spline eine Option. Hier können Sie einen Glättungsparameter (normalerweise zwischen 0 und 1) festlegen

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

Sie können Predict auch für Smooth.Spline-Objekte verwenden. Die Funktion wird mit Basis R geliefert. Weitere Informationen finden Sie unter? Smooth.spline.

Karsten W.
quelle
27

Um es wirklich glatt zu bekommen ...

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)

Dieser Stil interpoliert viele zusätzliche Punkte und liefert eine Kurve, die sehr glatt ist. Es scheint auch der Ansatz zu sein, den ggplot verfolgt. Wenn die Standardglätte in Ordnung ist, können Sie sie einfach verwenden.

scatter.smooth(x, y)
John
quelle
25

Die Funktion qplot () im Paket ggplot2 ist sehr einfach zu verwenden und bietet eine elegante Lösung, die Konfidenzbänder enthält. Zum Beispiel,

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

produziert Geben Sie hier die Bildbeschreibung ein

Underminer
quelle
Um der Frage nicht auszuweichen, finde ich die Meldung von R ^ 2-Werten (oder Pseudo-R ^ 2-Werten) für eine geglättete Anpassung zweifelhaft. Ein Glatter passt notwendigerweise näher an die Daten, wenn die Bandbreite abnimmt.
Underminer
Dies kann helfen: stackoverflow.com/questions/7549694/…
Underminer
Hmm, ich konnte Ihren Code in R 3.3.1 nicht endgültig ausführen. Ich habe ggplot2erfolgreich installiert, kann aber nicht ausgeführt werden, qplotda die Funktion in Debian 8.5 nicht gefunden werden kann.
Léo Léopold Hertz 준영
13

LOESS ist ein sehr guter Ansatz, wie Dirk sagte.

Eine andere Option ist die Verwendung von Bezier-Splines, die in einigen Fällen besser funktionieren als LOESS, wenn Sie nicht viele Datenpunkte haben.

Hier finden Sie ein Beispiel: 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
quelle
11

Die anderen Antworten sind alle gute Ansätze. Es gibt jedoch einige andere Optionen in R, die nicht erwähnt wurden, einschließlich lowessund approx, die möglicherweise eine bessere Passform oder eine schnellere Leistung ergeben.

Die Vorteile lassen sich mit einem alternativen Datensatz leichter demonstrieren:

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

Hier sind die Daten, die mit der Sigmoidkurve überlagert sind, die sie generiert hat:

Daten

Diese Art von Daten ist häufig, wenn ein binäres Verhalten in einer Population betrachtet wird. Dies kann beispielsweise ein Diagramm darüber sein, ob ein Kunde etwas gekauft hat (ein binäres 1/0 auf der y-Achse) oder nicht, und wie viel Zeit er auf der Site verbracht hat (x-Achse).

Eine große Anzahl von Punkten wird verwendet, um die Leistungsunterschiede dieser Funktionen besser zu demonstrieren.

Smooth, splineUndsmooth.spline alle Produkte Kauderwelsch auf einem Datensatz , wie dies mit jedem Satz von Parametern habe ich versucht, vielleicht aufgrund ihrer Neigung zu jedem Punkt auf der Karte, die für verrauschte Daten nicht funktioniert.

Die loess, lowessund approxFunktionen alle Produkte brauchbare Ergebnisse, wenn auch nur knapp für approx. Dies ist der Code für jeden, der leicht optimierte Parameter verwendet:

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

Und die Ergebnisse:

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

Passt

Wie Sie sehen können, lowessergibt sich eine nahezu perfekte Anpassung an die ursprüngliche Erzeugungskurve. Loessist nah, erfährt aber eine merkwürdige Abweichung an beiden Schwänzen.

Obwohl Ihr Datensatz sehr unterschiedlich sein wird, habe ich festgestellt, dass andere Datensätze ähnlich funktionieren, mit beiden loessund lowessin der Lage sind, gute Ergebnisse zu erzielen. Die Unterschiede werden bedeutender, wenn Sie sich Benchmarks ansehen:

> 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 

Loessist extrem langsam und dauert 100x so lange wie approx. Lowessliefert bessere Ergebnisse als approx, läuft aber immer noch ziemlich schnell (15x schneller als Löss).

Loess Mit zunehmender Anzahl von Punkten bleibt es auch zunehmend stecken und wird um 50.000 unbrauchbar.

BEARBEITEN: Zusätzliche Untersuchungen zeigen, dass loessbestimmte Datensätze besser passen. Wenn Sie mit einem kleinen Datensatz arbeiten oder die Leistung keine Rolle spielt, probieren Sie beide Funktionen aus und vergleichen Sie die Ergebnisse.

Craig
quelle
8

In ggplot2 können Sie Glättungen auf verschiedene Arten durchführen, zum Beispiel:

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) 

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

jsb
quelle
Ist es möglich, dieses geom_smooth für weitere Prozesse zu verwenden?
Ben
2

Diese Methode wurde nicht angezeigt. Wenn also jemand anderes dies tun möchte, stellte ich fest, dass in der ggplot-Dokumentation eine Technik für die Verwendung der gamMethode vorgeschlagen wurde, die ähnliche Ergebnisse wie loessbei der Arbeit mit kleinen Datenmengen liefert.

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

Erstens mit der Lössmethode und der automatischen Formel. Zweitens mit der Gam-Methode mit der vorgeschlagenen Formel

Adam Bunn
quelle