rplot

Setting xlim when the x-value is a factor causes the x-axis to extend beyond the plot window


When I plot my data without explicitly setting the x coordinate range (the fourth and lowest plot in the screenshot, "Sonntag"), par("usr") tells me what range R uses by default:

> par("usr")
[1]  -68.912 1797.112    0.000   87.400

But when I set the x coordinate range to those values using xlim = c(-68.912, 1797.112), the plot is narrower (the third plot in the screenshot, "Samstag"). And when I set a range that is narrower than the default, the x-axis extends beyond the plot window (the first and second plot in the screenshot).

screenshot of the plot

The data on the x-axis is a factor:

steps$time <- factor(format(steps$start_time, "%H:%M"),
                     levels = format(seq(as.POSIXct("2020-02-20 00:00:00"),
                                         as.POSIXct("2020-02-20 23:59:00"),
                                         by = "1 min"),
                                     "%H:%M"
                                     )
                     )

I want to limit the plot to the "time" between 6:00 and 23:00, which should correspond to the factor levels 360 and 1380 (used in the top two plots). How can I achieve that?


Here is the full plot code:

par(mfrow = c(4, 1), mar = c(5.1, 2.1, 4.1, 2.1))
barplot(count ~ time, dat[dat$group == "A",], xlim = c(360, 1380), ylim = c(0, max(dat$count)), col = "violet", border = NA, xlab = "", ylab = "", yaxt = "n", main = "Montag bis Donnerstag")
barplot(count ~ time, dat[dat$group == "B",], xlim = c(360, 1380), ylim = c(0, max(dat$count)), col = "violet", border = NA, xlab = "", ylab = "", yaxt = "n", main = "Freitag")
barplot(count ~ time, dat[dat$group == "C",], xlim = c(-68.912, 1797.112), ylim = c(0, max(dat$count)), col = "violet", border = NA, xlab = "", ylab = "", yaxt = "n", main = "Samstag")
barplot(count ~ time, dat[dat$group == "D",], ylim = c(0, max(dat$count)), col = "violet", border = NA, xlab = "", ylab = "", yaxt = "n", main = "Sonntag")

Data is here: https://pastebin.com/tnNwFywX


Solution

  • You're facing the problem, that barplot also plots unused levels, which is why @DaveArmstrong used droplevels. Here is a small example to illustrate this:

    foo <- data.frame(a=1:6, b=replace(rep_len(0, 6), 3:4, 100)) |> 
      transform(a1=as.factor(a))
    
    par(mfrow=1:2)
    barplot(b ~ a1, data=subset(foo, a > 1 & a < 6))
    barplot(b ~ droplevels(a1), data=subset(foo, a > 1 & a < 6))
    

    enter image description here

    Note, that in barplot, the x-axis denotes bar indices {1, 2, ..., n}, and positions are returned invisibly which you might exploit here. To get nice time labels, coerce factor times to character that are easily sub-settable using relational operators like >= '06:00' and this also works alphabetically. You won't need xlim then which might be tedious to use in this case.

    Crucial is using ylim on the entire range of count to make bar charts comparable, though.

    You also might turn group into a factor with the corresponding day labels, which will help when using a for loop.

    dat <- dat |> 
      transform(
        time_chr=as.character(time),
        days=factor(group, labels=c("Montag bis Donnerstag", "Freitag", "Samstag",
                                    "Sonntag"))
      )
    

    Make sure, that there are no "holes" in the data, i.e. times really are within 6–23 o'clock and otherwise have equal intervals, as specified in the OP, e.g., via a check like:

    stopifnot(with(dat, min(time_chr) <= '06:00' & max(time_chr) >= '23:00'))  ## for safety
    stopifnot(all(diff(strptime(unique(dat$time), '%H:%M')) == 1))
    

    Finally, just use endsWith() to find the full hours then.

    png('foo.png', 800, 800)
    par(mfrow=c(4, 1), mar=c(5, 2, 4, 2) + 0.1)
    for (g in unique(dat$group)) {
      pdat <- subset(dat, group == g & time_chr >= '06:00' & time_chr <= '23:00')
      b <- barplot(count ~ time_chr, pdat, xaxt='n', col='violet', border=NA, 
                   ylim=range(dat$count), xlab="", ylab="", yaxt="n", main=unique(pdat$days))
      full_h <- endsWith(pdat$time_chr, '00')
      axis(1, at=b[full_h], labels=pdat$time_chr[full_h])
    }
    dev.off()
    

    enter image description here