Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unexpected behaviour when a layer has no intralayer links #3

Closed
garciacallejas opened this issue Jan 8, 2025 · 4 comments
Closed
Assignees
Labels
bug Something isn't working
Milestone

Comments

@garciacallejas
Copy link

garciacallejas commented Jan 8, 2025

I am generating a series of in silico multilayer networks. It may be the case that in a given network, one or more layers will have nodes with valid interlayer links but no intralayer links. This would be coded in interlayer links appearing in the dataframe passed to the interlayer_links argument of create_multilayer_network, but that layer not appearing as an element of the list passed to list_of_layers. Note that that list is named, so the function should be able to identify which layers have valid intralayer links or not.

This, however, generates a wrong multilayer, whereby it creates intralayer links for the layer that should have none. I think it takes the elements of the list of intralayer links sequentially, without discarding the layers not present in that list. So, if I have three layers, where layer 2 has no intralayer links, that layer will not be present in the intralayer links list. But the function will assume that it is, assigning the intralayer links of layer 3 to layer 2 and leaving intralayer 3 empty.

Below is a minimal example to reproduce the behaviour. Thank you for your work on the package!
David

# multilayer minimal example

library(emln)
library(tidyverse)

# -------------------------------------------------------------------------
num.sp <- 3
sp.names <- paste("sp",sprintf("%02d", 1:num.sp),sep="")

num.patches <- 3
patch.names <- paste("patch",sprintf("%02d", 1:num.patches),sep="")

# -------------------------------------------------------------------------
# layers: species
# nodes: patches

# total number of interlayer links
non.zero.interlayer.links <- 20

# intralayer links per sp
non.zero.intralayer.links <- 2

# some species will not have intralayer links
sp.without.intra <- 2
sp.without.intra.names <- sp.names[sp.without.intra]

# -------------------------------------------------------------------------
# layer attributes
layer_attributes <- tibble(layer_id = 1:num.sp, layer_name = sp.names)

# -------------------------------------------------------------------------
# interlayer links in a dataframe

my.interlayer.df <- expand_grid(layer_from = sp.names,node_from = patch.names,layer_to = sp.names, node_to = patch.names) %>%
  subset(layer_from != layer_to)

my.interlayer.df$weight <- 0
my.interlayer.df$weight[sample(nrow(my.interlayer.df),size = non.zero.interlayer.links,replace = F)] <- runif(non.zero.interlayer.links,0.1,0.5)

# -------------------------------------------------------------------------
# intralayer links in a list

my.intralayer.list <- list()
my.intralayer.names <- NULL
for(i.sp in 1:length(sp.names)){
  
  # only assign intralayer links to certain species
  if(!i.sp %in% sp.without.intra){
  my.intra.df <- expand_grid(from = patch.names, to = patch.names) %>%
    subset(from != to)
  my.intra.df$weight <- NA
  my.intra.df$weight[sample(nrow(my.intra.df),size = non.zero.intralayer.links,replace = F)] <- runif(non.zero.intralayer.links,0.1,0.5)
  
  my.intra.df.clean <- subset(my.intra.df, !is.na(weight))
  my.intralayer.list[[length(my.intralayer.list)+1]] <- my.intra.df.clean
  my.intralayer.names[length(my.intralayer.names)+1] <- sp.names[i.sp]
  }# if !sp in sp.without.intra
}# for i.sp

names(my.intralayer.list) <- my.intralayer.names

# -------------------------------------------------------------------------

test.multilayer <- NA
try(test.multilayer <- create_multilayer_network(list_of_layers = my.intralayer.list,
                                                 interlayer_links = my.interlayer.df,
                                                 layer_attributes = layer_attributes,
                                                 bipartite = F,
                                                 directed = T))
multilayer.link.list <- test.multilayer$extended

no.intra.sp.links <- subset(multilayer.link.list, layer_from == sp.without.intra.names & layer_to == sp.without.intra.names)

# check that no.intra.sp.links should be empty, but it is not.
@shainova shainova added the bug Something isn't working label Jan 13, 2025
@shainova shainova added this to the In progress milestone Jan 13, 2025
@shainova
Copy link
Member

@garciacallejas Thank you for finding this. We are on it

@Geutg
Copy link
Member

Geutg commented Jan 19, 2025

@garciacallejas Thank you for finding the issue and helping us improve the package.
version 1.0.2 now support layers without intralayer edges.
please note when creating the multilayer object that it requires a list_of_layers, so all layers should be present in it.
edgelists of empty layers will just be the same dataframe, but with 0 rows.

@Geutg Geutg closed this as completed Jan 19, 2025
@garciacallejas
Copy link
Author

Thank you, @Geutg! I can confirm it works now, with a caveat: at least when there is one or more empty layers, the list_of_layers needs all layers to be tibbles, not standard data frames. That's ok, but perhaps you can mention that explicitly in the documentation. Feel free to reuse the following code to check it out:

# multilayer minimal example

library(emln)
library(tidyverse)

# -------------------------------------------------------------------------
num.sp <- 3
sp.names <- paste("sp",sprintf("%02d", 1:num.sp),sep="")

num.patches <- 3
patch.names <- paste("patch",sprintf("%02d", 1:num.patches),sep="")

# -------------------------------------------------------------------------
# layers: species
# nodes: patches

# total number of interlayer links
non.zero.interlayer.links <- 20

# intralayer links per sp
non.zero.intralayer.links <- 2

# some species will not have intralayer links
sp.without.intra <- 2
sp.without.intra.names <- sp.names[sp.without.intra]

# -------------------------------------------------------------------------
# layer attributes
layer_attributes <- tibble(layer_id = 1:num.sp, layer_name = sp.names)

# -------------------------------------------------------------------------
# interlayer links in a dataframe

my.interlayer.df <- expand_grid(layer_from = sp.names,node_from = patch.names,layer_to = sp.names, node_to = patch.names) %>%
  subset(layer_from != layer_to)

my.interlayer.df$weight <- 0
my.interlayer.df$weight[sample(nrow(my.interlayer.df),size = non.zero.interlayer.links,replace = F)] <- runif(non.zero.interlayer.links,0.1,0.5)

# -------------------------------------------------------------------------
# intralayer links in a list

my.intralayer.list <- list()
my.intralayer.names <- NULL
for(i.sp in 1:length(sp.names)){
  
  # only assign intralayer links to certain species
  if(!i.sp %in% sp.without.intra){
    # with standard dataframes will give error
    # my.intra.df <- expand.grid(from = patch.names, to = patch.names) %>%
    # but with tibble it works
    my.intra.df <- expand_grid(from = patch.names, to = patch.names) %>%
      subset(from != to)
    my.intra.df$weight <- NA
    my.intra.df$weight[sample(nrow(my.intra.df),size = non.zero.intralayer.links,replace = F)] <- runif(non.zero.intralayer.links,0.1,0.5)
    
    my.intra.df.clean <- subset(my.intra.df, !is.na(weight))
    my.intralayer.list[[length(my.intralayer.list)+1]] <- my.intra.df.clean
    my.intralayer.names[length(my.intralayer.names)+1] <- sp.names[i.sp]
  }else{
    # with standard data frame will give error
    # my.intralayer.list[[length(my.intralayer.list)+1]] <- data.frame(from = character(), to = character(), weight = numeric())
    # but with a tibble it works
    my.intralayer.list[[length(my.intralayer.list)+1]] <- tibble(from = character(), to = character(), weight = numeric())
    my.intralayer.names[length(my.intralayer.names)+1] <- sp.names[i.sp]
  }
}# for i.sp

names(my.intralayer.list) <- my.intralayer.names

# -------------------------------------------------------------------------

test.multilayer <- NA
try(test.multilayer <- create_multilayer_network(list_of_layers = my.intralayer.list,
                                                 interlayer_links = my.interlayer.df,
                                                 layer_attributes = layer_attributes,
                                                 bipartite = F,
                                                 directed = T))
multilayer.link.list <- test.multilayer$extended

no.intra.sp.links <- subset(multilayer.link.list, layer_from == sp.without.intra.names & layer_to == sp.without.intra.names)

# check that no.intra.sp.links should be empty

@Geutg
Copy link
Member

Geutg commented Jan 21, 2025

@garciacallejas you are correct, thank you for the useful feedback.
we will make sure to improve this point in the next version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants