5  Module Identification

This chapter describes Step 4 of the FeatureMAP pipeline: identifying functional modules from the PCSF-derived subnetwork using identify_module().

Important

Prerequisites: You need a featuremap_result object from Subnetwork Extraction, with stage "subnetwork_extracted".

5.1 What identify_module() Does

identify_module() performs community detection on the extracted subnetwork and then evaluates module significance using permutation testing.

At a high level, this step performs the following operations:

  1. Construct a module detection graph: A metabolite-only subgraph is derived from the PCSF subnetwork. Metabolite nodes with low occurrence frequency across repeated PCSF runs are filtered out using node_freq. Nodes directly linked to observed features are labelled annotated; the remaining ones are labelled hidden.

  2. Adjust edge weights: Edge weights are taken from the PCSF edge frequency and divided by the square root of the product of the two endpoint degrees in the full MRN, so that highly connected hub metabolites do not dominate the partition.

  3. Detect network communities: One of four community detection algorithms partitions the network into candidate modules, which are then relabelled as compact IDs "M1", "M2", …

  4. Prune module boundaries: Within each module, non-annotated terminal nodes are iteratively removed. Nodes dropped this way are kept in the table but flagged with Use_for_annotation = FALSE, so they no longer influence Step 5.

  5. Compute module activity scores: Module scores summarize the statistical signal from features mapped to metabolites within the module.

  6. Assess statistical significance: A permutation procedure with a gamma null model estimates empirical significance and controls the false discovery rate.

This step converts the extracted metabolic network into functional modules supported by both network topology and feature-level statistical evidence.


5.2 Key Parameters

Parameter Default Description
res required featuremap_result returned by extract_subnetwork()
method "louvain" Community detection algorithm
min_size 3 Minimum module size retained
max_size 100000 Maximum module size retained
n_perm 100 Number of permutations for significance testing
seed 123 Random seed for reproducibility
workers 4 Parallel workers for permutation testing
node_freq 0.05 Frequency threshold for retaining metabolite nodes
louvain_resolution 1 Resolution parameter for Louvain clustering
walktrap_steps 4 Number of steps used in walktrap clustering

5.3 Basic Usage

res <- identify_module(
  res = res,
  method = "louvain",
  min_size = 3,
  max_size = 100000,
  n_perm = 100,
  seed = 123,
  workers = 4,
  node_freq = 0.05,
  louvain_resolution = 1
)
res <- identify_module(
  res = res,
  method = "leading_eigen",
  min_size = 3,
  max_size = 100000,
  n_perm = 100,
  seed = 123,
  workers = 4,
  node_freq = 0.05
)
res <- identify_module(
  res = res,
  method = "fast_greedy",
  min_size = 3,
  max_size = 100000,
  n_perm = 100,
  seed = 123,
  workers = 4,
  node_freq = 0.05
)
res <- identify_module(
  res = res,
  method = "walktrap",
  min_size = 3,
  max_size = 100000,
  n_perm = 100,
  seed = 123,
  workers = 4,
  node_freq = 0.05,
  walktrap_steps = 4
)

5.4 Clustering Methods

  • louvain
    A fast modularity optimization algorithm that iteratively merges nodes and communities to maximize network modularity. It is widely used for large biological networks.

  • leading_eigen
    A spectral method that partitions the network based on the leading eigenvectors of the modularity matrix. It identifies communities by separating nodes according to their contribution to modularity.

  • fast_greedy
    An agglomerative hierarchical clustering method that starts with each node as its own community and progressively merges communities to increase modularity.

  • walktrap
    A random-walk-based community detection algorithm that simulates short random walks on the network. The intuition is that random walks tend to remain within densely connected regions.

Different algorithms may produce slightly different module boundaries. Comparing multiple methods can help evaluate module robustness.

5.5 Result Interpretation

After successful module identification:

res$stage
# [1] "module_identified"

5.5.1 Module membership table

module_table records the assignment of metabolite nodes to modules, together with node-level metadata:

head(res$module_table[, c(
  "Module", "Compound_name", "HMDB_ID", "KEGG_ID",
  "Node_freq", "Type", "Importance", "Use_for_annotation"
)])
#   Module                    Compound_name     HMDB_ID KEGG_ID Node_freq      Type Importance
# 1     M1                    Tungstic acid HMDB0259326  C20679      1.00 annotated  0.2267914
# 2     M1                        Adenosine HMDB0000050  C00212      0.19 annotated  0.1321317
# 3     M1                       Salsolinol HMDB0042012  C09642      0.07 annotated  0.1183787
# 4     M1                              ADP HMDB0001341  C00008      0.94    hidden         NA
# 5     M1           Adenosine triphosphate HMDB0000538  C00002      1.00    hidden         NA
# 6     M1 Guanosine 3',5'-bis(diphosphate) HMDB0059638  C01228      0.75 annotated  0.2105361
#   Use_for_annotation
# 1               TRUE
# 2               TRUE
# 3               TRUE
# 4               TRUE
# 5               TRUE
# 6               TRUE

Each row represents a metabolite node belonging to an identified module:

Column Description
Module Module identifier ("M1", "M2", …)
INCHI_ID InChI of the metabolite node, used as the primary key of the network
HMDB_ID, KEGG_ID, Compound_name Metabolite identity carried over from the MRN
Node_freq Frequency of the node across repeated PCSF runs (robustness)
Type "annotated" if linked to an observed feature, "hidden" otherwise
Importance Feature-derived importance, averaged with weighted_anno_score as weights
Use_for_annotation FALSE for non-annotated terminal nodes pruned from the module boundary; those nodes are excluded from Step 5

Nodes with higher Node_freq and Importance values tend to represent more stable and feature-supported metabolites within the network.

Note

Here, annotated metabolites are nodes directly linked to observed LC–MS features through the feature annotation step, whereas hidden metabolites are Steiner nodes introduced during PCSF optimization to connect annotated metabolites within the metabolic reaction network. Hidden metabolites are not directly supported by measured features, but may provide important network context.

5.5.2 Module significance table

module_score summarizes statistics for each detected module:

head(res$module_score)
#   Module Module_size Annotated_nodes Module_score          p       fdr
# 1     M4          73              60    0.3278375 0.01174675 0.4463765
# 2    M12          51              44    0.2184320 0.08448563 0.6701799
# 3    M11          17              16    0.2090107 0.09908058 0.6701799
# 4     M3         123             113    0.1998797 0.11538420 0.6701799
# 5    M13          25              24    0.1797426 0.16015150 0.6701799
# 6    M17          32              31    0.1753071 0.17186291 0.6701799

sum(res$module_score$p < 0.05, na.rm = TRUE)
sum(res$module_score$fdr < 0.05, na.rm = TRUE)
# [1] 1
# [1] 0

Important columns include:

  • Module – module identifier, matching module_table
  • Module_size – number of metabolite nodes in the module
  • Annotated_nodes – number of nodes supported by feature annotations
  • Module_score – aggregated activity score of the module
  • p – permutation-based p-value
  • fdr – multiple-testing corrected p-value

Modules with lower p or fdr values indicate stronger statistical support and are typically prioritized for downstream biological interpretation.

Important

What this looks like on the demo data. With the default settings, the demo dataset returns one module with p < 0.05 and no module with fdr < 0.05. This is expected for a small demonstration dataset: the permutation p-values are corrected across all detected modules, so the FDR becomes conservative when only one module carries a strong signal.

Because of this, the demo examples in Function Annotation filter modules on the raw permutation p-value (p = "p") instead of the default "fdr". For a real study, keep "fdr" whenever enough modules pass it, and treat p-value-only modules as exploratory findings that need independent support.

Tip

If too many tiny modules appear, increase min_size or tighten node_freq. If modules are too coarse, try alternative methods, or lower louvain_resolution for broader and raise it for finer communities.

5.6 Next Step

Once modules are identified, continue to Function Annotation to interpret significant modules in biological terms.