7

Based on this great question: How to draw a smooth curve passing through some points

How would one do this in lattice?

plot(rnorm(120), rnorm(120), col="darkblue", pch=16, xlim=c(-3,3), ylim=c(-4,4))
points(rnorm(120,-1,1), rnorm(120,2,1), col="darkred", pch=16)
points(c(-1,-1.5,-3), c(4,2,0), pch=3, cex=3)
xspline(c(-1,-1.5,-3), c(4,2,0), shape = -1)

Should look like that:

Here is similar data, formatted more appropriately for a lattice plot:

dat <- data.frame(x=c(rnorm(120), rnorm(120,-1,1)),
                  y=c(rnorm(120), rnorm(120,2,1)),
                  l=factor(rep(c('B','R'),each=120))
)
spl <- data.frame(x=c(-1,-1.5,-3), 
                  y=c(4,2,0)
)

And here is what the linked question gave, translated to lattice:

xyplot(y ~ x,
       data=dat,
       groups=l,
       col=c("darkblue", "darkred"),
       pch=16,
       panel = function(x, y, ...) {
         panel.xyplot(x=spl$x, y=spl$y, pch=3, cex=3)
         ## panel.spline(x=spl$x, y=spl$y)              ## Gives an error, need at least four 'x' values
         panel.superpose(x, y, ...,
                         panel.groups = function(x, y, ...) {
                           panel.xyplot(x, y, ...)
                         }
         )
       },
       xlim=c(-3,3), ylim=c(-4,4)
)

enter image description here

Community
  • 1
  • 1
Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112
  • 1
    This question is producing some great answers. One of the tricky things in 'lattice' plotting is how one properly annotates existing plots. There are a variety of techniques that can be applied within the original function or to overly existing functions. – IRTFM Jan 13 '13 at 20:30
  • 1
    Whether or not you adopt the specific implementation in my answer below, the [`grid.xspline()`](http://stat.ethz.ch/R-manual/R-patched/library/grid/html/grid.xspline.html) function will likely come in handy... – Josh O'Brien Jan 14 '13 at 02:32
  • @JoshO'Brien Indeed, `grid.xspline()` works for the plain `lattice` code as well. – Matthew Lundberg Jan 14 '13 at 02:40
  • @MatthewLundberg I added a `ggplot2` solution using `grid.xspline()`. – agstudy Jan 14 '13 at 10:48

5 Answers5

4

Here's a line-for line 'translation' of the base graphics solution into lattice. (The directness of the translation is made possible by the + operator supplied by the latticeExtra package. See ?layer for details of its usage.)

The final line invokes grid.xspline(), an exact grid analogue of the base graphic function xspline().

library(lattice)
library(grid)
library(latticeExtra)

xyplot(rnorm(120)~rnorm(120), pch=16, col="darkblue", 
       xlim = c(-3.1, 3.1), ylim = c(-4.1, 4.1)) +
xyplot(rnorm(120,2,1) ~ rnorm(120,-1,1), pch=16, col="darkred") +
xyplot(c(4,2,0) ~ c(-1,-1.5,-3), pch=3, cex=3) +
layer(grid.xspline(c(-1,-1.5,-3), c(4,2,0), shape = -1, default.units="native"))

(One peculiar detail of grid does pop up in the final line above: like several other of its low-level line-drawing functions, grid.xspline() defaults to "npc" units instead of the usually-desired "native" units used as defaults by grid.points() and many other grid.*() functions. Obviously that's easy enough to change --- once you're aware of it!)

enter image description here

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • `latticeExtra` is something that I haven't used. I'll investigate it, although I prefer the "callback" nature of `lattice` as it is. – Matthew Lundberg Jan 14 '13 at 02:38
  • Love that grid-iffied answer! – IRTFM Jan 14 '13 at 02:47
  • +1 for this great answer! I am pretty sure that this can be generalized to any base graphic mathematical|statistical|graphical function.I mean every time you need something in base you find the homologous in grid. – agstudy Jan 14 '13 at 08:40
  • @agstudy -- Not sure if that's **strictly** true, but yeah, I've found it to be so every time I've needed it. It helps a lot that [Paul Murrel](http://www.stat.auckland.ac.nz/~paul/) (bless him) has been so deeply involved in implementing *both* the **base** and **grid** graphical systems! – Josh O'Brien Jan 14 '13 at 13:27
  • @DWin Thanks! The only tricky part was having to reset the `default.units` of `grid.xspline()`, a **grid** oddity [that has bit folks before](http://stackoverflow.com/questions/12376596/lines-using-the-r-grid-package/12379106#12379106). (I've now added a mention of that to my answer.) – Josh O'Brien Jan 14 '13 at 13:37
3

This is a little bit tricky but works.

plot(rnorm(120), rnorm(120), col="darkblue", pch=16, xlim=c(-3,3), ylim=c(-4,4))
points(rnorm(120,-1,1), rnorm(120,2,1), col="darkred", pch=16)
points(c(-1,-1.5,-3), c(4,2,0), pch=3, cex=3)

I use xspline without producing the draw

dd <- xspline(c(-1,-1.5,-3), c(4,2,0), shape = -1,draw=FALSE)

Then I use the points produced witn panel.lines

library(lattice)
xyplot(y ~ x,
       data=dat,
       groups=l,
       col=c("darkblue", "darkred"),
       pch=16,
       panel = function(x, y, ...) {
         panel.xyplot(x=spl$x, y=spl$y, pch=3, cex=3)
         panel.lines(dd$x,dd$y)
         panel.superpose(x, y, ...,
                         panel.groups = function(x, y, ...) {
                           panel.xyplot(x, y, ...)
                         }
         )
       },
       xlim=c(-3,3), ylim=c(-4,4)
)

enter image description here

agstudy
  • 119,832
  • 17
  • 199
  • 261
  • Nice solution, but I don't like that it requires the base plotting functions to work. – Matthew Lundberg Jan 13 '13 at 16:46
  • @MatthewLundberg Thanks. But why ? I am curious. because it generates the base plots? Does a wrapper to xspline where I call basic plot (in "invisible" manner) is an acceptable solution for you ? – agstudy Jan 13 '13 at 16:53
  • I'm looking to avoid base graphics functions. If I don't get anything better than my own answer, I'll accept this simply because it does work. – Matthew Lundberg Jan 13 '13 at 17:28
  • I don't think agstudy's answer actually depends on the preceding `plot` or `points` calls. It succeeds by just starting with the assignment to `dd`. – IRTFM Jan 13 '13 at 21:19
  • @DWin Sorry for my poor english. I didn't understand exactly your comment. have i to change something in my answer? – agstudy Jan 13 '13 at 21:40
  • My comment was not criticizing your post at all. I was suggesting that it was not deficient in the features that Matthew was suggesting. It is in the base graphics package but it is not using base graphic features to return the mathematical results. – IRTFM Jan 13 '13 at 22:02
  • @DWin It produces slightly different results (length of vectors), depending on the state of the active graphics device. For example, try the command after `plot(-1,-1)` then after `plot(100, 100)` and compare. It also fails if there is no active graphics device. – Matthew Lundberg Jan 13 '13 at 23:57
3

This is not a solution, by an attempt to use Josh solution with grid.xspline in ggplot2. I think it is interesting to get a parallel between ggplot2/lattice.

enter image description here

## prepare the data
dat <- data.frame(x=c(rnorm(120), rnorm(120,-1,1)),
                 y=c(rnorm(120), rnorm(120,2,1)),
                 l=factor(rep(c('B','R'),each=120))
)
spl <- data.frame(x=c(-2,-1.5,-3), 
                  y=c(4,2,0)
)
## prepare the scatter plot
library(ggplot(2))
p <- ggplot(data=dat,aes(x=x,y=y,color=l))+
  geom_point()+
  geom_point(data=spl,aes(x=x,y=y),color='darkred',size=5)
library(grid)
ff <- ggplot_build(p)

My idea is to use the scales generated by ggplot2, to create the spline in the same panel than the scatterplot. Personally I find this tricky, and I hope that someone comes with a better solution.

xsp.grob <- xsplineGrob(spl$x, spl$y,
                        vp=viewport(xscale =ff$panel$ranges[[1]]$x.range,
                                    yscale = ff$panel$ranges[[1]]$y.range),
                        shape = -1, default.units="native")
p
grid.add(gPath='panel.3-4-3-4',child=xsp.grob)
agstudy
  • 119,832
  • 17
  • 199
  • 261
2

I finally found a solution to this, based on the answer to this question: Quadratic spline

Using package splines

Replace panel.splines(...) (commented out above) with this code:

         local({
           model <- lm(y ~ bs(x, degree=2), data=spl)
           x0 <- seq(min(spl$x), max(spl$x), by=.1)
           panel.lines(x0, predict(model, data.frame(x=x0)))
         })

enter image description here

From Josh O'Brien's excellent suggestion, grid.xspline() can replace the commented-out panel.splines(...) line, resulting in the exact plot as in the base question, linked above (except for the margins):

         grid.xspline(spl$x, spl$y, shape = -1, default.units="native")

enter image description here

Community
  • 1
  • 1
Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112
0

Here is a variation of a spline panel from Deepayan Sarkar

panel.smooth.spline <-  function(x, y,
                                 w=NULL, df, spar = NULL, cv = FALSE,
                                 lwd=plot.line$lwd, lty=plot.line$lty,col, 
                                 col.line=plot.line$col,type, ... )
{
  x <- as.numeric(x)
  y <- as.numeric(y)
  ok <- is.finite(x) & is.finite(y)
  if (sum(ok) < 1)
    return()
  if (!missing(col)) {
    if (missing(col.line))
      col.line <- col
  }
  plot.line <- trellis.par.get("plot.line")
  spline <-   smooth.spline(x[ok], y[ok],
                            w=w, df=df, spar = spar, cv = cv)
  pred = predict(spline,x= seq(min(x),max(x),length.out=150))
  panel.lines(x = pred$x, y = pred$y, col = col.line,
              lty = lty, lwd = lwd, ...)
  panel.abline(h=y[which.min(x)],col=col.line,lty=2)
}
Dieter Menne
  • 10,076
  • 44
  • 67
  • This may solve the problem that I actually want to solve, but doesn't work for the example: `Error using packet 1 need at least 4 unique 'x' values` -- as does `panel.spline`. – Matthew Lundberg Jan 13 '13 at 16:16