Introduction to Jellyfisher

library(jellyfisher)

Jellyfisher is an R package (a htmlwidget) for visualizing tumor evolution and subclonal compositions using Jellyfish plots. The package is based on the Jellyfish visualization tool, bringing its functionality to R users. Jellyfisher supports both ClonEvol results and plain data frames, making it compatible with various tools and workflows.

Input data

The input data should follow specific structures for samples, phylogeny, subclonal compositions, and optional ranks.

The jellyfisher package includes an example data set (jellyfisher_example_tables) based on the following publication:
Lahtinen, A., Lavikka, K., Virtanen, A., et al. “Evolutionary states and trajectories characterized by distinct pathways stratify patients with ovarian high-grade serous carcinoma.” Cancer Cell 41, 1103–1117.e12 (2023). DOI: 10.1016/j.ccell.2023.04.017.

Samples

head(jellyfisher_example_tables$samples, 25)
#>                    sample displayName rank               parent patient
#> 35       EOC69_pOme1_DNA1       pOme1    1                        EOC69
#> 36       EOC69_pOva1_DNA2       pOva1    1                        EOC69
#> 37      EOC69_r1Vag1_DNA1      r1Vag1   10                        EOC69
#> 257     EOC495_pLNL1_DNA1       pLNL1    1                       EOC495
#> 258     EOC495_pLNL2_DNA1       pLNL2    1                       EOC495
#> 259      EOC495_pLNR_DNA1        pLNR    1                       EOC495
#> 260    EOC495_pOvaL6_DNA1      pOvaL6    1                       EOC495
#> 261    EOC495_pOvaL7_DNA1      pOvaL7    1                       EOC495
#> 262     EOC495_pPerL_DNA1       pPerL    1                       EOC495
#> 317      EOC677_pAsc_DNA1        pAsc    1                       EOC677
#> 318      EOC677_pPer1_DNA       pPer1    1                       EOC677
#> 319      EOC677_r2Asc_DNA       r2Asc   11     EOC677_rAsc_DNA4  EOC677
#> 320      EOC677_rAsc_DNA4        rAsc    9     EOC677_pAsc_DNA1  EOC677
#> 363  EOC809_p2Bow1_c_DNA2    p2Bow1_c    3                       EOC809
#> 364  EOC809_p2Ome1_c_DNA1    p2Ome1_c    3                       EOC809
#> 365 EOC809_p2OvaL1_c_DNA6   p2OvaL1_c    3                       EOC809
#> 366 EOC809_p2Per1_cO_DNA2   p2Per1_cO    3                       EOC809
#> 367    EOC809_r1Bow1_DNA1      r1Bow1   10 EOC809_p2Bow1_c_DNA2  EOC809

Phylogeny

head(jellyfisher_example_tables$phylogeny, 25)
#>     subclone parent   color branchLength patient
#> 44         1     -1 #cccccc         2742   EOC69
#> 45         2     12 #a6cee3          478   EOC69
#> 46         5      9 #ff99ff           68   EOC69
#> 47         6      5 #fdbf6f          244   EOC69
#> 48         8      1 #bbbb77         2433   EOC69
#> 49         9      8 #cf8d30          313   EOC69
#> 50        11      8 #ff7f00          868   EOC69
#> 51        12      1 #3de4c5         4762   EOC69
#> 52        13      9 #ff1aff         1017   EOC69
#> 411        1     -1 #cccccc         1426  EOC495
#> 412        2      5 #a6cee3          184  EOC495
#> 413        3      6 #b2df8a          246  EOC495
#> 414        4      1 #cab2d6         2874  EOC495
#> 415        5      7 #ff99ff          864  EOC495
#> 416        6      5 #fdbf6f          154  EOC495
#> 417        7      1 #fb9a99          179  EOC495
#> 418        8      7 #bbbb77          631  EOC495
#> 419        9      4 #cf8d30          415  EOC495
#> 501        1     -1 #cccccc         4961  EOC677
#> 502        2      1 #a6cee3          239  EOC677
#> 503        4      5 #cab2d6          437  EOC677
#> 504        5     10 #ff99ff         1802  EOC677
#> 505        6      9 #fdbf6f          979  EOC677
#> 506        9     10 #cf8d30          223  EOC677
#> 507       10      1 #41ae76          314  EOC677

Subclonal compositions

Subclonal compositions are specified in a tidy format, where each row represents a subclone in a sample.

head(jellyfisher_example_tables$compositions, 25)
#>                  sample subclone clonalPrevalence patient
#> 98     EOC69_pOme1_DNA1        5           0.2250   EOC69
#> 99     EOC69_pOme1_DNA1        6           0.0965   EOC69
#> 100    EOC69_pOme1_DNA1       13           0.6660   EOC69
#> 101    EOC69_pOva1_DNA2        6           0.4175   EOC69
#> 102    EOC69_pOva1_DNA2       11           0.5225   EOC69
#> 103    EOC69_pOva1_DNA2       13           0.0360   EOC69
#> 104   EOC69_r1Vag1_DNA1        2           0.3845   EOC69
#> 105   EOC69_r1Vag1_DNA1       12           0.5970   EOC69
#> 880   EOC495_pLNL1_DNA1        4           0.5575  EOC495
#> 881   EOC495_pLNL1_DNA1        9           0.4405  EOC495
#> 882   EOC495_pLNL2_DNA1        4           0.4635  EOC495
#> 883   EOC495_pLNL2_DNA1        9           0.5345  EOC495
#> 884    EOC495_pLNR_DNA1        1           0.1595  EOC495
#> 885    EOC495_pLNR_DNA1        4           0.5060  EOC495
#> 886    EOC495_pLNR_DNA1        5           0.0350  EOC495
#> 887    EOC495_pLNR_DNA1        9           0.2950  EOC495
#> 888  EOC495_pOvaL6_DNA1        3           0.5320  EOC495
#> 889  EOC495_pOvaL6_DNA1        5           0.0665  EOC495
#> 890  EOC495_pOvaL6_DNA1        6           0.3995  EOC495
#> 891  EOC495_pOvaL7_DNA1        2           0.5440  EOC495
#> 892  EOC495_pOvaL7_DNA1        5           0.4390  EOC495
#> 893   EOC495_pPerL_DNA1        1           0.1155  EOC495
#> 894   EOC495_pPerL_DNA1        8           0.8850  EOC495
#> 1063   EOC677_pAsc_DNA1        2           0.3180  EOC677
#> 1064   EOC677_pAsc_DNA1        9           0.2440  EOC677

Ranks

The ranks in the example data set are used to indicate the time points when the samples were acquired.

head(jellyfisher_example_tables$ranks, 6)
#>   rank       title
#> 1    1   Diagnosis
#> 2    2   Diagnosis
#> 3    3 Diagnosis 2
#> 4    4 Diagnosis 3
#> 5    5    Interval
#> 6    6    Interval

Plotting

Basic plotting

The three tables are passed to the jellyfisher function as a named list. The function generates an interactive Jellyfish plot based on the input data. If the data set contains multiple patients, the Jellyfisher htmlwidget shows navigation buttons to switch between patients.

jellyfisher(jellyfisher_example_tables,
            width = "100%", height = 450)

Plotting with custom options

jellyfisher(jellyfisher_example_tables,
            options = list(
              sampleHeight = 70,
              sampleTakenGuide = "none",
              tentacleWidth = 3,
              showLegend = FALSE
            ),
            width = "100%", height = 400)

Plotting a single patient

When plotting multiple patients, Jellyfisher shows buttons (Previous and Next) to navigate between patients. When the data contains only one patient, these buttons are hidden. The package also provides a select_patients function to filter the data set with ease.

jellyfisher_example_tables |>
  select_patients("EOC677") |>
  jellyfisher(width = "100%", height = 400)

Adjusting the sample tree structure

The sample trees in the example data set were constructed as follows:

“For each sample, we checked whether an earlier time point included exactly one sample from the same anatomical location. If such a sample existed, it was assigned as the parent; otherwise, the inferred root was used as the parent.”

However, this mechanistic approach may not always produce credible sample trees.

Changing parent

The r1Bow1 (bowel) sample in the following jellyfish plot is derived from an earlier bowel sample p2Bow1_c, which has no traces of the subclone 12.

jellyfisher_example_tables |>
  select_patients("EOC809") |>
  jellyfisher(width = "100%", height = 600)

Using the set_parents function, we can adjust the parent of the r1Bow1 sample to be p2Per1_cO (peritoneum), which is a possible source of the metastasis due to its proximity. The high prevalence of subclone 12 in this sample suggests that it is the likely source of the metastasis in the r1Bow1 sample.

jellyfisher_example_tables |>
  select_patients("EOC809") |>
  set_parents(list("EOC809_r1Bow1_DNA1" = "EOC809_p2Per1_cO_DNA2")) |>
  jellyfisher(width = "100%", height = 600)

Changing topology

While ranks (the columns) can indicate the time points when the samples were acquired, they can also be used to simply show the sample’s depth in the sample tree. For instance, the following plot shows all the samples on the same rank, indicating that they were diagnostic samples acquired at the same time.

jellyfisher_example_tables |>
  select_patients("EOC495") |>
  jellyfisher(width = "100%", height = 650)

However, one can argue that the LN (lymph node) samples represent a later development in the disease, and thus, they should be placed on a later rank. We can remove the existing ranks, define new parent-child relationships, and let Jellyfisher assign the ranks based on the sample tree depth.

tables <- jellyfisher_example_tables |>
  select_patients("EOC495")

# Remove existing ranks. The ranks will be assigned automatically based
# on samples' depths in the sample tree.
tables$samples$rank <- NA

# Rank titles should be removed as well because they are no longer valid.
tables$ranks <- NULL

tables |>
  set_parents(list("EOC495_pLNL1_DNA1" = "EOC495_pLNR_DNA1",
                   "EOC495_pLNL2_DNA1" = "EOC495_pLNL1_DNA1")) |>
  jellyfisher(width = "100%", height = 500)

If we think that the lymph node samples represent an even later development, we can manually assign ranks to the samples. The set_ranks function provides an easy way to do this.

tables |>
  set_parents(list("EOC495_pLNL1_DNA1" = "EOC495_pLNR_DNA1",
                   "EOC495_pLNL2_DNA1" = "EOC495_pLNL1_DNA1")) |>
  set_ranks(list("EOC495_pLNR_DNA1" = 2,
                 "EOC495_pLNL1_DNA1" = 3,
                 "EOC495_pLNL2_DNA1" = 4),
            default = 1) |>
  jellyfisher(width = "100%", height = 400)

Handling non-aberrant cells

Typically in tumor evolution studies, the focus is on the subclonal compositions of the tumor cells. Thus, the root node in the phylogenetic tree represents the founding clone. However, the data may also contain non-aberrant cells, i.e., the tumor purity is less than 100%. For these cases, the normalsInPhylogenyRoot option instructs Jellyfisher to treat the root node as non-aberrant cells. The option has two consequences: (1) No tentacles are drawn between the root subclones, and (2) the root subclone is colored as white when the phylogeny-aware color scheme is used.

# Subclone N at the root represents the non-aberrant cells.
# The letter N has no special meaning in Jellyfisher.
non_aberrant <- list(
  samples = data.frame(sample = c("A", "B")),
  compositions = data.frame(
    sample = c("A", "A", "A", "B", "B", "B"),
    subclone = c("N", "1", "2", "N", "1", "2"),
    clonalPrevalence = c(0.2, 0.4, 0.4, 0.3, 0.3, 0.4)
  ),
  phylogeny = data.frame(
    subclone = c("N", "1", "2"),
    parent = c(NA, "N", "1")
  )
)
non_aberrant |>
  jellyfisher(options = list(
    normalsAtPhylogenyRoot = TRUE
  ),
  width = "100%", height = 350)

In the above plot, the root clone, which represents the non-aberrant cells, is hidden from the inferred root sample. However, sometimes a patient may have multiple independent clones, and in these cases the root clone is shown:

# Change the parent of subclone 2 to N
non_aberrant$phylogeny$parent[non_aberrant$phylogeny$subclone == "2"] <- "N"

non_aberrant |>
  jellyfisher(options = list(
    normalsAtPhylogenyRoot = TRUE
  ),
  width = "100%", height = 350)

Session info

sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.3
#> 
#> Matrix products: default
#> BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
#> 
#> locale:
#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#> 
#> time zone: Europe/Helsinki
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] jellyfisher_1.0.4
#> 
#> loaded via a namespace (and not attached):
#>  [1] vctrs_0.6.5       cli_3.6.3         knitr_1.49        rlang_1.1.5      
#>  [5] xfun_0.50         stringi_1.8.4     generics_0.1.3    jsonlite_1.8.9   
#>  [9] glue_1.8.0        htmltools_0.5.8.1 sass_0.4.9        rmarkdown_2.29   
#> [13] evaluate_1.0.3    jquerylib_0.1.4   tibble_3.2.1      fastmap_1.2.0    
#> [17] yaml_2.3.10       lifecycle_1.0.4   stringr_1.5.1     compiler_4.4.2   
#> [21] dplyr_1.1.4       htmlwidgets_1.6.4 pkgconfig_2.0.3   rstudioapi_0.17.1
#> [25] digest_0.6.37     R6_2.5.1          tidyselect_1.2.1  pillar_1.10.1    
#> [29] magrittr_2.0.3    bslib_0.9.0       tools_4.4.2       cachem_1.1.0