-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathanalysis-visualisation.qmd
More file actions
404 lines (288 loc) · 10.3 KB
/
Copy pathanalysis-visualisation.qmd
File metadata and controls
404 lines (288 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# Visualisation of simulation results {#analysis-visualisation}
## 📊 Analysis Pipeline for Simulation Results
```
💾 Simulation outputs: BehaviorSpace CSV files
↓
📥 Import data: R, tidyverse
↓
🧹 Clean & prepare: Data wrangling, filtering, reshaping
↓
📈 Explore: Descriptive plots, distributions, correlations
↓
📊 Analyse: Statistical models, sensitivity analysis
↓
🖼️ Visualise: Publication-ready plots, dashboard
↓
🧩 Interpret: Compare with hypotheses, archaeological data
↓
📘 Report & share: RMarkdown, GitHub, Zenodo DOI
↺
🔁 Refine model or experiment design
```
## Set up R environment
To start, we should load and define all necessary elements in R that will be used through out our script:
```{r}
library(tidyverse)
experiments_path <- "assets/netlogo/experiments/Artificial Anasazi_experiments "
```
```{r}
color_mapping <- c("historical households" = "blue",
"simulation households" = "darkred")
```
## Loading datasets
**Single run**
```{r}
expname_single <- "experiment single run"
results_single <- readr::read_csv(paste0(experiments_path, expname_single, "-table.csv"), skip = 6)
```
**Repetitions in single configuration**
```{r}
expname_multiple <- "experiment multiple runs"
results_multiple <- readr::read_csv(paste0(experiments_path, expname_multiple, "-table.csv"), skip = 6)
```
**Parameter exploration - harvest adjustment**
```{r}
expname_harvest_adj <- "experiment harvest adjustment"
results_harvest_adj <- readr::read_csv(paste0(experiments_path, expname_harvest_adj, "-table.csv"), skip = 6)
```
**Parameter exploration - harvest adjustment vs. harvest-variance**
```{r}
expname_harvest_adj_var <- "experiment harvest adjustment variance"
results_harvest_adj_var <- readr::read_csv(paste0(experiments_path, expname_harvest_adj_var, "-table.csv"), skip = 6)
```
## First overview
In R, there are many ways of having our first look at the data. An useful function from the `tidyverse` family is `glimpse`.
```{r}
glimpse(results_single)
```
```{r}
glimpse(results_multiple)
```
```{r}
glimpse(results_harvest_adj)
```
```{r}
glimpse(results_harvest_adj_var)
```
This initial inspection confirms:
* how many runs were executed,
* was every time step recorded,
* whether repetitions are present,
* which parameters and outputs are available.
## Distributions of outcomes
A first visualisation focuses on **distributions of final outcomes**, ignoring internal dynamics.
```{r}
final_results <- results_multiple |>
filter(`[step]` == max(`[step]`))
ggplot(final_results, aes(x = `total-households`)) +
geom_histogram(bins = 30) +
labs(
x = "Final population size",
y = "Number of runs"
)
```
This plot answers questions such as:
* Are most runs clustered around similar outcomes?
* Are extreme collapses common or rare?
* Does the distribution suggest multiple regimes or attractors?
::: {.callout-note}
## Artificial Anasazi interpretation
A long left tail or bimodal distribution often indicates
coexistence of persistence and collapse trajectories.
:::
## Comparing parameter settings
When parameters were varied systematically, we can compare outcomes across settings.
```{r}
final_results <- results_harvest_adj |>
filter(`[step]` == max(`[step]`))
ggplot(final_results,
aes(x = `harvest-adjustment`, y = `total-households`, group = `harvest-adjustment`)) +
geom_boxplot() +
labs(
x = "Harvest adjustment",
y = "Final population size"
)
```
Boxplots highlight:
* median outcomes,
* variability across repetitions,
* possible threshold effects.
::: {.callout-warning}
## Interpretation caution
Do not over-interpret smooth trends:
BehaviorSpace grids can create visual regularities.
:::
## Repetitions and variability
To visualise **mean behaviour and uncertainty**, we summarise repetitions.
```{r}
summary_results <- final_results |>
group_by(`harvest-adjustment`) |>
summarise(
mean_pop = mean(`total-households`),
sd_pop = sd(`total-households`),
.groups = "drop"
)
ggplot(summary_results,
aes(x = `harvest-adjustment`, y = mean_pop)) +
geom_line() +
geom_point() +
geom_errorbar(
aes(ymin = mean_pop - sd_pop,
ymax = mean_pop + sd_pop),
width = 0.02
) +
labs(
x = "Harvest adjustment",
y = "Mean final population size"
)
```
This shifts attention from *individual histories* to *typical outcomes and robustness*.
## Time series and trajectories
When metrics are recorded at every time step, trajectories become central.
To plot a single run:
```{r}
plot_name <- paste0(experiments_path, expname_single, "-trajectories.png")
png(plot_name, width = 840, height = 540)
ggplot(results_single) +
geom_line(aes(x = `[step]`, y = `historical-total-households`, color = "historical data"),
linewidth = 1.2) +
geom_line(aes(x = `[step]`, y = `total-households`, color = "simulation households"),
linewidth = 1.2) +
labs(x = "steps", y = "households") +
scale_color_manual(name = "", values = color_mapping) +
theme(legend.position = "right")
dev.off()
```
```{r}
knitr::include_graphics(plot_name)
```
We may also sample a few runs from a multiple-run set and plot them together:
```{r}
sample_runs <- results_multiple |>
distinct(`[run number]`) |>
slice_sample(n = 5)
results_multiple |>
filter(`[run number]` %in% sample_runs$`[run number]`) |>
ggplot(aes(x = `[step]`, y = `total-households`,
group = `[run number]`)) +
geom_line(alpha = 0.7) +
labs(
x = "Time step",
y = "Population size"
)
```
Alternatively, we may want to plot all repetitions under a parameter configuration. Here is an example using a slightly more complex plot that includes the historical trajectory and saves the plot to a file:
```{r}
plot_name <- paste0(experiments_path, expname_multiple, "-trajectories.png")
png(plot_name, width = 840, height = 540)
ggplot(results_multiple) +
geom_line(aes(x = `[step]`, y = `total-households`, color = `[run number]`, group = `[run number]`),
linewidth = 1.2) +
geom_line(aes(x = `[step]`, y = `historical-total-households`),
color = color_mapping["historical households"],
linewidth = 1.2, linetype = 2) +
labs(x = "steps", y = "households") +
theme(legend.position = "right")
dev.off()
```
```{r}
knitr::include_graphics(plot_name)
```
Plotting a **small sample of runs** avoids overplotting while revealing:
* divergence between histories,
* timing of collapse,
* early-warning patterns.
## Linking parameters and outcomes
For randomly sampled parameter sets, scatter plots are particularly useful (see chapter on sensitivity analysis). However, it might still offer some insight when applied to regularly sampled parameters; unlike boxplots, scatter plots exposes the true variation of data points.
```{r}
final_results <- results_harvest_adj_var |>
filter(`[step]` == max(`[step]`))
ggplot(final_results,
aes(x = `harvest-variance`, y = `total-households`)) +
geom_point(alpha = 0.6) +
labs(
x = "Harvest variability",
y = "Final population size"
)
```
This allows us to ask:
* Is there a clear relationship between parameter value (x-axis) and the output variable (y-axis)?
* Are there large outcome ranges for similar inputs?
* Do interactions between parameters seem likely?
::: {.callout-note}
## Artificial Anasazi interpretation
Wide vertical spread suggests strong stochastic effects or interactions with other parameters.
:::
We may also want to detect effects that are only visible in the full trajectory of simulations. For this, we can use line plots as before, but there will be strong limits to how many runs can be included so that the plot is legible.
```{r}
plot_name <- paste0(experiments_path, expname_harvest_adj, "-trajectories.png")
png(plot_name, width = 840, height = 540)
ggplot(results_harvest_adj) +
geom_line(aes(x = `[step]`, y = `total-households`, color = `harvest-adjustment`, group = `[run number]`),
linewidth = 1.2) +
geom_line(aes(x = `[step]`, y = `historical-total-households`), color = "black",
linewidth = 1.2, linetype = 2) +
labs(x = "steps", y = "households") +
theme(legend.position = "right")
dev.off()
```
```{r}
knitr::include_graphics(plot_name)
```
```{r}
plot_name <- paste0(experiments_path, expname_harvest_adj_var, "-trajectories.png")
png(plot_name, width = 840, height = 540)
ggplot(results_harvest_adj_var) +
geom_line(aes(x = `[step]`, y = `total-households`, group = `[run number]`),
color = color_mapping["simulation households"],
linewidth = 1.2) +
geom_line(aes(x = `[step]`, y = `historical-total-households`),
color = color_mapping["historical households"],
linewidth = 1.2, linetype = 2) +
facet_grid(`harvest-adjustment` ~ `harvest-variance`) +
labs(x = "steps", y = "households", title = "harvest-adjustment (rows) vs harvest-variance (columns)") +
theme(legend.position = "right")
dev.off()
```
```{r}
knitr::include_graphics(plot_name)
```
## Identifying extreme and representative runs
We can isolate extreme cases for closer inspection.
```{r}
final_results <- results_harvest_adj_var |>
filter(`[step]` == max(`[step]`))
extremes <- final_results |>
arrange(`total-households`) |>
slice(c(1, n()))
results_harvest_adj_var |>
filter(`[run number]` %in% extremes$`[run number]`) |>
ggplot(aes(x = `[step]`, y = `total-households`,
group = `[run number]`,
colour = factor(`[run number]`))) +
geom_line() +
labs(
x = "Time step",
y = "Population size",
colour = "Run"
)
```
This reconnects **aggregate statistics** with **individual simulated histories**.
## Exercises (with code reuse)
::: {.callout-tip}
## Exercise 1: Distribution
Modify the histogram code to plot:
* time to collapse (if available),
* or number of occupied settlements.
What changes in the shape of the distribution?
:::
::: {.callout-tip}
## Exercise 2: Parameter comparison
Perform an experiment varying another parameter and compare with the results exploring `harvest-adjustment` and `harvest_variance`.
Does this parameter show a stronger or weaker association with outcomes?
:::
::: {.callout-tip}
## Exercise 3: Trajectories
Increase the number of sampled runs to 10.
At what point does overplotting begin to obscure interpretation?
:::