This is the third part of the tutorial, which uses again the Agents.jl
package for Julia to replicate some of the results of "Mis- and disinformation in a bounded confidence model" .
We start again by load the requisite packages:
using Agents using StatsBase using Distributions using DataFrames using DataFramesMeta using LinearAlgebra using RCall using GLM using Gadfly using LightGraphs using Colors using ColorSchemes using Cairo using Compose
The following define the types of agent that will populate our model. We make sure from the start the model has all the properties to implement the fullest version of the BCU model studied in our paper (i.e., including $\epsilon$-$\alpha$ dynamics). Note in particular that it allows agents to have parameters $\lambda_{\epsilon}$ and $\lambda_{\alpha}$, which regulate how fast they are willing to move away from their current values for $\epsilon$ and $\alpha$ when they come to know the values for those parameters held by their peers. (NB: the field previous opinion
in the agent definitions is needed to have the possibility of running the model till convergence; see the function terminate
below.)
mutable struct TruthSeeker <: AbstractAgent id::Int pos::Int ϵ_old::Float64 ϵ_new::Float64 α_old::Float64 α_new::Float64 λ_ϵ::Float64 λ_α::Float64 old_opinion::Float64 new_opinion::Float64 previous_opinion::Float64 end mutable struct FreeRider <: AbstractAgent id::Int pos::Int ϵ_old::Float64 ϵ_new::Float64 α_old::Float64 α_new::Float64 λ_ϵ::Float64 λ_α::Float64 old_opinion::Float64 new_opinion::Float64 previous_opinion::Float64 end mutable struct Campaigner <: AbstractAgent id::Int pos::Int ϵ_old::Float64 ϵ_new::Float64 α_old::Float64 α_new::Float64 λ_ϵ::Float64 λ_α::Float64 old_opinion::Float64 new_opinion::Float64 previous_opinion::Float64 end
This sets up the model:
function hk_model( τ::Float64, # truth ρ::Float64, # radical opinion ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, λ_ϵ_low::Float64, λ_ϵ_upp::Float64, λ_α_low::Float64, λ_α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int ) n0 = numagents + numfreeriders n = n0 + numcampaigners model = ABM(Union{TruthSeeker,FreeRider,Campaigner}, GraphSpace(complete_digraph(n)), scheduler = fastest, properties = Dict(:τ=>τ); warn = false) for i in 1 : numagents e = !(ϵ_low < ϵ_upp) ? ϵ_low : rand(Uniform(ϵ_low, ϵ_upp)) a = !(α_low < α_upp) ? α_low : rand(Uniform(α_low, α_upp)) l1 = !(λ_ϵ_low < λ_ϵ_upp) ? λ_ϵ_low : rand(Uniform(λ_ϵ_low, λ_ϵ_upp)) l2 = !(λ_α_low < λ_α_upp) ? λ_α_low : rand(Uniform(λ_α_low, λ_α_upp)) o = rand() add_agent_single!(TruthSeeker(i, i, e, e, a, a, l1, l2, o, o, -1), model) end for i in numagents + 1 : n0 e = !(ϵ_low < ϵ_upp) ? ϵ_low : rand(Uniform(ϵ_low, ϵ_upp)) a = !(α_low < α_upp) ? α_low : rand(Uniform(α_low, α_upp)) l = !(λ_ϵ_low < λ_ϵ_upp) ? λ_ϵ_low : rand(Uniform(λ_ϵ_low, λ_ϵ_upp)) o = rand() add_agent_single!(FreeRider(i, i, e, e, 0, 0, l, 0, o, o, -1), model) end for i in n0 + 1 : n add_agent_single!(Campaigner(i, i, 0, 0, 0, 0, 0, 0, ρ, ρ, -1), model) end return model end
The functions below define the BCI, selecting the opinions of the agents in that interval or – for the $\epsilon$-$\alpha$ dynamics – the $\epsilon$ values and $\alpha$ values:
function boundfilter(agent, model) filter(j->isapprox(agent.old_opinion, j; atol=agent.ϵ_old), [ a.old_opinion for a in allagents(model) ]) end function boundfilter_eps(agent, model) ind = findall(j->isapprox(agent.old_opinion, j; atol=agent.ϵ_old), [ a.old_opinion for a in allagents(model) ]) return [ a.ϵ_old for a in allagents(model) ][ind] end function boundfilter_alp(agent, model) ind = findall(j->isapprox(agent.old_opinion, j; atol=agent.ϵ_old), [ a.old_opinion for a in allagents(model) ]) return [ a.α_old for a in allagents(model) ][ind] end
The following functions define the updating:
function agent_static_step!(agent::TruthSeeker, model) agent.new_opinion = agent.α_old*model.τ + (1 - agent.α_old)*mean(boundfilter(agent, model)) agent.previous_opinion = agent.old_opinion end function agent_static_step!(agent::FreeRider, model) agent.new_opinion = mean(boundfilter(agent, model)) agent.previous_opinion = agent.old_opinion end function agent_static_step!(agent::Campaigner, model) agent.new_opinion = agent.old_opinion agent.previous_opinion = agent.old_opinion end function agent_dynamic_step!(agent::TruthSeeker, model) agent.new_opinion = agent.α_old*model.τ + (1 - agent.α_old)*mean(boundfilter(agent, model)) agent.ϵ_new = agent.λ_ϵ*agent.ϵ_old + (1 - agent.λ_ϵ)*mean(boundfilter_eps(agent, model)) agent.α_new = agent.λ_α*agent.α_old + (1 - agent.λ_α)*mean(boundfilter_alp(agent, model)) agent.previous_opinion = agent.old_opinion end function agent_dynamic_step!(agent::FreeRider, model) agent.new_opinion = mean(boundfilter(agent, model)) agent.ϵ_new = agent.λ_ϵ*agent.ϵ_old + (1 - agent.λ_ϵ)*mean(boundfilter_eps(agent, model)) agent.α_new = agent.α_old agent.previous_opinion = agent.old_opinion end function agent_dynamic_step!(agent::Campaigner, model) agent.old_opinion = agent.old_opinion agent.ϵ_new = agent.ϵ_old agent.α_new = agent.α_old agent.previous_opinion = agent.old_opinion end
Helper function to make the model as a whole evolve:
function model_step!(model) for a in allagents(model) a.old_opinion = a.new_opinion a.ϵ_old = a.ϵ_new a.α_old = a.α_new end end
Helper function for determining when convergence has been reached:
function terminate(model, s) if any(!isapprox(a.previous_opinion, a.new_opinion; atol=1e-5) for a in allagents(model)) return false else return true end end
To run the model, we need four methods, to run with and without $\epsilon$-$\alpha$ dynamics, and to run with and without a predetermined number of iterations ("without" means "running till convergence"):
function model_run( # without ϵ-α dynamcis τ::Float64, ρ::Float64, ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int, iterations ) model = hk_model(τ, ρ, ϵ_low, ϵ_upp, α_low, α_upp, 1., 1., 1., 1., numagents, numfreeriders, numcampaigners) adata = [:new_opinion, typeof] agent_data, _ = run!( model, agent_static_step!, model_step!, iterations; adata = adata ) return agent_data end function model_run( # without ϵ-α dynamcis τ::Float64, ρ::Float64, ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int; when = true ) model = hk_model(τ, ρ, ϵ_low, ϵ_upp, α_low, α_upp, 1., 1., 1., 1., numagents, numfreeriders, numcampaigners) adata = [:new_opinion, typeof] agent_data, _ = run!( model, agent_static_step!, model_step!, terminate; adata = adata, when = when ) return agent_data end function model_run( # with ϵ-α dynamcis τ::Float64, ρ::Float64, ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, λ_ϵ_low::Float64, λ_ϵ_upp::Float64, λ_α_low::Float64, λ_α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int, iterations::Int ) model = hk_model(τ, ρ, ϵ_low, ϵ_upp, α_low, α_upp, λ_ϵ_low, λ_ϵ_upp, λ_α_low, λ_α_upp, numagents, numfreeriders, numcampaigners) adata = [:new_opinion, :λ_ϵ, :λ_α, typeof] agent_data, _ = run!( model, agent_dynamic_step!, model_step!, iterations; adata = adata ) return agent_data end function model_run( # with ϵ-α dynamcis τ::Float64, ρ::Float64, ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, λ_ϵ_low::Float64, λ_ϵ_upp::Float64, λ_α_low::Float64, λ_α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int; when = true ) model = hk_model(τ, ρ, ϵ_low, ϵ_upp, α_low, α_upp, λ_ϵ_low, λ_ϵ_upp, λ_α_low, λ_α_upp, numagents, numfreeriders, numcampaigners) adata = [:new_opinion, :λ_ϵ, :λ_α, typeof] agent_data, _ = run!( model, agent_dynamic_step!, model_step!, terminate; adata = adata, when = when ) return agent_data end
Here is a run with the static model, including free riders and campaigners:
data = model_run(.7, .9, .2, .2, .5, .5, 15, 5, 5) palette = [colorant"darksalmon", colorant"gold", colorant"darkseagreen"] Gadfly.plot(data, x=:step, y=:new_opinion, group=:id, color=:typeof, Geom.point, Geom.line, Scale.color_discrete_manual(palette..., order=[3, 2, 1]), Coord.cartesian(xmax=maximum(data.step)), Guide.xlabel("Update"), Guide.ylabel("Opinion"), Guide.colorkey(title="Group", labels=["Campaigners", "Free riders", "Truth seekers"]), yintercept=[.7], Geom.hline(style=:dot, color=colorant"grey"), Guide.annotation(compose(context(), Compose.text(maximum(data.step) - 1, 0.685, "τ"), fontsize(11pt))))
Functions to calculate the average proportion of truth seekers that get converted by the campaigners (settings as in the paper).
function perc_conv(nmb_cmp, ϵ, τ, ρ) data = model_run(τ, ρ, ϵ, ϵ, 0., 0., 50, 0, nmb_cmp; when = terminate) dat_sel = @where(data, :typeof.==TruthSeeker) return sum(map(x->isapprox(x, ρ; atol=1e-3), dat_sel.new_opinion))/50 end function perc_sim(nmb_cmp, ϵ, τ, ρ) return [ perc_conv(nmb_cmp, ϵ, τ, ρ) for _ in 1:100 ] |> mean end perc_mat = Matrix{Float64}(undef, 50, 50) eps_vals = .01:.01:.5 cmp_nmbs = 1:50 ρ = .8 for i in 1:50, j in 1:50 e = eps_vals[j] c = cmp_nmbs[i] perc_mat[j, i] = perc_sim(c, e, .0, ρ) # note that because α = 0, the value of τ doesn't matter end
First some helper functions to create the plots:
function xlabelname(x) n = x/100 return "$n" end function ylabelname(x) n = abs(x - 50) return "$n" end ticks = collect(0:10:50)
Plotting the outcome (left panel of Figure 2):
Gadfly.spy(rotl90(perc_mat * 50), Guide.ColorKey(title="Converted\ntruth seekers"), Guide.xlabel("ϵ"), Guide.ylabel("Campaigners"), Guide.title("ρ = $ρ"), # given that alpha is zero, the value of tau doesn't matter here Guide.xticks(ticks=ticks), Scale.x_continuous(labels=xlabelname), Guide.yticks(ticks=ticks), Scale.y_continuous(labels=ylabelname), Scale.ContinuousColorScale(p -> get(ColorSchemes.viridis, p), minvalue=0, maxvalue=50), Theme(grid_color=colorant"white", panel_fill="white"))
Functions to calculate the average proportion of truth seekers that actually end up believing the truth (for Figure 3).
function perc_truth(nmb_cmp, nmb_free, ϵ, α, τ, ρ) data = model_run(τ, ρ, ϵ, ϵ, α, α, 50, nmb_free, nmb_cmp; when = terminate) dat_sel = @where(data, :typeof.==TruthSeeker) return sum(map(x->isapprox(x, τ; atol=1e-3), dat_sel.new_opinion))/50 end function truth_sim(nmb_cmp, nmb_free, ϵ, α, τ, ρ) return [ perc_truth(nmb_cmp, nmb_free, ϵ, α, τ, ρ) for _ in 1:100 ] |> mean end truth_mat = Matrix{Float64}(undef, 50, 50) τ = .1 ρ = .3 α = .5 for i in 1:50, j in 1:50 e = eps_vals[j] c = cmp_nmbs[i] truth_mat[j, i] = truth_sim(c, 0, e, α, τ, ρ) end Gadfly.spy(rotl90(truth_mat * 50), Guide.ColorKey(title="Truth\nseekers\nbelieving τ"), Guide.xlabel("ϵ"), Guide.ylabel("Campaigners"), Guide.title(" τ = $τ, ρ = $ρ, α = $α"), Guide.xticks(ticks=ticks), Scale.x_continuous(labels=xlabelname), Guide.yticks(ticks=ticks), Scale.y_continuous(labels=ylabelname), Scale.ContinuousColorScale(p -> get(ColorSchemes.viridis, p), minvalue=0, maxvalue=50), Theme(grid_color=colorant"white", panel_fill="white"))
Now we add 10 free riders and look at the difference that makes (see Figure 4):
truth_mat_free = Matrix{Float64}(undef, 50, 50) for i in 1:50, j in 1:50 e = eps_vals[j] c = cmp_nmbs[i] truth_mat_free[j, i] = truth_sim(c, 10, e, α, τ, ρ) end Gadfly.spy(rotl90((truth_mat - truth_mat_free) * 50), Guide.xlabel("ϵ"), Guide.ylabel("Campaigners"), Guide.ColorKey(title="Difference"), Guide.title(" τ = $τ, ρ = $ρ, α = $α, #FR = 10"), Guide.xticks(ticks=ticks), Scale.x_continuous(labels=xlabelname), Guide.yticks(ticks=ticks), Scale.y_continuous(labels=ylabelname), Scale.ContinuousColorScale(p -> get(ColorSchemes.viridis, p), minvalue=-50, maxvalue=50), Theme(grid_color=colorant"white", panel_fill="white"))
We are now going to look at the damage (in terms of loss of accuracy) inflicted upon truth seekers by the presence of campaigners in their community.
This will be easier if we add a function to model_run
to measure squared distance from τ for each agent.
function model_run_se( # without ϵ-α dynamcis τ::Float64, ρ::Float64, ϵ_low::Float64, ϵ_upp::Float64, α_low::Float64, α_upp::Float64, numagents::Int, numfreeriders::Int, numcampaigners::Int, iterations::Int ) model = hk_model(τ, ρ, ϵ_low, ϵ_upp, α_low, α_upp, 1., 1., 1., 1., numagents, numfreeriders, numcampaigners) dst(agent) = (agent.new_opinion - τ)^2 adata = [:new_opinion, typeof, dst] agent_data, _ = run!( model, agent_static_step!, model_step!, iterations; adata = adata ) return agent_data end function cost_sim(nmb_cmp, nmb_free, ϵ, α, ρ) data = model_run_se(.1, ρ, ϵ, ϵ, α, α, 50, nmb_free, nmb_cmp, 50) dat_sel = @where(data, :typeof.==TruthSeeker) return sum(dat_sel.dst) end wo = [ cost_sim(0, 0, .2, .5, .0) for _ in 1:1000 ] wn = [ cost_sim(25, 0, .2, .5, .3) for _ in 1:1000 ] wm = [ cost_sim(25, 0, .2, .5, .5) for _ in 1:1000 ] ww = [ cost_sim(25, 0, .2, .5, .7) for _ in 1:1000 ]
Visualization (corresponding to Figure 5)
mns = DataFrame(WO = wo, WN = wn, WM = wm, WW = ww) mns = stack(mns) rename!(mns, [:Condition, :SSE]) Gadfly.plot(mns, x=:Condition, y=:SSE, Geom.boxplot, Guide.title("τ = 0.1, ρ = 0.3/0.5/0.7 (WN/WM/WW), ϵ = 0.2, α = 0.5"), Theme(default_color=colorant"#66c2a5", panel_fill="white"))
ANOVA
data = vcat(wo, wn, wm, ww) groups = categorical(repeat(1:4, inner=1000)) df_aov = DataFrame(data = data, groups = groups) @rput df_aov R""" m <- aov(data ~ groups, df_aov) summary(m) """
RCall.RObject{RCall.VecSxp} Df Sum Sq Mean Sq F value Pr(>F) groups 3 24686 8229 1603 <2e-16 *** Residuals 3996 20512 5 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
R"TukeyHSD(m)"
RCall.RObject{RCall.VecSxp} Tukey multiple comparisons of means 95% family-wise confidence level Fit: aov(formula = data ~ groups, data = df_aov) $groups diff lwr upr p adj 2-1 5.78920515 5.52879209 6.0496182 0.0000000 3-1 0.27260372 0.01219067 0.5330168 0.0360512 4-1 -0.09140343 -0.35181648 0.1690096 0.8037217 3-2 -5.51660142 -5.77701448 -5.2561884 0.0000000 4-2 -5.88060857 -6.14102163 -5.6201955 0.0000000 4-3 -0.36400715 -0.62442021 -0.1035941 0.0018811
Basically the same as above but now with varying numbers of free riders
fr0 = [ cost_sim(15, 0, .2, .5, .3) for _ in 1:1000 ] fr10 = [ cost_sim(15, 10, .2, .5, .3) for _ in 1:1000 ] fr25 = [ cost_sim(15, 25, .2, .5, .3) for _ in 1:1000 ] fr50 = [ cost_sim(15, 50, .2, .5, .3) for _ in 1:1000 ]
Visualization (corresponding to Figure 6)
mns = DataFrame(X = fr0, Y = fr10, Z = fr25, W = fr50) rename!(mns, :X => Symbol("0"), :Y => Symbol("10"), :Z => Symbol("25"), :W => Symbol("50")) mns = stack(mns) rename!(mns, [:FR, :SSE]) Gadfly.plot(mns, x=:FR, y=:SSE, Geom.boxplot, Guide.xlabel("Free riders"), Guide.title("τ = 0.1, ρ = 0.3, ϵ = 0.2, α = 0.5"), Theme(default_color=colorant"#66c2a5", panel_fill="white"))
ANOVA
data = vcat(fr0, fr10, fr25, fr50) groups = categorical(repeat(1:4, inner=1000)) df_aov = DataFrame(data = data, groups = groups) @rput df_aov R""" m <- aov(data ~ groups, df_aov) summary(m) """
RCall.RObject{RCall.VecSxp} Df Sum Sq Mean Sq F value Pr(>F) groups 3 74 24.623 4.893 0.00214 ** Residuals 3996 20110 5.033 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
The same with slightly different settings (see Figure 7)
fr0 = [ cost_sim(15, 0, .3, .25, .3) for _ in 1:1000 ] fr10 = [ cost_sim(15, 10, .3, .25, .3) for _ in 1:1000 ] fr25 = [ cost_sim(15, 25, .3, .25, .3) for _ in 1:1000 ] fr50 = [ cost_sim(15, 50, .3, .25, .3) for _ in 1:1000 ];
Visualization
mns = DataFrame(X = fr0, Y = fr10, Z = fr25, W = fr50) rename!(mns, :X => Symbol("0"), :Y => Symbol("10"), :Z => Symbol("25"), :W => Symbol("50")) mns = stack(mns) rename!(mns, [:FR, :SSE]) Gadfly.plot(mns, x=:FR, y=:SSE, Geom.boxplot, Guide.xlabel("Free riders"), Guide.title("τ = 0.1, ρ = 0.3, ϵ = 0.3, α = 0.25"), Theme(default_color=colorant"#66c2a5", panel_fill="white"))
ANOVA
data = vcat(fr0, fr10, fr25, fr50) groups = categorical(repeat(1:4, inner=1000)) df_aov = DataFrame(data = data, groups = groups) @rput df_aov R""" m <- aov(data ~ groups, df_aov) summary(m) """
RCall.RObject{RCall.VecSxp} Df Sum Sq Mean Sq F value Pr(>F) groups 3 15336 5112 365.8 <2e-16 *** Residuals 3996 55846 14 --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
R"TukeyHSD(m)"
RCall.RObject{RCall.VecSxp} Tukey multiple comparisons of means 95% family-wise confidence level Fit: aov(formula = data ~ groups, data = df_aov) $groups diff lwr upr p adj 2-1 1.042752 0.6130666 1.472436 0 3-1 2.840690 2.4110053 3.270375 0 4-1 5.159952 4.7302668 5.589637 0 3-2 1.797939 1.3682538 2.227624 0 4-2 4.117200 3.6875153 4.546885 0 4-3 2.319262 1.8895766 2.748947 0
Produce Figures 8 and 9
data = model_run(.7, .0, .25, .25, .25, .25, 50, 0, 0) Gadfly.plot(data, x=:step, y=:new_opinion, group=:id, color=:id, Geom.point, Geom.line, Coord.cartesian(xmax=maximum(data.step)), Guide.xlabel("Update"), Guide.ylabel("Opinion"), Guide.title("τ = 0.7, ϵ = 0.25, α = 0.25"), yintercept=[.7], Geom.hline(style=:dot, color=colorant"grey"), Guide.annotation(compose(context(), Compose.text(maximum(data.step) - 3, 0.715, "τ"), fontsize(11pt))), Theme(key_position=:none))
data = model_run(.7, .0, .0, .5, .25, .25, .0, .0, .0, .0, 50, 0, 0) Gadfly.plot(data, x=:step, y=:new_opinion, group=:id, color=:id, Geom.point, Geom.line, Coord.cartesian(xmax=maximum(data.step)), Guide.xlabel("Update"), Guide.ylabel("Opinion"), Guide.title("τ = 0.7, ϵ⁰ ∼ U(0, .5), α = 0.25"), yintercept=[.7], Geom.hline(style=:dot, color=colorant"grey"), Guide.annotation(compose(context(), Compose.text(maximum(data.step) - 3, 0.715, "τ"), fontsize(11pt))), Theme(key_position=:none))
data = model_run(.65, .9, .25, .25, .75, .75, 25, 20, 10) Gadfly.plot(data, x=:step, y=:new_opinion, group=:id, color=:typeof, Geom.point, Geom.line, Scale.color_discrete_manual(palette..., order=[3, 2, 1]), Coord.cartesian(xmax=maximum(data.step)), Guide.xlabel("Update"), Guide.ylabel("Opinion"), Guide.title("τ = 0.65, ρ = 0.9, ϵ = 0.25, α = 0.75"), Guide.colorkey(title="Group", labels=["Campaigners", "Free riders", "Truth seekers"]), yintercept=[.65], Geom.hline(style=:dot, color=colorant"grey"), Guide.annotation(compose(context(), Compose.text(maximum(data.step) - 1, 0.635, "τ"), fontsize(11pt))))
data = model_run(.65, .9, .0, .5, .75, .75, .0, .0, .0, .0, 25, 20, 10) Gadfly.plot(data, x=:step, y=:new_opinion, group=:id, color=:typeof, Geom.point, Geom.line, Scale.color_discrete_manual(palette..., order=[3, 2, 1]), Coord.cartesian(xmax=maximum(data.step)), Guide.xlabel("Update"), Guide.ylabel("Opinion"), Guide.title("τ = 0.65, ρ = 0.9, ϵ⁰ ∼ U(0, .5), α = 0.75"), Guide.colorkey(title="Group", labels=["Campaigners", "Free riders", "Truth seekers"]), yintercept=[.65], Geom.hline(style=:dot, color=colorant"grey"), Guide.annotation(compose(context(), Compose.text(maximum(data.step) - 3, 0.635, "τ"), fontsize(11pt))))
To reproduce Figures 10 and 11, some functions to calculate the average proportion of truth seekers that get converted by the campaigners (settings as in the paper).
function perc_conv_ts(nmb_cmp, nmb_free, ϵ_low, ϵ_upp, τ, ρ) data = model_run(τ, ρ, ϵ_low, ϵ_upp, .5, .5, 50, nmb_free, nmb_cmp; when = terminate) dat_sel = @where(data, :typeof.==TruthSeeker) return sum(map(x->isapprox(x, τ; atol=1e-3), dat_sel.new_opinion))/50 end function perc_sim_ts(nmb_cmp, nmb_free, ϵ_low, ϵ_upp, τ, ρ) return [ perc_conv_ts(nmb_cmp, nmb_free, ϵ_low, ϵ_upp, τ, ρ) for _ in 1:100 ] |> mean end perc_mat = Matrix{Float64}(undef, 50, 50) τ = .65 ρ = .75 for i in 1:50, j in 1:50 perc_mat[j, i] = perc_sim_ts(j, i, .1, .1, τ, ρ) end Gadfly.spy(rotl90(perc_mat * 50), Guide.ColorKey(title="Truth seekers\nbelieving τ"), Guide.xlabel("Campaigners"), Guide.ylabel("Free riders"), Guide.title("τ = $τ, ρ = $ρ, ϵ = 0.1, α = 0.5"), # given that alpha is zero, the value of tau doesn't matter here Guide.xticks(ticks=ticks), Scale.x_continuous(labels=xlabelname), Guide.yticks(ticks=ticks), Scale.y_continuous(labels=ylabelname), Scale.ContinuousColorScale(p -> get(ColorSchemes.viridis, p), minvalue=0, maxvalue=50), Theme(grid_color=colorant"white", panel_fill="white"))