Skip to content

between_types

lab.connectome.between_types

Synapse totals between cell types, and the edges they make.

network_edges(out_mass, in_mass, lateral=None, *, top_per_type, min_synapses=0)

Pick the edges of a network of types and their partner types.

A partner type is kept for a type when it is among the top_per_type partner types of that type by the synapses in both directions together. The edges are those to the kept partner types and the lateral edges between different types, each with at least min_synapses.

Parameters:

Name Type Description Default
out_mass DataFrame

Columns type, partner_type, and synapses, as strength.type_mass returns them for outputs and for inputs.

required
in_mass DataFrame

Columns type, partner_type, and synapses, as strength.type_mass returns them for outputs and for inputs.

required
lateral DataFrame

Columns pre_type, post_type, and synapses, as synapses_between_types returns them. Without it the network has no lateral edges.

None
top_per_type int

Partner types kept for each type.

required
min_synapses int

Edges below this many synapses are dropped.

0

Returns:

Type Description
DataFrame

Columns src, dst, synapses, and kind, which is "out", "in", or "lateral".

See Also

synapses_between_types : Gives lateral.

Examples:

>>> import polars as pl
>>> out_mass = pl.DataFrame(
...     {
...         "type": ["T", "T"],
...         "partner_type": ["X", "Y"],
...         "synapses": [50, 10],
...     }
... )
>>> in_mass = pl.DataFrame(
...     {"type": ["T"], "partner_type": ["X"], "synapses": [5]}
... )
>>> network_edges(out_mass, in_mass, top_per_type=1).rows()
[('T', 'X', 50, 'out'), ('X', 'T', 5, 'in')]
Source code in lab/connectome/between_types.py
def network_edges(
    out_mass: pl.DataFrame,
    in_mass: pl.DataFrame,
    lateral: pl.DataFrame | None = None,
    *,
    top_per_type: int,
    min_synapses: int = 0,
) -> pl.DataFrame:
    """Pick the edges of a network of types and their partner types.

    A partner type is kept for a type when it is among the
    `top_per_type` partner types of that type by the synapses in both
    directions together.
    The edges are those to the kept partner types and the lateral
    edges between different types, each with at least `min_synapses`.

    Parameters
    ----------
    out_mass, in_mass : polars.DataFrame
        Columns ``type``, ``partner_type``, and ``synapses``, as
        `strength.type_mass` returns them for outputs and for inputs.
    lateral : polars.DataFrame, optional
        Columns ``pre_type``, ``post_type``, and ``synapses``, as
        `synapses_between_types` returns them.
        Without it the network has no lateral edges.
    top_per_type : int
        Partner types kept for each type.
    min_synapses : int, default 0
        Edges below this many synapses are dropped.

    Returns
    -------
    polars.DataFrame
        Columns ``src``, ``dst``, ``synapses``, and ``kind``, which is
        ``"out"``, ``"in"``, or ``"lateral"``.

    See Also
    --------
    synapses_between_types : Gives `lateral`.

    Examples
    --------
    >>> import polars as pl
    >>> out_mass = pl.DataFrame(
    ...     {
    ...         "type": ["T", "T"],
    ...         "partner_type": ["X", "Y"],
    ...         "synapses": [50, 10],
    ...     }
    ... )
    >>> in_mass = pl.DataFrame(
    ...     {"type": ["T"], "partner_type": ["X"], "synapses": [5]}
    ... )
    >>> network_edges(out_mass, in_mass, top_per_type=1).rows()
    [('T', 'X', 50, 'out'), ('X', 'T', 5, 'in')]
    """
    outputs = out_mass.select(
        src="type", dst="partner_type", synapses="synapses", kind=pl.lit("out")
    )
    inputs = in_mass.select(
        src="partner_type", dst="type", synapses="synapses", kind=pl.lit("in")
    )
    both = pl.concat(
        [
            outputs.with_columns(type=pl.col("src"), partner=pl.col("dst")),
            inputs.with_columns(type=pl.col("dst"), partner=pl.col("src")),
        ]
    )
    top = (
        both.group_by("type", "partner")
        .agg(pl.col("synapses").sum())
        .sort("synapses", descending=True)
        .group_by("type", maintain_order=True)
        .head(top_per_type)
        .select("type", "partner")
    )
    edges = both.join(top, on=["type", "partner"]).select(
        "src", "dst", "synapses", "kind"
    )
    if lateral is not None:
        edges = pl.concat(
            [
                lateral.filter(pl.col("pre_type") != pl.col("post_type"))
                .select(
                    src="pre_type",
                    dst="post_type",
                    synapses="synapses",
                    kind=pl.lit("lateral"),
                )
                .select(edges.columns),
                edges,
            ]
        )
    return edges.filter(pl.col("synapses") >= min_synapses)

synapses_between_types(connections, types)

Sum the synapses between every ordered pair of types.

Parameters:

Name Type Description Default
connections DataFrame

Has columns pre_type, post_type, and weight. A row where either type is null is dropped.

required
types sequence of str

The types to tabulate, in the order of the result.

required

Returns:

Type Description
DataFrame

Columns pre_type, post_type, and synapses, one row for every ordered pair of types, the diagonal included, 0 where there is no connection.

Examples:

>>> import polars as pl
>>> connections = pl.DataFrame(
...     {
...         "pre_type": ["A", "A", "B"],
...         "post_type": ["B", "B", "A"],
...         "weight": [10, 2, 7],
...     }
... )
>>> synapses_between_types(connections, ["A", "B"]).rows()
[('A', 'A', 0), ('A', 'B', 12), ('B', 'A', 7), ('B', 'B', 0)]
Source code in lab/connectome/between_types.py
def synapses_between_types(
    connections: pl.DataFrame, types: Sequence[str]
) -> pl.DataFrame:
    """Sum the synapses between every ordered pair of types.

    Parameters
    ----------
    connections : polars.DataFrame
        Has columns ``pre_type``, ``post_type``, and ``weight``.
        A row where either type is null is dropped.
    types : sequence of str
        The types to tabulate, in the order of the result.

    Returns
    -------
    polars.DataFrame
        Columns ``pre_type``, ``post_type``, and ``synapses``, one row
        for every ordered pair of `types`, the diagonal included, 0
        where there is no connection.

    Examples
    --------
    >>> import polars as pl
    >>> connections = pl.DataFrame(
    ...     {
    ...         "pre_type": ["A", "A", "B"],
    ...         "post_type": ["B", "B", "A"],
    ...         "weight": [10, 2, 7],
    ...     }
    ... )
    >>> synapses_between_types(connections, ["A", "B"]).rows()
    [('A', 'A', 0), ('A', 'B', 12), ('B', 'A', 7), ('B', 'B', 0)]
    """
    every_pair = pl.DataFrame(
        {
            "pre_type": [a for a in types for _ in types],
            "post_type": [b for _ in types for b in types],
        }
    )
    sums = (
        connections.drop_nulls(["pre_type", "post_type"])
        .group_by("pre_type", "post_type")
        .agg(synapses=pl.col("weight").sum())
    )
    return (
        every_pair.join(sums, on=["pre_type", "post_type"], how="left")
        .with_columns(pl.col("synapses").fill_null(0))
        .sort(
            pl.col("pre_type").replace_strict(
                {t: n for n, t in enumerate(types)}
            ),
            pl.col("post_type").replace_strict(
                {t: n for n, t in enumerate(types)}
            ),
        )
    )