Skip to main content
v2026.11,858 entries · CC-BY 4.0

Plotting a Line in R: plot(type = “l”), lines() and geom_line()

How to plot a line in R with plot(type = “l”), lines() and geom_line(), and how to fix zigzag lines, NA gaps and missing groups.

Written and maintained by CASRAI Editorial Board

Last updated

To plot a line in R, call plot(x, y, type = "l") in base R or add geom_line() to a ggplot2 chart; to add a second line to an existing plot, call lines(x, y). “Plot a line” is used for three different jobs, and each has a different function: connecting data points in order (this page), adding a line to a plot that already exists, and drawing a straight or fitted line through a scatterplot.

Which one do you need? Connecting measurements across time, dose or distance is covered below. A straight reference or regression line over a scatterplot is a different task, covered in CASRAI’s Scatterplots in R guide (base R’s abline() is the function documented for “drawing (single) straight lines”). A distribution rather than a trend belongs in a histogram, and categories belong in a bar plot. For a wider chooser, see Types of Graphs in Statistics.

The example data below are small vectors written into the code so you can paste and run them; they are illustrative values, not measurements from any study.

What it is used for in research

A line plot joins observations in order so the reader sees change: a time course, a growth curve, a dose-response series, a measurement repeated at fixed intervals. The joining is the claim the figure makes, so it is only appropriate when the x values have a meaningful order and the step between neighbouring points is something you are willing to interpolate over. For two unordered variables use a scatterplot; for modelling serial dependence in the data, see ARIMA Models for Research Time Series.

Install or access

Base R plotting needs no extra package: plot() and lines() belong to the graphics package that ships with R. R itself is downloaded from the Comprehensive R Archive Network (CRAN) at cran.r-project.org. ggplot2 is installed from within R:

install.packages("ggplot2")
library(ggplot2)

The ggplot2 documentation also shows install.packages("tidyverse") as the easiest route, since it installs ggplot2 along with the rest of the tidyverse. If you are new to the environment, CASRAI’s How to Use RStudio and What Is R Programming Used For cover the basics.

Step-by-step: plot a line

1. Base R: plot() with type = “l”

The default plot type is points (type = "p"). The documented one-character type codes include "l" for lines, "b" for both points and lines, "o" for points and lines overplotted, "s" and "S" for stair steps, "h" for histogram-like vertical lines, and "n" for no points or lines.

  1. Put the x values and y values in two vectors of equal length, with x in the order you want them joined.
  2. Call plot() with type = "l":
hours  <- c(0, 2, 4, 6, 8, 10)
signal <- c(1.0, 2.1, 3.9, 5.2, 5.8, 6.1)

plot(hours, signal, type = "l",
     xlab = "Time (h)", ylab = "Signal (arbitrary units)",
     main = "Signal over time")
  1. Style the line with col (colour), lty (line type) and lwd (line width), for example plot(hours, signal, type = "b", col = "blue", lty = 2, lwd = 2). Use type = "b" when readers should see where the actual measurements are, not only the joins between them.

2. Add a second line with lines()

lines() is documented as a generic function that takes coordinates and joins the points with line segments; its default method is lines(x, y = NULL, type = "l", ...). It draws on the current plot and does not rescale the axes, so set the y range on the first call to cover every series:

signal2 <- c(0.8, 1.4, 2.0, 2.4, 3.1, 3.3)

plot(hours, signal, type = "l", col = "black",
     ylim = range(c(signal, signal2)),
     xlab = "Time (h)", ylab = "Signal (arbitrary units)")
lines(hours, signal2, col = "red", lty = 2)
legend("topleft", legend = c("Group A", "Group B"),
       col = c("black", "red"), lty = c(1, 2))

If you do not want the first series to draw anything, plot(hours, signal, type = "n") sets up the axes without drawing points or lines, and every series is then added with lines().

3. ggplot2: geom_line()

ggplot2 expects a data frame with one row per observation. geom_line() “connects them in order of the variable on the x axis”:

library(ggplot2)

df <- data.frame(
  hours  = rep(c(0, 2, 4, 6, 8, 10), times = 2),
  signal = c(1.0, 2.1, 3.9, 5.2, 5.8, 6.1,
             0.8, 1.4, 2.0, 2.4, 3.1, 3.3),
  group  = rep(c("A", "B"), each = 6)
)

ggplot(df, aes(x = hours, y = signal, colour = group)) +
  geom_line(linewidth = 1) +
  labs(x = "Time (h)", y = "Signal (arbitrary units)")

Two lines are drawn because colour = group maps a grouping variable; ggplot2’s documentation states that “the group aesthetic determines which cases are connected together.” Line width is the linewidth aesthetic (for example linewidth = 3, colour = "red" in the documentation’s own examples). A single-series example from the documentation is ggplot(economics, aes(date, unemploy)) + geom_line(), using the economics data that ships with ggplot2.

Output and how to read it

Each line segment joins two adjacent observations, so the slope of a segment is the average rate of change between those two measurements and says nothing about what happened between them. Read the figure with three checks:

  • Axis range. In base R, the y axis is fixed by the first call; a line added with lines() that runs off the plot area means ylim was too narrow.
  • Markers. A line with no point markers hides how many observations exist. type = "b" or a geom_point() layer shows the sampling.
  • Legend and line type. If the figure may be printed in black and white, distinguish series by lty (base R) or linetype (ggplot2) as well as colour.

Troubleshooting

The line zigzags back and forth

Points are joined in the order the function uses. With plot(x, y, type = "l") that is the order of the rows you supplied, so unsorted x values produce a tangle. Sort first, for example o <- order(hours); plot(hours[o], signal[o], type = "l"). In ggplot2, geom_line() sorts by x for you, whereas geom_path() “connects the observations in the order in which they appear in the data.” Choose geom_path() deliberately, for a trajectory where the order of rows is the information; use geom_line() for a function of x.

There are gaps in the line

In base R, “if a point contains NA in either its x or y value, it is omitted from the plot,” and the documentation notes that “missing values can be used to achieve breaks in lines.” A gap therefore means a missing value, which is sometimes what you want (an instrument outage) and sometimes a data problem to fix upstream; count the NAs with sum(is.na(signal)) before deciding.

ggplot2 draws no line, only points or nothing

When a grouping variable puts every group at a single x value, there is nothing to connect. Check that each group has at least two observations, and that the variable you mapped to colour or group is not unique per row.

The second line is missing or clipped

lines() adds to the current plot and does not open a new one. Call it after plot(), not before, and widen ylim (and xlim if needed) on the first call.

Alternatives within R

Approach What it does Use it when
plot(x, y, type = "l") Draws a new plot and joins the points with lines (base R, graphics package). You want a quick line with no extra package.
lines(x, y) Joins points with line segments on an existing plot. You are adding a second or later series.
plot(x, y, type = "s") Stair-step plot. The value holds constant between observations.
ggplot2::geom_line() Connects observations in order of the x variable. You want grouping, colour mapping and automatic legends from one mapping.
ggplot2::geom_path() Connects observations in the order they appear in the data. Row order is meaningful, as in a trajectory.
ggplot2::geom_step() Creates a stairstep plot, highlighting exactly when changes occur. You need to show when a value changes rather than a smooth join.

FAQ

What is the difference between plot a line and line plot in R?

Nothing in the code: line plot, line graph and “plot a line” are all searched for the same output, a chart of joined points. In base R that is plot(x, y, type = "l"); in ggplot2 it is geom_line(). A straight line over a scatterplot is a different function; see the scatterplots guide.

How do I plot multiple lines in R?

In base R, call plot() for the first series with a ylim that covers all of them, then call lines() once per further series. In ggplot2, put all series in one data frame and map a grouping column to colour or group.

How do I plot a line and points together?

Use type = "b" (points and lines) or type = "o" (overplotted points and lines) in base R, or add geom_point() to a geom_line() chart.

Why does my line plot have breaks in it?

A missing value in x or y is omitted and breaks the line in base R; see the troubleshooting section above. To compare the same measurements as a distribution per group instead, see Box Plots in R.

Sources (official documentation)

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about Plotting a Line in R: plot(type = “l”), lines() and geom_line()

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →

Ask CASRAI · Regulatory Radar

Research-admin question? Get an answer that links its sources.

An AI assistant specialized in research administration. Every answer links its sources to check before you act. 2 questions free, no account. $29/month after.

  • Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.
  • Every answer numbers its sources and links each one, so you can check the source yourself.