In this example, we will show how EcologicalNetworks.jl can be integrated with Mangal.jl to analyse many ecological networks. Specifically, we will show how to analyse the association between meaningful network properties (i.e. connectance, nestedness, and modularity) using all food webs archived on the mangal.io online database.

To conduct this analysis, we need to upload the following packages:

using EcologicalNetworks
using Mangal
using DataFrames
using Plots
using Plots.PlotMeasures

We first retrieve relevant metadata for all 1,386 networks archived on mangal.io using the Mangal.jl package. We count the number of species $S$ and the total number of interactions $L$ in each network, as well as their number of trophic interactions (predation and herbivory). We store these information in a data frame along with the networks' ID numbers, and print the first 5 elements. Due to the high number of networks we handle, note that this step might take some time to run.

number_of_networks = count(MangalNetwork)
count_per_page = 100
number_of_pages = convert(Int, ceil(number_of_networks/count_per_page))

mangal_networks = DataFrame(fill(Int64, 5),
                 [:id, :S, :L, :pred, :herb],
                 number_of_networks)

global cursor = 1
for page in 1:number_of_pages
    global cursor
    networks_in_page = Mangal.networks("count" => count_per_page, "page" => page-1)
    for current_network in networks_in_page
        S = count(MangalNode, current_network)
        L = count(MangalInteraction, current_network)
        pred = count(MangalInteraction, current_network, "type" => "predation")
        herb = count(MangalInteraction, current_network, "type" => "herbivory")
        mangal_networks[cursor,:] .= (current_network.id, S, L, pred, herb)
        cursor = cursor + 1
    end
end

first(mangal_networks, 5)

5 rows × 5 columns

idSLpredherb
Int64Int64Int64Int64Int64
11915001525500
21211518400
313549200
418293800
515317112100

We now have all the information we need to identify all food webs archived on mangal.io. Here we consider as food webs any ecological networks mainly composed of trophic interactions. We find that 259 networks meet this condition.

foodwebs = mangal_networks[mangal_networks[!, :pred] .+ mangal_networks[!, :herb] ./ mangal_networks[!, :L] .> 0.5, :]

first(foodwebs, 5)

5 rows × 5 columns

idSLpredherb
Int64Int64Int64Int64Int64
1325920604119
2262434257
3281838288
494719584513
5126226601444

To analyse their properties, we first need to read all of these food webs using the Mangal.jl package, and then convert them to UnipartiteNetworks using the EcologicalNetworks.jl package.

mangal_foodwebs = network.(foodwebs.id)

unipartite_foodwebs = convert.(UnipartiteNetwork, mangal_foodwebs)

unipartite_foodwebs[1:5]
5-element Array{UnipartiteNetwork{Bool,Mangal.MangalNode},1}:
 20×20 (Mangal.MangalNode) unipartite ecological network (L: 60 - Bool)
 24×24 (Mangal.MangalNode) unipartite ecological network (L: 34 - Bool)
 18×18 (Mangal.MangalNode) unipartite ecological network (L: 38 - Bool)
 19×19 (Mangal.MangalNode) unipartite ecological network (L: 58 - Bool)
 26×26 (Mangal.MangalNode) unipartite ecological network (L: 60 - Bool)

We can then compute any measure supported by EcologicalNetworks.jl for UnipartiteNetworks. In this example, we compute connectance, nestedness, and modularity. To compute network modularity, we use 100 random species assignments in 3 to 15 groups as our starters, the BRIM algorithm to optimize the modularity for each of these random partitions, and retain the maximum value for each food web. Readers are invited to take a look at the documentation for further details on how to compute modularity.

foodweb_measures = DataFrame(fill(Float64, 3),
                 [:connect, :nested, :modul],
                 length(unipartite_foodwebs))

# connectance
foodweb_measures.connect = connectance.(unipartite_foodwebs)

# nestedness
foodweb_measures.nested = ρ.(unipartite_foodwebs)

# modularity (BRIM algorithm)
number_of_modules = repeat(3:15, outer=100)
modules = Array{Dict}(undef, length(number_of_modules))

for i in eachindex(unipartite_foodwebs)
    current_network = unipartite_foodwebs[i]
    for j in eachindex(number_of_modules)
        _, modules[j] = n_random_modules(number_of_modules[j])(current_network) |> x -> brim(x...)
    end
    partition_modularity = map(x -> Q(current_network,x), modules);
    foodweb_measures.modul[i] = maximum(partition_modularity)
end

first(foodweb_measures, 5)

5 rows × 3 columns

connectnestedmodul
Float64Float64Float64
10.150.6367550.0466667
20.05902780.4300890.183391
30.1172840.5439760.0637119
40.1606650.6259020.209275
50.08875740.5795680.153889

The association between these food-web measures can then be plotted. We find that modularity is negatively associated with connectance and nestedness, whereas nestesdness and connectance are positively associated.

pal=RGB(204/255,121/255,167/255)

scatter(foodweb_measures.connect, foodweb_measures.nested,
    alpha=0.6, color=pal,
    lab="", framestyle=:box,
    xlabel="Connectance",
    ylabel="Nestedness",
    margin = 10mm)
scatter(foodweb_measures.connect, foodweb_measures.modul,
    alpha=0.6, color=pal,
    lab="",framestyle=:box,
    xlabel="Connectance",
    ylabel="Modularity",
    margin = 10mm)
scatter(foodweb_measures.modul, foodweb_measures.nested,
    alpha=0.6, color=pal,
    lab="", framestyle=:box,
    xlabel="Modularity",
    ylabel="Nestedness",
    margin = 10mm)