Skip to content

Fold Change

Raw fold-change comparison: b / a per aligned pair. No pseudocount, no normalization, no statistics. Apply add_pseudocount() to your tracks before calling interval_fold_change() if you need to avoid division-by-zero.

For publication-quality analysis, wrap edgeR, DESeq2, or similar in a function with the same signature.

fold_change

Naive fold-change comparison for exploratory differential analysis.

Computes raw fold change (b / a) per aligned pair. No pseudocount, no normalization, no statistics.

Arithmetic edge cases
  • 0 / 0 produces NaN
  • x / 0 produces inf
  • 0 / x produces 0.0

interval_fold_change

interval_fold_change(a: Track, b: Track, *, label_a: str = 'a', label_b: str = 'b', missing: float = float('nan'), mode: Literal['exact', 'overlap'] = 'exact', bin_size: int = 200, chrom_sizes: dict[str, int] | None = None) -> IntervalTrack

Compare two Tracks by naive fold change (b / a).

Aligns the two tracks, computes fold change per pair, and returns an IntervalTrack of Regions tagged with comparison metadata.

Parameters:

Name Type Description Default
a Track

First track (typically control/baseline).

required
b Track

Second track (typically treatment/condition).

required
label_a str

Label for the first track.

'a'
label_b str

Label for the second track.

'b'
missing float

Value for regions absent from one track. Defaults to NaN.

float('nan')
mode Literal['exact', 'overlap']

IntervalTrack alignment mode. "exact" (default) pairs only regions with identical boundaries. "overlap" uses the union of all boundaries.

'exact'
bin_size int

Window size for SignalTrack alignment. Defaults to 200.

200
chrom_sizes dict[str, int] | None

Chromosome sizes for SignalTrack alignment.

None

Returns:

Type Description
IntervalTrack

IntervalTrack of Regions with comparison tags:

IntervalTrack

fold_change, log2fc, mean_a, mean_b,

IntervalTrack

label_a, label_b, in_a, in_b.

Examples:

>>> result = interval_fold_change(ctrl, treat)
Source code in src/seqchain/compare/fold_change.py
def interval_fold_change(
    a: Track,
    b: Track,
    *,
    label_a: str = "a",
    label_b: str = "b",
    missing: float = float("nan"),
    mode: Literal["exact", "overlap"] = "exact",
    bin_size: int = 200,
    chrom_sizes: dict[str, int] | None = None,
) -> IntervalTrack:
    """Compare two Tracks by naive fold change (b / a).

    Aligns the two tracks, computes fold change per pair, and
    returns an IntervalTrack of Regions tagged with comparison
    metadata.

    Args:
        a: First track (typically control/baseline).
        b: Second track (typically treatment/condition).
        label_a: Label for the first track.
        label_b: Label for the second track.
        missing: Value for regions absent from one track.
            Defaults to ``NaN``.
        mode: IntervalTrack alignment mode. ``"exact"`` (default)
            pairs only regions with identical boundaries.
            ``"overlap"`` uses the union of all boundaries.
        bin_size: Window size for SignalTrack alignment.
            Defaults to ``200``.
        chrom_sizes: Chromosome sizes for SignalTrack alignment.

    Returns:
        IntervalTrack of Regions with comparison tags:
        ``fold_change``, ``log2fc``, ``mean_a``, ``mean_b``,
        ``label_a``, ``label_b``, ``in_a``, ``in_b``.

    Examples:
        >>> result = interval_fold_change(ctrl, treat)
    """
    result_regions = []

    for pair in align_tracks(
        a, b,
        missing=missing,
        mode=mode,
        bin_size=bin_size,
        chrom_sizes=chrom_sizes,
    ):
        fc = fold_change(pair.value_a, pair.value_b)
        fc_log2 = log2fc(fc)

        region = replace(
            pair.region,
            score=fc,
            tags={
                **pair.region.tags,
                "fold_change": fc,
                "log2fc": fc_log2,
                "mean_a": pair.value_a,
                "mean_b": pair.value_b,
                "label_a": label_a,
                "label_b": label_b,
                "in_a": pair.in_a,
                "in_b": pair.in_b,
            },
        )
        result_regions.append(region)

    return IntervalTrack(
        TrackLabel(f"{label_b}_vs_{label_a}"),
        result_regions,
    )

fold_change

fold_change(a: float, b: float) -> float

Compute b / a with edge-case handling.

Parameters:

Name Type Description Default
a float

Denominator value.

required
b float

Numerator value.

required

Returns:

Type Description
float

b / a. NaN if both zero, inf if only a is zero.

Examples:

>>> fold_change(2.0, 4.0)
2.0
Source code in src/seqchain/compare/fold_change.py
def fold_change(a: float, b: float) -> float:
    """Compute b / a with edge-case handling.

    Args:
        a: Denominator value.
        b: Numerator value.

    Returns:
        ``b / a``. ``NaN`` if both zero, ``inf`` if only a is zero.

    Examples:
        >>> fold_change(2.0, 4.0)
        2.0
    """
    if a == 0.0:
        if b == 0.0:
            return float("nan")
        return float("inf")
    return b / a

log2fc

log2fc(fc: float) -> float

Compute log2 of a fold change value.

Parameters:

Name Type Description Default
fc float

Fold change value.

required

Returns:

Type Description
float

log2(fc), or NaN if fc is NaN, 0, or negative.

Examples:

>>> log2fc(2.0)
1.0
Source code in src/seqchain/compare/fold_change.py
def log2fc(fc: float) -> float:
    """Compute log2 of a fold change value.

    Args:
        fc: Fold change value.

    Returns:
        ``log2(fc)``, or ``NaN`` if fc is NaN, 0, or negative.

    Examples:
        >>> log2fc(2.0)
        1.0
    """
    if math.isnan(fc) or math.isinf(fc) or fc <= 0.0:
        return float("nan")
    return math.log2(fc)