mkt.databases.conservation.KLIFSHierarchicalConservation

class mkt.databases.conservation.KLIFSHierarchicalConservation(*, names: list[str] = <factory>, pockets: list[str] = <factory>, groups: list[str] = <factory>, position_labels: list[str] = <factory>, metric: str = 'blosum', blosum_name: str = 'BLOSUM62', linkage_method: str = 'average', conservation_threshold: float = 0.8, min_child_members: int = 2, weighting: str = 'none', exclude_pseudokinases: bool = False, gap_chars: str = '-X', distance_matrix: ~numpy.ndarray | None = None, linkage_matrix: ~numpy.ndarray | None = None, tree: ~scipy.cluster.hierarchy.ClusterNode | None = None)[source]

Bases: BaseModel

Hierarchically cluster human kinases on KLIFS pocket similarity.

Builds a pairwise distance matrix over the gapless 85-column KLIFS pocket alignment (_build_klifs_panel()), clusters it with SciPy linkage, then traces per-residue conservation up the tree. Per-node conservation reuses MSAConservationAnalyzer.column_conservation(), keeping the tree and the conservation read-out in the same coordinate system.

Two metrics are provided behind a common interface: substitution-score similarity (Metric A, "blosum", default) and percent identity (Metric B, "identity", baseline). Two views of conservation are emitted at each internal node: per-subtree (recompute on that node’s members) and survives-up (the residue must be conserved at the node and hold — same consensus residue — in both child clades).

__init__(**data: Any) None

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Methods

_aggregate_singletons(splits, leaves)

Fold singleton leaves by tree adjacency so no leaf row holds one sequence.

_annotation_callouts(aa)

Find the shared-residue columns for the curated kinases of interest.

_annotation_lines(kinases, pos, label)

"{kinase} {residue}{uniprot_idx}" per kinase at KLIFS column pos.

_blosum_distance_matrix()

Substitution-score similarity distance (Metric A).

_conserved_columns(conservation)

Filter per-column conservation to the conserved consensus residues.

_drop_pseudokinases()

Filter the panel to catalytically active kinases (in place).

_encode_pockets()

Integer-encode the pocket panel against the substitution-matrix alphabet.

_family_label(i)

Family used for the color: Lipid or Manning group (pseudo folds in).

_family_name(value)

Label for a family field, which may be a Family enum or a bare string.

_henikoff_weights(sequences)

Henikoff (1994) position-based sequence weights for a set of members.

_hover_families(i)

(kinhub_family, klifs_family) labels for leaf i's dot hover.

_identity_distance_matrix()

Percent-identity distance (Metric B): 1 - matches / non_gap_columns.

_is_kept(node_id)

True if both children of node_id are clades (>=2 members each).

_kinome_background()

Kinome amino-acid background (20-vector) over all KLIFS panel pockets.

_member_style(i)

(fill, edge, linewidth) for leaf i's name box / dot.

_nodelist()

Return the cached SciPy node list, indexed by node id.

_pseudocount_model([background])

Cached pseudocount model for the requested KL background.

_uniprot_index_at(i, pos)

1-based UniProt canonical index of leaf i at KLIFS column pos.

_walk_node(node, depth, records)

Recursively score a node and its children, appending one record per node.

analyze_nodes()

Walk the tree and compute per-node conservation, cached after first call.

build_display_tree([min_cluster_size, ...])

Build the renderer-neutral display tree from the linkage.

compute_distance_matrix()

Dispatch to the configured pairwise distance metric.

cophenetic_correlation()

Cophenetic correlation between the tree and the input distances.

critical_depth()

Most ancestral node at which each KLIFS column still survives-up.

from_conservation_data(data, **kwargs)

Build a renderer from a persisted KLIFSConservationData artifact.

gather_children(node_id)

Singleton-contracted multifurcating children of node_id.

group_concordance([n_clusters])

Adjusted Rand index between flat clusters and Manning groups.

members_consensus(member_idx)

Per-column >=threshold consensus residue for a member set (else None).

node_conservation(member_idx)

Per-column conservation for a set of member leaves.

node_information(member_idx[, background])

Per-column background-relative information content (bits) and effective count.

nodes_summary()

Tabular summary of per-node conservation counts (internal nodes only).

plot_critical_depth([figsize, cmap])

Plot per-KLIFS-column critical depth as an opaque track with a region bar.

residue_dot_layout([min_cluster_size])

Row/leaf assignment for the per-amino-acid dot plot.

to_conservation_data([min_cluster_size, ...])

Package the clustering output as a serializable KLIFSConservationData.

tree_children(node_id)

The two linkage children of an internal node id.

tree_count(node_id)

Number of leaves under node_id (1 for a leaf).

tree_members(node_id)

Leaf indices under node_id (the node itself if it is a leaf).

Attributes

names

List of HGNC kinase names (one per pocket).

pockets

List of 85-character KLIFS pocket strings, column-aligned by construction.

groups

Manning group per kinase, used for the external concordance check.

position_labels

KLIFS region labels (e.g. "g.l:4") for the 85 pocket columns.

metric

"blosum" (default) or "identity".

blosum_name

Substitution matrix name used by the "blosum" metric.

linkage_method

"average" (UPGMA, default) or "complete".

conservation_threshold

Minimum consensus-residue frequency for a column to count as conserved.

min_child_members

Minimum child-clade size considered in survives-up pruning; children smaller than this (e.g. singleton outliers peeled off at the root) are ignored.

weighting

"none" (default) or "henikoff".

exclude_pseudokinases

If True, drop predicted pseudokinases (KinaseInfo.is_pseudokinase()) from the panel before clustering, so the tree and conservation reflect catalytically active kinases only (default: False).

gap_chars

Characters treated as gap/unknown and excluded from scoring and conservation.

distance_matrix

Square N x N pairwise distance matrix; computed post-init unless supplied (e.g. injected by from_conservation_data() from a persisted artifact).

linkage_matrix

SciPy linkage matrix; computed post-init unless supplied.

tree

Root ClusterNode of the hierarchical tree, computed post-init.

_node_records

Cached per-node conservation records (see analyze_nodes()).

_nodelist_cache

Cached SciPy to_tree(rd=True) node list, indexed by node id.

_kinome_bg_cache

Cached kinome amino-acid background (from the KLIFS panel) for the IC measure.

_pssm_cache

Cached substitution-aware pseudocount models, keyed by background choice.

_aggregate_singletons(splits: list[KLIFSDisplaySplit], leaves: list[KLIFSDisplayLeaf]) None[source]

Fold singleton leaves by tree adjacency so no leaf row holds one sequence.

Mutates splits/leaves in place: >=2 sibling singletons fold into one aggregated clade row, a lone sibling singleton folds into its nearest sibling clade, otherwise it bubbles up to the parent split (with a final safety net into the largest clade). Dead leaves are dropped and child references are remapped.

_annotation_callouts(aa: str) list[tuple[list[str], int]][source]

Find the shared-residue columns for the curated kinases of interest.

Searches every KLIFS column and, at each, collects the kinases from LIST_RESIDUE_DOT_KINASES that carry aa there; a column is called out when at least INT_RESIDUE_DOT_MIN_SHARED of them share it. There is no pre-grouping, so any kinases sharing a homologous residue at one column are boxed together, and the callouts are discovered from the current alignment (surviving a shift in the KLIFS mapping) rather than hardcoded.

Parameters:

aa (str) – The plotted residue (e.g. "C").

Returns:

(shared_kinases, column_index) per discovered callout.

Return type:

list[tuple[list[str], int]]

_annotation_lines(kinases: list[str], pos: int, label: str) list[str][source]

"{kinase} {residue}{uniprot_idx}" per kinase at KLIFS column pos.

Used for the curated conserved-residue callout boxes; the residue letter and UniProt index are read from each kinase’s pocket and KLIFS2UniProtIdx in DICT_KINASE, so the numbers stay correct without hardcoding.

_blosum_distance_matrix() ndarray[source]

Substitution-score similarity distance (Metric A).

Sums the substitution score over jointly non-gap columns, normalizes by each sequence’s self-score (cosine-like in score space), and converts to a distance 1 - sim.

Returns:

np.ndarray

Square N x N distance matrix with a zero diagonal.

_conserved_columns(conservation: list[tuple[str | None, float]]) dict[int, str][source]

Filter per-column conservation to the conserved consensus residues.

Parameters:

conservationlist[tuple[str | None, float]]

Per-column (consensus_aa, fraction) tuples.

Returns:

dict[int, str]

Mapping of column index to consensus residue for columns whose consensus frequency meets conservation_threshold.

_drop_pseudokinases() None[source]

Filter the panel to catalytically active kinases (in place).

Drops any panel member for which KinaseInfo.is_pseudokinase() is True, keeping names, pockets and groups in sync. Names absent from DICT_KINASE (e.g. a custom panel) cannot be assessed and are kept.

Returns:

None

_encode_pockets() tuple[ndarray, ndarray, ndarray][source]

Integer-encode the pocket panel against the substitution-matrix alphabet.

Returns:

tuple[np.ndarray, np.ndarray, np.ndarray]

(enc, valid, mat_arr) where enc is the N x 85 integer code (gaps mapped to 0 but masked out), valid is the N x 85 boolean non-gap mask, and mat_arr is the dense substitution-score matrix.

_family_label(i: int) str[source]

Family used for the color: Lipid or Manning group (pseudo folds in).

static _family_name(value) str[source]

Label for a family field, which may be a Family enum or a bare string.

_henikoff_weights(sequences: list[str]) list[float][source]

Henikoff (1994) position-based sequence weights for a set of members.

Each non-gap residue contributes 1 / (r_c * s_c) to its sequence’s weight, where r_c is the number of distinct residues in column c and s_c is the number of sequences sharing that residue. This down-weights redundant subfamilies and up-weights divergent sequences. Weights are scaled to mean 1; the per-column conservation fraction is scale-invariant regardless.

Parameters:

sequenceslist[str]

Aligned member sequences.

Returns:

list[float]

One weight per sequence.

_hover_families(i: int) tuple[str, str][source]

(kinhub_family, klifs_family) labels for leaf i’s dot hover.

_identity_distance_matrix() ndarray[source]

Percent-identity distance (Metric B): 1 - matches / non_gap_columns.

Returns:

np.ndarray

Square N x N distance matrix with a zero diagonal.

_is_kept(node_id: int) bool[source]

True if both children of node_id are clades (>=2 members each).

A node fails this when one child is a singleton leaf peeling off, which is exactly the pattern gather_children() contracts away.

_kinome_background() ndarray[source]

Kinome amino-acid background (20-vector) over all KLIFS panel pockets.

The preferred KL background for the information-content measure (“surprising relative to kinases”): a residue conserved because it is the kinome norm scores low, while a subfamily-specific residue scores high (plan Addendum A.1).

_member_style(i: int) tuple[str, str, float][source]

(fill, edge, linewidth) for leaf i’s name box / dot.

Lipid kinases get no border, pseudokinases a black border, active kinases a thin white border; the fill is the Manning-group (or Lipid) color. Shared by the static tree figure and the interactive dot-plot explorer.

_nodelist() list[source]

Return the cached SciPy node list, indexed by node id.

_pseudocount_model(background: str = 'swissprot') SubstitutionPseudocounts[source]

Cached pseudocount model for the requested KL background.

"swissprot" (default) scores conservation relative to a random protein – the continuous companion to the >=80% rule (pan-invariants score high). "kinome" scores conservation relative to the KLIFS panel itself, so subfamily-defining residues pop while pan-invariants score near zero (plan-preferred for the subfamily-delta view).

_uniprot_index_at(i: int, pos: int) int | None[source]

1-based UniProt canonical index of leaf i at KLIFS column pos.

Read from KinaseInfo.KLIFS2UniProtIdx keyed by the column’s region label; None when the kinase, mapping, or that position is unavailable.

_walk_node(node: ClusterNode, depth: int, records: list[dict]) dict[int, str][source]

Recursively score a node and its children, appending one record per node.

For each node the per-subtree conserved columns are recomputed on its members. At an internal node the survives-up set keeps only columns conserved at the node whose consensus residue is also conserved (identical) in every child clade with at least min_child_members members. Children below that size (e.g. a singleton outlier peeled off at the root by UPGMA) are ignored so they cannot veto an otherwise-robust residue.

Parameters:

nodeClusterNode

Current node in the tree.

depthint

Depth of the node (root is 0).

recordslist[dict]

Accumulator the per-node record dicts are appended to.

Returns:

dict[int, str]

The node’s per-subtree conserved columns (column index -> consensus aa), so the parent can intersect against it for the survives-up set.

analyze_nodes() list[dict][source]

Walk the tree and compute per-node conservation, cached after first call.

Returns:

list[dict]

One record per node with keys node_id, depth, n_members, is_leaf, conserved (per-subtree column->aa) and survives_up (the pruned, robust column->aa).

blosum_name: str

Substitution matrix name used by the "blosum" metric.

build_display_tree(min_cluster_size: int = 12, aggregate_singletons: bool = True) KLIFSDisplayTree[source]

Build the renderer-neutral display tree from the linkage.

Starting at the root, gather_children() contracts singleton-peel chains; each gathered child then either expands into its own split (>= min_cluster_size members), collapses to a clade leaf, or is a peeled singleton leaf. When aggregate_singletons is True (default) singleton leaves are folded by tree adjacency so no leaf row holds a single sequence (which would read as artificially 100% conserved): >=2 sibling singletons fold into one row, a lone sibling singleton folds into its nearest sibling clade, and otherwise it bubbles up to the parent split.

Parameters:
  • min_cluster_size (int, optional) – Members below which a gathered subtree collapses to a single leaf bar, by default INT_TREE_MIN_CLUSTER_DISPLAY.

  • aggregate_singletons (bool, optional) – Fold singleton leaves so every leaf row has >=2 members, by default True.

Returns:

Splits, leaves, and the in-order leaf display order.

Return type:

KLIFSDisplayTree

compute_distance_matrix() ndarray[source]

Dispatch to the configured pairwise distance metric.

Returns:

np.ndarray

Square N x N distance matrix.

conservation_threshold: float

Minimum consensus-residue frequency for a column to count as conserved.

cophenetic_correlation() float[source]

Cophenetic correlation between the tree and the input distances.

Returns:

float

Pearson correlation of the cophenetic distances against the condensed input distance matrix (higher is a more faithful tree).

critical_depth() DataFrame[source]

Most ancestral node at which each KLIFS column still survives-up.

A column’s critical depth is the shallowest (closest to root) depth at which it appears in a node’s survives-up set. Columns surviving at depth 0 are the pan-kinase invariants (expect the glycine-rich loop, the HRD arginine and the DFG aspartate).

Returns:

pd.DataFrame

One row per KLIFS column with column (0-based), position_label, critical_depth (NaN if never robust) and consensus_aa at the shallowest surviving node, sorted by critical depth.

distance_matrix: ndarray | None

Square N x N pairwise distance matrix; computed post-init unless supplied (e.g. injected by from_conservation_data() from a persisted artifact).

exclude_pseudokinases: bool

If True, drop predicted pseudokinases (KinaseInfo.is_pseudokinase()) from the panel before clustering, so the tree and conservation reflect catalytically active kinases only (default: False).

classmethod from_conservation_data(data: KLIFSConservationData, **kwargs) KLIFSHierarchicalConservation[source]

Build a renderer from a persisted KLIFSConservationData artifact.

Reuses the stored distances + linkage (so the rendered tree matches the persisted one and no clustering is recomputed), recovering the per-kinase pocket/group panel from DICT_KINASE by name. Any renderer-specific keyword (e.g. min_cluster_size, font_size) is passed through.

Parameters:
  • data (KLIFSConservationData) – The persisted conservation-data artifact.

  • **kwargs – Extra fields forwarded to the (sub)class constructor.

Returns:

An instance of cls with injected distances, linkage, and tree.

Return type:

KLIFSHierarchicalConservation

gap_chars: str

Characters treated as gap/unknown and excluded from scoring and conservation.

gather_children(node_id: int) list[int][source]

Singleton-contracted multifurcating children of node_id.

Descends through singleton-peel chains (nodes that are not _is_kept()) so a kinase that peels off attaches directly to its clade, yielding a multifurcating child list of leaves and kept clades.

Parameters:

node_id (int) – Internal linkage node id.

Returns:

Child node ids (leaves and kept internal nodes).

Return type:

list[int]

group_concordance(n_clusters: int | None = None) float[source]

Adjusted Rand index between flat clusters and Manning groups.

Parameters:

n_clustersint | None

Number of flat clusters to cut the tree into. Defaults to the number of distinct Manning groups in the panel.

Returns:

float

Adjusted Rand index (1.0 = perfect concordance, ~0 = chance).

groups: list[str]

Manning group per kinase, used for the external concordance check.

linkage_matrix: ndarray | None

SciPy linkage matrix; computed post-init unless supplied.

linkage_method: str

"average" (UPGMA, default) or "complete".

Type:

SciPy linkage method

members_consensus(member_idx: list[int]) list[str | None][source]

Per-column >=threshold consensus residue for a member set (else None).

Works for any set of leaves – a single leaf, a clade, or an aggregated row of unrelated singletons – so it backs both the per-node logo and the static per-leaf conservation table.

Parameters:

member_idx (list[int]) – Leaf indices into pockets.

Returns:

One entry per KLIFS column: the consensus residue if its frequency meets conservation_threshold, else None.

Return type:

list[str | None]

metric: str

"blosum" (default) or "identity".

Type:

Pairwise distance metric

min_child_members: int

Minimum child-clade size considered in survives-up pruning; children smaller than this (e.g. singleton outliers peeled off at the root) are ignored. Set to 1 to require every immediate child to agree (the strict, topology-sensitive definition).

names: list[str]

List of HGNC kinase names (one per pocket).

node_conservation(member_idx: list[int]) list[tuple[str | None, float]][source]

Per-column conservation for a set of member leaves.

Reuses MSAConservationAnalyzer.column_conservation() so the per-node read-out matches the panel-wide conservation analysis. When weighting is "henikoff" the consensus is computed with per-node Henikoff sequence weights (_henikoff_weights()).

Parameters:

member_idxlist[int]

Indices into pockets of the node’s member kinases.

Returns:

list[tuple[str | None, float]]

One (consensus_aa, fraction) tuple per KLIFS column.

node_information(member_idx: list[int], background: str = 'swissprot') tuple[list[float | None], list[float]][source]

Per-column background-relative information content (bits) and effective count.

Continuous companion to members_consensus() (plan Addendum A.2): for each KLIFS column, the KL divergence of the members’ substitution-smoothed residue distribution against the chosen background, plus the effective (weighted) count so the reader can see where the score is prior-influenced vs data-dominated. Sequence weighting follows weighting (uniform by default; Henikoff when set), the substitution-aware pseudocount is always applied.

Parameters:
  • member_idx (list[int]) – Leaf indices into pockets.

  • background (str, optional) – KL background: "swissprot" (default) or "kinome" (see _pseudocount_model()).

Returns:

(ic_per_column, n_eff_per_column); ic is None for an all-gap column.

Return type:

tuple[list[float | None], list[float]]

nodes_summary() DataFrame[source]

Tabular summary of per-node conservation counts (internal nodes only).

Returns:

pd.DataFrame

One row per internal node with node_id, depth, n_members, n_conserved (per-subtree), n_survives_up (pruned) and n_lost (conserved but not robust), sorted by depth.

plot_critical_depth(figsize: tuple[float, float] = (15, 3), cmap: str = 'plasma') tuple[Figure, Axes][source]

Plot per-KLIFS-column critical depth as an opaque track with a region bar.

Each of the 85 KLIFS columns is colored by its critical depth — the shallowest (most ancestral) node at which it still survives-up (critical_depth()). Lower depth means the residue holds across more of the kinome (depth 0 = a pan-kinase invariant); columns that never survive-up are drawn in grey. A KLIFS region color bar and position labels run beneath the track. Colors are flattened to opaque RGB (no alpha) for PowerPoint/PDF safety.

Parameters:

figsizetuple[float, float]

Figure size (default: (15, 3)).

cmapstr

Matplotlib colormap name for the depth scale (default: CMAP_CRITICAL_DEPTH).

Returns:

tuple[plt.Figure, plt.Axes]

Figure and axis objects.

pockets: list[str]

List of 85-character KLIFS pocket strings, column-aligned by construction.

position_labels: list[str]

KLIFS region labels (e.g. "g.l:4") for the 85 pocket columns.

residue_dot_layout(min_cluster_size: int = 12) tuple[KLIFSDisplayTree, list[int], dict[int, int], dict[int, int], list[list[int]]][source]

Row/leaf assignment for the per-amino-acid dot plot.

Orders kinases by the display-tree leaf order (dendrogram order), assigning each kinase a y row and the 1-based number of the display leaf (clade) it belongs to. Shared by the interactive Bokeh explorer and the static companion figure so their ordering and clade grouping cannot diverge.

Parameters:

min_cluster_size (int, optional) – Display-tree collapse threshold, by default INT_TREE_MIN_CLUSTER_DISPLAY.

Returns:

(tree, ordered_members, row_of_member, leaf_of_member, leaf_members): ordered_members are leaf indices (into names) top-to-bottom, row_of_member maps each to its y row, leaf_of_member to its 1-based display-leaf number, and leaf_members lists members per display leaf in display order.

Return type:

tuple

to_conservation_data(min_cluster_size: int = 12, aggregate_singletons: bool = True) KLIFSConservationData[source]

Package the clustering output as a serializable KLIFSConservationData.

Stores the condensed upper-triangle distances, the SciPy linkage matrix, the leaf order, and the display-tree leaf composition (all as plain Python numbers), alongside the provenance metadata describing how they were assembled.

Parameters:
  • min_cluster_size (int, optional) – Display-tree collapse threshold passed to build_display_tree(), by default INT_TREE_MIN_CLUSTER_DISPLAY.

  • aggregate_singletons (bool, optional) – Whether to fold singleton leaves in the display tree, by default True.

Returns:

The serializable conservation-data artifact.

Return type:

KLIFSConservationData

tree: ClusterNode | None

Root ClusterNode of the hierarchical tree, computed post-init.

tree_children(node_id: int) tuple[int, int][source]

The two linkage children of an internal node id.

tree_count(node_id: int) int[source]

Number of leaves under node_id (1 for a leaf).

tree_members(node_id: int) list[int][source]

Leaf indices under node_id (the node itself if it is a leaf).

weighting: str

"none" (default) or "henikoff".

Type:

Per-node consensus weighting