Source code for finaletoolkit.frag._delfi_gc_correct
"""
GC-bias correction for DELFI windows via LOESS smoothing (Cleveland, 1979).
"""
from __future__ import annotations
import time
from sys import stderr
import numpy as np
import pandas
from loess.loess_1d import loess_1d
__all__ = ["delfi_gc_correct"]
# Columns corrected against GC content.
_GC_CORRECT_COLUMNS = ["short", "long", "num_frags", "ratio"]
[docs]
def delfi_gc_correct(
windows: pandas.DataFrame,
alpha: float = 0.75,
it: int = 8,
verbose: bool = False,
) -> pandas.DataFrame:
"""GC-correct DELFI window features with a LOESS fit per feature.
For each of ``short``, ``long``, ``num_frags`` and ``ratio``, a degree-2
LOESS curve is fit over GC content and subtracted (re-centered on the
feature median), producing ``{feature}_corrected`` columns.
Parameters
----------
windows : pandas.DataFrame
DELFI windows; must contain a ``gc`` column and the feature columns.
alpha : float, optional
LOESS smoothing fraction (default 0.75).
it : int, optional
Retained for signature compatibility (unused).
verbose : bool, optional
Print timing.
Returns
-------
pandas.DataFrame
A copy of ``windows`` with ``{feature}_corrected`` columns added.
"""
if verbose:
start_time = time.time()
stderr.write(
f"""
windows: {windows}
alpha: {alpha}
it: {it}
verbose: {verbose}
\n"""
)
corrected_windows = windows.copy()
corrected_windows.replace([np.inf, -np.inf], np.nan, inplace=True)
valid = corrected_windows.dropna()
gc_range = np.arange(
valid["gc"].min(),
valid["gc"].max() + 0.01,
0.01,
)
loess_lines: dict[str, np.ndarray] = {}
medians: dict[str, float] = {}
for column in _GC_CORRECT_COLUMNS:
_, loess_lines[column], _ = loess_1d(
valid["gc"].to_numpy(),
valid[column].to_numpy(),
xnew=gc_range,
degree=2,
frac=alpha,
)
medians[column] = valid[column].median()
for column in _GC_CORRECT_COLUMNS:
corrected = (
corrected_windows[column]
- np.interp(corrected_windows["gc"], gc_range, loess_lines[column])
+ medians[column]
)
corrected_windows[f"{column}_corrected"] = corrected
if verbose:
end_time = time.time()
stderr.write(
f"delfi_gc_correct took {end_time - start_time} s to complete\n"
)
return corrected_windows