We relied on a combination of spatial, statistical, and visualization packages in R to conduct this analysis. Each library played a specific role:
tidyverse
: A suite of packages for
data manipulation and visualization. Core functions from
dplyr
, ggplot2
, tibble
, and
readr
were used to wrangle and plot data in a consistent
and readable syntax.
terra
: Used for handling spatial
raster data. We imported and processed the global canopy height raster
(Lang et al. 2022), extracted values, and performed cropping and zooming
for localized analysis.
suncalc
: Provided astronomical
calculations, including moon illumination fraction, moonrise and moonset
times, and phase information. This allowed us to incorporate lunar
variables into our ecological questions.
purrr
: Part of the tidyverse
ecosystem, purrr
was essential for iterating over rows and
applying functions (e.g., moon calculations) across date and location
combinations in a clean and vectorized way.
hexbin
: Enabled the creation of
hexagonal binned plots to explore the density distribution of sleeping
sites in relation to canopy height and moonlight, which helps overcome
overplotting in dense datasets.
viridis
: Provided perceptually
uniform and colorblind-friendly color scales for raster and density
plots, improving visual interpretation of ecological gradients.
geosphere
: Used for accurate
geographic calculations such as generating random points within a radius
from a known location (destPoint()
), taking Earth’s
curvature into account — crucial for ecological realism in spatial
simulation.
library(tidyverse)
library(terra)
library(suncalc)
library(purrr)
library(hexbin)
library(viridis)
library(geosphere)
library(scales)
The moon.rds
file contains georeferenced data on
sleeping sites of four groups of black capuchin monkeys (Sapajus
nigritus) in **Iguazú National Park, Argentina, collected between
2011 and 2022.
Only night-time sleeping locations were considered. Specifically, each record corresponds to the last GPS point of the day (and always after sunset), collected after the monkeys had settled for the night, and does not include morning sleeping positions to avoid duplicates.
Each observation includes:
This dataset is designed to support analyses exploring the relationship between lunar cycles and illumination and sleeping site selection, such as sleeping height or location in relation to moonlight.
To evaluate how lunar conditions might influence the sleeping site
selection of black capuchin monkeys (Sapajus nigritus), we
calculated key lunar variables using the
suncalc
package.
In the code below, for each observation date in the moon
dataset, we extracted:
These lunar metrics might be ecologically relevant because moonlight intensity can influence nocturnal visibility and perceived predation risk. In primates, including capuchins, we predict that individuals may select higher sleeping sites during dark nights, which might be linked to a reduction in predation risk perception.
By incorporating lunar illumination data, we can test whether capuchin monkeys adjust their sleeping site height or location in response to varying moonlight conditions across nights and seasons.
To further interpret lunar illumination patterns, we classified each
observation into a moon phase category based on the
moon_phase_angle
value. The moon’s cycle is continuous, but
for ecological analyses, it is often useful to group nights into broader
phase categories that may reflect different levels of moonlight or
perceived predation risk.
We defined the categories as follows:
moon_phase_angle
< 0.1 or
> 0.9Ecologically, these categories can help determine whether capuchin monkeys adjust their sleeping behavior, such as height in the canopy or site exposure, in response to the amount of ambient nocturnal light.
moon <- moon %>%
mutate(
moon_phase_name = case_when(
moon_phase_angle < 0.1 | moon_phase_angle > 0.9 ~ "New Moon",
moon_phase_angle >= 0.1 & moon_phase_angle < 0.4 ~ "Waxing",
moon_phase_angle >= 0.4 & moon_phase_angle <= 0.6 ~ "Full Moon",
moon_phase_angle > 0.6 & moon_phase_angle <= 0.9 ~ "Waning" ))
To better understand the temporal availability of
moonlight, we calculated the moonrise and
moonset times for each observation using the
getMoonTimes()
function from the suncalc
package. This helps determine whether the moon was actually
present in the sky during the night — a crucial complement to
the phase and illumination fraction.
We specified the location (latitude and longitude) of each
observation, along with the observation date and the appropriate time
zone for Iguazú National Park
(America/Argentina/Cordoba
)
These variables allow us to identify whether the moon was above the horizon during the active nighttime period. This is ecologically meaningful because a night with high lunar illumination on paper (e.g., a full moon) might still be functionally dark if the moon had already set by the time the monkeys were selecting or using their sleeping site.
In follow-up analyses, we can calculate:
Whether the moon was visible during key night hours (e.g., 19:00–05:00),
The duration the moon was above the horizon,
And how these metrics relate to sleeping site selection and height.
moon <- moon %>%
mutate(
moon_times = pmap(
list(fecha, lat, lon),
function(f, la, lo) {
getMoonTimes(
date = f,
lat = la,
lon = lo,
keep = c("rise", "set"),
tz = "America/Argentina/Cordoba"
)
}
)
) %>%
mutate(
moon_rise = map(moon_times, "rise") %>% flatten_dbl(),
moon_set = map(moon_times, "set") %>% flatten_dbl()
) %>%
select(-moon_times)
moon <- moon %>%
mutate(
moon_rise = as.POSIXct(moon_rise, origin = "1970-01-01", tz = "America/Argentina/Cordoba"),
moon_set = as.POSIXct(moon_set, origin = "1970-01-01", tz = "America/Argentina/Cordoba")
)
We also calculated whether the moon was actually visible in the sky during each night, and for how long, based on the previously extracted moonrise and moonset times.
moon_duration_hr: the number of hours the moon was above the horizon that night.
moon_visible: a logical indicator (TRUE or FALSE) denoting whether the moon rose and set that night (i.e., was visible at some point).
These variables are ecologically important because they account for actual moonlight exposure at ground level. For example, even on a night with a full moon, if the moon had already set before the monkeys began selecting sleeping sites, its potential influence on behavior would be minimal. Conversely, on darker nights (low lunar fraction), a visible moon even for a few hours could still provide light that may influence perceived predation risk, vigilance, or site choice.
By integrating both moon phase and sky visibility, we can better capture the real nocturnal light environment experienced by the monkeys.
moon <- moon %>%
mutate(
moon_duration_hr = as.numeric(difftime(moon_set, moon_rise, units = "hours")),
moon_visible = !is.na(moon_rise) & !is.na(moon_set) & moon_duration_hr > 0
)
ggplot(moon, aes(x = group, y = moon_fraction, color = group)) +
geom_jitter(width = 0.2, height = 0, alpha = 0.85, size = 2.8) +
annotate("text", x = 4.5, y = 0, label = "🌑", size = 6) +
annotate("text", x = 4.5, y = 0.5, label = "🌓", size = 6) +
annotate("text", x = 4.5, y = 1, label = "🌕", size = 6) +
scale_color_manual(values = c(
"gun" = "darkorange3",
"gue" = "midnightblue",
"mac" = "goldenrod2",
"spo" = "darkseagreen4" # "slategray4" could be a nice option
)) +
labs(
title = "Observed Moon Illumination per Group",
x = "Monkey Group",
y = "Moon Illumination Fraction"
) +
coord_cartesian(ylim = c(-0.05, 1.05)) +
theme_bw(base_size = 14) +
theme(
legend.position = "none",
axis.text.x = element_text(face = "bold")
)
Distribution of moon illumination fractions across monkey groups. Each point represents a recorded sleeping site, colored by group. Lunar phase icons on the right (🌑 new moon, 🌓 half moon, 🌕 full moon) provide a visual scale reference.
This plot shows the distribution of observed lunar
illumination fractions (moon_fraction
) across all
recorded sleeping site events, separated by monkey group.
Each point represents a single observation, reflecting the proportion of the moon’s surface illuminated that night (0 = 🌑 New Moon, 1 = 🌕 Full Moon). The data appears to be well-distributed across the lunar cycle, with no strong evidence of sampling bias across groups — all groups show a broad range of moonlight conditions.
This preliminary exploration supports the idea that the dataset is suitable for investigating whether capuchin monkeys adjust their sleeping site choices (e.g., height or exposure) in response to nocturnal moonlight levels.
To explore the seasonality in sleeping site selection, two visualizations were created using the moon illumination fraction as the key variable. The first plot (circular) positions each observation according to the day of the year (angle) and moon illumination (radius), with a continuous color scale indicating the brightness of the moon. This layout helps reveal potential seasonal concentrations and lunar phase patterns in spatial behavior. The second plot, shown in a cartesian format, displays the same information across calendar months, allowing for clearer month-to-month comparisons. In both cases, the use of color to represent moonlight intensity provides an intuitive visual cue for interpreting temporal patterns in sleeping site usage.
moon <- moon %>%
mutate(
yday = yday(fecha),
angle = 2 * pi * yday / 365 )
ggplot(moon, aes(x = angle, y = moon_fraction, fill = moon_fraction)) +
geom_tile(width = 0.75, height = 0.19) + # cuadraditos circulares
scale_fill_viridis_c(
option = "B",
name = "Moon Illum.",
guide = guide_colorbar(barheight = 8)) +
coord_polar(start = -pi/2) +
scale_x_continuous(
breaks = seq(0, 2*pi, length.out = 13)[-13],
labels = month.abb
) +
scale_y_continuous(limits = c(0, 1), expand = c(0, 0)) +
labs(
title = "Seasonality of Sleeping Sites by Moon Phase",
x = NULL, y = NULL) +
theme_minimal(base_size = 15) +
theme(
axis.text.x = element_text(face = "bold"),
axis.text.y = element_blank(),
panel.grid = element_blank(),
axis.ticks = element_blank(),
legend.title = element_text(),
plot.title = element_text(face = "bold")
)
Circular visualization of sleeping site observations throughout the year. Each tile corresponds to a record, positioned by day of year (angle) and moon illumination (radius and color). This layout highlights seasonal patterns under varying moonlight conditions.
moon <- moon %>%
mutate(month = month(fecha, label = TRUE, abbr = TRUE))
ggplot(moon, aes(x = month, y = moon_fraction, fill = moon_fraction)) +
geom_violin(scale = "width", fill = "gray90", color = NA, alpha = 0.5) +
geom_jitter(shape = 21, color = "black", size = 2.5, stroke = 0.3, width = 0.2, alpha = 0.8) +
scale_fill_viridis_c(
option = "B",
name = "Moon Phase",
limits = c(0, 1),
breaks = c(0, 0.25, 0.5, 0.75, 1),
labels = c("0.00 - 🌑", "0.25 - 🌘", "0.50 - 🌗", "0.75 - 🌒", "1.00 - 🌕"),
guide = guide_colorbar(barheight = 12)) +
coord_cartesian(ylim = c(0, 1)) +
labs(
title = "Seasonality of Sleeping Sites and Moon Illumination",
x = "Month",
y = "Moon Illumination Fraction"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(face = "bold"),
legend.title = element_text(face = "bold"),
legend.text = element_text(size = 13),
legend.position = "right" )
Distribution of moonlight conditions during sleeping site observations throughout the year. Each point corresponds to an observation, jittered for visibility, and colored by lunar illumination. Violin plots represent the monthly density of observations. Emojis in the legend indicate approximate moon phases (new to full).
We used the high-resolution global canopy height dataset produced by Lang et al. (2022), derived from GEDI LiDAR observations and machine learning models, to extract vegetation structure information across the Iguazú National Park. The original raster (ETH_GlobalCanopyHeight_10m_2020_S27W057) provides canopy height estimates at a 10-meter spatial resolution, allowing for detailed landscape-scale ecological analysis.
In this section, we cropped the raster to focus on the Iguazú National Park area (between -55 and -54 longitude, and -26.5 to -25.3 latitude), and applied a vegetation-like green color gradient to better visualize canopy height variations. This visualization enables us to interpret habitat vertical structure and its potential influence on monkey sleeping site selection.
deep_zoom_ext <- ext(-54.5, -54.4, -25.75, -25.65)
greens <- colorRampPalette(c("#edf8e9", "#a1d99b", "#31a354", "#001709"))
plot(canopy, ext = deep_zoom_ext, col = greens(100),
main = "Canopy Height, Iguazu National Park",
axes = TRUE)
To explore the vertical structure of habitats associated with
sleeping sites, we cropped the canopy height raster to a buffered extent
that covers all recorded locations (±0.02°
around minimum
and maximum coordinates). This focused raster allows for better spatial
correspondence between sleeping sites and vegetation structure.
We then overlaid all sleeping site locations
(n = {nrow(moon)}
), color-coded by group identity, on the
canopy height map. The green gradient reflects vegetation height, with
darker shades representing taller canopy. Each monkey group was assigned
a distinctive color (e.g., gun in orange, gue in
blue), allowing clear visual differentiation of their spatial
patterns.
This visualization helps assess whether different groups use sleeping sites in areas of varying canopy height and can inform hypotheses regarding group-specific strategies for predator avoidance, thermoregulation, or social cohesion.
ext_moon <- ext(min(moon$lon)-0.02, max(moon$lon)+0.02,
min(moon$lat)-0.02, max(moon$lat)+0.02)
canopy_zoomed <- crop(canopy, ext_moon)
group_cols <- c(
"gun" = "darkorange3",
"gue" = "midnightblue",
"mac" = "goldenrod2",
"spo" = "darkseagreen4")
moon$group <- as.character(moon$group)
plot(canopy_zoomed, col = greens(100),
main = "Canopy Height Around Sleeping Sites",
axes = TRUE)
with(moon, points(lon, lat,
pch = 21,
bg = group_cols[group],
col = "black",
cex = 1.2))
To quantify the vegetation structure at each sleeping site, we
extracted canopy height values directly from the Lang et al. (2022)
raster using the GPS coordinates of the sites. The sleeping site data
were converted into spatial vectors (SpatVector
) and
projected to match the coordinate reference system (CRS) of the canopy
raster.
This allowed us to assign a canopy height value to every sleeping location based on its exact position. The resulting distribution shows that most sites are associated with mid- to high-canopy areas, suggesting a potential preference for structurally complex or elevated sleeping locations.
The density plot below summarizes the canopy height values at all recorded sites. This forms the basis for comparing real sleeping site locations with randomly generated control points in later analyses.
moon_vect <- vect(moon, geom = c("lon", "lat"), crs = crs(canopy))
moon$canopy_height <- extract(canopy, moon_vect)[,2]
ggplot(moon, aes(x = canopy_height)) +
geom_density(fill = "darkseagreen", alpha = 0.6) +
labs(
title = "Density of Canopy Height at Sleeping Sites",
x = "Canopy Height (meters)",
y = "Density"
) +
theme_minimal(base_size = 14)
Distribution of canopy height across observed sleeping sites. The density plot shows the overall tendency of monkeys to select sleeping locations at specific vertical strata within the forest structure.
This figure explores the potential relationship between lunar illumination and the vertical position of monkey sleeping sites. Each point represents a site, with jitter added to improve visibility, and background shading representing a 2D kernel density estimate.
The x-axis shows the fraction of the moon illuminated (from new moon at 0.0 to full moon at 1.0), while the y-axis indicates the canopy height (in meters) at the site. The color gradient (darker = higher density) reveals the most frequent combinations of moonlight and canopy structure.
This visualization helps assess whether sleeping sites under darker nights (low moon fraction) tend to occur in taller vegetation, which could suggest behavioral adaptation for predator avoidance or improved concealment during high-risk conditions.
ggplot(moon, aes(x = moon_fraction, y = canopy_height)) +
geom_point(
aes(fill = moon_fraction),
shape = 21,
color = "black",
size = 3,
stroke = 0.3,
alpha = 0.85) +
scale_fill_viridis_c(
option = "B",
name = "Moon Illum.",
guide = guide_colorbar(barheight = 8),
limits = c(0, 1),
breaks = c(0, 0.25, 0.5, 0.75, 1),
labels = c("Dark (0.00)", "Low", "Half (0.50)", "High", "Full (1.00)")
) +
coord_cartesian(ylim = c(0, 40)) +
labs(
title = "Canopy Height vs. Lunar Illumination",
x = "Moon Illumination Fraction",
y = "Canopy Height (m)"
) +
theme_minimal(base_size = 15) +
theme(
plot.title = element_text(face = "bold", size = 16),
axis.title = element_text(),
legend.title = element_text(face = "bold"),
legend.text = element_text(size = 11),
legend.position = "right"
)
To assess whether monkeys are actively selecting taller canopy areas for sleeping under varying moonlight conditions, we generated random control points around each observed sleeping location. For every real site, we simulated 10 random points positioned within a radius of 10 to 100 meters, ensuring they remained within the monkeys’ likely perceptual or movement range for choosing a sleeping location.
We used the {geosphere}
package to calculate these new
coordinates based on random bearings (0–360°) and
distances. The destPoint()
function allows
us to project a new coordinate a given distance and direction from a
known point, taking into account the Earth’s curvature, a key advantage
over simple Euclidean methods, especially in geospatial analysis.
These pseudo-absence points share the same date and lunar attributes as the original site, allowing for controlled comparisons while isolating the role of vegetation structure in sleeping site selection.
n_fake <- 10
set.seed(42)
pseudo_data <- moon %>%
rowwise() %>%
do({
lat <- .$lat
lon <- .$lon
distances <- runif(n_fake, min = 40, max = 100)
bearings <- runif(n_fake, min = 0, max = 360)
new_coords <- destPoint(p = c(lon, lat),
b = bearings,
d = distances)
tibble(
lon = new_coords[,1],
lat = new_coords[,2],
fecha = .$fecha,
group = .$group,
moon_fraction = .$moon_fraction,
moon_phase_angle = .$moon_phase_angle,
moon_phase_name = .$moon_phase_name,
moon_visible = .$moon_visible,
used = FALSE
)
}) %>%
ungroup()
After generating pseudo-absence (control) points around each real
sleeping site, we merged both datasets into a single unified table. The
original sleeping sites were labeled with used = TRUE
,
while the randomly generated points were marked as
used = FALSE
.
Each entry, whether real or simulated, includes: - Spatial
coordinates (lon
, lat
) - Group identity - Date
of observation - Moon illumination metrics (moon_fraction
,
moon_phase_angle
, etc.) - Whether the moon was visible that
night - A usage indicator (used
)
This combined dataset (moon_all
) allows for
controlled comparisons between actual monkey sleeping
locations and randomly available but unused alternatives. These
comparisons are central to testing whether canopy structure or lunar
conditions significantly influence sleeping site choice.
moon_used <- moon %>%
mutate(used = TRUE) %>%
select(lon, lat, fecha, group, moon_fraction, moon_phase_angle,
moon_phase_name, moon_visible, used)
moon_all <- bind_rows(moon_used, pseudo_data)
To enable grouped comparisons between used and available points for
each sleeping event, we created a unique identifier (id
) by
combining monkey group and date (group_fecha
). This
identifier represents a single nightly sleeping site selection
event for a given group.
Using dplyr::cur_group_id()
, we assigned a numeric ID to
each group-date combination, which is shared across the real sleeping
site and its 10 associated random control points. This structure is
essential for conditional logistic regression or matched comparisons,
ensuring that each set of observations is analyzed within its ecological
context.
moon_all <- moon_all %>%
mutate(event_key = paste(group, fecha, sep = "_")) %>%
group_by(event_key) %>%
mutate(id = cur_group_id()) %>%
ungroup() %>%
select(-event_key)
To illustrate how random control points are spatially distributed around an actual sleeping site, we visualized a single selection event (one group on one night). The focal site (orange) and its 10 random alternatives (gray) are plotted on top of the canopy height raster, cropped to a ~260 m × 260 m window centered on the true location.
A dashed red circle indicates the 100-meter radius within which the control points were generated, while a horizontal arrow provides a visual scale reference (“100 m”). A north indicator is also included for orientation.
This visualization helps communicate the spatial logic behind the pseudo-absence design: real choices are compared to nearby alternatives that share the same lunar and temporal context, isolating the potential effect of vegetation structure.
target_id <- moon_all %>% filter(used) %>% slice(1) %>% pull(id)
subset_points <- moon_all %>% filter(id == target_id)
pt_real <- subset_points %>% filter(used) %>%
select(lon, lat) %>% unlist()
zoom_ext <- ext(pt_real["lon"] - 0.0013, pt_real["lon"] + 0.0013,
pt_real["lat"] - 0.0013, pt_real["lat"] + 0.0013)
canopy_focus <- crop(canopy, zoom_ext)
plot(canopy_focus, col = greens(100),
main = paste("Zoomed Canopy Height - ID", target_id),
axes = TRUE)
with(subset_points %>% filter(!used),
points(lon, lat, pch = 21, bg = "gray80", col = "black", cex = 1.4))
with(subset_points %>% filter(used),
points(lon, lat, pch = 21, bg = "darkorange", col = "black", cex = 1.8))
circle_100 <- destPoint(pt_real, b = seq(0, 360, length.out = 100), d = 100)
lines(circle_100, col = "darkred", lty = 3, lwd = 2)
scale_line <- destPoint(pt_real, b = 90, d = 100)
x0 <- pt_real["lon"]
y0 <- pt_real["lat"] + 0.0011
x1 <- scale_line[1]
y1 <- y0
segments(x0, y0, x1, y1, col = "black", lwd = 2)
arrows(x0, y0, x1, y1, length = 0.05, angle = 90, code = 3, col = "black")
text(x = mean(c(x0, x1)), y = y0 - 0.0001, labels = "100 m", cex = 0.9)
text(x = zoom_ext[1] + 0.0002, y = zoom_ext[4] - 0.0002, labels = "↑ N", cex = 1.1)
Figure: Example of a sleeping site selection event. The real sleeping site (orange) is surrounded by 10 randomly generated control points (gray), constrained within a 100-meter radius (red dashed circle). Background raster shows canopy height. A horizontal arrow denotes a 100 m scale bar, and a north indicator provides spatial orientation.
To visually compare sleeping site selection patterns under contrasting lunar conditions, we randomly selected 8 sleeping events: 4 occurred under low moon illumination (< 0.2 moon fraction) and 4 under high moon illumination (> 0.8). For each event, the real sleeping site (orange) and its associated pseudo-absence points (gray) are plotted within a 100-meter radius, overlaid on high-resolution canopy height data.
This side-by-side layout enables visual inspection of whether monkeys tend to select taller canopy during darker nights, possibly for predator avoidance or concealment. The consistent structure across panels, including a 100 m scale bar and north orientation, facilitates direct ecological comparison between conditions.
These visualizations set the stage for formal statistical testing of habitat selection as a function of moonlight and vegetation structure.
set.seed(222)
ids_dark <- moon_all %>%
filter(used, moon_fraction < 0.2) %>%
distinct(id) %>%
slice_sample(n = 4) %>%
pull(id)
ids_bright <- moon_all %>%
filter(used, moon_fraction > 0.8) %>%
distinct(id) %>%
slice_sample(n = 4) %>%
pull(id)
ids_selected <- c(ids_dark, ids_bright)
breaks_fixed <- seq(10, 26, by = 2)
cols_fixed <- greens(length(breaks_fixed) - 1)
par(mfrow = c(2, 4), mar = c(3, 3, 2, 1), cex = 1.2)
for (target_id in ids_selected) {
subset_points <- moon_all %>% filter(id == target_id)
pt_real <- subset_points %>% filter(used) %>%
select(lon, lat) %>% unlist()
zoom_ext <- ext(pt_real["lon"] - 0.0013, pt_real["lon"] + 0.0013,
pt_real["lat"] - 0.0013, pt_real["lat"] + 0.0013)
canopy_focus <- crop(canopy, zoom_ext)
plot(canopy_focus,
col = cols_fixed,
breaks = breaks_fixed,
main = paste0("ID ", target_id,
"\nMoon: ", round(unique(subset_points$moon_fraction), 2)),
axes = FALSE,
legend = TRUE)
with(subset_points %>% filter(!used),
points(lon, lat, pch = 21, bg = "gray80", col = "black", cex = 1.6))
with(subset_points %>% filter(used),
points(lon, lat, pch = 21, bg = "darkorange", col = "black", cex = 1.6))
circle_100 <- destPoint(pt_real, b = seq(0, 360, length.out = 100), d = 100)
lines(circle_100, col = "darkred", lty = 3, lwd = 4)
x0 <- pt_real["lon"] + 0.0003
y0 <- pt_real["lat"] + 0.0012
scale_line <- destPoint(c(x0, y0), b = 90, d = 100)
x1 <- scale_line[1]
y1 <- y0
segments(x0, y0, x1, y1, col = "black", lwd = 2)
arrows(x0, y0, x1, y1, length = 0.05, angle = 90, code = 3, col = "black")
text(mean(c(x0, x1)), y0 - 0.0001, "100 m")
text(x = zoom_ext[1] + 0.0002, y = zoom_ext[4] - 0.0002, labels = "↑ N")
}
Examples of sleeping site selection events under contrasting moonlight conditions. Each panel shows a real sleeping site (orange) and 10 random control points (gray) within a 100 m radius (red dashed circle), overlaid on local canopy height. Four events occurred during low moon illumination (< 0.2), and four during high illumination (> 0.8). A 100-meter scale bar and north indicator are included for spatial reference.
To better capture the ecological relevance of nocturnal lunar illumination at sleeping sites, we computed an index of effective moonlight, combining two key astronomical components:
We used the suncalc
package to calculate moon altitude
for each observation, and then estimated effective moonlight as:
\[ \text{moon light effective} = \text{moon_fraction} \times max(0, \sin(\text{moon_altitude})) \]
This formulation ensures that only moons above the horizon contribute to illumination (values below 0 are truncated), and that altitude amplifies the effect of the moon’s light, since a higher moon faces less atmospheric scattering and attenuation.
From an ecological perspective, this metric serves as a biologically meaningful proxy for perceived lunar brightness, which may influence behavioral decisions like sleeping site selection, foraging activity, or predator avoidance among nocturnal or crepuscular species.
To maintain consistency across observations and ensure comparability, we calculate the effective moonlight at three fixed time 20:00 PM (of the previous day) UTC, 00:00 AM and 04:00 AM—each night. This standardized time represents a midpoint when nocturnal primates are typically active and when the moon, if present, is often near its zenith. Using a single reference hour simplifies comparisons by avoiding variation due to changing night lengths, moon rise/set times, or unequal sampling times across dates.
Although this approach does not capture the complete nightly dynamics of lunar brightness, it effectively reflects relative nighttime light conditions across the lunar cycle and seasons. The moon’s altitude—and thus light intensity—varies seasonally (with higher maximum elevations in winter) and cyclically (across lunar phases), so comparing a consistent hour still captures biologically relevant variation at international and annual scales.
Several ecological studies have employed similar approximations. For example, research on black swifts and small songbirds found that both the illuminated fraction and altitude of the moon are strong predictors of nocturnal behavior (e.g., Prinz et al. 2025. In predator–prey systems, models using moon phase and elevation have illuminated habitat selection shifts in felids like ocelots and bobcats (Sergeyev et al. 2023).
We calculated moon altitude at a fixed time (~ 02:00 AM UTC) to standardize our measure of nocturnal lunar illumination. This approach helps us compare different nights and sites under consistent conditions—critical when moonrise, moonset, and night length vary seasonally and by phase.
While this does not capture the full nightly variation in moonlight, it effectively reflects relative illumination across the lunar cycle and seasonal changes in the moon’s elevation. For instance, Śmielak (2023) demonstrated that models combining moon phase and altitude explain > 92% of the variability in actual ground-level moonlight, outperforming phase alone (∼ 60%)
That said, some studies use moon altitude and phase dynamically throughout the night. To better approximate overall lunar illumination, one could sample multiple time points (e.g. 20:00, 00:00, and 04:00 local time) or compute a nightly average of moon_altitude and illumination.
This alternative method—sampling at multiple night-time intervals, would provide a more complete picture of illumination dynamics, especially in studies interested in behavior spanning the entire night. However, it also increases computational complexity and may require integrating over longer periods.
moon_all <- moon_all %>%
mutate(
fecha_a = as.POSIXct(paste(fecha - 1, "20:00:00"), tz = "UTC"),
fecha_b = as.POSIXct(paste(fecha, "00:00:00"), tz = "UTC"),
fecha_c = as.POSIXct(paste(fecha, "04:00:00"), tz = "UTC"))
pos_a <- getMoonPosition(data = moon_all %>% rename(date = fecha_a) %>% select(date, lat, lon))
pos_b <- getMoonPosition(data = moon_all %>% rename(date = fecha_b) %>% select(date, lat, lon))
pos_c <- getMoonPosition(data = moon_all %>% rename(date = fecha_c) %>% select(date, lat, lon))
alt_scaled_a <- pmax(0, sin(pos_a$altitude))
alt_scaled_b <- pmax(0, sin(pos_b$altitude))
alt_scaled_c <- pmax(0, sin(pos_c$altitude))
moon_all <- moon_all %>%
mutate(
moon_altitude_avg = (alt_scaled_a + alt_scaled_b + alt_scaled_c) / 3,
moon_light_effective_avg = moon_fraction * moon_altitude_avg)
First lets ckeck for a simpler moonlight metric: moon fraction, then the effective moonlight
ggplot(moon_all, aes(x = moon_fraction, y = canopy_height)) +
geom_jitter(
aes(fill = used),
shape = 21, size = 3, stroke = 0.3,
alpha = 0.05, color = "black",
width = 0.03,
show.legend = TRUE) +
geom_smooth(
aes(color = used),
method = "lm",
se = TRUE,
size = 1.9
) +
scale_fill_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c( "Available", "Used"),
name = "Points"
) +
scale_color_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c( "Available", "Used"),
name = "Trend"
) +
guides(
fill = guide_legend(override.aes = list(alpha = 1)),
color = guide_legend(override.aes = list(size = 1.5))
) +
labs(
x = "Moon Fraction",
y = "Canopy Height (m)"
) +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(size = 15, face = "bold"),
axis.title = element_text(),
legend.title = element_text(face = "bold"),
legend.position = "right"
)
ggplot(moon_all, aes(x = moon_light_effective_avg, y = canopy_height)) +
geom_jitter(
aes(fill = used),
shape = 21, size = 3, stroke = 0.3,
alpha = 0.05, color = "black",
width = 0.03,
show.legend = TRUE) +
geom_smooth(
aes(color = used),
method = "lm",
se = TRUE,
size = 1.9
) +
scale_fill_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c( "Available", "Used"),
name = "Points"
) +
scale_color_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c( "Available", "Used"),
name = "Trend"
) +
guides(
fill = guide_legend(override.aes = list(alpha = 1)),
color = guide_legend(override.aes = list(size = 1.5))
) +
labs(
title = "Effective Moonlight vs. Canopy Height",
x = "Effective Moonlight Index",
y = "Canopy Height (m)"
) +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(size = 15, face = "bold"),
axis.title = element_text(),
legend.title = element_text(face = "bold"),
legend.position = "right"
)
Relationship between effective moonlight and canopy height across sleeping site observations. Points represent both used (orange) and available (blue-gray) locations, while lines show linear trends per group. The effective moonlight index combines lunar illumination fraction and altitude to approximate actual night-time light availability.
ggplot(moon_all %>% filter(used == TRUE), aes(x = moon_light_effective_avg, y = canopy_height)) +
geom_point(
aes(fill = used),
shape = 21, size = 5, stroke = 0.3,
alpha = 0.1, color = "goldenrod4",
show.legend = TRUE
) +
geom_smooth(
aes(color = used),
method = "lm",
se = TRUE,
size = 2.9
) +
scale_fill_manual(
values = c("TRUE" = "#E1B000"),
labels = c("Used"),
name = "Points"
) +
scale_color_manual(
values = c("TRUE" = "goldenrod4"),
labels = c("Used"),
name = "Trend"
) +
guides(
fill = guide_legend(override.aes = list(alpha = 1)),
color = guide_legend(override.aes = list(size = 1.5))
) +
coord_cartesian(ylim = c(0, 30)) +
labs(
title = "Effective Moonlight vs. Canopy Height",
x = "Effective Moonlight Index",
y = "Canopy Height (m)"
) +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(size = 15, face = "bold"),
axis.title = element_text(),
legend.title = element_text(face = "bold"),
legend.position = "right"
)
To incorporate environmental context into our analysis of sleeping site selection, we obtained daily weather data from a meteorological station operated by the Argentine National Meteorological Service (SMN). This station is located approximately 7 km from the study area and provides reliable long-term climatic records.
The dataset includes maximum, minimum, and mean daily temperatures, as
well as daily precipitation (mm). We parsed the date
field using the lubridate::dmy()
function and converted all
numeric fields to proper numeric
types for further
analysis. These data allow us to explore whether rainfall patterns
influence the characteristics of sleeping site selection, particularly
with regard to vegetation structure and canopy cover.
As precipitation may affect microhabitat conditions, such as humidity, substrate moisture, and thermal regulation, it is plausible that primates modulate their choice of sleeping sites in response to recent rainfall events.
weather <- weather %>%
mutate(
fecha = dmy(fecha), # formato "d/m/Y"
t_max = as.numeric(t_max),
t_min = as.numeric(t_min),
t_mean = as.numeric(t_mean),
pp = as.numeric(pp)
)
To investigate how recent rainfall may influence sleeping site
structure, we joined the weather dataset to our spatial records using
the fecha
(date) field as a key. Specifically, we added the
daily precipitation amount (pp
, in mm) to the
moon_all
dataset:
moon_all <- moon_all %>%
left_join(weather %>% select(fecha, pp), by = "fecha")
We then categorized daily rainfall into three meaningful bins: no rain (0 mm), light rain (1–5 mm), and heavy rain (>5 mm). This allowed us to visually explore whether rainfall levels interact with canopy height and effective moonlight in driving sleeping site selection.
The resulting plot combines:
used
and available
sites, helping assess how rainfall may modulate site choice in different
light environments
Together, this visualization provides insight into whether monkeys prefer different canopy structures under varying light and moisture conditions, potentially reflecting thermoregulatory or safety tradeoffs.
moon_all <- moon_all %>%
mutate(pp_cat = cut(
pp,
breaks = c(-0.01, 0, 5, Inf),
labels = c("0 mm", "1–5 mm", ">5 mm")
))
ggplot(moon_all, aes(x = moon_light_effective_avg, y = canopy_height)) +
geom_jitter(
aes(fill = pp_cat),
shape = 21, size = 3, stroke = 0.3,
alpha = 0.5, color = "black", width = 0.03
) +
geom_smooth(
aes(color = used),
method = "lm", se = TRUE, size = 1.6
) +
scale_fill_manual(
values = c("0 mm" = "gray80", "1–5 mm" = "#6baed6", ">5 mm" = "steelblue4"),
name = "Precipitation"
) +
scale_color_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c("Used", "Available"),
name = "Trend"
) +
labs(
title = "Canopy Height vs. Effective Moonlight and Precipitation",
x = "Effective Moonlight Index",
y = "Canopy Height (m)"
) +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 15),
legend.title = element_text(face = "bold")
)
Figure: Relationship between canopy height and effective moonlight under varying precipitation levels. Points are colored by precipitation categories recorded on the same date: no rain (gray), light rain (blue), and moderate to heavy rain (dark blue). Trend lines show linear patterns for used (yellow) and available (blue) locations. While moonlight may influence site selection, canopy height patterns do not appear strongly driven by precipitation.
ggplot(moon_all, aes(x = pp, y = canopy_height, color = used)) +
geom_jitter(
shape = 21, fill = "#6baed6", color = "black",
alpha = 0.1, width = 0.3, size = 2.6
) +
geom_smooth(method = "lm", se = TRUE, size = 2.5) +
scale_color_manual(
values = c("TRUE" = "#E1B000", "FALSE" = "#1F3552"),
labels = c("Used", "Available"),
name = "Trend"
) +
labs(
title = "Canopy Height vs. Precipitation",
x = "Precipitation (mm)",
y = "Canopy Height (m)"
) +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(face = "bold"),
legend.title = element_text(face = "bold")
)
Figure: Relationship between daily precipitation and canopy height at sleeping sites. Each point represents a used (yellow) or available (blue) location on a given date, with precipitation recorded at a nearby weather station. Smoother lines represent linear trends for each category. Although precipitation varies substantially, no strong association with canopy height is evident across categories.