Jak dopasować płynną krzywą do moich danych w R?

87

Próbuję narysować gładką krzywą R. Mam następujące proste dane dotyczące zabawki:

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

Teraz, kiedy kreślę to za pomocą standardowego polecenia, wygląda to oczywiście nierówno i ostro:

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

Jak mogę wygładzić krzywą, tak aby 3 krawędzie były zaokrąglone przy użyciu wartości szacunkowych? Wiem, że istnieje wiele metod dopasowania gładkiej krzywej, ale nie jestem pewien, która z nich byłaby najbardziej odpowiednia dla tego typu krzywej i jak byś ją zapisał R.

Szczery
źródło
3
Zależy to całkowicie od tego, jakie są Twoje dane i dlaczego je wygładzasz! Czy dane się liczą? Gęstości? Pomiary? Jaki może być błąd pomiaru? Jaką historię próbujesz opowiedzieć swoim czytelnikom za pomocą wykresu? Wszystkie te kwestie wpływają na to, czy i jak należy wygładzić dane.
Harlan
To są dane pomiarowe. Przy wartościach x 1, 2, 3, ..., 10 jakiś system popełnił 2, 4, 6, ..., 20 błędów. Te współrzędne prawdopodobnie nie powinny być zmieniane przez algorytm dopasowania. Ale chcę zasymulować błędy (y) przy brakujących wartościach x, na przykład w danych, f (4) = 8 if (5) = 7, więc przypuszczalnie f (4,5) jest czymś między 7 a 8, używając jakiś wielomian lub inne wygładzanie.
Frank
2
W takim przypadku z jednym punktem danych dla każdej wartości x nie wygładziłbym w ogóle. Miałbym po prostu duże kropki dla mierzonych punktów danych, połączone cienkimi liniami. Wszystko inne sugeruje widzowi, że wiesz o swoich danych więcej niż ty.
Harlan
Możesz mieć rację na tym przykładzie. Dobrze jest jednak wiedzieć, jak to zrobić, a później mogę zechcieć użyć tego do innych danych, np. Ma to sens, jeśli masz tysiące bardzo ostrych punktów danych, które rosną i maleją, ale istnieje ogólny trend , na przykład idąc w górę, jak tutaj: plot (seq (1,100) + runif (100, 0,10), type = 'l').
Frank
Oto dobry sposób, stats.stackexchange.com/a/278666/134555
Belter

Odpowiedzi:

104

Bardzo lubię loess()wygładzanie:

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)

Książka Venables and Ripley's MASS zawiera całą sekcję dotyczącą wygładzania, która obejmuje również splajny i wielomiany - ale loess()jest prawie ulubiona przez wszystkich.

Dirk Eddelbuettel
źródło
Jak zastosujesz to do tych danych? Nie jestem pewien, jak, ponieważ oczekuje formuły. Dzięki!
Frank
7
Jak pokazałem w przykładzie, kiedy zmienne if xi ysą widoczne. Jeśli są kolumny o data.frame nazwie fooThe dodać data=fooopcję do loess(y ~ x. data=foo)rozmowy - tak jak w prawie wszystkich innych funkcji modelowania w R.
Dirk Eddelbuettel
4
Lubię też supsmu()jako wygładzacz po
wyjęciu z
4
jak by to działało, gdyby x jest parametrem daty? Jeśli spróbuję z tabelą danych, która mapuje datę na liczbę (używając lo <- loess(count~day, data=logins_per_day) ), otrzymuję to:Error: NA/NaN/Inf in foreign function call (arg 2) In addition: Warning message: NAs introduced by coercion
Wichert Akkerman
1
@Wichert Akkerman Wygląda na to, że format daty jest znienawidzony przez większość funkcji R. Zwykle robię coś takiego jak nowa $ date = as.numeric (nowa $ data, as.Date ("2015-01-01"), jednostki = "dni") (zgodnie z opisem na stat.ethz.ch/pipermail/r- help / 2008-May / 162719.html )
zmniejszanie aktywności
58

Może smooth.spline jest opcją. Możesz tutaj ustawić parametr wygładzania (zazwyczaj od 0 do 1)

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

możesz także użyć predykcji na obiektach smooth.spline. Funkcja jest dostarczana z podstawą R, patrz? Smooth.spline po szczegóły.

Karsten W.
źródło
27

Aby uzyskać NAPRAWDĘ płynną ...

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)

Ten styl interpoluje wiele dodatkowych punktów i zapewnia bardzo płynną krzywą. Wydaje się również, że jest to podejście przyjęte przez ggplot. Jeśli standardowy poziom gładkości jest w porządku, możesz po prostu użyć.

scatter.smooth(x, y)
Jan
źródło
25

funkcja qplot () w pakiecie ggplot2 jest bardzo prosta w użyciu i zapewnia eleganckie rozwiązanie, które zawiera pasma ufności. Na przykład,

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

produkuje wprowadź opis obrazu tutaj

Underminer
źródło
Nie żeby uniknąć pytania, ale uważam, że raportowanie wartości R ^ 2 (lub pseudo R ^ 2) dla wygładzonego dopasowania jest wątpliwe. Płynniejszy z pewnością będzie pasował bliżej danych, gdy przepustowość zmniejszy się.
Underminer
To może pomóc: stackoverflow.com/questions/7549694/…
Underminer
Hmm, nie mogłem w końcu uruchomić twojego kodu w R 3.3.1. Zainstalowałem ggplot2pomyślnie, bu nie może działać, qplotponieważ nie może znaleźć funkcji w Debianie 8.5.
Léo Léopold Hertz 준영
13

LOESS to bardzo dobre podejście, jak powiedział Dirk.

Inną opcją jest użycie splajnów Beziera, które w niektórych przypadkach mogą działać lepiej niż LOESS, jeśli nie masz wielu punktów danych.

Tutaj znajdziesz przykład: 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
źródło
11

Pozostałe odpowiedzi to dobre podejścia. Jest jednak kilka innych opcji w R, o których nie wspomniano, w tym lowessi approx, które mogą zapewnić lepsze dopasowanie lub szybszą wydajność.

Zalety można łatwiej wykazać za pomocą alternatywnego zestawu danych:

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

Oto dane nałożone na sigmoidalną krzywą, która ją wygenerowała:

Dane

Ten rodzaj danych jest powszechny, gdy patrzy się na zachowanie binarne wśród populacji. Na przykład może to być wykres przedstawiający, czy klient coś kupił (wartość binarna 1/0 na osi y) w porównaniu z czasem spędzonym w witrynie (oś x).

Aby lepiej pokazać różnice w działaniu tych funkcji, używa się dużej liczby punktów.

Smooth, splinei smooth.splinewszystkie generują bełkot na takim zbiorze danych z dowolnym zestawem parametrów, które wypróbowałem, być może z powodu ich tendencji do mapowania do każdego punktu, co nie działa w przypadku zaszumionych danych.

Te loess, lowessoraz approxfunkcje produkują użytecznych wyników, chociaż ledwo za approx. Oto kod dla każdego z lekko zoptymalizowanymi parametrami:

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

A wyniki:

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

Pasuje

Jak widać, lowessdaje prawie idealne dopasowanie do oryginalnej krzywej generowania. Loessjest blisko, ale doświadcza dziwnego odchylenia na obu ogonach.

Chociaż Twój zestaw danych będzie bardzo różny, odkryłem, że inne zestawy danych działają podobnie, z obydwoma loessi lowesszdolnymi do generowania dobrych wyników. Różnice stają się bardziej znaczące, gdy spojrzysz na testy porównawcze:

> 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 

Loessjest niezwykle powolny, trwa 100 razy dłużej approx. Lowessdaje lepsze wyniki niż approx, podczas gdy nadal działa dość szybko (15x szybciej niż less).

Loess również staje się coraz bardziej grzęznąć w miarę wzrostu liczby punktów, a około 50 000 staje się bezużytecznych.

EDYTUJ: Dodatkowe badania pokazują, że loesszapewnia lepsze dopasowanie do niektórych zestawów danych. Jeśli masz do czynienia z małym zestawem danych lub wydajność nie jest brana pod uwagę, wypróbuj obie funkcje i porównaj wyniki.

Craig
źródło
8

W ggplot2 możesz wykonywać wygładzanie na wiele sposobów, na przykład:

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) 

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

jsb
źródło
czy można użyć tej geom_smooth do dalszych procesów?
Ben
2

Nie widziałem tej metody pokazanej, więc jeśli ktoś inny chce to zrobić, stwierdziłem, że dokumentacja ggplot zasugerowała technikę użycia gammetody, która dała podobne wyniki do loesspracy z małymi zbiorami danych.

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

Najpierw metodą lessu i formułą auto Drugą metodą gam z sugerowaną formułą

Adam Bunn
źródło