Skip to main content

Extract Fragment Lengths

Multiple studies have used fragment lengths (count distributions) to detect cancer (Renaud et al. 2022, Cristiano et al. 2019).

Examples​

The following examples show different aspects of the cfdna lengths command. They can of course be combined in a multitude of ways, but for simplification we just show one aspect at a time.

When running many jobs at once on an HPC cluster, use --temp-dir to place temporary files on node-local scratch and significantly reduce shared-filesystem contention.

Global length distribution​

Count the global fragment length distribution:

cfdna lengths --help

cfdna lengths \
--bam <sample>.bam \
--output-dir <sample_directory>/lengths \
--output-prefix <sample_id> \
--n-threads 12 \
--blacklist <path>/hg38-blacklist.v2.bed \
--blacklist <path>/<another_blacklist>.bed

Specify length bins​

By default, the command counts each fragment length from 30-1000bp. To change those limits or to count in bins of fragment lengths, you can specify the --length-bins as start:stop:step or as space-separated edges like --length-bins 100 151 221. The default is --length-bins 30:1001:1.

cfdna lengths \
... \
# Bin every 10bp from 30-500bp
--length-bins 30:500:10
# OR Specify edges directly (last end is exclusive)
--length-bins 100 151 221

GC-bias correction example​

To correct the sample-specific GC-bias, you need to precompute the correction matrix (see the GC-bias guide). Then you provide that correction file as:

cfdna lengths \
--bam <sample>.bam \
--output-dir <sample_directory>/lengths \
--output-prefix <sample_id> \
--n-threads 12 \
--blacklist <path>/hg38-blacklist.v2.bed \
# Precomputed GC correction file and reference genome
--gc-file <sample_directory>/gc_bias/gc_bias_correction.zarr \
--ref-2bit <path>/hg38.2bit

Genomic smoothing example​

When you're interested in local, relative coverage changes instead of large-scale changes from CNVs etc., you can use genomic smoothing to weight contributions more similarly across genomic regions. In some analyses, this can be thought of as a copy-number normalization.

Since we're counting fragments per lengths, we suggest using the cfdna fragment-count-weights command for calculating scaling factors (see the genomic smoothing guide).

cfdna lengths \
--bam <sample>.bam \
--output-dir <sample_directory>/lengths \
--output-prefix <sample_id> \
--n-threads 12 \
--blacklist <path>/hg38-blacklist.v2.bed \
# Precomputed scaling factors
--scaling-factors <sample_directory>/count_weights/<sample_id>.fragment_counts.scaling_factors.tsv

GC-bias correction + genomic smoothing​

NOTE: Requires using GC-bias correction when calculating the scaling factors to avoid double-correction.

cfdna lengths \
--bam <sample>.bam \
--output-dir <sample_directory>/lengths \
--output-prefix <sample_id> \
--n-threads 12 \
--blacklist <path>/hg38-blacklist.v2.bed \
# Precomputed GC correction file and reference genome
--gc-file <sample_directory>/gc_bias/gc_bias_correction.zarr \
--ref-2bit <path>/hg38.2bit \
# Precomputed scaling factors
--scaling-factors <sample_directory>/gc_corrected_count_weights/<sample_id>.fragment_counts.scaling_factors.tsv

Adjusting for indels​

The default lengths behavior is to use the fragment span on the reference genome and ignore whether insertions or deletions (InDels) are present in the reads.

By specifying --indel-mode adjust, the fragment lengths are adjusted for indels, which should be closer to the size of the original DNA molecule.

cfdna lengths \
... \
--indel-mode adjust

This is an analysis choice, not a requirement. If you are unsure, start without it.

Load length counts in Python​

Our cfdnalab Python package can help you load the <sample_id>.length_counts.tsv.zst output and extract data frames, arrays and metadata.

The methods differ when the data was built with --by-size / --by-bed, by-grouped-bed or the default "global" window. The below examples are a small subset of the options for subsetting the counts. See more in the Python package documentation.

import cfdnalab as cfl

lengths = cfl.read_lengths("sample.length_counts.tsv.zst")

# Extract metadata (depends on applied windowing and grouping)
length_bins = lengths.length_bins()
groups = lengths.group_metadata()
windows = lengths.window_metadata()

# Extract long data frame with all data
length_data = lengths.data_frame()

# Extract length bins containing a given length range
length_range_data = lengths.data_frame(with_length_range=(100, 220))

# Normalize to fractions across all length bins
fraction_data = lengths.data_frame(value="fraction")

# Normalize to fractions of only the selected bins
selected_fraction_data = lengths.data_frame(
with_length_range=(100, 220),
value="fraction",
denominator="selected_bins",
)

# For windowed counts, filter highly blacklisted windows/groups
filtered = lengths.data_frame(max_blacklisted_fraction=0.3)

# Select counts for specific group(s) or window(s)
group_data = lengths.data_frame(groups="t-cells", value="fraction")
window_data = lengths.data_frame(window_idxs=0, value="fraction")

# Extract counts as an array
counts = lengths.counts_array()

Load length counts in R​

Our cfdnalab R package can help you load the wide <sample_id>.length_counts.tsv.zst output and extract data frames, arrays, and metadata.

The methods differ when the data was built with by-size/by-bed, by-grouped-bed or the default "global" window. The below examples are a small subset of the options for subsetting the counts. See more in the R package documentation.

library(cfdnalab)

lengths <- read_lengths("sample.length_counts.tsv.zst")

# Extract metadata (depends on applied windowing and grouping)
length_bins(lengths)
window_metadata(lengths)
group_metadata(lengths)

# Extract long data frame with all data
length_data_frame(lengths)

# Extract length bins containing a given length range
length_data_frame(lengths, with_length_range = c(100L, 220L))

# Normalize to fractions across all length bins
length_data_frame(lengths, value = "fraction")

# Extract length bins containing a given length range
# And normalize to fractions of only those selected bins
length_data_frame(
lengths,
with_length_range = c(100L, 220L),
value = "fraction",
denominator = "selected_bins"
)

# For windowed counts, filter highly blacklisted windows/groups
length_data_frame(lengths, max_blacklisted_fraction = 0.3)

# Select counts for specific group(s) or window(s)
length_data_frame(lengths, groups = "t-cells", value = "fraction")
length_data_frame(lengths, window_idxs = 1L, value = "fraction")

# Extract counts as a matrix
length_counts_matrix(lengths)

References​