## Workflow 2: Transcription Factor Activity Scoring

### Overview
This workflow explains how to infer transcription factor (TF) activity from single-cell RNA-seq data. Instead of relying on the often-sparse expression of TF genes, this method uses a gene regulatory network (GRN) to estimate TF activity based on the collective expression of its target genes (its "regulon"). This provides a more robust and biologically meaningful measure of regulatory activity.

### Standard Steps
1.  **Data Input**
    -   **Data Source**: An `AnnData` object from local memory.
    -   **Data Format**: The `.X` attribute should contain a log-normalized gene expression matrix (cells x genes).
    -   **Data Structure Check**: The object should have cell type annotations (e.g., `adata.obs['celltype']`) for downstream comparative analysis.

2.  **External Resources**
    -   **Database Calls**: Uses `decoupler` to access `CollecTRI`, a comprehensive GRN.
    -   **Reference Data**: A network of TF-target interactions for a specific organism (e.g., human), which includes the mode of regulation (activation/inhibition) as weights.

3.  **Preprocessing**
    -   The `CollecTRI` network is loaded directly. No significant preprocessing is needed as it's already curated and formatted correctly for `decoupler` (`source`, `target`, `weight`).

4.  **Core Analysis**
    -   **Method Name**: TF activities are estimated using a univariate linear model via the `decoupler.mt.ulm` function.
    -   **Key Parameters**: None specified beyond the input data and network, but the method internally uses the `weight` column from the GRN to model activation and inhibition.
    -   **Comparison Logic**: To identify key TFs for each cell type, `decoupler.tl.rankby_group` is used. It performs a statistical test (e.g., t-test) to find "marker" TFs with significantly higher activity in a given cell type compared to the rest.

5.  **Result Storage**
    -   **Data Structure Updates**: The primary `AnnData` object is updated with the analysis results.
    -   **Result Location**: TF activity scores are stored in `adata.obsm['score_ulm']` and p-values in `adata.obsm['padj_ulm']`.
    -   **Output Format**: The `rankby_group` function returns a pandas DataFrame with ranked TFs per cell type.

6.  **Visualization**
    -   **Chart Types**: UMAPs, violin plots, matrix plots, and network graphs.
    -   **Visualization Tools**: `scanpy.pl.umap`, `scanpy.pl.violin`, `scanpy.pl.matrixplot`, and `decoupler.pl.network`.
    -   **Presentation Purpose**:
        -   **UMAP/Violin Plot**: To visualize and compare the activity of a specific TF across different cell types. This is contrasted with the TF's gene expression to highlight the benefit of activity scoring.
        -   **Matrix Plot**: To display the activities of top "marker" TFs for each cell type, revealing regulatory signatures.
        -   **Network Graph**: To visualize the GRN for specific TFs, showing their target genes and the expression levels of those targets within a chosen cell population.

### Technical Details
-   **Key Functions**:
    -   `decoupler.op.collectri()`: Fetches the CollecTRI gene regulatory network.
    -   `decoupler.mt.ulm()`: Runs the TF activity inference.
    -   `decoupler.pp.get_obsm()`: Extracts the TF activity scores into a new `AnnData` object.
    -   `decoupler.tl.rankby_group()`: Identifies marker TFs for each cell type.
    -   `decoupler.pl.network()`: Visualizes TF-target interactions.

-   **Data Object Changes**:
    1.  **Initial State**: `adata` with expression data and cell type labels.
    2.  **After `dc.mt.ulm`**: `adata.obsm` is updated with `'score_ulm'` (DataFrame: cells x TFs) and `'padj_ulm'`. Note that this overwrites any previous `score_ulm` results.
    3.  **For Visualization**: A new `AnnData` object, `score`, is created from `adata.obsm['score_ulm']` to facilitate plotting.

-   **Data Object Management**: As in the previous workflow, a separate `AnnData` object (`score`) is created to manage the TF activity scores, keeping the analysis modular.

-   **Result Location**:
    -   TF activity scores: `adata.obsm['score_ulm']`
    -   Ranked marker TFs: A pandas DataFrame generated by `dc.tl.rankby_group()`.

-   **Data Flow Tracking**:
    `adata` (expression) + `collectri` (GRN) → `dc.mt.ulm` → `adata.obsm['score_ulm']` → `dc.pp.get_obsm` → `score` AnnData object → `dc.tl.rankby_group` → Ranked TF DataFrame → Visualization.

### Generality Assessment
-   **Dependency Conditions**: The analysis is dependent on a species-specific GRN (`organism='human'`). The choice of GRN (e.g., CollecTRI, DoRothEA) can influence the results.
-   **Extensibility**: Very high. This workflow can be used with any GRN, including custom ones inferred from data (e.g., from SCENIC+ or CellOracle). The enrichment method is also interchangeable.
-   **Applicable Scenarios**: This workflow is applicable to any single-cell or bulk transcriptomics dataset where the goal is to understand the regulatory landscape. It is particularly powerful for identifying key regulatory drivers of cell identity, state, or response to perturbation.

### CODE EXAMPLE
```python
# CODE EXAMPLE for Transcription Factor Activity Scoring

import scanpy as sc
import decoupler as dc

# Load pre-processed AnnData object with expression and celltype labels
adata = dc.ds.pbmc3k()

# --- 1. External Resources ---
# Fetch the CollecTRI gene regulatory network for human
collectri = dc.op.collectri(organism="human")

# --- 2. Core Analysis ---
# Infer TF activities using the ulm method
# Results are stored in adata.obsm, overwriting previous scores
dc.mt.ulm(data=adata, net=collectri)

# --- 3. Result Handling & Visualization ---
# Extract TF activity scores into a new AnnData object
score = dc.pp.get_obsm(adata=adata, key="score_ulm")

# Visualize the activity of a specific TF (e.g., PAX5)
tf = "PAX5"
sc.pl.umap(score, color=[tf, "celltype"], cmap="RdBu_r", vcenter=0, title=[f"{tf} activity", "celltype"])
sc.pl.violin(score, keys=[tf], groupby="celltype", rotation=90, ylabel=f"{tf} activity")

# Compare with the TF's gene expression
sc.pl.umap(adata, color=[tf, "celltype"], title=[f"{tf} expression", "celltype"])

# Identify marker TFs for each cell type
ranked_df = dc.tl.rankby_group(adata=score, groupby="celltype", method="t-test_overestim_var")

# Get top 3 marker TFs per cell type
top_markers = (
    ranked_df[ranked_df["stat"] > 0]
    .groupby("group")
    .head(3)
    .drop_duplicates("name")
    .groupby("group")["name"]
    .apply(list)
    .to_dict()
)

# Visualize marker TFs in a heatmap
sc.pl.matrixplot(
    adata=score,
    var_names=top_markers,
    groupby="celltype",
    dendrogram=True,
    standard_scale="var",
    cmap="RdBu_r",
)

# Visualize the regulatory network for key TFs in B cells
gex_bcells = adata[adata.obs["celltype"] == "B cells"].to_df().mean(0).to_frame().T
score_bcells = score[score.obs["celltype"] == "B cells"].to_df().mean(0).to_frame().T

dc.pl.network(
    data=gex_bcells,
    score=score_bcells,
    net=collectri,
    sources=["PAX5", "EBF1"], # Key B-cell TFs
    targets=15,
    figsize=(5, 5),
    s_cmap="Reds",
)
```