#!/usr/bin/env python
"""
Filename: src/ASOkai/Analysis/_intrinsic_features.py
Author: Arash Ayat
Copyright: 2026, Alexander Schliep
Description: This file defines the IntrinsicFeaturesAnalysis class for analyzing intrinsic features of target sites.
License: LGPL-3.0-or-later
"""
from collections.abc import Iterable
from typing import ClassVar
from ..Sites import Site
from ..Types import AnalysisUnitType, Scalar
from ._base import PerUnitAnalysis
[docs]
class IntrinsicFeaturesAnalysis(PerUnitAnalysis):
"""
Analyzes intrinsic features for each target site.
Available features:
- **GC_content**: The proportion of Guanine (G) and Cytosine (C) bases (between 0 and 1).
- **AT_content**: The proportion of Adenine (A) and Thymine (T) bases (between 0 and 1).
- **T_count**: The count of Thymine (T) bases.
- **CpG_count**: The total count of CpG dinucleotides (a Cytosine followed by a Guanine).
- **T_content**: The proportion of Thymine (T) bases (between 0 and 1).
- **CpG_content**: CpG count divided by sequence length (between 0 and 1).
"""
analysis_unit_type: ClassVar[AnalysisUnitType] = "site"
supported_features: ClassVar[tuple[str, ...]] = (
"GC_content",
"AT_content",
"T_count",
"CpG_count",
"T_content",
"CpG_content",
)
def __init__(
self,
features: Iterable[str] | None = None,
) -> None:
"""Initialize an intrinsic-feature analysis.
Args:
features: Features to calculate. Defaults to every supported
feature.
Raises:
ValueError: If an unknown feature is requested.
"""
selected_features = list(
self.supported_features if features is None else features
)
unknown_features = sorted(
set(selected_features) - set(self.supported_features)
)
if unknown_features:
formatted = ", ".join(repr(name) for name in unknown_features)
raise ValueError(f"Unknown intrinsic features: {formatted}.")
self.features = tuple(selected_features)
[docs]
def analyze_unit(self, site: Site) -> list[dict[str, Scalar]]:
"""Calculate the selected intrinsic features for one site.
Returns:
One complete row containing the site ID and selected features.
"""
sequence = str(site.sequence).upper()
sequence_length = len(sequence)
denominator = sequence_length or 1
g_count = sequence.count("G")
c_count = sequence.count("C")
a_count = sequence.count("A")
t_count = sequence.count("T")
cpg_count = sequence.count("CG")
all_results: dict[str, Scalar] = {
"GC_content": (g_count + c_count) / denominator,
"AT_content": (a_count + t_count) / denominator,
"T_count": t_count,
"CpG_count": cpg_count,
"T_content": t_count / denominator,
"CpG_content": cpg_count / denominator,
}
values: dict[str, Scalar] = {
feature: all_results[feature]
for feature in self.supported_features
if feature in self.features
}
return [{"site_id": site.id, **values}]