JuliaDynamics / Agents.jl

Agent-based modeling framework in Julia
https://juliadynamics.github.io/Agents.jl/stable/
MIT License
757 stars 120 forks source link

make `abmexploration` use `Agents.dataname` #926

Open rmellema opened 2 years ago

rmellema commented 2 years ago

I'm trying to count all the agents with a certain property. In particular, my agents are "at" a certain type of location, and I would like to know how many agents are at home, at work, at school, etc. In order to do this, I set up a function that takes a type of location, and returns a function that counts the number of agents at that type of location. the dataname of the returned function is meaningful and can easily be used. It would be something like count_type=home for one, and then count_type=school for another. Using the same mdata vector in run! also gives back the correct dataframe.

However, when I try to use this with abmexploration, it throws an error that "column name :count not found in dataframe". Closer inspection of the code seems to indicate that abmexploration uses string on the function, not dataname (this happens at src/agents/convenience.jl:75). This causes errors on code that works fine with run!, meaning that the documentation for abmexploration is also incorrect.

I tried replacing the call to string into one to Agents.dataname, but that caused other errors down the line, so someone that knows what they are doing should probably look into this.

Datseris commented 1 year ago

Thanks! what were the errors down the line? can you paste them here? (because yes, what you say makes sense, dataname should be used instead)

rmellema commented 1 year ago

Actually, I looked at the error some further, and instead of it being an issue with InteractiveDynamics, the problem was that my mlabels were symbols, and there was no function for displaying symbols, but it expected strings. I've included part of the stack trace at the bottom anyway, but its not a problem.

So then I guess its just replacing all occurences of string with Agents.dataname and it should work?

ERROR: MethodError: no method matching _get_glyphcollection_and_linesegments(::Symbol, ::Int64, ::Float32, ::FreeTypeAbstraction.FTFont, ::Tuple{Symbol, Symbol}, ::Quaternionf, ::MakieCore.Automatic, ::Fl
oat64, ::ColorTypes.RGBA{Float32}, ::ColorTypes.RGBA{Float32}, ::Int64, ::Int64)
Closest candidates are:
  _get_glyphcollection_and_linesegments(::LaTeXStrings.LaTeXString, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) at ~/.julia/packages/Makie/Ppzqh/src/basic_recipes/text.jl:
72
  _get_glyphcollection_and_linesegments(::AbstractString, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) at ~/.julia/packages/Makie/Ppzqh/src/basic_recipes/text.jl:68
Stacktrace:
  [1] (::ComposedFunction{Makie.var"#push_args#1814"{Vector{Int64}, Vector{ColorTypes.RGBA{Float32}}, Vector{Float32}, Vector{Point{2, Float32}}, Vector{Makie.GlyphCollection}}, typeof(Makie._get_glyphcol
lection_and_linesegments)})(::Symbol, ::Vararg{Any}; kw::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./operators.jl:1035
  [2] (::ComposedFunction{Makie.var"#push_args#1814"{Vector{Int64}, Vector{ColorTypes.RGBA{Float32}}, Vector{Float32}, Vector{Point{2, Float32}}, Vector{Makie.GlyphCollection}}, typeof(Makie._get_glyphcol
lection_and_linesegments)})(::Symbol, ::Vararg{Any})
    @ Base ./operators.jl:1033
  [3] macro expansion
    @ ~/.julia/packages/Makie/Ppzqh/src/utilities/utilities.jl:201 [inlined]
  [4] broadcast_foreach(::ComposedFunction{Makie.var"#push_args#1814"{Vector{Int64}, Vector{ColorTypes.RGBA{Float32}}, Vector{Float32}, Vector{Point{2, Float32}}, Vector{Makie.GlyphCollection}}, typeof(Ma
kie._get_glyphcollection_and_linesegments)}, ::Symbol, ::UnitRange{Int64}, ::Float32, ::FreeTypeAbstraction.FTFont, ::Tuple{Symbol, Symbol}, ::Quaternionf, ::MakieCore.Automatic, ::Float64, ::ColorTypes.R
GBA{Float32}, ::ColorTypes.RGBA{Float32}, ::Int64, ::Int64)
    @ Makie ~/.julia/packages/Makie/Ppzqh/src/utilities/utilities.jl:187
  [5] (::Makie.var"#1810#1813"{Base.RefValue{Vector{Int64}}, Observable{Vector{ColorTypes.RGBA{Float32}}}, Observable{Vector{Float32}}, Observable{Vector{Point{2, Float32}}}, Observable{Vector{Makie.Glyph
Collection}}})(str::Symbol, ts::Float64, f::FreeTypeAbstraction.FTFont, al::Tuple{Symbol, Symbol}, rot::Float32, jus::MakieCore.Automatic, lh::Float64, col::ColorTypes.RGBA{Float32}, scol::Tuple{Symbol, F
loat64}, swi::Int64, www::Int64)
    @ Makie ~/.julia/packages/Makie/Ppzqh/src/basic_recipes/text.jl:34
  [6] (::Observables.var"#callback#13"{Makie.var"#1810#1813"{Base.RefValue{Vector{Int64}}, Observable{Vector{ColorTypes.RGBA{Float32}}}, Observable{Vector{Float32}}, Observable{Vector{Point{2, Float32}}},
 Observable{Vector{Makie.GlyphCollection}}}, NTuple{11, Observable{Any}}})(x::Any)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:339
  [7] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
  [8] invokelatest
    @ ./essentials.jl:726 [inlined]
  [9] notify
    @ ~/.julia/packages/Observables/ynr7h/src/Observables.jl:143 [inlined]
 [10] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:86
 [11] (::Observables.var"#8#9"{Observable{Any}})(x::Symbol)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:120
 [12] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [13] invokelatest
    @ ./essentials.jl:726 [inlined]
 [14] notify
    @ ~/.julia/packages/Observables/ynr7h/src/Observables.jl:143 [inlined]
 [15] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:86
 [16] (::Observables.var"#8#9"{Observable{Any}})(x::Symbol)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:120
 [17] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [18] invokelatest
    @ ./essentials.jl:726 [inlined]
 [19] notify
    @ ~/.julia/packages/Observables/ynr7h/src/Observables.jl:143 [inlined]
 [20] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/ynr7h/src/Observables.jl:86
 [21] setproperty!(x::Axis, key::Symbol, value::Symbol)
    @ Makie ~/.julia/packages/Makie/Ppzqh/src/makielayout/blocks.jl:475
 [22] init_abm_data_plots!(fig::Figure, p::ABMObservable{AgentBasedModel{ContinuousSpace{2, false, Float64, typeof(Agents.no_vel_update)}, ToySim.Person, typeof(Agents.Schedulers.fastest), ToySim.ModelPro
perties, Random.TaskLocalRNG}, typeof(dummystep), typeof(ToySim.model_step!), Nothing, Vector{Any}, Nothing, Observable{DataFrames.DataFrame}, Bool}, adata::Nothing, mdata::Vector{Any}, alabels::Nothing,
mlabels::Vector{Any}, plotkwargs::NamedTuple{(), Tuple{}}, stepclick::Observable{Any})
    @ InteractiveDynamics ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/convenience.jl:79
 [23] abmexploration(model::AgentBasedModel{ContinuousSpace{2, false, Float64, typeof(Agents.no_vel_update)}, ToySim.Person, typeof(Agents.Schedulers.fastest), ToySim.ModelProperties, Random.TaskLocalRNG}
; alabels::Nothing, mlabels::Vector{Any}, plotkwargs::NamedTuple{(), Tuple{}}, kwargs::Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:model_step!, :mdata, :static_preplot!), Tuple{typ
eof(ToySim.model_step!), Vector{Any}, typeof(plot_locations!)}}})
    @ InteractiveDynamics ~/.julia/packages/InteractiveDynamics/EThtU/src/agents/convenience.jl:48
 [24] main()
    @ Main ~/Source/ToySim/src/gui.jl:78
 [25] top-level scope
    @ REPL[79]:1
fbanning commented 1 year ago

Ah, thanks for bringing this up. Indeed, passing Symbols should also be allowed.

Please correct me if I'm wrong, but I think the _get_glyphcollection_and_linesegments function that errors is from the Makie stack which seems to allow only for AbstractString or LatexString. Is that correct? 🤔

fbanning commented 1 year ago

I set up a function that takes a type of location, and returns a function that counts the number of agents at that type of location.

On a slightly unrelated sidenote and just out of curiosity because I probably don't fully understand what you're aiming for: Why did you choose to do it like this? It seems to me as if it would be quite easy to define count_at_location(loc, model) = count(a -> a.location == loc, allagents(model)) (or sth along those lines, didn't test it 😆) and use that as the function in your mdata array. Would that do something else than what you want to achieve?

rmellema commented 1 year ago

Please correct me if I'm wrong, but I think the _get_glyphcollection_and_linesegments function that errors is from the Makie stack which seems to allow only for AbstractString or LatexString. Is that correct? 🤔

Seems that way, but I'm just looking at file names to draw that conclusion

It seems to me as if it would be quite easy to define count_at_location(loc, model) = count(a -> a.location == loc, allagents(model)) (or sth along those lines, didn't test it 😆) and use that as the function in your mdata array. Would that do something else than what you want to achieve?

From what I understand from the documentation the mdata functions need to only take the model, and can return only one value (the value to be plotted). So I want to be able to do what you did, but I thought I had to apply partial function application to do that. I used a closure to achieve that.

Datseris commented 1 year ago

yes, that's the expected way to do it if you want muktiuple data plots each for each location. You make closures over the function Fredrik typed for each location you want as a line plot.

I've renamed the issue now to reflect what we need to do.