OP. The comments to your question have already referenced some good approaches, although it seems you're still having some trouble to integrate these. I'll try to make it simple and complete here for the beginner programming in R
and using ggplot2
. It will be somewhat long, but if you just want to skip:
TL;DR - convert your dataframe to long format using
tidyr::pivot_longer()
, then assign the color=
aesthetic to match
the newly-created column for names and map y=
to the newly-created
column for values.
Example Data
First, let's create an example dataset that mimics your question's case. In the future, I would advise you do the same in your question or use any one of the built-in datasets from base R
or ggplot2
(you can view them by typing data()
into the console).
library(ggplot2)
# example dataset
wide_data <- data.frame(
Days=rep(1:5, 3),
Wildtype = c(100, 120, 124, 105, 108),
Winter = c(80, 82, 83, 90, 84),
Flagella_less = c(25, 84, 90, 110, 113))
# basic long plot
p <- ggplot(data=wide_data) +
geom_line(aes(x=Days, y=Wildtype, color="red"), color="red") +
geom_point(aes(x=Days, y=Wildtype, color="red"), color="red") +
geom_line(aes(x=Days, y=Winter, color="blue"), color="blue") +
geom_point(aes(x=Days, y=Winter, color="blue"), color="blue") +
geom_line(aes(x=Days, y=Flagella_less, color="green3"), color="green3") +
geom_point(aes(x=Days, y=Flagella_less, color="green3"), color="green3")
p

Placement of Aesthetic Arguments
There is one issue right from the start with the plot code for p
above, which is that you have the general format: geom_*(aes(... color="red"), color="red...)
. It's a good opportunity to demonstrate first how the placement of the color=
aesthetic changes things if it's placed inside the aes()
function vs. outside the aes()
function.
When color=
is placed outside aes()
, then the aesthetic assigned is drawn for the whole geom. So, if you specify color="red"
, then you get a red geom. If you specify size=4
, you get a geom drawn at size 4, and so on.
When color=
is placed inside aes()
, then the characteristics of that particular aesthetic are mapped according to that assignment. So, geom_point(aes(color=columnA...))
would generate points and decide if the points share the same color based on whatever the value of columnA
was in your dataset. If you assign a character, like color="darkred"
, this will not actually color the point red, but instead will assign each observation in your dataset to be kind of labeled as being part of the group that is called "darkred"
. Color will then be chosen based on the default color scale.
If you specify in both places (like you have), then color=
outside aes()
will override any mapping (same result as not mapping in aes()
at all).
This can be quite useful in that when you place something inside the aes()
function, the result of mapping will be to create a legend for that particular aesthetic.. .but again, the colors are not actually mapped correctly. Instead, the part assigned to color=
inside aes()
will be used as the label in the legend. See this example if we remove the assignment of color=
outside of aes()
:
p_inside_aes <- ggplot(data=wide_data) +
geom_line(aes(x=Days, y=Wildtype, color="red")) +
geom_point(aes(x=Days, y=Wildtype, color="red")) +
geom_line(aes(x=Days, y=Winter, color="blue")) +
geom_point(aes(x=Days, y=Winter, color="blue")) +
geom_line(aes(x=Days, y=Flagella_less, color="green3")) +
geom_point(aes(x=Days, y=Flagella_less, color="green3"))
p_inside_aes

The legend is created, named according to the assignment, but the color is determined based on a default color option.
First (Bad) Solution
The above point leads to the first solution. This is to assign the label for your data within the geom calls to color
inside of aes()
, and then assign colors manually via scale_color_manual()
if you are unhappy with the default coloring:
ggplot(data=wide_data) +
geom_line(aes(x=Days, y=Wildtype, color="Wildtype")) +
geom_point(aes(x=Days, y=Wildtype, color="Wildtype")) +
geom_line(aes(x=Days, y=Winter, color="Winter")) +
geom_point(aes(x=Days, y=Winter, color="Winter")) +
geom_line(aes(x=Days, y=Flagella_less, color="Flagella_less")) +
geom_point(aes(x=Days, y=Flagella_less, color="Flagella_less")) +
scale_color_manual(values=c("Wildtype"="red", "Winter"="blue", "Flagella_less"="green3"))

This "solves" your problem, but it's a really bad approach to plotting and completely counter to the Principles of Tidy Data upon which the ggplot
package was built.
The Best Solution - Pivot your Data
The problem with this approach would become apparent when you have multiple columns in your data. With only three columns of data, you can see how complicated the code is becoming. What about when you have 20 columns? 50? You can imagine the horror of typing out 100 lines of code for a simple plot.
The better way of thinking is to understand that each column is giving you values of a particular nature (your y axis values), and each column name is actually indicating something else (the "type" or whatever term you want to specify). What you actually want is to ask ggplot
to plot the data where you assign columns for each aesthetic:
- x = Days column
- y = the value.. .here I guess "OD"? It's not a column that exists in your dataset, but is embedded... hidden... or spread among all the other columns
- color = the type or name of the column. Here color is also not in one column, but spread out among the column names.
The best way to solve this is to gather all your spread out data into this more meaningful structure, which is called tidy data. Lucikly, it's easy to do this via code, as there are a number of functions that can do this sort of thing: reshape()
, gather()
, and melt()
to name a few. Here I'll show you how to do this via pivot_longer()
:
long_data <- tidyr::pivot_longer(
data=wide_data,
cols=-Days,
names_to="Type",
values_to="OD")
We keep the Days
column as is, but for everything else, we gather all the names of the columns into a new column called "Type"
and we gather all the values in each of those columns into a new column called "OD"
.
Then, the plot code is way way simpler and easier to understand:
ggplot(long_data, aes(x=Days, y=OD, color=Type)) +
geom_line() + geom_point() +
scale_color_manual(values=c("Wildtype"="red", "Winter"="blue", "Flagella_less"="green3"))
Line-by-line, it's doing this:
- In the primary
ggplot()
call, you associate the dataset, and assign aesthetics that are applied to every geom: x
, y
, and color
in one place.
- The second line just asks to draw two geoms... not 6 like before, since
ggplot
will know how to color and draw separate lines based on the mapping defined in aes()
.
- The
scale_color_manual()
function is not strictly necessary, but if you must define what colors you want to use, you can do it this way. There are a few other methods to use to define colors automatically, or you can just accept the default color scheme. Up to you.
Here's the result:

Long answer, but hopefully quite clear now.