Graph representation#
A graph drawing and its matrix are two views of the same result. The numbers in the matrix can mean endpoint marks, directed-edge presence, or coefficients, depending on the package. The row can identify a source, an effect, or the endpoint being marked.
Andrey uses endpoint marks for topology and a separate array for weights. A StructureOutput
holds the graph, optional causal order and weights, and method-specific metadata.
multi_group_direct_lingam returns one such result per group.
For the meaning of DAGs, CPDAGs, PAGs, and temporal results, see Graph types.
Same graph, different numbers#
Choose an edge to see its encoding. Focus or hover over a matrix cell to highlight the same node
pair across representations. The meanings of those cells depend on the matrix convention. The
coefficient 0.8 is an illustration, not an estimate from data.
kind="dag"
Tail at X, arrowhead at Y.
M[X, Y] = 1 (TAIL at X)M[Y, X] = 2 (ARROW at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 1 |
| Y | 2 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | -1 |
| Y | 1 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
| X | Y | |
|---|---|---|
| X | 0 | 1 |
| Y | 0 | 0 |
1 records a directed edge; no coefficient.
LiNGAM
| X | Y | |
|---|---|---|
| X | 0 | 0 |
| Y | 0.8 | 0 |
Illustrative coefficient: 0.8. Andrey weights use B.T.
kind="dag"
Arrowhead at X, tail at Y.
M[X, Y] = 2 (ARROW at X)M[Y, X] = 1 (TAIL at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 2 |
| Y | 1 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | 1 |
| Y | -1 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
| X | Y | |
|---|---|---|
| X | 0 | 0 |
| Y | 1 | 0 |
1 records a directed edge; no coefficient.
LiNGAM
| X | Y | |
|---|---|---|
| X | 0 | 0.8 |
| Y | 0 | 0 |
Illustrative coefficient: 0.8. Andrey weights use B.T.
kind="cpdag"
Direction is unresolved in this CPDAG.
M[X, Y] = 1 (TAIL at X)M[Y, X] = 1 (TAIL at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 1 |
| Y | 1 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | -1 |
| Y | -1 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
Binary directed adjacency
Needs an extra convention to retain these endpoint marks.
LiNGAM
Directed coefficients
A coefficient alone does not encode this endpoint uncertainty.
kind="pag"
Two arrowheads in a PAG; not feedback.
M[X, Y] = 2 (ARROW at X)M[Y, X] = 2 (ARROW at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 2 |
| Y | 2 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | 1 |
| Y | 1 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
Binary directed adjacency
Needs an extra convention to retain these endpoint marks.
LiNGAM
Directed coefficients
A coefficient alone does not encode this endpoint uncertainty.
kind="pag"
The endpoint at X is unresolved.
M[X, Y] = 3 (CIRCLE at X)M[Y, X] = 2 (ARROW at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 3 |
| Y | 2 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | 2 |
| Y | 1 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
Binary directed adjacency
Needs an extra convention to retain these endpoint marks.
LiNGAM
Directed coefficients
A coefficient alone does not encode this endpoint uncertainty.
kind="pag"
Both endpoints are unresolved.
M[X, Y] = 3 (CIRCLE at X)M[Y, X] = 3 (CIRCLE at Y)
Andrey
| X | Y | |
|---|---|---|
| X | 0 | 3 |
| Y | 3 | 0 |
0 absent / 1 tail / 2 arrow / 3 circle
causal-learn
| X | Y | |
|---|---|---|
| X | 0 | 2 |
| Y | 2 | 0 |
0 absent / -1 tail / 1 arrow / 2 circle
Binary DAG
Binary directed adjacency
Needs an extra convention to retain these endpoint marks.
LiNGAM
Directed coefficients
A coefficient alone does not encode this endpoint uncertainty.
Focus or hover over a matrix cell to follow the same node pair across representations. Matrix rows and columns are labeled X and Y.
At a glance#
Representation |
Object or array |
Meaning of cell |
|---|---|---|
Andrey endpoints |
|
|
causal-learn endpoints |
|
|
pcalg |
|
|
pcalg |
|
|
Binary directed adjacency |
|
|
lingam coefficients |
|
Effect of column |
A binary directed adjacency matrix needs extra conventions to distinguish an undirected edge, two directed edges, and uncertain endpoint marks. A coefficient matrix describes a fitted directed model. Andrey retains endpoint uncertainty in the graph and stores coefficients separately. The package references document the compared conventions.
Endpoint marks#
A GraphStructure stores one mark at each end of each edge. M[i, j] is the mark at node i on
the edge between i and j, so every edge fills two cells. The codes are exported from
andrey.core:
Mark |
Code |
Symbol |
|---|---|---|
|
0 |
no edge |
|
1 |
|
|
2 |
|
|
3 |
|
Edge |
|
|
Valid in |
|---|---|---|---|
|
|
|
every kind |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The codes are nonnegative, with 0 as the only empty value. The store is a CSR matrix of the
nonzero marks (int64 row pointers, int32 columns, int8 marks), so memory grows with the number
of edges, not with . The graph’s kind states which edge types are valid; validate()
rejects a CIRCLE outside a pag, a double arrowhead in a dag or cpdag, and an undirected edge
in a dag or digraph.
Run extras#
ordering- the causal order, fromdirect_lingam,ica_lingam,multi_group_direct_lingam, andvarma_lingam.weighted_adjacency- densefloat64weights fromcalmand the static LiNGAM methods.W[i, j]is the weight ofi -> j, so rows are causes. This is the transpose of lingam’sadjacency_matrix_, whereB[i, j]is the effect of on in .metadata- JSON-safe, method-specific values. GES, GIES, XGES, HC, BOSS, and GRaSP reportscore; CALM reportsobjective. ExactSearch does not report a score. ANM and PNL report their two directional p-values and HSIC statistics; each two-node graph holds the direction they decide, by the rule the method’salphaparameter selects. PCMCI reports each lagged link’s MCIp_valuesandstatistics, and theparentsits first step selected; PCMCI+ reports the same for every lag from 0, plus the same-stepconflictsandambiguous_triples. LPCMCI reports every link with all its marks aslinks, plusp_values,statistics, its preliminaryparents, and the links with a conflicting mark asconflicts.save(path, fmt="json")andStructureOutput.load(path);fmt="npz"supports graphs only.
Temporal results#
TemporalStructure is a stack of GraphStructure objects, one per lag. lags lists the lag
values, lag(k) selects a graph by lag value, and lag_weights holds the (n_lags, n, n)
coefficients (lag_weights_ma adds the moving-average stack for VARMA); a pcmci result has lags 1
to tau_max and no weights, and a pcmciplus result adds lag 0 as a cpdag. An lpcmci result
has lag 0 as a pag and holds only the lagged links whose earlier end is a tail; a lagged link
with a circle or an arrowhead at its earlier end is in metadata["links"]. An autoregressive
edge X(t-k) -> X(t) is a self-loop in its lag graph. A panel result from longitudinal_lingam
adds a time axis: times, at(time, lag), and time_weights, where NaN marks a block that
cannot be computed. summary_graph() merges the lags into one digraph, and its lags_of(i, j)
returns the lags at which i -> j appears. For a panel, lag(k), lag_weights, and
summary_graph() refer to the final occasion; use at(time, lag) and time_weights to inspect
earlier occasions. The temporal diagrams show how collapsing time
can create cycles.
Reading and converting#
print(out), or out alone in a notebook, shows the algorithm, the graph kind, the node and
edge counts, and the first 30 edges, then the causal order and metadata values: the text
andrey run prints in a terminal. print(out.structure) shows the graph part alone.
import numpy as np
import andrey
rng = np.random.default_rng(0)
X = rng.uniform(size=(500, 3))
X[:, 1] += 2 * X[:, 0]
X[:, 2] += 1.5 * X[:, 1]
out = andrey.direct_lingam(X)
g = out.structure
g.kind # "dag"
g.endpoints(0, 1) # (1, 2): TAIL at 0, ARROW at 1, that is, 0 -> 1
g.oriented_edges() # [(0, 1, "directed"), (1, 2, "directed")]
g.to_edges() # one row per edge: i, j, mark_i, mark_j
out.ordering # (0, 1, 2)
oriented_edges() returns one (source, target, type) row per edge. A directed edge runs from
source to target whatever the node order. The types are directed, undirected, bidirected,
circle (o-o), partially_directed (o->), and partially_undirected (-o, a tail at
source and a circle at target). The three symmetric types list the lower node index first.
In a digraph, an ARROW/ARROW pair produces two directed rows, lower-to-higher node index
first, then the reverse. Printed summaries count and list both directions.
Nodes are numbered from 0 for an array. For a pandas DataFrame the column names, as text, become
the structure’s labels, and oriented_edges() returns names instead of indices:
import pandas as pd
df = pd.DataFrame(X, columns=["rain", "wet", "slippery"])
andrey.direct_lingam(df).structure.oriented_edges()
# [("rain", "wet", "directed"), ("wet", "slippery", "directed")]
andrey run reads a CSV or TSV header row the same way. Its JSON result lists the names in
labels, and each edge’s source and target are indices into that list. A first row of numbers
is data, not a header, so quote a name that is a number, as in "1","0","2"; a first row that
mixes numbers with text is an error.
Call |
Returns |
|---|---|
|
dense |
|
|
|
list of |
|
structured array with fields |
|
|
|
dense |
|
the marks on one edge; the adjacent nodes |
GraphStructure.from_numpy, from_edges, and from_networkx build a graph back from each. They
take each label or NetworkX node as text, str(label), and raise a ValueError when two are the
same text, such as 1 and "1". Pass the graph’s kind: when it is omitted, they infer it from
the marks, and the marks alone do not always decide it. A digraph 2-cycle, for one, comes back
as a pag with a bidirected edge.
to_networkx() returns a DiGraph for dag, cpdag, and digraph, and a MultiDiGraph for
pag or multigraph=True. Every edge carries endpoints=(mark_i, mark_j). For dag, cpdag,
and pag, each edge becomes one arc from the lower to the higher node index, whatever its
direction, so read endpoints or oriented_edges() rather than the arc. A MultiDiGraph adds the
reverse arc for <-> and o-o. A digraph emits each arc from tail to arrowhead, so NetworkX’s
cycle functions see its cycles. from_networkx reads endpoints; an edge without it is read as
u -> v.
For pcalg, GraphStructure.from_pcalg(amat, kind) reads its amat and to_pcalg() writes one for
the graph’s kind. amat.pag (fit@amat from fci or rfci; kind="pag", a MAG too) holds the
mark at the column node, coded 1 circle, 2 arrowhead, 3 tail, so the methods transpose it and
swap pcalg’s codes for circle and tail. amat.cpdag (as(fit, "amat") from pc; kind="cpdag"
or "dag") writes i -> j at [j, i], the child’s row, and an undirected edge as 1 in both
cells, so the methods transpose it too. A digraph has no pcalg matrix.
Andrey has no converters for other packages’ objects. Build their matrices into a GraphStructure
with from_numpy:
import numpy as np
from andrey import GraphStructure
from andrey.core import ARROW, CIRCLE, TAIL
def from_signed(G, kind):
"""Signed endpoint matrix (-1 tail, 1 arrowhead, 2 circle); G[i, j] is the mark at i."""
G = np.asarray(G)
if not np.isin(G, (-1, 0, 1, 2)).all():
raise ValueError("only the codes -1, 0, 1, 2 have an Andrey equivalent")
M = np.zeros(G.shape, dtype=np.int8)
M[G == -1], M[G == 1], M[G == 2] = TAIL, ARROW, CIRCLE
return GraphStructure.from_numpy(M, kind=kind)
def from_binary_dag(A):
"""Binary matrix with A[i, j] = 1 for i -> j."""
A = np.asarray(A) != 0
M = np.zeros(A.shape, dtype=np.int8)
M[A], M[A.T] = TAIL, ARROW
return GraphStructure.from_numpy(M, kind="dag")
causal-learn -
G.graph[i, j]is also the mark ati, sofrom_signed(G.graph, kind)needs no transpose. Its wildcard and compound codes (3 and above) have no Andrey equivalent.lingam - pass
adjacency_matrix_.Ttofrom_binary_dag. Some estimators writeNaNfor an unobserved common cause; decide how to encode those pairs first.
References#
causal-learn graph implementation: signed endpoint marks.
LiNGAM model definition: the coefficient convention in .
pcalg
amatType: theamat.cpdagandamat.pagcodes, and the row or column the mark belongs to.