In previous NetLogo tutorials, I discussed some of the powerful features of NetLogo, and the use of extensions. In this post, we will look at the extension which links to the R toolkit (see this article on the extension, and this documentation).
The R extension can be used for data analysis and visualisation, as an alternative to writing data to files and reading them back. Even more helpful is the use of R to implement agent decision-making. Here we provide very simple examples of both uses of R (a third use would be hybrid models such as this).
To use the R extension, follow the installation instructions here. In addition, the rJava package must be installed in R, and the jri folder inside the rJava folder must contain the files
REngine.jar (if need be, Google can find these files). The NetLogo file must begin with
extensions [ r ].
Our first demonstration is an implementation of Conway’s “Game of Life.” A global variable (
history) stores a list of the fraction of cells that are alive at each step. The NetLogo code below uses R to plot that list. The
r:eval function executes arbitrary R code, while
r:put assigns a NetLogo value to a named R variable (converting, for example, NetLogo lists into vectors). There are also variations of
r:putdataframe – see this table).
to rplot-graph ;; code for "Plot Life" button if (is-life?) [ r:put "life.hist" history r:eval "png (filename = \"C:/NetLogo/Life_plot.png\", width = 1600, height = 800, pointsize=18)" r:eval "par (mar=c(5,5,0.8,0.5))" r:eval "xs <- 0:(length(life.hist)-1)" r:eval "plot (life.hist ~ xs, type=\"l\", lwd=4, col=\"red\", xlab=\"Step\", ylab=\"Density\", ylim=c(0,max(life.hist)), cex.lab=2, cex.axis=1.5)" r:eval "dev.off()" ] end
The resulting plot is as follows:
More sophisticated statistical analysis in R includes the use of spatial techniques.
The implementation combines variables for both examples in this tutorial:
globals [ is-life? ;; distinguish between the two demos ;; for LIFE cell-count ;; total number of cells total ;; total number of live cells history ;; list of totals ;; for BACTERIA window ;; number of steps over which to analyse ] patches-own [ ;; for LIFE alive? ;; is cell alive? counter ;; used to pass number of live neighbours between Phase 1 and Phase 2 ;; for BACTERIA value ;; simulated chemical concentration ] turtles-own [ ;; for BACTERIA hist ;; history of concentration values ]
The setup code for the “Game of Life” is fairly straightforward, using two utility functions to make a patch alive or dead:
;; code for SETUP button -- Life to setup-life clear-all set is-life? true set cell-count (count patches) ask patches [ ifelse (random-float 1 < average-life-density) [birth] [death] ] reset-ticks set history (list (total / cell-count)) end to death ;; this procedure applies to a specific patch set alive? false set pcolor white end to birth ;; this procedure applies to a specific patch set alive? true set pcolor blue set total (total + 1) end
The step code is also straightforward, using two phases (one to count live neighbours, and one to compute the future state):
to go ;; single simulation step for both demos if-else (is-life?) [ ;; LIFE ;; First phase ask patches [ set counter (count neighbors with [alive?]) ] ;; Second phase set total 0 ask patches [ ifelse (counter = 3 or (counter = 2 and alive?)) [ birth ] [ death ] ] set history (lput (total / cell-count) history) ;; Record current density tick ] [ ;; BACTERIA ... snip ... ] end
The second demo (see screenshot above) simulates bacterial chemotaxis. Bacteria move up a chemical gradient by moving in a straight line if the concentration is increasing, and by randomly changing direction if it is not. We make the decision using the reporter below, which calculates the slope of the best-fit line through the datapoints (this is a very simple example of using R code to implement an agent’s decision-making). The
r:get reporter is used to return the value of an R expression (in this case, the slope):
to-report calculate-slope [ lst ] ;; R interface -- calculate slope for values in lst r:put "valu.hist" lst r:eval "valu.index <- 1:length(valu.hist)" r:eval "valu.lm <- lm(valu.hist ~ valu.index)" report r:get "valu.lm$coefficients" end
A slightly more efficient option would have been to use
r:eval to define an R function in setup, and then to call it here with
valu.hist as an argument.
Related uses of R for agent decision-making include implementations of neural networks, clustering, pattern-recognition, and the like.
Returning to our example, most of the setup code calculates a chemical gradient, but some bacteria are also created:
;; code for SETUP button -- Bacteria to setup-bacteria clear-all set is-life? false set window 4 ;; create gradient repeat 3 [ let x random-xcor let y random-ycor let v (0.1 + random-float 0.5) let r (1 + v * world-width / 2) ask patches [ let d ((distancexy x y) / r) set value (value + v * exp (- d)) ] ] let min-value (min ([value] of patches)) let max-value (max ([value] of patches)) ask patches [ set pcolor (30 + (9.9 * (value - min-value) / (max-value - min-value))) ] ;; create some bacteria crt 20 [ setxy random-xcor random-ycor set color red set size 6 set hist (list value) ] reset-ticks end
The implementation of bacterial chemotaxis is straightforward, using the previously defined reporter
to go ;; single simulation step for both demos if-else (is-life?) [ ;; LIFE ... snip ... ] [ ;; BACTERIA ask turtles [ fd 1 set hist (lput value hist) if (length(hist) > window) [ set hist (bf hist) ] if (length(hist) = window) [ if (calculate-slope hist <= 0) [ set heading (random-float 360) set hist (list value) ] ] ] tick ] end