Skip to contents

This vignette assumes you have already read the main example workflow. Here we focus on a local parameter neighbourhood rather than a set of named metrics. The question is whether a small sweep around Minkowski p = 0.5 carries coherent structure.

The parameter sweep creates a local family of related distances. The graph tier then handles what it is responsible for: retaining and normalizing graph structure.

Setup

Use the same balanced subset as the main workflow.

set.seed(1)

label_col <- data_cell_cycle$Cell_cycle
rows_by_label <- split(seq_len(nrow(data_cell_cycle)), label_col)
workflow_rows <- unlist(lapply(rows_by_label, head, 20), use.names = FALSE)
workflow_features <- seq_len(500)

cell_cycle <- as.matrix(data_cell_cycle[workflow_rows, -1, drop = FALSE][, workflow_features])
storage.mode(cell_cycle) <- "double"

cell_cycle_labels <- label_col[workflow_rows]
rownames(cell_cycle) <- sprintf("%s_%02d", cell_cycle_labels, seq_along(cell_cycle_labels))
cell_cycle_groups <- setNames(cell_cycle_labels, rownames(cell_cycle))
cell_cycle_palette <- c(G1 = "#0017FF", G2M = "#000000", S = "#FF0017")

dim(cell_cycle)
#> [1]  60 500
table(cell_cycle_labels)
#> cell_cycle_labels
#>  G1 G2M   S 
#>  20  20  20

Build the Parameter Sweep

Create ten Minkowski functions with p values near 0.5.

cell_cycle_minkowski_params <- seq(0.45, 0.54, length.out = 10)

cell_cycle_minkowski_funcs <- precise_func_fact(
  func = "minkowski",
  params = cell_cycle_minkowski_params
)

names(cell_cycle_minkowski_funcs)
#>  [1] "minkowski_0.45" "minkowski_0.46" "minkowski_0.47" "minkowski_0.48"
#>  [5] "minkowski_0.49" "minkowski_0.5"  "minkowski_0.51" "minkowski_0.52"
#>  [9] "minkowski_0.53" "minkowski_0.54"
cell_cycle_minkowski_dists <- precise_dist(
  cell_cycle,
  dist_funcs = cell_cycle_minkowski_funcs,
  suffix = "cell_minkowski_",
  file = NULL,
  parallel = FALSE,
  verbose = FALSE
)

cell_cycle_minkowski_dists[, c("distance", "metric", "type")]
#> # A tibble: 10 × 3
#>    distance                      metric type    
#>    <chr>                         <chr>  <chr>   
#>  1 cell_minkowski_minkowski_0.45 NA     distance
#>  2 cell_minkowski_minkowski_0.46 NA     distance
#>  3 cell_minkowski_minkowski_0.47 NA     distance
#>  4 cell_minkowski_minkowski_0.48 NA     distance
#>  5 cell_minkowski_minkowski_0.49 NA     distance
#>  6 cell_minkowski_minkowski_0.5  NA     distance
#>  7 cell_minkowski_minkowski_0.51 NA     distance
#>  8 cell_minkowski_minkowski_0.52 NA     distance
#>  9 cell_minkowski_minkowski_0.53 NA     distance
#> 10 cell_minkowski_minkowski_0.54 NA     distance

Normalize Graph Structure

Laplacian normalization is graph normalization: D^(-1/2) W D^(-1/2). So this workflow first builds graph matrices from each distance, then normalizes those graph matrices.

cell_cycle_minkowski_distances <- precise_transform(
  cell_cycle_minkowski_dists,
  to = "distance",
  normalize = "range01",
  diagonal = 0
)

cell_cycle_minkowski_graphs <- precise_graph(
  cell_cycle_minkowski_distances,
  methods = "knn",
  params = list(
    knn = list(k = 15)
  ),
  parallel = FALSE,
  verbose = FALSE
)

cell_cycle_minkowski_laplacian <- precise_graph(
  cell_cycle_minkowski_graphs,
  methods = "laplacian",
  parallel = FALSE,
  verbose = FALSE
)

cell_cycle_minkowski_laplacian[, c("distance", "graph_method", "input", "input_type")]
#> # A tibble: 10 × 4
#>    distance                                      graph_method input   input_type
#>    <chr>                                         <chr>        <chr>   <chr>     
#>  1 cell_minkowski_minkowski_0.45__knn__laplacian laplacian    cell_m… similarity
#>  2 cell_minkowski_minkowski_0.46__knn__laplacian laplacian    cell_m… similarity
#>  3 cell_minkowski_minkowski_0.47__knn__laplacian laplacian    cell_m… similarity
#>  4 cell_minkowski_minkowski_0.48__knn__laplacian laplacian    cell_m… similarity
#>  5 cell_minkowski_minkowski_0.49__knn__laplacian laplacian    cell_m… similarity
#>  6 cell_minkowski_minkowski_0.5__knn__laplacian  laplacian    cell_m… similarity
#>  7 cell_minkowski_minkowski_0.51__knn__laplacian laplacian    cell_m… similarity
#>  8 cell_minkowski_minkowski_0.52__knn__laplacian laplacian    cell_m… similarity
#>  9 cell_minkowski_minkowski_0.53__knn__laplacian laplacian    cell_m… similarity
#> 10 cell_minkowski_minkowski_0.54__knn__laplacian laplacian    cell_m… similarity

Fuse and Inspect

Now fuse the normalized graph matrices. This is the graph-before-fusion spine, but with a parameter sweep rather than a set of named metrics:

precise_dist() -> precise_transform() -> precise_graph() -> precise_graph() -> precise_fusion()
cell_cycle_minkowski_fused <- precise_fusion(
  cell_cycle_minkowski_laplacian,
  methods = "mean",
  verbose = FALSE
)

cell_cycle_minkowski_fused[, c("method", "type", "inputs")]
#> # A tibble: 1 × 3
#>   method type       inputs    
#>   <chr>  <chr>      <list>    
#> 1 mean   similarity <chr [10]>

Finally, render the fused graph with a three-dimensional DRL layout. The point of this view is to rotate the object, inspect the edges, and ask whether the structure survives a different route through the workflow.

cell_cycle_minkowski_viz <- precise_viz(
  cell_cycle_minkowski_fused,
  views = "graph_layout",
  params = list(
    graph_layout = list(
      layout = "drl",
      dimensions = 3,
      engine = "threejs",
      render = "graph",
      seed = 1,
      color = cell_cycle_groups,
      colors = cell_cycle_palette,
      show_labels = FALSE,
      size = 1
    )
  ),
  verbose = FALSE
)

cell_cycle_minkowski_viz$panel[[1]]