--- title: "Introduction to logolink" format: html: toc: true html-math-method: mathjax engine: knitr eval: false vignette: > %\VignetteIndexEntry{Introduction to logolink} %\VignetteEngine{quarto::html} %\VignetteEncoding{UTF-8} --- ```{r} #| label: Setup #| eval: false #| include: false library(logolink) library(cli) library(dplyr) library(ggplot2) library(ggimage) library(ggtext) library(here) library(magick) library(magrittr) library(ragg) library(stringr) library(tidyr) source(here("R", ".setup.R")) ``` ## Overview This vignette will walk you through `logolink`, an R package that connects [R](https://www.r-project.org/) with [NetLogo](https://www.netlogo.org) models. By the end, you'll know how to create experiments, run simulations, and analyze model results, all without ever leaving R. We'll work with Wilensky's [Spread of Disease](https://www.netlogoweb.org/launch#https://www.netlogoweb.org/assets/modelslib/IABM%20Textbook/chapter%206/Spread%20of%20Disease.nlogox) model, an epidemiological simulation based on the [SIR equations](https://danielvartan.github.io/sir/) originally proposed by Kermack and McKendrick ([1927](https://doi.org/10.1098/rspa.1927.0118)). This model is included with NetLogo, so no separate download is required. This guide assumes familiarity with [NetLogo](https://www.netlogo.org) (version 7.0.1 or above) and R programming, particularly the [tidyverse](https://tidyverse.org/) ecosystem. ## Setting the Stage You can install `logolink` like any other R package, using the following command: ```r install.packages("logolink") ``` After installation, start by loading the package to access its functions: ```{r} library(logolink) ``` `logolink` automatically detects your NetLogo installation path. If automatic detection fails, you can specify it manually. See the [`run_experiment()`](https://danielvartan.github.io/logolink/reference/run_experiment.html) documentation for details. We'll also need to specify the path to the model file. Since we're using a built-in NetLogo model, we can retrieve its path easily using the [`find_netlogo_home()`](https://danielvartan.github.io/logolink/reference/find_netlogo_home.html) function: ```{r} model_path <- find_netlogo_home() |> file.path( "models", "IABM Textbook", "chapter 6", "Spread of Disease.nlogox" ) ``` ## Creating an Experiment Now that we have our environment set up, let's start by creating an experiment. We can do this using the [`create_experiment()`](https://danielvartan.github.io/logolink/reference/create_experiment.html) function. This function allows us to define the parameters we want to vary, the metrics we want to collect, and other settings for our simulation runs. ```{r} 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 = '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 ) ) ``` Alternatively, you can define your experiment directly within NetLogo and save it as part of the model file. If you do this, you can skip the [`create_experiment()`](https://danielvartan.github.io/logolink/reference/create_experiment.html) step and simply pass the experiment name to [`run_experiment()`](https://danielvartan.github.io/logolink/reference/run_experiment.html). If you're familiar with the [BehaviorSpace XML file structure](https://github.com/NetLogo/NetLogo/wiki/XML-File-Format#behaviorspace-experiments), you can easily inspect the created experiment using the [`inspect_experiment`](https://danielvartan.github.io/logolink/reference/inspect_experiment.html) function: ```{r} setup_file |> inspect_experiment() #> #> sequentialRunOrder="true" runMetricsEveryStep="true" timeLimit="1000"> #> setup #> go #> #> count turtles with [infected?] #> #> #> #> #> #> #> #> #> #> #> #> #> #> #> #> #> #> #> ``` ## Running the Simulation With our experiment defined, we can now run the simulation using the [`run_experiment()`](https://danielvartan.github.io/logolink/reference/run_experiment.html) function. This function takes care of launching NetLogo, executing the experiment, and collecting the results as [tidy data frames](https://r4ds.hadley.nz/data-tidy.html). ```{r} #| output: false results <- model_path |> run_experiment( setup_file = setup_file ) #> ✔ Running model [4.2s] #> ✔ Gathering metadata [21ms] #> ✔ Processing table output [7ms] ``` ## Checking the Results We can now examine the results. The output from [`run_experiment()`](https://danielvartan.github.io/logolink/reference/run_experiment.html) is a list of data frames. These can be any of the [four output formats](https://docs.netlogo.org/behaviorspace.html#run-options-formats) available in [BehaviorSpace](https://docs.netlogo.org/behaviorspace.html): [Table](https://docs.netlogo.org/behaviorspace.html#table-output), [Spreadsheet](https://docs.netlogo.org/behaviorspace.html#spreadsheet-output), [Lists](https://docs.netlogo.org/behaviorspace.html#lists-output), and [Statistics](https://docs.netlogo.org/behaviorspace.html#statistics-output). By default, only the [Table](https://docs.netlogo.org/behaviorspace.html#table-output) format is returned, along with some metadata about the experiment run. The `glimpse()` function from the [`dplyr`](https://dplyr.tidyverse.org/) R package can help us take a quick look at the results: ```{r} library(dplyr) results |> glimpse() #> List of 2 #> $ metadata:List of 6 #> ..$ timestamp : POSIXct[1:1], format: "2026-01-08 03:51:43" #> ..$ 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" ... #> $ table : tibble [8,006 × 8] (S3: tbl_df/tbl/data.frame) #> ..$ run_number : num [1:8006] 1 1 1 1 1 1 1 1 1 1 ... #> ..$ variant : chr [1:8006] "mobile" "mobile" ... #> ..$ num_people : num [1:8006] 50 50 50 50 50 50 ... #> ..$ connections_per_node : num [1:8006] 4.1 4.1 4.1 4.1 ... #> ..$ num_infected : num [1:8006] 1 1 1 1 1 1 1 1 1 1 ... #> ..$ disease_decay : num [1:8006] 0 0 0 0 0 0 0 0 0 0 ... #> ..$ step : num [1:8006] 0 1 2 3 4 5 6 7 8 9 ... #> ..$ count_turtles_with_infected: num [1:8006] 1 1 1 1 1 2 2 2 ... ``` If you already have an file with [BehaviorSpace](https://docs.netlogo.org/behaviorspace.html) experiment results, you can read it into R using the [`read_experiment()`](https://danielvartan.github.io/logolink/reference/read_experiment.html) function. The output will be the same tidy data frames as those returned by [`run_experiment()`](https://danielvartan.github.io/logolink/reference/run_experiment.html). ## Analyzing the Data (Bonus Section) We now have all the data we need to analyze the simulation results, and we did it all without leaving R! Why stop here, right? Let's do some basic data wrangling using the [`dplyr`](https://dplyr.tidyverse.org/) and [`magrittr`](https://magrittr.tidyverse.org/) packages to prepare the data for analysis and visualization. ```{r} #| eval: false library(dplyr) library(magrittr) data <- results |> extract2("table") |> rename(infected = count_turtles_with_infected) |> mutate( variant = as.factor(variant), frac_infected = infected / num_people ) |> summarize( mean = mean(frac_infected, na.rm = TRUE), sd = sd(frac_infected, na.rm = TRUE), .by = c(num_people, step) ) |> arrange(num_people, step) ``` ```{r} data |> glimpse() ``` Now we can create a simple plot using the [`ggplot2`](https://ggplot2.tidyverse.org/) package to visualize how the fraction of infected individuals changes over time for different population sizes along with error bars representing the standard error. ```{r} library(ggplot2) 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(data$step), 100) ) + labs( title = "Infected Over Time", x = "Steps", y = "Fraction of Infected", color = "People", shape = "People" ) ``` ![](../man/figures/vignette-spread-of-disease-model-plot-1.png) ```{r} #| eval: false #| include: false ggsave( filename = "vignette-spread-of-disease-model-plot-1.png", plot = get_last_plot(), device = agg_png, path = here("man", "figures"), width = 7, height = 5, units = "in", dpi = 96 ) ``` ## Visualizing the NetLogo World (Bonus Section) `logolink` also allows you to capture screenshots of the NetLogo world during the simulation runs. This is particularly useful for producing publication-quality figures or presentations. ![](../man/figures/vignette-spread-of-disease-model-animation-1.gif) See the [Visualizing the NetLogo World](https://danielvartan.github.io/logolink/articles/visualizing-the-netlogo-world.html) tutorial to learn how to do this by using [`parse_netlogo_color()`](https://danielvartan.github.io/logolink/reference/parse_netlogo_color.html) and [`get_netlogo_shape()`](https://danielvartan.github.io/logolink/reference/get_netlogo_shape.html) functions. You can customize these snapshots and animations as needed. Below is an example of an animation using Wilensky's [Wolf Sheep Simple](https://www.netlogoweb.org/launch#https://www.netlogoweb.org/assets/modelslib/IABM%20Textbook/chapter%204/Wolf%20Sheep%20Simple%205.nlogox) model. ![](../man/figures/vignette-wolf-sheep-model-animation-1.gif) ```{r} #| eval: false #| include: false person_shape <- get_netlogo_shape("person") ``` ```{r} #| eval: false #| include: false setup_file <- create_experiment( name = "Wolf Sheep Simple Model Analysis", repetitions = 1, sequential_run_order = TRUE, run_metrics_every_step = FALSE, setup = c('random-seed 2026', 'setup'), go = 'go', time_limit = 300, run_metrics_condition = 'ticks mod 50 = 0', metrics = c( '[xcor] of turtles', '[ycor] of turtles', '[color] of turtles', '[infected?] of turtles', '[pxcor] of patches', '[pycor] of patches' ), constants = list( "variant" = "mobile", "num-people" = 50, "connections-per-node" = 4.1, "num-infected" = 1, "disease-decay" = 0 ) ) ``` ```{r} #| eval: false #| include: false results <- model_path |> run_experiment( setup_file = setup_file, output = c("table", "lists") ) ``` ```{r} #| eval: false #| include: false plot_data <- results |> extract2("lists") |> mutate( pcolor_of_patches = parse_netlogo_color(0) ) ``` ```{r} #| eval: false #| include: false plot_data |> glimpse() ``` ```{r} #| eval: false #| include: false plot_netlogo_world <- function( data, run_number = 1, step = 0, step_label = TRUE ) { showtext_auto(FALSE) data <- data |> filter( run_number == .env$run_number, step == .env$step ) person_size <- 0.03 plot <- data |> ggplot( aes( x = pxcor_of_patches, y = pycor_of_patches, fill = pcolor_of_patches ) ) + geom_raster() + coord_fixed(expand = FALSE) + geom_image( data = data |> drop_na(xcor_of_turtles) |> filter(infected_of_turtles == FALSE), mapping = aes( x = xcor_of_turtles, y = ycor_of_turtles, image = person_shape ), size = person_size, color = parse_netlogo_color(5) ) + geom_image( data = data |> drop_na(xcor_of_turtles) |> filter(infected_of_turtles == TRUE), mapping = aes( x = xcor_of_turtles, y = ycor_of_turtles, image = person_shape ), size = person_size, color = parse_netlogo_color(15) ) + scale_fill_identity(na.value = parse_netlogo_color(0)) + theme_void() + theme(legend.position = "none") if (isTRUE(step_label)) { plot + labs(title = paste0("Step: **", step, "**")) + theme( plot.title.position = "plot", plot.title = element_markdown(size = 20, margin = margin(b = 10)), plot.background = element_rect(fill = "white", color = NA), plot.margin = margin(1.5, 1.5, 1.5, 1.5, "line") ) } else { plot } } ``` ```{r} #| eval: false #| include: false plot_netlogo_world(plot_data) ``` ```{r} #| eval: false #| include: false ggsave( filename = "vignette-spread-of-disease-model-plot-2.png", plot = get_last_plot(), device = agg_png, path = here("man", "figures"), width = 7, height = 7.4, units = "in", dpi = 96 ) ``` ```{r} #| eval: false #| include: false steps <- plot_data |> pull(step) |> unique() ``` ```{r} #| eval: false #| include: false steps ``` ```{r} #| eval: false #| include: false files <- character() cli_progress_bar("Generating frames", total = length(steps)) for (i in steps) { i_plot <- plot_netlogo_world(plot_data, step = i, step_label = TRUE) i_file <- tempfile(pattern = paste0("step-", i, "-"), fileext = ".png") ggsave( filename = i_file, plot = i_plot, device = agg_png, width = 7, height = 7.4, units = "in", dpi = 96 ) files <- append(files, i_file) cli_progress_update() } cli_progress_done() ``` ```{r} #| eval: false #| include: false animation <- files |> lapply(image_read) |> image_join() |> image_animate(fps = 1) ``` ```{r} #| eval: false #| include: false animation |> image_write( here( "man", "figures", "vignette-spread-of-disease-animation-1.gif" ) ) ``` ## Wrapping up You now have a basic understanding of how to use `logolink` to run NetLogo experiments directly from R. This opens up a world of possibilities for integrating agent-based modeling with statistical analysis and data visualization. Click [here](https://danielvartan.github.io/logolink/reference/index.html) to explore the full list of functions available in `logolink`.