Slices and subgraphs¶
Slices keep multiple graph contexts in one AnnNet object. Operations materialize smaller graphs from those contexts.
In [1]:
Copied!
import annnet as an
import annnet as an
Create condition slices¶
In [2]:
Copied!
G = an.AnnNet(directed=True)
G.add_vertices(['A', 'B', 'C', 'D', 'E'])
G.slices.add('control', cohort='wildtype')
G.slices.add('treated', cohort='drug')
G.add_edges('A', 'B', edge_id='e1', slice='control', weight=1.0)
G.add_edges('B', 'C', edge_id='e2', slice='control', weight=2.0)
G.add_edges('B', 'C', edge_id='e2_treated', slice='treated', weight=5.0)
G.add_edges('C', 'D', edge_id='e3', slice='treated', weight=3.0)
G.add_edges('D', 'E', edge_id='e4', slice='treated', weight=1.5)
G.add_edges(
'A',
'E',
edge_id='context_binding',
directed=False,
slice='treated',
weight=0.6,
)
print(G.slices.summary())
G = an.AnnNet(directed=True)
G.add_vertices(['A', 'B', 'C', 'D', 'E'])
G.slices.add('control', cohort='wildtype')
G.slices.add('treated', cohort='drug')
G.add_edges('A', 'B', edge_id='e1', slice='control', weight=1.0)
G.add_edges('B', 'C', edge_id='e2', slice='control', weight=2.0)
G.add_edges('B', 'C', edge_id='e2_treated', slice='treated', weight=5.0)
G.add_edges('C', 'D', edge_id='e3', slice='treated', weight=3.0)
G.add_edges('D', 'E', edge_id='e4', slice='treated', weight=1.5)
G.add_edges(
'A',
'E',
edge_id='context_binding',
directed=False,
slice='treated',
weight=0.6,
)
print(G.slices.summary())
slices: 3 ├─ default: 5 vertices, 0 edges ├─ control: 3 vertices, 2 edges └─ treated: 5 vertices, 4 edges
Slice membership and algebra¶
In [3]:
Copied!
edge_lookup = {}
for row in G.views.edges().iter_rows(named=True):
arrow = '->' if row['directed'] else '--'
edge_lookup[row['edge_id']] = f"{row['source']} {arrow} {row['target']}"
readable = lambda edge_ids: [edge_lookup[eid] for eid in sorted(edge_ids)]
union = G.slices.union(['control', 'treated'])
common = G.slices.intersect(['control', 'treated'])
treated_only = G.slices.difference('treated', 'control')
print('control:', readable(G.slices.edges('control')))
print('treated:', readable(G.slices.edges('treated')))
print('union:', readable(union['edges']))
print('common:', readable(common['edges']))
print('treated only:', readable(treated_only['edges']))
edge_lookup = {}
for row in G.views.edges().iter_rows(named=True):
arrow = '->' if row['directed'] else '--'
edge_lookup[row['edge_id']] = f"{row['source']} {arrow} {row['target']}"
readable = lambda edge_ids: [edge_lookup[eid] for eid in sorted(edge_ids)]
union = G.slices.union(['control', 'treated'])
common = G.slices.intersect(['control', 'treated'])
treated_only = G.slices.difference('treated', 'control')
print('control:', readable(G.slices.edges('control')))
print('treated:', readable(G.slices.edges('treated')))
print('union:', readable(union['edges']))
print('common:', readable(common['edges']))
print('treated only:', readable(treated_only['edges']))
control: ['A -> B', 'B -> C'] treated: ['A -- E', 'B -> C', 'C -> D', 'D -> E'] union: ['A -- E', 'A -> B', 'B -> C', 'B -> C', 'C -> D', 'D -> E'] common: [] treated only: ['A -- E', 'B -> C', 'C -> D', 'D -> E']
Draw the full context graph¶
The graph contains all context-specific edges. Slices decide which of those edges participate in a given context.
In [4]:
Copied!
from annnet.utils import plotting
plotting.plot(G, backend='graphviz', show_edge_labels=True)
from annnet.utils import plotting
plotting.plot(G, backend='graphviz', show_edge_labels=True)
Out[4]:
Per-slice edge weights¶
In [5]:
Copied!
G.slices.add_edge_to_slice('treated', 'e2')
G.attrs.set_edge_slice_attrs('treated', 'e2', weight=8.0)
print('base e2 weight:', G.attrs.get_effective_edge_weight('e2'))
print('treated e2 weight:', G.attrs.get_effective_edge_weight('e2', slice='treated'))
G.slices.add_edge_to_slice('treated', 'e2')
G.attrs.set_edge_slice_attrs('treated', 'e2', weight=8.0)
print('base e2 weight:', G.attrs.get_effective_edge_weight('e2'))
print('treated e2 weight:', G.attrs.get_effective_edge_weight('e2', slice='treated'))
base e2 weight: 2.0 treated e2 weight: 8.0
Materialize subgraphs¶
In [6]:
Copied!
treated_graph = G.subgraph_from_slice('treated')
edge_graph = G.ops.edge_subgraph(['e2', 'e3'])
reversed_graph = G.ops.reverse()
print('treated shape:', treated_graph.shape)
print('edge-subgraph shape:', edge_graph.shape)
print('reversed e1:', reversed_graph.edge_definitions['e1'])
treated_graph = G.subgraph_from_slice('treated')
edge_graph = G.ops.edge_subgraph(['e2', 'e3'])
reversed_graph = G.ops.reverse()
print('treated shape:', treated_graph.shape)
print('edge-subgraph shape:', edge_graph.shape)
print('reversed e1:', reversed_graph.edge_definitions['e1'])
treated shape: (5, 5)
edge-subgraph shape: (3, 2)
reversed e1: ('B', 'A', 'binary')
Draw the treated subgraph¶
In [7]:
Copied!
plotting.plot(treated_graph, backend='graphviz', show_edge_labels=True)
plotting.plot(treated_graph, backend='graphviz', show_edge_labels=True)
Out[7]:
The treated subgraph materializes only the selected context:
treated-only edges, the undirected context edge, and the
slice-specific override on e2 are included in the derived graph.