Your first problem is that the hour_bin variable of your dataframe is probably a character vector, not a datetime. That means ggplot will try to render all the values of hours_bin on the x axis. To get a proper datetime axis, yo you need to convert text strings like "05/02/2025 21:00"to a datetime object. The {lubridate} package has functions to help you do that. Then you want some way of indicating when it's night. If you want a bracket on your axis, the {legendry} package has a function to help you do that.
For example:
library(ggplot2)
library(legendry)
library(dplyr)
library(lubridate)
df <- data.frame(
hour_bin = c(
"05/02/2025 18:00",
"05/02/2025 19:00",
"05/02/2025 20:00",
"05/02/2025 21:00",
"05/02/2025 22:00",
"05/02/2025 23:00",
"06/02/2025 00:00",
"06/02/2025 01:00",
"06/02/2025 02:00",
"06/02/2025 03:00",
"06/02/2025 04:00",
"06/02/2025 05:00",
"06/02/2025 18:00",
"06/02/2025 19:00",
"06/02/2025 20:00",
"06/02/2025 21:00",
"06/02/2025 22:00",
"06/02/2025 23:00"
),
mean_calling_activity = c(
0.916666667,
1,
1,
0.65,
0.183333333,
0.133333333,
0.066666667,
0.083333333,
0,
0,
0,
0.183333333,
0.65,
1,
1,
0.966666667,
0.533333333,
0.083333333
)
)
# convert datestrings to datetime
df <- df |>
dplyr::mutate(
hour_bin = lubridate::parse_date_time(hour_bin, "%d/%m/%Y %H:%M")
)
ggplot(df) +
geom_point(aes(x = hour_bin, y = mean_calling_activity)) +
scale_x_datetime(date_breaks = "1 hour", date_labels = "%H") +
guides(
x = guide_axis_nested(
key = key_range_manual(
start = c(
lubridate::parse_date_time("05/02/2025 21:00", "%d/%m/%Y %H:%M"),
lubridate::parse_date_time("06/02/2025 21:00", "%d/%m/%Y %H:%M")
),
end = c(
lubridate::parse_date_time("06/02/2025 05:00", "%d/%m/%Y %H:%M"),
lubridate::parse_date_time("07/02/2025 05:00", "%d/%m/%Y %H:%M")
),
name = c("First Night", "Second Night")
),
bracket = "square"
)
)

data.frame(...)or the output fromdput(head(x))) directly into a code block.Date- orPOSIXt-class values. Once you resolve that, I think you can control the axis withscale_x_date(or*_datetime) using itsdate_labels=fairly well. Lacking usable data, I think the answer you have so far may be a good enough start.