Skip to content

Aggregating Samples

Developer Documentation

This page is intended for users who develop tailored visualizations using the GenomeSpy app.

aggregateSamples adds one or more summary tracks to a sample view. Use it to compare patterns across sample subgroups in addition to inspecting individual samples.

In the GenomeSpy paper, a summary track shows a copy-number landscape above the main heatmap. The summary is computed separately for each visible group, making recurrent amplification and deletion patterns easier to compare between groups.

Each entry in aggregateSamples is a normal unit or layer spec. It may define its own transform, encoding, and params. GenomeSpy prepends a mergeFacets transform automatically and removes the sample channel from the summary encoding.

sampleCount

Aggregate tracks receive the sampleCount parameter. It contains the number of samples in the current group and can normalize summary values.

Normalizing an aggregate
{
  "type": "formula",
  "expr": "datum.coverage / sampleCount",
  "as": "coverage"
}

Examples

The examples below use synthetic toy data for demonstration purposes.

Copy-number landscape

This example shows per-sample segments with layered aggregate amplification and deletion tracks.

The two aggregate layers first select positive or negative logR values. Each then uses the "coverage" transform to split overlapping segments into non-overlapping intervals and sum their logR values. Dividing that sum by sampleCount normalizes the track by the number of samples in the current group.

{
  "description": "Aggregated copy-number-style segments with logR values across a toy genome indexed from 0 to 100.",

  "data": {
    "values": [
      { "sample": "A", "start": 0, "end": 18, "logR": -0.3 },
      { "sample": "A", "start": 18, "end": 42, "logR": 0.0 },
      { "sample": "A", "start": 42, "end": 58, "logR": 0.45 },
      { "sample": "A", "start": 58, "end": 80, "logR": -0.15 },
      { "sample": "A", "start": 80, "end": 100, "logR": 0.25 },

      { "sample": "B", "start": 0, "end": 25, "logR": 0.1 },
      { "sample": "B", "start": 25, "end": 50, "logR": -0.4 },
      { "sample": "B", "start": 50, "end": 67, "logR": 0.35 },
      { "sample": "B", "start": 67, "end": 100, "logR": 0.05 },

      { "sample": "C", "start": 0, "end": 30, "logR": -0.1 },
      { "sample": "C", "start": 30, "end": 55, "logR": 0.2 },
      { "sample": "C", "start": 55, "end": 72, "logR": 0.6 },
      { "sample": "C", "start": 72, "end": 100, "logR": -0.25 },

      { "sample": "D", "start": 0, "end": 20, "logR": 0.05 },
      { "sample": "D", "start": 20, "end": 45, "logR": 0.4 },
      { "sample": "D", "start": 45, "end": 70, "logR": -0.2 },
      { "sample": "D", "start": 70, "end": 100, "logR": 0.15 },

      { "sample": "E", "start": 0, "end": 15, "logR": -0.5 },
      { "sample": "E", "start": 15, "end": 38, "logR": 0.0 },
      { "sample": "E", "start": 38, "end": 62, "logR": 0.3 },
      { "sample": "E", "start": 62, "end": 88, "logR": -0.1 },
      { "sample": "E", "start": 88, "end": 100, "logR": 0.2 },

      { "sample": "F", "start": 0, "end": 22, "logR": 0.2 },
      { "sample": "F", "start": 22, "end": 48, "logR": -0.15 },
      { "sample": "F", "start": 48, "end": 77, "logR": 0.55 },
      { "sample": "F", "start": 77, "end": 100, "logR": -0.35 }
    ]
  },

  "samples": {},

  "spec": {
    "name": "copy-numbers",

    "view": { "fill": "#f4f4f4" },

    "mark": {
      "type": "rect",
      "minOpacity": 1.0,
      "minWidth": 1.0
    },

    "encoding": {
      "sample": { "field": "sample" },
      "x": {
        "field": "start",
        "type": "index",
        "scale": {
          "domain": [0, 100],
          "zoom": true
        },
        "axis": { "grid": true }
      },
      "x2": { "field": "end" },
      "fill": {
        "field": "logR",
        "type": "quantitative",
        "scale": {
          "domain": [-2.5, 0, 2.5],
          "range": ["#0050f8", "#f6f6f6", "#ff3000"],
          "clamp": true
        }
      }
    },

    "aggregateSamples": [
      {
        "name": "logR-summary",
        "view": { "stroke": "lightgray" },

        "title": {
          "text": "logR summary",
          "style": "overlay-title"
        },

        "height": 50,
        "padding": { "bottom": 4 },

        "transform": [
          {
            "type": "filter",
            "expr": "abs(datum.logR) > 0.1"
          },
          {
            "type": "project",
            "fields": ["sample", "start", "end", "logR"]
          }
        ],

        "encoding": {
          "y": {
            "field": "coverage",
            "type": "quantitative",
            "title": null,
            "axis": { "grid": true },
            "scale": { "nice": true }
          }
        },
        "templates": {
          "coverage": {
            "transform": [
              {
                "type": "filter",
                "expr": "logRSign * datum.logR > 0"
              },
              {
                "type": "coverage",
                "start": "start",
                "end": "end",
                "weight": "logR"
              },
              {
                "type": "formula",
                "expr": "datum.coverage / sampleCount",
                "as": "coverage"
              }
            ],
            "encoding": {
              "fill": { "value": { "expr": "color" } }
            },
            "mark": {
              "type": "rect",
              "tooltip": null
            }
          }
        },
        "layer": [
          {
            "name": "amplification",
            "params": { "logRSign": 1, "color": "#e45756" },
            "import": { "template": "coverage" }
          },
          {
            "name": "deletion",
            "params": { "logRSign": -1, "color": "#4c78a8" },
            "import": { "template": "coverage" }
          }
        ]
      }
    ]
  }
}

Further reading

This technique was used in the GenomeSpy paper to visualize the copy-number landscape of the DECIDER cohort.

Mean z-score example

This example aggregates each gene's z-scores with the "aggregate" transform. It displays the mean z-score for the samples in the current group as a bar extending from zero, using the same color scale as the per-sample heatmap.

{
  "description": "Toy expression heatmap with five samples and gene-wise z-scores.",

  "data": {
    "values": [
      {
        "gene": "TP53",
        "S1.z": 1.8,
        "S2.z": 1.2,
        "S3.z": 0.7,
        "S4.z": -0.1,
        "S5.z": -0.8
      },
      {
        "gene": "BRCA1",
        "S1.z": -0.6,
        "S2.z": -0.9,
        "S3.z": -1.3,
        "S4.z": -1.0,
        "S5.z": -0.5
      },
      {
        "gene": "EGFR",
        "S1.z": 0.2,
        "S2.z": 0.6,
        "S3.z": 1.0,
        "S4.z": 1.5,
        "S5.z": 2.1
      },
      {
        "gene": "CDKN2A",
        "S1.z": -1.7,
        "S2.z": -1.3,
        "S3.z": -0.8,
        "S4.z": -0.2,
        "S5.z": 0.1
      },
      {
        "gene": "MYC",
        "S1.z": 0.4,
        "S2.z": 0.8,
        "S3.z": 1.1,
        "S4.z": 1.4,
        "S5.z": 1.0
      },
      {
        "gene": "PTEN",
        "S1.z": -0.3,
        "S2.z": -0.5,
        "S3.z": -0.7,
        "S4.z": -1.0,
        "S5.z": -1.4
      }
    ]
  },

  "transform": [
    {
      "type": "regexFold",
      "columnRegex": ["^(S[1-5])\\.z$"],
      "asKey": "sample",
      "asValue": ["zScore"]
    }
  ],

  "samples": {},

  "spec": {
    "name": "expression-zscores",

    "view": { "fill": "#f4f4f4" },
    "mark": {
      "type": "rect",
      "stroke": "white",
      "strokeWidth": 1
    },
    "encoding": {
      "sample": { "field": "sample" },
      "x": {
        "field": "gene",
        "type": "nominal",
        "scale": {
          "domain": ["TP53", "BRCA1", "EGFR", "CDKN2A", "MYC", "PTEN"]
        },
        "axis": {
          "title": "Gene",
          "grid": true
        }
      },
      "fill": {
        "field": "zScore",
        "type": "quantitative",
        "scale": {
          "domain": [-2.5, 0, 2.5],
          "range": ["#2166ac", "#f7f7f7", "#b2182b"],
          "clamp": true
        }
      }
    },

    "aggregateSamples": [
      {
        "name": "mean-zscores",
        "height": 70,
        "view": { "stroke": "lightgray" },
        "padding": { "bottom": 4 },
        "transform": [
          {
            "type": "aggregate",
            "groupby": ["gene"],
            "ops": ["mean"],
            "fields": ["zScore"],
            "as": ["meanZScore"]
          }
        ],
        "mark": {
          "type": "rect",
          "minWidth": 1,
          "stroke": "white",
          "strokeWidth": 1,
          "tooltip": null
        },
        "encoding": {
          "x": {
            "field": "gene",
            "type": "nominal"
          },
          "y": {
            "field": "meanZScore",
            "type": "quantitative",
            "axis": {
              "title": "Mean z-score",
              "grid": true
            },
            "scale": {
              "nice": true
            }
          },
          "y2": { "datum": 0, "type": "quantitative" },
          "fill": {
            "field": "meanZScore",
            "type": "quantitative",
            "scale": {
              "domain": [-2.5, 0, 2.5],
              "range": ["#2166ac", "#f7f7f7", "#b2182b"],
              "clamp": true
            },
            "legend": null
          }
        }
      }
    ]
  }
}