Visualizing pathway-level data with wikiprofiler

## Overview

`wikiprofiler` is designed around a pipe-friendly grammar for pathway graphics. The core idea is simple: start with a pathway plot, add a data-mapping layer, and then add optional visual refinements. The newer APIs extend that same design upstream and downstream: `wp_map()` prepares data before plotting, `wp_comparefill()` adds a comparison layer, and `wp_render()` scales the workflow to pathway batches.

This vignette walks through the current workflow in English, with the `wp_*` functions written in a pipe-oriented style whenever composition makes sense.

## Setup

    library(wikiprofiler)
    library(clusterProfiler)
    library(DOSE)
    library(org.Hs.eg.db)
    library(knitr)

## A minimal input object

The example below uses `DOSE::geneList` together with `clusterProfiler::enrichWP()` so that the same objects can be reused across the basic plotting, comparison plotting, and batch rendering sections.

    data(geneList, package = "DOSE")

    de <- names(geneList)[1:100]
    wp_res <- enrichWP(de, organism = "Homo sapiens")
    wp_tbl <- as.data.frame(wp_res)

    de_symbol <- bitr(
      de,
      fromType = "ENTREZID",
      toType = "SYMBOL",
      OrgDb = "org.Hs.eg.db"
    )

    value <- stats::setNames(geneList[de_symbol[, 1]], de_symbol[, 2])
    pathway_id <- wp_tbl$ID[1]

    kable(head(wp_tbl[, c("ID", "Description", "p.adjust")], 5), digits = 4)

| ID | Description | p.adjust
--- | --- | --- | ---
WP2446 | WP2446 | Retinoblastoma gene in cancer | 0.0000
WP2361 | WP2361 | Gastric cancer network 1 | 0.0000
WP179 | WP179 | Cell cycle | 0.0011
WP5039 | WP5039 | SARS CoV 2 innate immunity evasion and cell specific immune response | 0.0043
WP4240 | WP4240 | Regulation of sister chromatid separation at the metaphase anaphase transition | 0.0043

## Build a single pathway plot with pipes

The most direct workflow starts with `wpplot()`, adds a fill layer with `wp_bgfill()`, and then improves label readability with `wp_shadowtext()`.

    wpplot(pathway_id) |>
      wp_bgfill(
        value = value,
        low = "darkgreen",
        high = "firebrick",
        legend_x = 0.88,
        legend_y = 0.95
      ) |>
      wp_shadowtext(bg.r = 2, bg.col = "white")

If you want to save the final plot, keep the piped object and pass it to `wpsave()`.

    p_single <- wpplot(pathway_id) |>
      wp_bgfill(
        value = value,
        low = "darkgreen",
        high = "firebrick",
        legend_x = 0.88,
        legend_y = 0.95
      ) |>
      wp_shadowtext()

    single_png <- file.path(tempdir(), "wikiprofiler-single-demo.png")
    wpsave(p_single, single_png, width = 11, height = 7)
    single_png
    #> [1] "C:\\Users\\HUAWEI\\AppData\\Local\\Temp\\RtmpKqDcnS/wikiprofiler-single-demo.png"

## Prepare plotting values with `wp_map()`

`wp_bgfill()` expects a named numeric vector keyed by gene symbol. In real analyses, that is often not the format you start with. `wp_map()` fills that gap by:

- mapping identifiers such as `ENTREZID` to `SYMBOL`
- aggregating duplicated rows that end up at the same symbol

The next example intentionally duplicates part of the table so that the aggregation step is visible.

    expr_tbl <- de_symbol[1:40, c("ENTREZID", "SYMBOL")]
    expr_tbl$score <- unname(geneList[expr_tbl$ENTREZID])

    expr_tbl_dup <- expr_tbl[1:10, ]
    expr_tbl_dup$score <- expr_tbl_dup$score * 0.5
    expr_tbl2 <- rbind(expr_tbl, expr_tbl_dup)

    mapped_value <- wp_map(
      expr_tbl2,
      value_col = "score",
      id_col = "ENTREZID",
      mapping = de_symbol[, c("ENTREZID", "SYMBOL")],
      mapping_from = "ENTREZID",
      mapping_to = "SYMBOL",
      aggregator = "mean"
    )

    head(mapped_value, 10)
    #> APOBEC3B     ASPM   BCL2A1    CCNB2    CDC20    CDC45    CDCA3    CDCA8
    #> 2.674310 2.877002 2.967223 3.125239 2.758393 3.385945 2.595700 3.108056
    #>    CENPE    CEP55
    #> 2.414821 2.784238

`wp_map()` also stores a mapping table as an attribute so that you can inspect how each symbol-level value was produced.

    mapping_table <- attr(mapped_value, "mapping_table")
    kable(head(mapping_table, 10), digits = 4)

input_id | symbol | value | aggregated_value
--- | --- | --- | ---
4312 | MMP1 | 4.5726 | 3.4295
8318 | CDC45 | 4.5146 | 3.3859
10874 | NMU | 4.4182 | 3.3137
55143 | CDCA8 | 4.1441 | 3.1081
55388 | MCM10 | 3.8763 | 2.9072
991 | CDC20 | 3.6779 | 2.7584
6280 | S100A9 | 3.5020 | 2.6265
2305 | FOXM1 | 3.2918 | 2.4689
9493 | KIF23 | 3.2862 | 2.4647
1062 | CENPE | 3.2198 | 2.4148

Once the values are prepared, they drop directly into the same pipe-oriented plotting workflow.

    wpplot(pathway_id) |>
      wp_bgfill(
        value = mapped_value,
        low = "navy",
        high = "goldenrod",
        legend_x = 0.88,
        legend_y = 0.95
      ) |>
      wp_shadowtext()

## Compare two conditions with `wp_comparefill()`

When both conditions are already represented as named numeric vectors keyed by symbol, `wp_comparefill()` computes the comparison values and reuses the same plotting grammar.

Here the second condition is simulated from `mapped_value` only to illustrate the API. The point is the workflow shape, not the biology of this toy example.

    control_value <- mapped_value
    case_value <- mapped_value + rep(c(-0.6, 0.9), length.out = length(mapped_value))

    p_compare <- wpplot(pathway_id) |>
      wp_comparefill(
        value = case_value,
        control = control_value,
        mode = "difference",
        low = "steelblue4",
        high = "darkorange2",
        legend_x = 0.88,
        legend_y = 0.95
      ) |>
      wp_shadowtext()

    p_compare

The comparison table is stored on the returned `wpplot` object.

    kable(head(p_compare$comparison, 10), digits = 4)

symbol | case | control | comparison
--- | --- | --- | ---
APOBEC3B | 2.0743 | 2.6743 | -0.6
ASPM | 3.7770 | 2.8770 | 0.9
BCL2A1 | 2.3672 | 2.9672 | -0.6
CCNB2 | 4.0252 | 3.1252 | 0.9
CDC20 | 2.1584 | 2.7584 | -0.6
CDC45 | 4.2859 | 3.3859 | 0.9
CDCA3 | 1.9957 | 2.5957 | -0.6
CDCA8 | 4.0081 | 3.1081 | 0.9
CENPE | 1.8148 | 2.4148 | -0.6
CEP55 | 3.6842 | 2.7842 | 0.9

If you prefer ratio-based contrasts, switch the mode to `log2_ratio`.

    wpplot(pathway_id) |>
      wp_comparefill(
        value = case_value,
        control = control_value,
        mode = "log2_ratio",
        pseudocount = 1
      ) |>
      wp_shadowtext()

## Render multiple pathways with `wp_render()`

`wp_render()` is the batch entry point. It accepts pathway IDs directly, a `data.frame`, or an enrichment-like S4 object with a `result` slot. It can return a named list of `wpplot` objects, write files to disk, or do both.

The example below renders the top two enriched pathways and writes them with a filename template that combines rank, pathway ID, and pathway name.

    batch_dir <- file.path(tempdir(), "wikiprofiler-batch-demo")
    if (dir.exists(batch_dir)) {
      unlink(batch_dir, recursive = TRUE)
    }

    batch_plots <- wp_render(
      pathway = wp_tbl[, c("ID", "Description")],
      value = mapped_value,
      n = 2,
      name_col = "Description",
      dir = batch_dir,
      file_ext = "png",
      filename_template = "{index}_{id}_{name}",
      shadowtext = TRUE,
      width = 11,
      height = 7
    )

    batch_files <- list.files(batch_dir, full.names = TRUE)
    batch_files
    #> [1] "C:\\Users\\HUAWEI\\AppData\\Local\\Temp\\RtmpKqDcnS/wikiprofiler-batch-demo/1_WP2446_Retinoblastoma_gene_in_cancer.png"
    #> [2] "C:\\Users\\HUAWEI\\AppData\\Local\\Temp\\RtmpKqDcnS/wikiprofiler-batch-demo/2_WP2361_Gastric_cancer_network_1.png"

The returned object is still useful even when you also export files.

    names(batch_plots)
    #> [1] "WP2446" "WP2361"

To work entirely in memory, omit `dir`.

    plots <- wp_render(
      pathway = wp_res,
      value = mapped_value,
      n = 6,
      shadowtext = TRUE
    )

## A typical analysis pattern

In practice, the workflow often looks like this:

    wp_res <- enrichWP(gene_ids, organism = "Homo sapiens")

    mapped_value <- wp_map(
      expr_table,
      value_col = "logFC",
      id_col = "ENTREZID",
      mapping = id_map,
      mapping_from = "ENTREZID",
      mapping_to = "SYMBOL",
      aggregator = "mean"
    )

    plots <- wp_render(
      pathway = wp_res,
      value = mapped_value,
      n = 6,
      dir = "wp_batch",
      name_col = "Description",
      filename_template = "{index}_{id}_{name}",
      shadowtext = TRUE
    )

For two-condition analyses, pass both `value` and `control`.

    plots <- wp_render(
      pathway = wp_res,
      value = case_value,
      control = control_value,
      n = 6,
      dir = "wp_compare_batch",
      name_col = "Description",
      filename_template = "{index}_{id}_{name}",
      shadowtext = TRUE
    )

## Summary

The package now exposes a clearer layered workflow:

- `wpplot()` starts a pathway graphic
- `wp_bgfill()` and `wp_shadowtext()` add visual layers
- `wp_map()` prepares data for plotting
- `wp_comparefill()` adds condition-to-condition contrasts
- `wp_render()` scales the same grammar to pathway batches

The important part is that the plotting grammar still reads from left to right. The newer APIs do not replace that design; they make the same modular approach easier to use in real analysis pipelines.