0

I´m trying to plot a heart beat for 24 hours, I have data for every second. The problem I´m running through is that when I plot the data and assign to each beat the "intensity zone", I get a straight line in the graph (check the blue line between 4:00 and 10:00) instead of just the color for that data. Also, I´m getting a line for NA values, and although I have added na.rm=TRUE it still shows in the graph.

enter image description here

Here's the code and the data:

library(tidyverse)
library(hms)
library(dplyr)
library(lubridate)
library(ggplot2)


#Import dataset#

graph <-read.csv("graph.csv", sep=";", na.strings = "?")
graph$HR <- as.numeric(as.character(graph$HR))
graph$Zone <- as.numeric(as.character(graph$Zone))
graph$Time =as.POSIXct(graph$Time,format="%H:%M:%S") #convert to time
graph$Time = as_hms(graph$Time) #only maintaining hours
str(graph) # check to make sure variables are changed

View(graph)

####Creating figure####

graph$intensity<-factor(graph$Intensity, 
                    levels =c("Sedentary", "Light", "Moderate", "Vigorous"))

ggplot(graph, aes(x = Time, y = HR, na.rm=TRUE,color = intensity)) +
  geom_line() +
  labs(x = "Time", y = "Heart Rate (bpm)")+
  scale_color_discrete("Intensity:", labels = c("Sedentary", "Light",
                                              "Moderate", "Vigorous")) +
  theme(legend.title = element_text(family = "Playfair",
                                    color = "chocolate",
                                    size = 10, face = 2))+
  theme(panel.grid = element_blank()) +
  geom_rug(show.legend = FALSE)+
  scale_x_datetime(date_breaks = "4 hours", date_labels = "%H:%M")+
  ylim(c(0,160))+
  scale_y_continuous(breaks = seq(0, 160, by =40))

Thanks for the help!

tjebo
  • 21,977
  • 7
  • 58
  • 94
elemarles
  • 27
  • 7
  • see my answer = I am using a random data set in order to make this more useful for posterity. I'd suggest you do the same, so people will be able to reproduce this even if your data won't be accessible any more. (which is very likely going to happen) – tjebo Nov 20 '21 at 13:29

2 Answers2

2

Try using geom_segment.


graph <- graph[complete.cases(graph),]
graph$time_end <- lead(graph$Time, n=1)
graph$HR_end <- lead(graph$HR, n = 1)


####Creating figure####

graph$intensity<-factor(graph$Intensity, 
                        levels =c("Sedentary", "Light", "Moderate", "Vigorous"))

ggplot(graph, aes(x = Time, y = HR)) +
    geom_segment(aes(xend = time_end,  yend = HR_end, color = intensity)) +
    labs(x = "Time", y = "Heart Rate (bpm)")+
    scale_color_discrete("Intensity:", labels = c("Sedentary", "Light",
                                                  "Moderate", "Vigorous")) +
    theme(legend.title = element_text(family = "Playfair",
                                      color = "chocolate",
                                      size = 10, face = 2))+
    theme(panel.grid = element_blank()) +
    geom_rug(show.legend = FALSE)+
    scale_x_datetime(date_breaks = "4 hours", date_labels = "%H:%M")+
    ylim(c(0,160))+
    scale_y_continuous(breaks = seq(0, 160, by =40))

Is this what you are looking for?

enter image description here

As tjebo noted, there is still some overlap in the lines. I understand, though, that there is a specific aesthetic you are looking to achieve, and I'd like a cigar. If we go back to your original solution, geom_line() didn't work because it creates a continuous line for each factor, in this case the Intensity. I suggested you use geom_segment(). With this, you define the start and end points and can determine the color of the line. The problem here is that the points fall into one zone or another, but the lines may cross over the zone/intensities. That is why my early graph had overlaps. If you think it is appropriate, you need to add more points to your data to account for when the thresholds are crossed. Essentially, it adds two new points that are just at and over the threshold. So I've done that in the code below and have gotten you closer. The problem I didn't solve for are the cases when a zone is skipped, for example, it goes from zone 1/sedentary to zone 3/moderate. So you'll note there appear some small overlaps. That could be worked out, but will leave that to you.

library(tidyverse)
library(hms)
library(dplyr)
library(lubridate)
library(ggplot2)


#Import dataset#

graph <-read.csv("graph.csv", sep=";", na.strings = "?")
graph$HR <- as.numeric(as.character(graph$HR))
graph$Zone <- as.numeric(as.character(graph$Zone))
graph$Time =as.POSIXct(graph$Time,format="%H:%M:%S")

#graph$Time = as_hms(graph$Time)  This throws off scale_x_datetime() so didn't use

#remove NA values
graph <- graph[complete.cases(graph),]

#understand zones
graph %>%
    group_by(Zone) %>%
    summarise(min = min(HR), max = max(HR))


#create Heart Rate Theshold values
thresholds <- tibble("Zone" = c(1, 2, 3, 4), "HRT" = c(78, 97, 116, Inf))
graph <- merge(thresholds, graph, by = "Zone")

graph <- graph[order(graph$Time),]




#generate new data points for instances the Zone is crossed
#note we introduce a new time point that is between the two original times
new_points <- tibble()
    
for(r in 1:(nrow(graph)-1)){
    if(graph[r,"Zone"] == graph[r+1, "Zone"]){
        next
    } else if(graph[r,"Zone"] < graph[r+1, "Zone"]) {
        new <- tibble(Zone = c(graph[r,"Zone"], graph[r+1, "Zone"]),
                    Time = c(graph[r,"Time"] + 0.5, graph[r+1, "Time"] - 0.4),
                    HR = c(graph[r, "HRT"], graph[r, "HRT"] + 1),
                    Intensity = c(graph[r,"Intensity"], graph[r+1, "Intensity"]))
    } else if(graph[r,"Zone"] > graph[r+1, "Zone"]) {
        new <- tibble(Zone = c(graph[r,"Zone"], graph[r+1, "Zone"]),
                      Time = c(graph[r,"Time"] + 0.5, graph[r+1, "Time"] - 0.4),
                      HR = c(graph[r+1, "HRT"] +1 , graph[r+1, "HRT"]),
                      Intensity = c(graph[r,"Intensity"], graph[r+1, "Intensity"]))
    }
    new_points <- rbind(new_points, new)
}

new_points <- merge(thresholds, new_points, by = "Zone")

#merge and reorder
graph <- rbind(graph, new_points)
graph <- graph[order(graph$Time),]

#create end points for segments
graph$time_end <- lead(graph$Time, n=1)
graph$HR_end <- lead(graph$HR, n = 1)


####Creating figure####

graph$intensity<-factor(graph$Intensity, 
                        levels =c("Sedentary", "Light", "Moderate", "Vigorous"))

ggplot(graph, aes(x = Time, y = HR)) +
    geom_segment(aes(xend = time_end,  yend = HR_end, color = intensity)) +
    labs(x = "Time", y = "Heart Rate (bpm)")+
    scale_color_discrete("Intensity:", labels = c("Sedentary", "Light",
                                                  "Moderate", "Vigorous")) +
    theme(legend.title = element_text(#family = "Playfair",
                                      color = "chocolate",
                                      size = 10, face = 2))+
    theme(panel.grid = element_blank()) +
    geom_rug(show.legend = FALSE, aes(color = intensity))+
    scale_x_datetime(date_breaks = "4 hours", date_labels = "%H:%M")+
    ylim(c(0,160))+
    scale_y_continuous(breaks = seq(0, 160, by =40))

enter image description here

stomper
  • 1,252
  • 1
  • 7
  • 12
  • close but no cigar :) still some "overshooting lines" – tjebo Nov 20 '21 at 13:27
  • Thank you tjebo for the explanation and the suggestion about using geom_segment(). My main concern was actually having the lines accross the plot, but this is solve I can work on the overshooting lines. Thanks again! – elemarles Nov 21 '21 at 20:01
1

May I suggest a different (and easier!) visualisation - just color the background :)

library(tidyverse)

hf <- data.frame(heartrate = rnorm(100), time= 1:100)
zones <- data.frame(xmin = -Inf, xmax = Inf, ymin = c(-Inf, -1, 1), ymax = c(-1, 1, Inf), 
                    zones = fct_inorder(c("low", "moderate", "high")))

ggplot(hf, aes(time, heartrate, group = 1 )) +
  geom_rect(data = zones, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, 
                              fill = zones), inherit.aes = FALSE) +
  geom_line() +
  scale_fill_brewer()

Created on 2021-11-20 by the reprex package (v2.0.1)

tjebo
  • 21,977
  • 7
  • 58
  • 94
  • just found the duplicate question to this thread and someone answered with exactly the same idea :) https://stackoverflow.com/a/35040652/7941188 – tjebo Nov 20 '21 at 18:02
  • Thanks tjebo! It was an option that I did consider but I´m going to be working with the plot more and having the colors in the background would affect the rest of the aesthetic I was going for. Still, thanks for providing another solution! – elemarles Nov 21 '21 at 20:04