Related
I have some fake data representing the answering times of different users answering an online survey.
The dataset has three variables: the id of the respondent (user), the name of the question (question) and the answering time for each question (time).
n <- 1000
dat <- data.frame(user = 1:n,
question = sample(paste("q", 1:4, sep = ""), size = n, replace = TRUE),
time = round(rnorm(n, mean = 10, sd=4), 0)
)
pltSingleRespondent <- function(df, highlightUsers){
dat %>%
ggplot(aes(x = question, y = time)) +
geom_boxplot(fill = 'orange') + coord_flip() +
ggtitle("Answering time per question")
}
pltSingleRespondent(dat, c(1, 31) )
I was creating a function that plots a boxplot with the answering times for each question. However, now I'd like to overlay that plot with the answering times of specific respondents (highlightUsers). The following image shows an example:
Can someone please explain me how to do this?
I think the most direct way to do this is to subset your data within a call to geom_line.
I'll start with a different set of random data, since the sample data in the question does not include all questions for a user.
set.seed(2021)
dat <- expand.grid(user = factor(1:50), question = paste0("q", 1:4))
dat$time <- round(rnorm(200, mean = 10, sd = 4), 0)
dat %>%
ggplot(aes(x = question, y = time)) +
geom_boxplot(fill = 'orange') + coord_flip() +
ggtitle("Answering time per question") +
geom_line(aes(color = user, group = user), size = 2,
data = ~ subset(., user %in% c(1L, 34L)))
You can functionize it however you want. If you're using dplyr, you can use dplyr::filter instead of subset with no other change.
Also, I chose to factor(user), since otherwise ggplot2 tends to think its data is continuous (for color=user). You can choose to use or not use this, though you may need more wrangling to get it to be discrete.
Slightly different approach. Add a column to the data that indicates the highlighted users and map that variable to geom_line. Use scale_color_discrete(na.translate = FALSE) to color only the non-NA values.
library(dplyr)
library(ggplot2)
pltSingleRespondent <- function(df, highlightUsers) {
df %>%
mutate(User = factor(ifelse(user %in% highlightUsers, user, NA))) %>%
ggplot(aes(question, time)) +
geom_boxplot(fill = "orange") +
geom_line(aes(color = User, group = User)) +
ggtitle("Answering time per question") +
scale_color_discrete(na.translate = FALSE) +
coord_flip() +
theme_bw()
}
Using the example data from #r2evans
pltSingleRespondent(dat, c(1, 34))
I have data in the following form:
sample <- data.frame(
id = c(seq(1,9,1)),
drives = sample(0:1, 9, replace = T),
bikes = sample(0:1, 9, replace = T),
outcomes = sample(1000:3000, 9, replace = F))
)
I would like to create a boxplot using ggplot2 with outcomes on the y-axis and the following groups on the x-axis: (1) people who drive (regardless of whether they bike), (2) people who bike (regardless of whether they drive), (2) people who neither drive nor bike. However, I'm having trouble, since I'm unclear how to assign a single ID (i.e. observation) to multiple groups. I just know how to assign groups based on a single variable:
p <- ggplot(sample, aes(x=as.factor(drives), y=outcomes)) +
geom_boxplot()
p
Thank you!
You can create a new data frame with these logical tests encoded as a factor:
df <- rbind(cbind(sample[sample$drives == 1,], group = "drives"),
cbind(sample[sample$bikes == 1,], group = "bikes"),
cbind(sample[sample$drives == 0 & sample$bikes == 0,], group = "neither"))
ggplot(df, aes(x=group, y=outcomes)) + geom_boxplot()
I am trying to plot multiple box plots as a single graph. The data is where I have done a wilcoxon test. It should be like this
I have four/five questions and I want to plot the respondent score for two sets as a box plot. This should be done for all questions (Two groups for each question).
I am thinking of using ggplot2. My data is like
q1o <- c(4,4,5,4,4,4,4,5,4,5,4,4,5,4,4,4,5,5,5,5,5,5,5,5,5,3,4,4,3,4)
q1s <- c(5,4,4,5,5,5,5,5,4,5,4,4,5,4,5,5,5,5,5,5,5,5,5,5,5,5,4,5,4,4)
q2o <- c(3,3,3,4,3,4,4,3,3,3,4,4,3,4,3,3,4,3,3,3,3,4,4,4,4,3,3,3,3,4)
q2s <- c(5,4,4,5,5,5,5,5,4,5,4,4,5,4,5,5,5,5,5,5,5,5,5,5,5,5,4,3,4,4)
....
....
q1 means question 1 and q2 means question 2. I also want to know how to align these stacked box plots based on my need. Like one row or two rows.
This should get you started:
Unfortunately you don't provide a minimal example with sample data, so I will generate some random sample data.
# Generate sample data
set.seed(2017);
df <- cbind.data.frame(
value = rnorm(1000),
Label = sample(c("Good", "Bad"), 1000, replace = T),
variable = sample(paste0("F", 5:11), 1000, replace = T));
# ggplot
library(tidyverse);
df %>%
mutate(variable = factor(variable, levels = paste0("F", 5:11))) %>%
ggplot(aes(variable, value, fill = Label)) +
geom_boxplot(position=position_dodge()) +
facet_wrap(~ variable, ncol = 3, scale = "free");
You can specify the number of columns and rows in your 2d panel layout through arguments ncol and nrow, respectively, of facet_wrap. Many more details and examples can be found if you follow ?geom_boxplot and ?facet_wrap.
Update 1
A boxplot based on your sample data doesn't make too much sense, because your data are not continuous. But ignoring that, you could do the following:
df <- data.frame(
q1o = c(4,4,5,4,4,4,4,5,4,5,4,4,5,4,4,4,5,5,5,5,5,5,5,5,5,3,4,4,3,4),
q1s = c(5,4,4,5,5,5,5,5,4,5,4,4,5,4,5,5,5,5,5,5,5,5,5,5,5,5,4,5,4,4),
q2o = c(3,3,3,4,3,4,4,3,3,3,4,4,3,4,3,3,4,3,3,3,3,4,4,4,4,3,3,3,3,4),
q2s = c(5,4,4,5,5,5,5,5,4,5,4,4,5,4,5,5,5,5,5,5,5,5,5,5,5,5,4,3,4,4));
df %>%
gather(key, value, 1:4) %>%
mutate(
variable = ifelse(grepl("q1", key), "F1", "F2"),
Label = ifelse(grepl("o$", key), "Bad", "Good")) %>%
ggplot(aes(variable, value, fill = Label)) +
geom_boxplot(position = position_dodge()) +
facet_wrap(~ variable, ncol = 3, scale = "free");
Update 2
One way of visualising discrete data would be in a mosaicplot.
mosaicplot(table(df2));
The plot shows the count of value (as filled rectangles) per Variable per Label. See ?mosaicplot for details.
I am trying to create a time series plot for each individual (ID) I have in my dataset.
Example data:
ID <- rep(c(2:5), each = 9, times = 4)
Attitude <- rep(c('A1', 'A2','A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'), 16)
Answer <- rep(1:5, length.out = 144)
time <- as.character(rep(c(0, 1, 3, 4), each = 9, times = 4))
first_answer <- rep(1:5, length.out = 144)
df <- data.frame(ID, Attitude, Answer, time, first_answer)
df$time <- as.character(df$time)
The function code I am currently using:
library(dplyr)
spaghetti_plot <- function(input, MV, item_level){
MV <- enquo(MV)
titles <- enquo(item_level)
input %>%
filter(!!(MV) == item_level) %>%
mutate(first_answer = first_answer) %>%
ggplot(.,aes( x = time, y = jitter(Answer), group = ID)) +
geom_line(aes(colour = first_answer)) +
labs(title = titles ,x = 'Time', y = 'Answer', colour = 'Answer given at time 0')
}
This gives me a graph where I have a line for each individual, i.e. one plot for all individuals (equal to number of ID). Instead of this, I would like to have 1 plot with # panels = ID. For example, if I have data of 10 individuals, I would like to have a graph with 10 panels.
I tried using facet_wrap and facet_panel to get the job done, but I haven't found a proper solution yet.
EDIT using facet_wrap(~ID) gives
The result that I am after would look something like this:
Which was originally made in SAS.
EDIT2 Solution is in the comments.
The data from your reproducible example are a bit weird because you have only one value per ID, but I believe this is the code you are looking for:
library(ggplot2)
ggplot(df,aes(x = time, y = Answer)) +
geom_line()+
facet_grid(. ~ ID)
If you have too many facets the data may not show up, try to increase the size of the plot window or export the image directly with ggsave. If you find the right parameters for ggsave all the plots should be visible on the saved image.
I need to make some plots for work and I've been learning to use ggplot2, but I can't quite figure out how to get it to work with the dataset I'm using. I can't post my actual data here, but can give a brief example of what it is like. I have two main dataframes; one contains quarterly total revenue for a variety of companies and the other contains quarterly revenue for various segments within each company. For example:
Quarter, CompA, CompB, CompC...
2011.0, 1, 2, 3...
2011.25, 2, 3, 4...
2011.5, 3, 4, 5...
2011.75, 4, 5, 6...
2012.0, 5, 6, 7...
and
Quarter, CompA_Footwear, CompA_Apparel, CompB_Wholesale...
2011.0, 1, 2, 3...
2011.25, 2, 3, 4...
2011.5, 3, 4, 5...
2011.75, 4, 5, 6...
2012.0, 5, 6, 7...
The script I've been building loops through each company in the first table and uses select() to grab all of the columns in the second table, so for the purposes of this question, forget about the other companies and assume that the first table is just CompA and the second table is all of the different CompA segments.
What I'm trying to do is for each segment, create a line plot that has both the total company revenue and the segment revenue charted over time. Something like this is what it would look like. Ideally, I'd like to be able to use a facet_wrap() or something to be able to make all the different graphs for each segment at once, but that's not absolutely necessary. To clarify, each individual graph should only have two lines: the overall company and one specific segment.
I'm fine with having to restructure my data in any way necessary. Does anyone know how I can get this to work?
I think the below should work. Note that you need to move data around a fair bit.
# Load packages
library(dplyr)
library(ggplot2)
library(reshape2)
library(tidyr)
Make a reproducible data set:
# Create companies
# Could pull this from column names in your data
companies <- paste0("Comp",LETTERS[1:4])
set.seed(12345)
sepData <-
lapply(companies, function(thisComp){
nDiv <- sample(3:6,1)
temp <-
sapply(1:nDiv,function(idx){
round(rnorm(24, rnorm(1,100,25), 6))
}) %>%
as.data.frame() %>%
setNames(paste(thisComp,sample(letters,nDiv), sep = "_"))
}) %>%
bind_cols()
sepData$Quarter <-
rep(2010:2015
, each = 4) +
(0:3)/4
meltedSep <-
melt(sepData, id.vars = "Quarter"
, value.name = "Revenue") %>%
separate(variable
, c("Company","Division")
, sep = "_") %>%
mutate(Division = factor(Division
, levels = c(sort(unique(Division))
, "Total")))
fullCompany <-
meltedSep %>%
group_by(Company, Quarter) %>%
summarise(Revenue = sum(Revenue)) %>%
mutate(Division = factor("Total"
, levels = levels(meltedSep$Division)))
The plot you say you want is here. Note that you need to set Divison = NULL to prevent the total from showing up in its own facet:
theme_set(theme_minimal())
catch <- lapply(companies, function(thisCompany){
tempPlot <-
meltedSep %>%
filter(Company == thisCompany) %>%
ggplot(aes(y = Revenue
, x = Quarter)) +
geom_line(aes(col = "Division")) +
facet_wrap(~Division) +
geom_line(aes(col = "Total")
, fullCompany %>%
filter(Company == thisCompany) %>%
mutate(Division = NULL)
) +
ggtitle(thisCompany) +
scale_color_manual(values = c(Division = "darkblue"
, Total = "green3"))
print(tempPlot)
})
Example of the output:
Note, however, that that looks sort of terrible. The difference between the "Total" and any one division is always going to be huge. Instead, you may want to just plot all the divisions on one plot:
allData <-
bind_rows(meltedSep, fullCompany)
catch <- lapply(companies, function(thisCompany){
tempPlot <-
allData %>%
filter(Company == thisCompany) %>%
ggplot(aes(y = Revenue
, x = Quarter
, col = Division)) +
geom_line() +
ggtitle(thisCompany)
# I would add manual colors here, assigned so that, e.g. "Clothes" is always the same
print(tempPlot)
})
Example:
The difference between Total and each is still large, but at least you can compare the divisions.
If it were mine to make though, I would probably make two plots. One with each division from each company (faceted) and one with the totals:
meltedSep %>%
ggplot(aes(y = Revenue
, x = Quarter
, col = Division)) +
geom_line() +
facet_wrap(~Company)
fullCompany %>%
ggplot(aes(y = Revenue
, x = Quarter
, col = Company)) +
geom_line()
There are two other ways I can think to do it using facet_wrap() that are a little more bare-bones:
using annotate() in ggplot2 (simple approach)
doubling your data frames for each company (still relatively simple, just more prone to errors)
Either way, let's recreate your two data frames so that we can reproduce your example:
First create the "total company revenue" data frame:
Quarter <- seq(2011, 2012, by = .25)
CompA <- as.integer(runif(5, 5, 15))
CompB <- as.integer(runif(5, 6, 16))
CompC <- as.integer(runif(5, 7, 17))
df1 <- data.frame(Quarter, CompA, CompB, CompC)
Next, the "segment revenue" data frame of Company A:
CompA_Footwear <- as.integer(runif(5, 0, 5))
CompA_Apparel <- as.integer(runif(5,1 , 6))
CompA_Wholesale <- as.integer(runif(5, 2, 7))
df2 <- data.frame(Quarter, CompA_Footwear, CompA_Apparel, CompA_Wholesale)
Now we will re-arrage your data to be something more recognizable for ggplot2 using melt() from reshape2
require(reshape2)
melt.df1 <- melt(df1, id = "Quarter")
melt.df2 <- melt(df2, id = "Quarter")
df <- rbind(melt.df1, melt.df2)
We are mostly ready to graph now. For sake of example, I'll only focus on "Company A"
Using annotate()
Subset the data so that it only contains "segment revenue" for Company A
CompA.df2 <- df[grep("CompA_", df$variable),]
This assumes all your segment revenue is coded starting with "CompA_*". You will have to subset according to your data.
Now plot:
require(ggplot2)
ggplot(data = CompA.df2, aes(x = Quarter, y = value,
group = variable, colour = variable)) +
geom_line() +
geom_point() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
facet_wrap(~variable) + # Facets by segment
# Next, adds the total revenue data as an annotation
annotate(geom = "line", x = Quarter, y = df1$CompA) +
annotate(geom = "point", x = Quarter, y = df1$CompA)
Basically, we are just annotating the graph with a line and points from our original "total company revenue" data frame for Company A. The major downside to this is the lack of a legend.
The second approach will produce a legend for all values
Duplicating your data
The way facet_wrap() works, we need to define the same facet variables for each of the intended plotted lines on each facet. So we are going to replicate our total revenue for each "segment revenue" level, and group each of these pairs together.
Using the same data frames as above, we are going to separate out the Total Company A Revenue and the Segment Revenue of Company A
CompA.df1 <- df[which(df$variable == "CompA"),] # Total Company A Revenue
CompA.df2 <- droplevels(df[grep("CompA_", df$variable),]) # Segment Revenue of Company A
Now repeat the total revenue data frame for Company A based on how many levels we have for the "Segment Revenue"
rep.CompA.df1 <- CompA.df1[rep(seq_len(nrow(CompA.df1)), nlevels(CompA.df2$variable)), ]
This might be prone to errors if you have NA's or NaN's
Now merge the repeated data frame, and add a facet variable (facet.var here) to pair these together.
CompA.df3 <- rbind(rep.CompA.df1, CompA.df2)
CompA.df3$facet.var <- rep(CompA.df2$variable,2)
Now you are ready to graph. You can still define group = variable, but this time we will set facet_wrap() to our newly created facet.var
require(ggplot2)
ggplot(data = CompA.df3, aes(x = Quarter, y = value,
group = variable, colour = variable)) +
geom_line() +
geom_point() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
facet_wrap(~facet.var)
As you can see, we now have our "Total Revenue" added to the legend:
That plot's a real beaut