Skill Tracer
Skills › Scientific Agent Skills › arboreto › basic_inference.md

basic_inference.md

119 lines · skills/arboreto/references/basic_inference.md at 5f2bc03

Open on GitHub
1# Basic GRN inference
2
3Use the exact environment and pre-import Dask configuration in `SKILL.md`.
4All file/data examples below are illustrative adapters; the equivalent dense and
5CSC API calls were executed on a synthetic fixture during the 2026-09-30 review.
6Put inference calls inside a main guard when Dask creates worker processes.
7

Basic GRN inference

Use the exact environment and pre-import Dask configuration in SKILL.md. All file/data examples below are illustrative adapters; the equivalent dense and CSC API calls were executed on a synthetic fixture during the 2026-09-30 review. Put inference calls inside a main guard when Dask creates worker processes.

Raw lines 1 to 7
8## Data contract
9
10Arboreto accepts a pandas DataFrame, dense two-dimensional NumPy array, or
11`scipy.sparse.csc_matrix`. Rows are observations and columns are genes in all
12three cases. For arrays/matrices, `gene_names` must match every column in order;
13for DataFrames, Arboreto takes gene names from the columns.
15- Use unique, nonempty gene identifiers in the same species and namespace as TFs.
16- Remove sample labels, annotations, and other non-expression columns explicitly.
17- Check finite numeric values, dimensions, TF overlap, and sufficient variation.
18- Select and document the intended preprocessing upstream. Arboreto does not
19 perform cell QC, normalize libraries, correct batches, or identify cell types.
20- TF restriction limits predictors, not targets: every expression column is still
21 modeled as a target, with self-prediction excluded.
23```python
24import numpy as np
25import pandas as pd
27# This example file has observation IDs in its first column.
28expression_matrix = pd.read_csv("expression_data.tsv", sep="\t", index_col=0)
29assert expression_matrix.columns.is_unique
30assert expression_matrix.shape[0] >= 2 and expression_matrix.shape[1] >= 2
31assert np.isfinite(expression_matrix.to_numpy(dtype=float)).all()
32```
34If there is no row-ID column, omit `index_col=0`; otherwise it would silently
35remove the first gene. The bundled wrapper supports `--index-col 0` and checks
36raw headers before pandas can suffix duplicate names.

Data contract

Arboreto accepts a pandas DataFrame, dense two-dimensional NumPy array, or scipy.sparse.csc_matrix. Rows are observations and columns are genes in all three cases. For arrays/matrices, gene_names must match every column in order; for DataFrames, Arboreto takes gene names from the columns.

  • Use unique, nonempty gene identifiers in the same species and namespace as TFs.
  • Remove sample labels, annotations, and other non-expression columns explicitly.
  • Check finite numeric values, dimensions, TF overlap, and sufficient variation.
  • Select and document the intended preprocessing upstream. Arboreto does not perform cell QC, normalize libraries, correct batches, or identify cell types.
  • TF restriction limits predictors, not targets: every expression column is still modeled as a target, with self-prediction excluded.
import numpy as np
import pandas as pd

# This example file has observation IDs in its first column.
expression_matrix = pd.read_csv("expression_data.tsv", sep="\t", index_col=0)
assert expression_matrix.columns.is_unique
assert expression_matrix.shape[0] >= 2 and expression_matrix.shape[1] >= 2
assert np.isfinite(expression_matrix.to_numpy(dtype=float)).all()

If there is no row-ID column, omit index_col=0; otherwise it would silently remove the first gene. The bundled wrapper supports --index-col 0 and checks raw headers before pandas can suffix duplicate names.

Raw lines 8 to 37
38## TF input
40`arboreto.utils.load_tf_names(path)` reads one line per TF and strips whitespace;
41it does not remove empty lines, deduplicate, or resolve identifiers. Normalize
42those explicitly and report how many names overlap:
44```python
45from arboreto.utils import load_tf_names
47requested = list(dict.fromkeys(tf for tf in load_tf_names("tfs.txt") if tf))
48tf_names = [tf for tf in requested if tf in expression_matrix.columns]
49if not tf_names:
50 raise ValueError("No TFs match expression column names")
51print(f"[OK] {len(tf_names)} of {len(requested)} TF names match")
52```
54Use a Python list, not a NumPy array, for `tf_names` (the upstream sentinel check
55compares it to `'all'`). `None` or `'all'` uses every expression gene as a
56candidate regulator. An empty list or a list with no overlap raises `ValueError`.
57With only one matched TF, its own target fit has no remaining predictor and is
58skipped with warnings; interpret target coverage accordingly.

TF input

arboreto.utils.load_tf_names(path) reads one line per TF and strips whitespace; it does not remove empty lines, deduplicate, or resolve identifiers. Normalize those explicitly and report how many names overlap:

from arboreto.utils import load_tf_names

requested = list(dict.fromkeys(tf for tf in load_tf_names("tfs.txt") if tf))
tf_names = [tf for tf in requested if tf in expression_matrix.columns]
if not tf_names:
    raise ValueError("No TFs match expression column names")
print(f"[OK] {len(tf_names)} of {len(requested)} TF names match")

Use a Python list, not a NumPy array, for tf_names (the upstream sentinel check compares it to 'all'). None or 'all' uses every expression gene as a candidate regulator. An empty list or a list with no overlap raises ValueError. With only one matched TF, its own target fit has no remaining predictor and is skipped with warnings; interpret target coverage accordingly.

Raw lines 38 to 59
60## Dense and sparse adapters
62These snippets assume an explicitly managed Dask `client`, loaded expression
63matrix, and matching `tf_names`:
65```python
66from arboreto.algo import grnboost2
67from scipy.sparse import csc_matrix
69genes = expression_matrix.columns.tolist()
70array = expression_matrix.to_numpy(dtype=float)
71network = grnboost2(expression_data=array, gene_names=genes, tf_names=tf_names,
72 seed=777, client_or_address=client)
74sparse_matrix = csc_matrix(array)
75network_sparse = grnboost2(expression_data=sparse_matrix, gene_names=genes,
76 tf_names=tf_names, seed=777, client_or_address=client)
77```
79Converting an already-dense array to CSC is only an input-format example; it does
80not avoid the initial dense allocation. Load large sparse data as sparse and
81preserve it. Use `csc_matrix`, not `csc_array`; retain the SciPy compatibility pin
82because Arboreto converts each sparse target using `.A`.
84For AnnData, verify that `.X` or the selected layer contains the intended
85normalized expression. This adapter avoids densifying the full matrix:
87```python
88from scipy import sparse
90values = adata.layers["log_normalized"] # choose an existing, documented layer
91names = adata.var_names.tolist()
92assert len(names) == len(set(names))
93expression = sparse.csc_matrix(values) if sparse.issparse(values) else values
94network = grnboost2(expression_data=expression, gene_names=names,
95 tf_names=tf_names, seed=777, client_or_address=client)
96```
98`adata.to_df()` produces a dense DataFrame even for sparse `.X`; use it only when
99that allocation fits in memory. AnnData integration is illustrative, not a
100separately executed dependency in this skill's tests.

Dense and sparse adapters

These snippets assume an explicitly managed Dask client, loaded expression matrix, and matching tf_names:

from arboreto.algo import grnboost2
from scipy.sparse import csc_matrix

genes = expression_matrix.columns.tolist()
array = expression_matrix.to_numpy(dtype=float)
network = grnboost2(expression_data=array, gene_names=genes, tf_names=tf_names,
                    seed=777, client_or_address=client)

sparse_matrix = csc_matrix(array)
network_sparse = grnboost2(expression_data=sparse_matrix, gene_names=genes,
                           tf_names=tf_names, seed=777, client_or_address=client)

Converting an already-dense array to CSC is only an input-format example; it does not avoid the initial dense allocation. Load large sparse data as sparse and preserve it. Use csc_matrix, not csc_array; retain the SciPy compatibility pin because Arboreto converts each sparse target using .A.

For AnnData, verify that .X or the selected layer contains the intended normalized expression. This adapter avoids densifying the full matrix:

from scipy import sparse

values = adata.layers["log_normalized"]  # choose an existing, documented layer
names = adata.var_names.tolist()
assert len(names) == len(set(names))
expression = sparse.csc_matrix(values) if sparse.issparse(values) else values
network = grnboost2(expression_data=expression, gene_names=names,
                    tf_names=tf_names, seed=777, client_or_address=client)

adata.to_df() produces a dense DataFrame even for sparse .X; use it only when that allocation fits in memory. AnnData integration is illustrative, not a separately executed dependency in this skill's tests.

Raw lines 60 to 101
102## Output and selection
104```python
105# Upstream example convention: no header.
106network.to_csv("network.tsv", sep="\t", index=False, header=False)
107loaded = pd.read_csv("network.tsv", sep="\t", header=None,
108 names=["TF", "target", "importance"])
110# For a downstream tool expecting named columns, write a separate headered file.
111network.to_csv("adjacencies.tsv", sep="\t", index=False)
113# Exploratory ranking: top 10 links per target, not a confidence threshold.
114per_target = network.sort_values("importance", ascending=False).groupby("target").head(10)
115```
117`limit=5000` limits the global ranked output; it does not reduce the number of
118models fitted. Neither a top-N rule nor an importance threshold is a significance
119test. Scores do not encode activation/repression or direct causal effects.

Output and selection

# Upstream example convention: no header.
network.to_csv("network.tsv", sep="\t", index=False, header=False)
loaded = pd.read_csv("network.tsv", sep="\t", header=None,
                     names=["TF", "target", "importance"])

# For a downstream tool expecting named columns, write a separate headered file.
network.to_csv("adjacencies.tsv", sep="\t", index=False)

# Exploratory ranking: top 10 links per target, not a confidence threshold.
per_target = network.sort_values("importance", ascending=False).groupby("target").head(10)

limit=5000 limits the global ranked output; it does not reduce the number of models fitted. Neither a top-N rule nor an importance threshold is a significance test. Scores do not encode activation/repression or direct causal effects.

Raw lines 102 to 119