library(logolink) # github.com/danielvartan/logolink
library(dplyr)
library(ggplot2)
library(magrittr)LogoActions: GitHub Actions for the NetLogo Community 🚀
Overview
This document presents the output of a GitHub Action workflow that sets up NetLogo and runs experiments using Quarto and the logolink R package.
See the repository README for more details.
Set the Environment
Load Packages
Set ggplot2 Theme
theme_set(
theme_bw() +
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.frame = element_blank(),
legend.ticks = element_line(color = "white")
)
)Wolf Sheep Predation Model
Set Model Path
model_path <-
find_netlogo_home() |>
file.path(
"models",
"IABM Textbook",
"chapter 4",
"Wolf Sheep Simple 5.nlogox"
)Create Experiment
setup_file <- create_experiment(
name = "Wolf Sheep Simple Model Analysis",
repetitions = 10,
sequential_run_order = TRUE,
run_metrics_every_step = TRUE,
time_limit = 1000,
setup = 'setup',
go = 'go',
metrics = c(
'count wolves',
'count sheep'
),
constants = list(
"number-of-sheep" = 500,
"number-of-wolves" = list(
first = 5,
step = 1,
last = 15
),
"movement-cost" = 0.5,
"grass-regrowth-rate" = 0.3,
"energy-gain-from-grass" = 2,
"energy-gain-from-sheep" = 5
)
)Run Experiment
results <-
model_path |>
run_experiment(setup_file = setup_file)results |> glimpse()List of 2
$ metadata:List of 6
..$ timestamp : POSIXct[1:1], format: "2026-01-11 21:17:22"
..$ netlogo_version : chr "7.0.3"
..$ output_version : chr "2.0"
..$ model_file : chr "Wolf Sheep Simple 5.nlogox"
..$ experiment_name : chr "Wolf Sheep Simple Model Analysis"
..$ world_dimensions: Named int [1:4] -17 17 -17 17
.. ..- attr(*, "names")= chr [1:4] "min-pxcor" "max-pxcor" "min-pycor" "max-pycor"
$ table : tibble [110,110 × 10] (S3: tbl_df/tbl/data.frame)
..$ run_number : num [1:110110] 1 1 1 1 1 1 1 1 1 1 ...
..$ number_of_sheep : num [1:110110] 500 500 500 500 500 500 500 500 500 500 ...
..$ number_of_wolves : num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
..$ movement_cost : num [1:110110] 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ...
..$ grass_regrowth_rate : num [1:110110] 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 ...
..$ energy_gain_from_grass: num [1:110110] 2 2 2 2 2 2 2 2 2 2 ...
..$ energy_gain_from_sheep: num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
..$ step : num [1:110110] 0 1 2 3 4 5 6 7 8 9 ...
..$ count_wolves : num [1:110110] 5 5 5 5 5 5 5 5 5 5 ...
..$ count_sheep : num [1:110110] 500 498 497 495 493 491 491 490 489 487 ...
results$metadata
$metadata$timestamp
[1] "2026-01-11 21:17:22 GMT"
$metadata$netlogo_version
[1] "7.0.3"
$metadata$output_version
[1] "2.0"
$metadata$model_file
[1] "Wolf Sheep Simple 5.nlogox"
$metadata$experiment_name
[1] "Wolf Sheep Simple Model Analysis"
$metadata$world_dimensions
min-pxcor max-pxcor min-pycor max-pycor
-17 17 -17 17
$table
# A tibble: 110,110 × 10
run_number number_of_sheep number_of_wolves movement_cost grass_regrowth_rate
<dbl> <dbl> <dbl> <dbl> <dbl>
1 1 500 5 0.5 0.3
2 1 500 5 0.5 0.3
3 1 500 5 0.5 0.3
4 1 500 5 0.5 0.3
5 1 500 5 0.5 0.3
6 1 500 5 0.5 0.3
7 1 500 5 0.5 0.3
8 1 500 5 0.5 0.3
9 1 500 5 0.5 0.3
10 1 500 5 0.5 0.3
# ℹ 110,100 more rows
# ℹ 5 more variables: energy_gain_from_grass <dbl>,
# energy_gain_from_sheep <dbl>, step <dbl>, count_wolves <dbl>,
# count_sheep <dbl>
Plot Results
plot_data <-
results |>
extract2("table") |>
summarize(
across(everything(), ~ mean(.x, na.rm = TRUE)),
.by = c(step, number_of_wolves)
) |>
arrange(number_of_wolves, step)plot_data |>
mutate(
number_of_wolves = as.factor(number_of_wolves)
) |>
ggplot(
aes(
x = step,
y = count_sheep,
group = number_of_wolves,
color = number_of_wolves
)
) +
geom_line() +
labs(
x = "Time Step",
y = "Average Number of Sheep",
color = "Wolves"
)
Spread of Disease Model
Set Model Path
model_path <-
find_netlogo_home() |>
file.path(
"models",
"IABM Textbook",
"chapter 6",
"Spread of Disease.nlogox"
)Create Experiment
setup_file <- create_experiment(
name = "Population Density (Runtime)",
repetitions = 10,
sequential_run_order = TRUE,
run_metrics_every_step = TRUE,
time_limit = 1000,
setup = 'setup',
go = 'go',
metrics = c(
'count turtles with [infected?]'
),
constants = list(
"variant" = "mobile",
"num-people" = list(
first = 50,
step = 50,
last = 200
),
"connections-per-node" = 4.1,
"num-infected" = 1,
"disease-decay" = 0
)
)Run Experiment
results <-
model_path |>
run_experiment(setup_file = setup_file)results |> glimpse()List of 2
$ metadata:List of 6
..$ timestamp : POSIXct[1:1], format: "2026-01-11 21:17:46"
..$ netlogo_version : chr "7.0.3"
..$ output_version : chr "2.0"
..$ model_file : chr "Spread of Disease.nlogox"
..$ experiment_name : chr "Population Density (Runtime)"
..$ world_dimensions: Named int [1:4] -20 20 -20 20
.. ..- attr(*, "names")= chr [1:4] "min-pxcor" "max-pxcor" "min-pycor" "max-pycor"
$ table : tibble [8,465 × 8] (S3: tbl_df/tbl/data.frame)
..$ run_number : num [1:8465] 1 1 1 1 1 1 1 1 1 1 ...
..$ variant : chr [1:8465] "mobile" "mobile" "mobile" "mobile" ...
..$ num_people : num [1:8465] 50 50 50 50 50 50 50 50 50 50 ...
..$ connections_per_node : num [1:8465] 4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1 4.1 ...
..$ num_infected : num [1:8465] 1 1 1 1 1 1 1 1 1 1 ...
..$ disease_decay : num [1:8465] 0 0 0 0 0 0 0 0 0 0 ...
..$ step : num [1:8465] 0 1 2 3 4 5 6 7 8 9 ...
..$ count_turtles_with_infected: num [1:8465] 1 1 1 1 1 1 1 1 1 1 ...
results$metadata
$metadata$timestamp
[1] "2026-01-11 21:17:46 GMT"
$metadata$netlogo_version
[1] "7.0.3"
$metadata$output_version
[1] "2.0"
$metadata$model_file
[1] "Spread of Disease.nlogox"
$metadata$experiment_name
[1] "Population Density (Runtime)"
$metadata$world_dimensions
min-pxcor max-pxcor min-pycor max-pycor
-20 20 -20 20
$table
# A tibble: 8,465 × 8
run_number variant num_people connections_per_node num_infected disease_decay
<dbl> <chr> <dbl> <dbl> <dbl> <dbl>
1 1 mobile 50 4.1 1 0
2 1 mobile 50 4.1 1 0
3 1 mobile 50 4.1 1 0
4 1 mobile 50 4.1 1 0
5 1 mobile 50 4.1 1 0
6 1 mobile 50 4.1 1 0
7 1 mobile 50 4.1 1 0
8 1 mobile 50 4.1 1 0
9 1 mobile 50 4.1 1 0
10 1 mobile 50 4.1 1 0
# ℹ 8,455 more rows
# ℹ 2 more variables: step <dbl>, count_turtles_with_infected <dbl>
Tidy Data
data <-
results |>
extract2("table") |>
rename(infected = count_turtles_with_infected) |>
mutate(
variant = as.factor(variant),
frac_infected = infected / num_people
) |>
arrange(run_number, num_infected, step)data |> glimpse()Rows: 8,465
Columns: 9
$ run_number <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ variant <fct> mobile, mobile, mobile, mobile, mobile, mobile, m…
$ num_people <dbl> 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 5…
$ connections_per_node <dbl> 4.1, 4.1, 4.1, 4.1, 4.1, 4.1, 4.1, 4.1, 4.1, 4.1,…
$ num_infected <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ disease_decay <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ step <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,…
$ infected <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ frac_infected <dbl> 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0…
dataPlot Data
data |>
mutate(
num_people = as.factor(num_people)
) |>
ggplot(
aes(
x = step,
y = frac_infected,
color = num_people
)
) +
geom_point(
aes(shape = num_people),
size = 1,
alpha = 0.75
) +
scale_x_continuous(breaks = seq(0, max(data$step), 100)) +
labs(
x = "Steps",
y = "Fraction of Infected",
title = "Infected Over Time",
color = "People",
shape = "People"
)
plot <-
data |>
ggplot(
aes(
x = step,
y = frac_infected, # infected
color = as.factor(num_people)
)
) +
scale_x_continuous(
breaks = seq(0, max(data$step), 100)
) +
labs(
x = "Steps",
y = "Fraction of Infected",
title = "Infected Over Time",
color = "People"
)
data_i <-
data |>
group_by(num_people, run_number) |>
group_split()
for (i in data_i) {
plot <-
plot +
geom_line(
data = i,
aes(x = step, y = frac_infected),
linewidth = 1,
alpha = 0.75
)
}
plot
Summarize Data
summarized_data <-
data |>
select(num_people, step, infected, frac_infected) |>
summarize(
mean = mean(frac_infected, na.rm = TRUE),
sd = sd(frac_infected, na.rm = TRUE),
.by = c(num_people, step)
) |>
arrange(num_people, step)summarized_data |> glimpse()Rows: 1,080
Columns: 4
$ num_people <dbl> 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,…
$ step <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1…
$ mean <dbl> 0.020, 0.022, 0.022, 0.026, 0.026, 0.026, 0.026, 0.026, 0.0…
$ sd <dbl> 0.000000000, 0.006324555, 0.006324555, 0.009660918, 0.00966…
summarized_dataPlot Summarized Data with SD Error Bars
summarized_data |>
filter(step %% 10 == 0) |>
ggplot(
aes(
x = step,
y = mean,
color = as.factor(num_people)
)
) +
geom_point(
aes(shape = as.factor(num_people)),
size = 1,
alpha = 0.75
) +
geom_errorbar(
aes(
ymin = mean + sd,
ymax = mean - sd
),
width = 5
) +
geom_line() +
scale_x_continuous(
breaks = seq(0, max(summarized_data$step), 100)
) +
labs(
title = "Infected Over Time",
x = "Steps",
y = "Fraction of Infected",
color = "People",
shape = "People"
)
Plot Summarized Data with SE Error Bars
summarized_data |>
filter(step %% 10 == 0) |>
mutate(se = sd / sqrt(10)) |>
ggplot(aes(
x = step,
y = mean,
color = as.factor(num_people)
)) +
geom_point(
aes(shape = as.factor(num_people)),
size = 1,
alpha = 0.75
) +
geom_errorbar(
aes(
ymin = mean + se,
ymax = mean - se
),
width = 5
) +
geom_line() +
scale_x_continuous(
breaks = seq(0, max(summarized_data$step), 100)
) +
labs(
title = "Infected Over Time",
x = "Steps",
y = "Fraction of Infected",
color = "People",
shape = "People"
)