Skip to content

store worldArray character layers as levels - #59

Merged
achubaty merged 1 commit into
developmentfrom
issue-49-worldarray-levels
Aug 10, 2026
Merged

store worldArray character layers as levels#59
achubaty merged 1 commit into
developmentfrom
issue-49-worldarray-levels

Conversation

@achubaty

@achubaty achubaty commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #49.

The problem

An array holds a single type. Stacking a character worldMatrix next to a numeric one therefore turned every layer into character, and the character values were then coerced back to NA on the way out:

w1 <- createWorld(minPxcor = 0, maxPxcor = 4, minPycor = 0, maxPycor = 4, data = 1:25)
w2 <- createWorld(minPxcor = 0, maxPxcor = 4, minPycor = 0, maxPycor = 4, data = rep("sea", 25))
w3 <- stackWorlds(w1, w2)

of(world = w3, agents = cbind(pxcor = 1, pycor = 1), var = "w2")
#> NA
#> Warning: NAs introduced by coercion

plot(w3)
#> Warning: NAs introduced by coercion

The numeric layer was collateral damage too — of(w3, var = "w1") returned the string "17" rather than 17.

The fix

worldArray gains a levels slot. Character layers are stored as integer codes with their categories recorded there, exactly as agentMatrix already does for its character columns, so the array itself stays numeric.

Accessors report the characters, never the codes:

before after
of(w3, var = "w2") NA + warning "sea"
of(w3, var = "w1") "17" (character) 17 (numeric)
of(w3, var = c("w1","w2")) coerced matrix data.frame
plot(w3) warns, layer all NA categorical layer
w3[["w2"]] numeric worldMatrix character worldMatrix

NLwith() compares against the characters, NLset() encodes them and extends the levels when it meets a new category, and [[<- re-encodes so swapping layers cannot leave the array and the levels inconsistent. world2spatRast() hands terra the characters and lets terra build the categorical layer — which is exactly what a standalone character worldMatrix has always produced, so the two paths now agree.

An all-numeric worldArray is untouched by any of this: empty levels, matrix returns, same types.

One deliberate restriction

Turning some patches of a numeric layer into characters is rejected:

NLset(world = w, agents = cbind(pxcor = 0, pycor = 0), var = "num", val = "oops")
#> Error: cannot assign character values to only some patches of the numeric worldArray layer 'num'.
#> Assign the whole layer instead, e.g. world[['num']] <- aCharacterWorld.

The numbers left in the unassigned patches would silently be reinterpreted as codes. Assigning the whole layer is allowed, as is world[["num"]] <- aCharacterWorld.

Note that cbind(num = 1, hab = "ice") is already a character matrix before NLset() ever sees it, so numbers arrive at a numeric layer as strings. Those are converted back when they parse cleanly, rather than being treated as new categories. Pass a data.frame to keep the column types distinct.

Rebased onto #57 and #58

Both prerequisites are merged, and this branch has been rebased onto them. Conflicts resolved:

  • R/world-functions.R — kept drop the quickPlot dependency #57's ncols=/nrows= fix and this PR's decoded vals, in the same terra::rast() call.
  • NEWS.md and tests/testthat/test-NetLogoR-classes.R — both PRs' entries and test blocks retained.

With #57 in the base, plot(w3) is now completely silent: its nrow/ncol partial-match warnings are gone and the NAs introduced by coercion warning this PR removes is gone, so the two fixes compose as intended.

Verification

🤖 Generated with Claude Code

@achubaty
achubaty force-pushed the issue-49-worldarray-levels branch from 27766ba to d5533ab Compare August 10, 2026 18:22
An array holds a single type, so stacking a character worldMatrix next to
a numeric one turned every layer into character, and the character values
were then coerced back to NA on the way out (#49):

  w3 <- stackWorlds(w1, w2)          # w1 numeric, w2 character
  of(world = w3, agents = cbind(pxcor = 1, pycor = 1), var = "w2")
  #> NA
  #> Warning: NAs introduced by coercion

worldArray now keeps character layers as integer codes and records their
categories in a new levels slot, the same way agentMatrix already stores
its character columns, so the array itself stays numeric.

Accessors report the characters rather than the codes. of() decodes, and
returns a data.frame when a mix of coded and numeric layers is asked for,
since a matrix cannot hold both. NLwith() compares against the characters
and NLset() encodes them, extending the levels when it meets a category
the layer has not seen. [[ and $ hand back a character worldMatrix, and
[[<- re-encodes so that swapping layers in and out cannot leave the array
or the levels inconsistent. world2spatRast() passes the characters to
terra, which builds a categorical layer from them, which is what plot()
on a standalone character worldMatrix has always produced.

Numeric layers stacked beside a character one now keep their own type:
of() on one used to report "17" and now reports 17 again.

Turning only some patches of a numeric layer into characters is rejected,
because the numbers left in the unassigned patches would silently become
codes. Assigning the whole layer is fine, as is world[["x"]] <- charWorld.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@achubaty
achubaty force-pushed the issue-49-worldarray-levels branch from d5533ab to ebdb079 Compare August 10, 2026 18:32
@achubaty achubaty changed the title store worldArray character layers as levels (needs #57 and #58 first) store worldArray character layers as levels Aug 10, 2026
@achubaty
achubaty merged commit 64b9030 into development Aug 10, 2026
14 checks passed
@achubaty
achubaty deleted the issue-49-worldarray-levels branch August 10, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant