Finding a line from two points in R

Sometimes you’ll have two points in R and you’ll want to find the slope and intercept of the line going through those two points.

Manual Version

Suppose you know the line goes through (3, 6) and (4, 7). All you have to do to find the line going through those two points is

x <- c(3, 4)
y <- c(6, 7)
lm(y ~ x)

That’s simple and gives you the answer you want.

Writing a Function

You probably don’t want to do that repeatedly. As with any programming problem, it’s better to write a function to do something you’re going to do more than twice. One such convenience function looks like this (note that there are no checks for proper input):

find.line <- function(p1, p2) {
  x <- c(p1[1], p2[1])
  y <- c(p1[2], p2[2])
  fit <- as.numeric(coef(lm(y ~ x)))
  return(c(b=fit[1], m=fit[2]))
}

The as.numeric call strips the metadata from coef(fit) that will mess up the variable names. You call it using the two points, which is more intuitive than creating an x and y vector yourself:

find.line(c(3,6), c(4,7))

Finding Points on the Line

One reason to find the slope and intercept is because you want to find values of y on the line. If you really want to go crazy, you can write a function that takes your line (the output of a call to find.line) and uses it to calculate y. That’s something you might do inside an optimization function that finds an equilibrium:

find.y <- function(line, x) {
  return(line["m"]*x + line["b"])
}

You can verify that it returns the right values for the two points we know are on the line:

find.y(line, 3)  # 6
find.y(line, 4)  # 7

Advanced: Using a Closure

It’s kind of messy to always have to pass line to the find.y function. That’s kind of dumb when you think about it: line is the same every time.

Let’s do something a little more advanced and write a function that returns a find.y function. What’s different is that you’ll only need to pass x to the function because it will know the value of line.

make.fnc <- function(p1, p2) {
  x <- c(p1[1], p2[1])
  y <- c(p1[2], p2[2])
  fit <- as.numeric(coef(lm(y ~ x)))
  b <- fit[1]
  m <- fit[2]
  
  return(function(xval) {
    return(m*xval + b)
  })
}

If you’ve never done any functional programming before, the above code will give you something to think about. You’ve got a function that returns a function. You use it like this:

find.y <- make.fnc(c(3, 6), c(4, 7))
find.y(3)
find.y(4)

Advanced: Creating an Object

The last thing we can do is maybe kind of cool but less often used. We’re going to return a list that includes the function above, the slope, and the intercept. This is a primitive form of an “object” since we’re holding data and functions together.

make.obj <- function(p1, p2) {
  x <- c(p1[1], p2[1])
  y <- c(p1[2], p2[2])
  fit <- as.numeric(coef(lm(y ~ x)))
  b <- fit[1]
  m <- fit[2]
  
  return(list(b=fit[1], m=fit[2],
    y=function(xval) { return(m*xval + b) }))
}

If the previous example took a while to understand, this only takes a little more effort to figure out. Call it like this:

line <- make.obj(c(3, 6), c(4, 7))
line$b
line$m
line$y(3)
line$y(4)

That’s definitely more than anyone has ever wanted to know about finding lines in R.


⤺ Back to the List of Posts