Skip to content

Attributes

anet.attrs holds all attributes of a graph, at eight addresses: nodes, edges, slices, aspects, layers, edge_slices, node_layers and elementary_layers. You read and write each address the same way:

  • a read (nodes, table, row, rows) gives a copy;
  • a write is a named call (update, replace, delete);
  • select is a live query, and from_frame makes a fixed selection from the rows of a table that you filtered yourself.

anet.attrs is an Attrs object. select and from_frame give a RowSelection, and schema gives a Schema. The annotations guide shows the use.

Addresses

An address is the thing that a set of attributes belongs to. Each address has its key:

address key key columns in a table
nodes node_id node_id
edges edge_id edge_id
slices slice_id slice_id
aspects aspect aspect
layers layer coordinate tuple layer
edge_slices (slice_id, edge_id) slice_id, edge_id
node_layers (node_id, layer) node_id, layer
elementary_layers (aspect, label) aspect, elementary_layer
# Read: each call gives a copy. A change to the copy does not change the graph.
anet.attrs.nodes  # the table of one address; one property for each address
anet.attrs.table('nodes', derived=False, backend=None, columns=None, limit=None, layout=None)
anet.attrs.row('nodes', key)  # one row, as a dict
anet.attrs.rows('nodes', keys=None)  # {key: dict}
anet.attrs.schema('nodes')  # the keys and fields, without a table

# Write
anet.attrs.update('nodes', {key: {...}, ...})  # merge the given fields
anet.attrs.replace('nodes', table)  # replace all attributes of the address
anet.attrs.delete('nodes', keys=None, names=None)  # remove attributes

# Select
anet.attrs.select('nodes', score__gte=0.5)  # a live selection
anet.attrs.from_frame('nodes', frame)  # a fixed selection

anet.attrs has no item access, and you cannot assign to its table properties. anet.attrs['nodes'] and anet.attrs.nodes = table raise TypeError, with the name of the call to use.

Rules for writes

  • A null value removes the field. If update gets None or NaN for a field, annnet removes the field from the row. A null cell in a table means the same.
  • delete removes attributes, never elements. The node, edge, slice or layer stays. The return value is the number of rows that lost an attribute.

    keys names result
    given not given remove all attributes of these rows
    not given given remove these fields from all rows
    given given remove these fields from these rows
    not given not given ValueError

    An empty keys or names selects nothing. A name that the address does not have raises KeyError. - replace needs the key columns of the address. A key that the table does not have loses its attributes. A table never adds or removes elements. - A write is all or nothing. annnet checks the full write before it changes anything. If one part fails, the attributes, the fields and their types, and the cached matrices stay as they were. - Reserved fields are refused with ValueError: the key columns, and for edges source, target, head, tail, members, weight, directed, kind, slice, slice_weight, flexible and edge_type. - Keys are checked. An unknown key raises KeyError, a key of the wrong shape TypeError, and a key that occurs two times in one write ValueError. For node_layers, give (node_id, layer), or a node ID with layer= in update.

Derived tables

table(address, derived=True) adds what the graph knows about each row. For edges, these columns: kind (binary, hyper or node_edge), directed, weight, ml_kind (intra, inter or coupling), source, target, src_layer, dst_layer, head, tail and members.

With layout='incidences', the table has one row for each endpoint of each edge: edge_id, position, entity_id, entity_kind, layer, layer_id, role, coefficient, kind and directed. You can join an endpoint to its node, layer and edge rows without parsing.

limit= builds that number of rows and no more: use it for a preview, not as a selection. The arguments slice=, in_slice=, layer=, include_hyper=, include_binary=, include_directed=, include_weight= and resolved_weight= apply to the derived edge table only.

A live query and a filtered table

select is a query on the graph. It keeps its conditions, and gives a new result after the graph changes. A table from anet.attrs.table is a copy of one moment. To filter it, use your dataframe library, then make a selection from the result:

table = anet.attrs.table('nodes')
kept = table.filter(pl.col('score') > 0.5)
chosen = anet.attrs.from_frame('nodes', kept)  # a fixed selection

from_frame reads only the key columns. It gives the keys of the frame, in the order of the address, without repeats. It does not keep the filter: a node that you add later is not in it. A missing key column or a null key raises ValueError, an unknown key KeyError.

A selection of node_layers or edge_slices changes into a selection of another address only with project: select('node_layers', ...).project('nodes'), select('edge_slices', ...).project('edges') or .project('slices').

annnet.core._attribute_api.Attrs

Every attribute of a graph, at eight addresses (anet.attrs).

See the module docstring for the shape. The reader protocol the selection machinery uses is the set of underscored methods _domain, _row, _rows_where, _key, _selection_owner and _root_reader.

Attributes

backend property writable
backend

The dataframe backend every table is rendered in.

Functions

row
row(address, key)

Return one row as a detached dict.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
key key

The key of the row, as the address keys it: a node id, an edge id, a slice id, an aspect name, a layer coordinate, a (slice_id, edge_id) pair, a (node_id, layer) placement or an (aspect, label) pair.

required

Returns:

Type Description
dict

{name: value}; {} when the element carries no attribute. Editing it changes nothing in the graph. A cell is row(address, key).get(name, default).

Raises:

Type Description
KeyError

For a key the address does not hold (or, on a view, one outside it).

rows
rows(address, keys=None)

Return detached row dictionaries keyed by the address's keys.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
keys key | Iterable[key]

One key or many. Default: every key of the address.

None

Returns:

Type Description
dict

{key: {name: value}}. A key that carries nothing maps to {}.

schema
schema(address, *, derived=False)

Report what an address is keyed by and what it holds, cheaply.

No frame is built and no backing is scanned. The value fields are the names the store holds; their types are read off the column dtype or from the values present.

select
select(address, conditions=None, /, **keywords)

Select the rows of one address that satisfy every condition.

The conditions are the ones anet.N.select takes, over the address's key columns, its stored fields, and — for the layer-keyed addresses — the declared aspects, compared by their order when ordered. Keys are kept as the address keys them; see :meth:RowSelection.project.

project_keys
project_keys(rows, target)

The ids of target named by the rows, deduplicated in this reader's order.

update
update(address, updates, /, *, layer=None, key=None)

Merge attribute rows at one address, all of them or none.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
updates Mapping[key, Mapping[str, Any]] | Iterable[tuple[key, Mapping]]

The rows to merge. A field set to None (or NaN) is removed.

required
layer tuple

node_layers only: the layer of every bare node id in updates.

None
key str

node_layers only: the attribute name of every scalar value.

None

Returns:

Type Description
int

The number of rows written.

Raises:

Type Description
KeyError

For a key the address does not hold. Nothing is written.

ValueError

For a reserved field, a duplicate key or a composite-key collision.

TypeError

For a row that is not a mapping or a value of the wrong type.

Notes

Everything is checked before the first cell changes, and a failure inside the commit — a flexible-direction policy raising after it has re-oriented an edge, for instance — rolls back the rows, the composite-key index and the rewritten incidence columns. See :mod:annnet.core._transaction.

replace
replace(address, table)

Replace the attributes of a whole address with the rows of a table.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
table DataFrame - like

A table in any supported backend with the address's key columns and one column per attribute. Null cells mean "no such attribute".

required
Notes

A key the table omits loses its attributes and keeps its existence: the nodes, edges, slices and layers themselves are never added or removed by a table. The table is validated whole before anything changes, and the old attributes are restored if the write fails. Use :meth:update to change some fields of some rows.

Raises:

Type Description
ValueError

For a missing key column, a null or duplicate key or a reserved field.

KeyError

For a key the address does not hold.

delete
delete(address, keys=None, names=None)

Remove attributes, never the structure that carries them.

================ ====================================================== keys names ================ ====================================================== given omitted: clear every attribute of those rows omitted given: drop those fields from every row of the address given given: drop those fields from those rows omitted omitted: :class:ValueError, never "everything" ================ ======================================================

An explicit empty keys or names selects nothing and does nothing.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
keys key | Iterable[key] | RowSelection

The rows, keyed as the address keys them.

None
names str | Iterable[str]

The attribute fields.

None

Returns:

Type Description
int

The number of rows that lost at least one attribute.

Raises:

Type Description
ValueError

When neither keys nor names is given, or a name is reserved.

KeyError

For a key the address does not hold, or a name the address has no such attribute for.

from_frame
from_frame(address, frame)

Select the rows of a frame, by the keys it names.

Read a table, filter it with the dataframe library it came in, and pass what is left here to get a selection of the graph's own keys::

table = anet.attrs.table('nodes')
kept = table.filter(pl.col('score') > 0.5)
chosen = anet.attrs.from_frame('nodes', kept)

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
frame DataFrame - like

Any supported backend. Only the address's key columns are read.

required

Returns:

Type Description
RowSelection

A selection of exactly those keys, in the address's own order and without repeats. It is fixed: it is the identities the frame named, not the condition that produced the frame, so a key added later is not picked up, and a key that is removed and added again is the same key. An empty frame gives an empty selection.

Raises:

Type Description
ValueError

For a missing key column or a null key.

KeyError

For a key the address does not hold (or, on a view, one outside it).

table
table(
    address,
    *,
    derived=False,
    backend=None,
    columns=None,
    limit=None,
    layout=None,
    **query
)

Render one address as a table, stored or derived.

Parameters:

Name Type Description Default
address str

One of :data:ADDRESSES.

required
derived bool

True asks for everything the graph can say about the subject — structural columns, joins and filters — through **query. False is the stored attributes alone.

False
backend str

One of the dataframe backends; defaults to :attr:backend.

None
columns Iterable[str]

Keep only these value columns. The key columns are always kept.

None
limit int

Keep only the first limit rows, in the address's order. A preview, never a selection: nothing about the graph or the view changes.

None
layout ('edges', 'incidences')

edges (the default) gives one row per edge; incidences gives one row per edge endpoint with the participant's identity, layer, role and coefficient. Edges only, derived only.

"edges"
**query

Derived-table arguments: slice= (join one slice's overrides), in_slice= (keep members of one slice), layer=, include_hyper=, include_binary=, include_directed=, include_weight=, resolved_weight= for edges. See :mod:annnet.core._tables.

{}

Returns:

Type Description
DataFrame - like

A caller-owned frame; editing it changes nothing.

audit
audit()

Report attribute rows whose structural keys no longer exist.

The generic tables are derived from slot-indexed columns, so they can hold no extra row and miss none; what an audit still finds is a contextual row naming a slice, edge, node, layer or placement the graph does not hold.

annnet.core._attribute_api.Schema

What one address is keyed by and what it holds, without building a frame.

Functions

names
names()

Every column name, keys first.

annnet.core._select.RowSelection

The rows of one attribute address that satisfy a query, keyed as the address keys them.

Built by anet.attrs.select / V.attrs.select. It keeps the full keys — a node-layer row keeps its (node_id, layer) — until :meth:project is called. It is live: .keys re-evaluates the query against the graph as it is now, each leaf inside the scope it was asked in. Two selections of the same address on the same graph combine with &, | and -; the result keeps the address's own order.

Attributes

keys property
keys

The keys of the matching rows, in the address's own order.

ids property
ids

Alias of :attr:keys, so every selection answers .ids.

Functions

rows
rows()

The matching rows as detached dictionaries, keyed by their keys.

project
project(target)

Project the selected rows onto one axis of their keys.

node_layers rows project to nodes; edge_slices rows project to edges or slices. The projection is existential — an element is selected when at least one matching row names it — and it keeps the target axis's order with duplicates removed. It does not say "in every slice"; that is a reduction over the frame, not a selection.

Returns:

Type Description
NodeSequence | EdgeSequence | SliceSelection

A live selection of the target axis, in the scope of this selection.

Predicates

All select calls (anet.N.select, anet.E.select, anet.attrs.select, anet.layers.where) take the same conditions: field=value, or field__operator=value. The operators are eq, ne, in, not_in, lt, lte, gt, gte and isnull.

  • A null value matches only isnull=True.
  • An ordered aspect compares labels in their declared order. An ordering operator on a categorical aspect raises ValueError.
  • An unknown field raises KeyError. Values that cannot be compared raise TypeError.
  • For a field name that contains __, give a mapping with (field, operator) keys.

annnet.core._predicate

The one predicate engine every selection surface shares.

anet.N.select(score__gt=0.5), anet.E.select(kind='hyper'), anet.attrs.select('node_layers', score__gte=0.8) and anet.layers.where(time__lte='12h') are four spellings of one question: which rows of this axis satisfy these conditions. They parse the conditions here, evaluate them here, and report a bad field, operator or value the same way.

A condition is field=value (equality) or field__operator=value with one of :data:OPERATORS. A literal field whose own name contains __ wins over suffix parsing, and a positional mapping may use explicit (field, operator) keys where a keyword would be ambiguous.

Null handling is one rule everywhere: a missing value — None or a float NaN — is excluded by every comparison, including ne and not_in, and selected only by an explicit field__isnull=True.

An ordered aspect compares by its declared order, through order=; a categorical aspect refuses the four ordering operators. A numeric attribute compares numerically. Two values that cannot be compared raise, naming the field and the operator, rather than silently dropping the row.

Attributes

OPERATORS module-attribute
OPERATORS = (
    "eq",
    "ne",
    "in",
    "not_in",
    "lt",
    "lte",
    "gt",
    "gte",
    "isnull",
)

Functions

parse_conditions
parse_conditions(
    conditions=None,
    /,
    *,
    fields=None,
    noun="field",
    **keywords,
)

Parse conditions into :class:Condition triples.

Parameters:

Name Type Description Default
conditions Mapping

A positional mapping. A key is a keyword as below, or an explicit (field, operator) tuple.

None
fields Iterable[str]

The fields the axis knows. A field that is not one of them raises, naming the known fields. A field whose name holds __ is matched literally before any suffix is read off it. None skips the check.

None
noun str

What a field is called in the error message ("aspect" for a layer window).

"field"
**keywords

field=value or field__operator=value.

{}

Returns:

Type Description
list[Condition]

Raises:

Type Description
KeyError

If a field is not one of fields.

ValueError

If an operator is not one of :data:OPERATORS.

TypeError

If the value does not fit the operator (a membership operator needs a collection, isnull needs a bool).