Skip to content

formula

Formula helpers and DSL.

This module provides a small Pythonic DSL for composing brms formulas. The public functions (bf, lf, nlf, acformula, set_rescor, set_mecor, set_nl) build a FormulaConstruct that can be passed to brmspy.brms.brm() or combined using the + operator.

Notes
  • The returned objects are lightweight formula specifications; the actual R brms formula object is built in the worker when fitting / generating Stan code.
  • This module is part of the public API documented under docs/api/brms_functions/formula.md.

Attributes

_FORMULA_FUNCTION_WHITELIST = Literal['bf', 'lf', 'nlf', 'acformula', 'set_rescor', 'set_mecor', 'set_nl'] module-attribute

ProxyListSexpVector = Union[SexpWrapper, ListSexpVector, None] module-attribute

Classes

SexpWrapper dataclass

Lightweight handle for an R object stored in the worker.

The worker keeps the real rpy2 Sexp in an internal cache and replaces it in results with this wrapper. When passed back to the worker, the wrapper is resolved to the original Sexp again.

Notes
  • SexpWrapper instances are only meaningful within the lifetime of the worker process that produced them. After a worker restart, previously returned wrappers can no longer be reattached.
  • This type exists to keep the main process free of rpy2 / embedded-R state.
Source code in brmspy/types/session.py
@dataclass
class SexpWrapper:
    """
    Lightweight handle for an R object stored in the worker.

    The worker keeps the real rpy2 `Sexp` in an internal cache and replaces it in
    results with this wrapper. When passed back to the worker, the wrapper is
    resolved to the original `Sexp` again.

    Notes
    -----
    - `SexpWrapper` instances are only meaningful within the lifetime of the
      worker process that produced them. After a worker restart, previously
      returned wrappers can no longer be reattached.
    - This type exists to keep the main process free of rpy2 / embedded-R state.
    """

    _rid: int
    _repr: str

    def __str__(self) -> str:
        return self._repr

    def __repr__(self) -> str:
        return self._repr

Attributes

_rid instance-attribute
_repr instance-attribute

Functions

__str__()
Source code in brmspy/types/session.py
def __str__(self) -> str:
    return self._repr
__repr__()
Source code in brmspy/types/session.py
def __repr__(self) -> str:
    return self._repr
__init__(_rid, _repr)

FormulaConstruct dataclass

A composite formula expression built from parts.

FormulaConstruct stores a tree of nodes (FormulaPart and/or R objects) representing expressions combined with +. It is primarily created by calling the public formula helpers exposed by brmspy.brms.

Notes

The + operator supports grouping:

  • a + b + c becomes a single summand (one “group”)
  • (a + b) + (a + b) becomes two summands (two “groups”)

Use iter_summands() to iterate over these groups in a deterministic way.

Source code in brmspy/types/formula_dsl.py
@dataclass
class FormulaConstruct:
    """
    A composite formula expression built from parts.

    `FormulaConstruct` stores a tree of nodes (`FormulaPart` and/or R objects)
    representing expressions combined with `+`. It is primarily created by
    calling the public formula helpers exposed by [`brmspy.brms`][brmspy.brms].

    Notes
    -----
    The `+` operator supports grouping:

    - `a + b + c` becomes a single summand (one “group”)
    - `(a + b) + (a + b)` becomes two summands (two “groups”)

    Use [`iter_summands()`][brmspy.types.formula_dsl.FormulaConstruct.iter_summands]
    to iterate over these groups in a deterministic way.
    """

    _parts: list[Node]

    @classmethod
    def _formula_parse(cls, obj: Other) -> "FormulaConstruct":
        """
        Convert a supported value into a `FormulaConstruct`.

        Parameters
        ----------
        obj
            One of: `FormulaConstruct`, `FormulaPart`, string (interpreted as `bf(<string>)`),
            or `ProxyListSexpVector`.

        Returns
        -------
        FormulaConstruct
        """
        if isinstance(obj, FormulaConstruct):
            return obj
        if isinstance(obj, ProxyListSexpVector):
            return FormulaConstruct(_parts=[obj])
        if isinstance(obj, FormulaPart):
            return FormulaConstruct(_parts=[obj])
        if isinstance(obj, str):
            part = FormulaPart(_fun="bf", _args=[obj], _kwargs={})
            return FormulaConstruct(_parts=[part])
        raise TypeError(
            f"Cannot parse object of type {type(obj)!r} into FormulaConstruct"
        )

    def __add__(self, other: Other):
        """
        Combine two formula expressions with `+`.

        Parameters
        ----------
        other
            Value to add. Strings are treated as `bf(<string>)`.

        Returns
        -------
        FormulaConstruct
            New combined expression.
        """
        if isinstance(other, (FormulaPart, str, ProxyListSexpVector)):
            other = FormulaConstruct._formula_parse(other)

        if not isinstance(other, FormulaConstruct):
            raise ArithmeticError(
                "When adding values to formula, they must be FormulaConstruct or parseable to FormulaConstruct"
            )

        if len(other._parts) <= 1:
            return FormulaConstruct(_parts=self._parts + other._parts)
        else:
            return FormulaConstruct(_parts=[self._parts, other._parts])

    def __radd__(self, other: Other) -> "FormulaConstruct":
        """Support `"y ~ x" + bf("z ~ 1")` by coercing the left operand."""
        return self._formula_parse(other) + self

    def iter_summands(self) -> Iterator[Summand]:
        """
        Iterate over arithmetic groups (summands).

        Returns
        -------
        Iterator[tuple[FormulaPart | ProxyListSexpVector, ...]]
            Each yielded tuple represents one summand/group.

        Examples
        --------
        ```python
        from brmspy.brms import bf, gaussian, set_rescor

        f = bf("y ~ x") + gaussian() + set_rescor(True)
        for summand in f.iter_summands():
            print(summand)
        ```
        """

        def _groups(node: Node) -> Iterator[list[FormulaPart | ProxyListSexpVector]]:
            # Leaf node: single bf/family/etc
            if isinstance(node, (FormulaPart, ProxyListSexpVector)):
                return ([node],)  # one group with one element

            if isinstance(node, list):
                # If any child is a list, this node represents a "+"
                # between sub-expressions, so recurse into each child.
                if any(isinstance(child, list) for child in node):
                    for child in node:
                        yield from _groups(child)
                else:
                    # All children are leaves -> one summand
                    out: list[FormulaPart | ProxyListSexpVector] = []
                    for child in node:
                        if isinstance(child, (FormulaPart, ProxyListSexpVector, Sexp)):
                            child = cast(FormulaPart | ProxyListSexpVector, child)
                            out.append(child)
                        else:
                            raise TypeError(
                                f"Unexpected leaf node type in FormulaConstruct: {type(child)!r}"
                            )
                    yield out
                return

            raise TypeError(f"Unexpected node type in FormulaConstruct: {type(node)!r}")

        # self._parts is always a list[Node]
        for group in _groups(self._parts):
            yield tuple(group)

    # Make __iter__ return summands by default
    def __iter__(self) -> Iterator[Summand]:
        """Alias for [`iter_summands()`][brmspy.types.formula_dsl.FormulaConstruct.iter_summands]."""
        return self.iter_summands()

    def iterate(self) -> Iterator[FormulaPart | ProxyListSexpVector]:
        """
        Iterate over all leaf nodes in left-to-right order.

        This flattens the expression tree, unlike
        [`iter_summands()`][brmspy.types.formula_dsl.FormulaConstruct.iter_summands], which
        respects grouping.

        Returns
        -------
        Iterator[FormulaPart | ProxyListSexpVector]
        """

        def _walk(node: Node) -> Iterator[FormulaPart | ProxyListSexpVector]:
            if isinstance(node, FormulaPart):
                yield node
            elif isinstance(node, ProxyListSexpVector):
                yield node
            elif isinstance(node, list):
                for child in node:
                    yield from _walk(child)
            else:
                raise TypeError(
                    f"Unexpected node type in FormulaConstruct: {type(node)!r}"
                )

        for root in self._parts:
            yield from _walk(root)

    def __str__(self) -> str:
        return self._pretty(self._parts)

    def _pretty(self, node, _outer=True) -> str:
        if isinstance(node, FormulaPart):
            return str(node)

        if isinstance(node, (ProxyListSexpVector, Sexp)):
            return _sexp_to_str(node)

        if isinstance(node, list):
            # Pretty-print each child
            rendered = [self._pretty(child, _outer=False) for child in node]

            # If only one child, no parentheses needed
            if len(rendered) == 1:
                return rendered[0]

            # Multiple children → join with " + "
            inner = " + ".join(rendered)
            if _outer:
                return inner
            else:
                return f"({inner})"

        raise TypeError(f"Unexpected node type {type(node)!r} in pretty-printer")

    def __repr__(self) -> str:
        return self.__str__()

Attributes

_parts instance-attribute

Functions

_formula_parse(obj) classmethod

Convert a supported value into a FormulaConstruct.

Parameters:

Name Type Description Default
obj Other

One of: FormulaConstruct, FormulaPart, string (interpreted as bf(<string>)), or ProxyListSexpVector.

required

Returns:

Type Description
FormulaConstruct
Source code in brmspy/types/formula_dsl.py
@classmethod
def _formula_parse(cls, obj: Other) -> "FormulaConstruct":
    """
    Convert a supported value into a `FormulaConstruct`.

    Parameters
    ----------
    obj
        One of: `FormulaConstruct`, `FormulaPart`, string (interpreted as `bf(<string>)`),
        or `ProxyListSexpVector`.

    Returns
    -------
    FormulaConstruct
    """
    if isinstance(obj, FormulaConstruct):
        return obj
    if isinstance(obj, ProxyListSexpVector):
        return FormulaConstruct(_parts=[obj])
    if isinstance(obj, FormulaPart):
        return FormulaConstruct(_parts=[obj])
    if isinstance(obj, str):
        part = FormulaPart(_fun="bf", _args=[obj], _kwargs={})
        return FormulaConstruct(_parts=[part])
    raise TypeError(
        f"Cannot parse object of type {type(obj)!r} into FormulaConstruct"
    )
__add__(other)

Combine two formula expressions with +.

Parameters:

Name Type Description Default
other Other

Value to add. Strings are treated as bf(<string>).

required

Returns:

Type Description
FormulaConstruct

New combined expression.

Source code in brmspy/types/formula_dsl.py
def __add__(self, other: Other):
    """
    Combine two formula expressions with `+`.

    Parameters
    ----------
    other
        Value to add. Strings are treated as `bf(<string>)`.

    Returns
    -------
    FormulaConstruct
        New combined expression.
    """
    if isinstance(other, (FormulaPart, str, ProxyListSexpVector)):
        other = FormulaConstruct._formula_parse(other)

    if not isinstance(other, FormulaConstruct):
        raise ArithmeticError(
            "When adding values to formula, they must be FormulaConstruct or parseable to FormulaConstruct"
        )

    if len(other._parts) <= 1:
        return FormulaConstruct(_parts=self._parts + other._parts)
    else:
        return FormulaConstruct(_parts=[self._parts, other._parts])
__radd__(other)

Support "y ~ x" + bf("z ~ 1") by coercing the left operand.

Source code in brmspy/types/formula_dsl.py
def __radd__(self, other: Other) -> "FormulaConstruct":
    """Support `"y ~ x" + bf("z ~ 1")` by coercing the left operand."""
    return self._formula_parse(other) + self
iter_summands()

Iterate over arithmetic groups (summands).

Returns:

Type Description
Iterator[tuple[FormulaPart | ProxyListSexpVector, ...]]

Each yielded tuple represents one summand/group.

Examples:

from brmspy.brms import bf, gaussian, set_rescor

f = bf("y ~ x") + gaussian() + set_rescor(True)
for summand in f.iter_summands():
    print(summand)
Source code in brmspy/types/formula_dsl.py
def iter_summands(self) -> Iterator[Summand]:
    """
    Iterate over arithmetic groups (summands).

    Returns
    -------
    Iterator[tuple[FormulaPart | ProxyListSexpVector, ...]]
        Each yielded tuple represents one summand/group.

    Examples
    --------
    ```python
    from brmspy.brms import bf, gaussian, set_rescor

    f = bf("y ~ x") + gaussian() + set_rescor(True)
    for summand in f.iter_summands():
        print(summand)
    ```
    """

    def _groups(node: Node) -> Iterator[list[FormulaPart | ProxyListSexpVector]]:
        # Leaf node: single bf/family/etc
        if isinstance(node, (FormulaPart, ProxyListSexpVector)):
            return ([node],)  # one group with one element

        if isinstance(node, list):
            # If any child is a list, this node represents a "+"
            # between sub-expressions, so recurse into each child.
            if any(isinstance(child, list) for child in node):
                for child in node:
                    yield from _groups(child)
            else:
                # All children are leaves -> one summand
                out: list[FormulaPart | ProxyListSexpVector] = []
                for child in node:
                    if isinstance(child, (FormulaPart, ProxyListSexpVector, Sexp)):
                        child = cast(FormulaPart | ProxyListSexpVector, child)
                        out.append(child)
                    else:
                        raise TypeError(
                            f"Unexpected leaf node type in FormulaConstruct: {type(child)!r}"
                        )
                yield out
            return

        raise TypeError(f"Unexpected node type in FormulaConstruct: {type(node)!r}")

    # self._parts is always a list[Node]
    for group in _groups(self._parts):
        yield tuple(group)
__iter__()

Alias for iter_summands().

Source code in brmspy/types/formula_dsl.py
def __iter__(self) -> Iterator[Summand]:
    """Alias for [`iter_summands()`][brmspy.types.formula_dsl.FormulaConstruct.iter_summands]."""
    return self.iter_summands()
iterate()

Iterate over all leaf nodes in left-to-right order.

This flattens the expression tree, unlike iter_summands(), which respects grouping.

Returns:

Type Description
Iterator[FormulaPart | ProxyListSexpVector]
Source code in brmspy/types/formula_dsl.py
def iterate(self) -> Iterator[FormulaPart | ProxyListSexpVector]:
    """
    Iterate over all leaf nodes in left-to-right order.

    This flattens the expression tree, unlike
    [`iter_summands()`][brmspy.types.formula_dsl.FormulaConstruct.iter_summands], which
    respects grouping.

    Returns
    -------
    Iterator[FormulaPart | ProxyListSexpVector]
    """

    def _walk(node: Node) -> Iterator[FormulaPart | ProxyListSexpVector]:
        if isinstance(node, FormulaPart):
            yield node
        elif isinstance(node, ProxyListSexpVector):
            yield node
        elif isinstance(node, list):
            for child in node:
                yield from _walk(child)
        else:
            raise TypeError(
                f"Unexpected node type in FormulaConstruct: {type(node)!r}"
            )

    for root in self._parts:
        yield from _walk(root)
__str__()
Source code in brmspy/types/formula_dsl.py
def __str__(self) -> str:
    return self._pretty(self._parts)
_pretty(node, _outer=True)
Source code in brmspy/types/formula_dsl.py
def _pretty(self, node, _outer=True) -> str:
    if isinstance(node, FormulaPart):
        return str(node)

    if isinstance(node, (ProxyListSexpVector, Sexp)):
        return _sexp_to_str(node)

    if isinstance(node, list):
        # Pretty-print each child
        rendered = [self._pretty(child, _outer=False) for child in node]

        # If only one child, no parentheses needed
        if len(rendered) == 1:
            return rendered[0]

        # Multiple children → join with " + "
        inner = " + ".join(rendered)
        if _outer:
            return inner
        else:
            return f"({inner})"

    raise TypeError(f"Unexpected node type {type(node)!r} in pretty-printer")
__repr__()
Source code in brmspy/types/formula_dsl.py
def __repr__(self) -> str:
    return self.__str__()
__init__(_parts)

FormulaPart dataclass

A single formula helper invocation.

Instances of this type represent a call like bf("y ~ x") or set_rescor(True) without executing anything. They are primarily used as nodes inside a FormulaConstruct.

Parameters:

Name Type Description Default
_fun Literal[...]

Whitelisted formula helper name.

required
_args Sequence[Primitive]

Positional arguments for the helper.

required
_kwargs Mapping[str, Primitive]

Keyword arguments for the helper.

required
Notes

This is a low-level type. Most users should construct these via the public helper functions in brmspy.brms.

Source code in brmspy/types/formula_dsl.py
@dataclass
class FormulaPart:
    """
    A single formula helper invocation.

    Instances of this type represent a call like `bf("y ~ x")` or `set_rescor(True)`
    without executing anything. They are primarily used as nodes inside a
    [`FormulaConstruct`][brmspy.types.formula_dsl.FormulaConstruct].

    Parameters
    ----------
    _fun : Literal[...]
        Whitelisted formula helper name.
    _args : Sequence[Primitive]
        Positional arguments for the helper.
    _kwargs : Mapping[str, Primitive]
        Keyword arguments for the helper.

    Notes
    -----
    This is a low-level type. Most users should construct these via the public
    helper functions in [`brmspy.brms`][brmspy.brms].
    """

    _fun: _FORMULA_FUNCTION_WHITELIST
    _args: Sequence[Primitive]
    _kwargs: Mapping[str, Primitive]

    def __post_init__(self):
        """Validate `_fun`, `_args`, and `_kwargs` types after construction."""
        # Validate function name first
        if self._fun not in get_args(_FORMULA_FUNCTION_WHITELIST):
            raise ValueError(
                f"FormulaPart._fun must be one of {_FORMULA_FUNCTION_WHITELIST!r}, "
                f"got {self._fun!r}"
            )

        # Enforce _args is a list
        if not isinstance(self._args, Sequence):
            raise TypeError(
                f"FormulaPart._args must be a Sequence, got {type(self._args).__name__}"
            )

        # Enforce _kwargs is a dict
        if not isinstance(self._kwargs, Mapping):
            raise TypeError(
                f"FormulaPart._kwargs must be a Mapping, got {type(self._kwargs).__name__}"
            )

    def __str__(self) -> str:
        """Render a readable `fun(arg1, ..., kw=...)` representation."""
        args = ", ".join(repr(a) for a in self._args)
        kwargs = ", ".join(f"{k}={v!r}" for k, v in self._kwargs.items())
        inner = ", ".join(x for x in (args, kwargs) if x)
        return f"{self._fun}({inner})"

    def __repr__(self) -> str:
        return self.__str__()

Attributes

_fun instance-attribute
_args instance-attribute
_kwargs instance-attribute

Functions

__post_init__()

Validate _fun, _args, and _kwargs types after construction.

Source code in brmspy/types/formula_dsl.py
def __post_init__(self):
    """Validate `_fun`, `_args`, and `_kwargs` types after construction."""
    # Validate function name first
    if self._fun not in get_args(_FORMULA_FUNCTION_WHITELIST):
        raise ValueError(
            f"FormulaPart._fun must be one of {_FORMULA_FUNCTION_WHITELIST!r}, "
            f"got {self._fun!r}"
        )

    # Enforce _args is a list
    if not isinstance(self._args, Sequence):
        raise TypeError(
            f"FormulaPart._args must be a Sequence, got {type(self._args).__name__}"
        )

    # Enforce _kwargs is a dict
    if not isinstance(self._kwargs, Mapping):
        raise TypeError(
            f"FormulaPart._kwargs must be a Mapping, got {type(self._kwargs).__name__}"
        )
__str__()

Render a readable fun(arg1, ..., kw=...) representation.

Source code in brmspy/types/formula_dsl.py
def __str__(self) -> str:
    """Render a readable `fun(arg1, ..., kw=...)` representation."""
    args = ", ".join(repr(a) for a in self._args)
    kwargs = ", ".join(f"{k}={v!r}" for k, v in self._kwargs.items())
    inner = ", ".join(x for x in (args, kwargs) if x)
    return f"{self._fun}({inner})"
__repr__()
Source code in brmspy/types/formula_dsl.py
def __repr__(self) -> str:
    return self.__str__()
__init__(_fun, _args, _kwargs)

Functions

log(*msg, method_name=None, level=logging.INFO)

Log a message with automatic method name detection.

Parameters:

Name Type Description Default
msg str

The message to log

()
method_name str

The name of the method/function. If None, will auto-detect from call stack.

None
level int

Logging level (default: logging.INFO)

INFO
Source code in brmspy/helpers/log.py
def log(*msg: str, method_name: str | None = None, level: int = logging.INFO):
    """
    Log a message with automatic method name detection.

    Parameters
    ----------
    msg : str
        The message to log
    method_name : str, optional
        The name of the method/function. If None, will auto-detect from call stack.
    level : int, optional
        Logging level (default: logging.INFO)
    """
    if method_name is None:
        method_name = _get_caller_name()

    msg_str = " ".join(str(v) for v in msg)

    logger = get_logger()
    logger.log(level, msg_str, extra={"method_name": method_name})

kwargs_r(kwargs)

Convert Python keyword arguments to R-compatible format.

Convenience function that applies py_to_r() to all values in a keyword arguments dictionary, preparing them for R function calls.

Parameters:

Name Type Description Default
kwargs dict or None

Dictionary of keyword arguments where values may be Python objects (dicts, lists, DataFrames, arrays, etc.)

required

Returns:

Type Description
dict

Dictionary with same keys but R-compatible values, or empty dict if None

Notes

This is a thin wrapper around py_to_r() that operates on dictionaries. It's commonly used to prepare keyword arguments for R function calls via rpy2.

Examples:

from brmspy.helpers.conversion import kwargs_r
import pandas as pd
import numpy as np

# Prepare kwargs for R function
py_kwargs = {
    'data': pd.DataFrame({'y': [1, 2], 'x': [1, 2]}),
    'prior': {'b': [0, 1]},
    'chains': 4,
    'iter': 2000
}

r_kwargs = kwargs_r(py_kwargs)
# All values converted to R objects
# Can now call: r_function(**r_kwargs)
See Also

py_to_r : Underlying conversion function for individual values brmspy.brms.fit : Uses this to prepare user kwargs for R

Source code in brmspy/helpers/_rpy2/_conversion.py
def kwargs_r(kwargs: dict | None) -> dict:
    """
    Convert Python keyword arguments to R-compatible format.

    Convenience function that applies py_to_r() to all values in a
    keyword arguments dictionary, preparing them for R function calls.

    Parameters
    ----------
    kwargs : dict or None
        Dictionary of keyword arguments where values may be Python objects
        (dicts, lists, DataFrames, arrays, etc.)

    Returns
    -------
    dict
        Dictionary with same keys but R-compatible values, or empty dict if None

    Notes
    -----
    This is a thin wrapper around `py_to_r()` that operates on dictionaries.
    It's commonly used to prepare keyword arguments for R function calls via rpy2.

    Examples
    --------

    ```python
    from brmspy.helpers.conversion import kwargs_r
    import pandas as pd
    import numpy as np

    # Prepare kwargs for R function
    py_kwargs = {
        'data': pd.DataFrame({'y': [1, 2], 'x': [1, 2]}),
        'prior': {'b': [0, 1]},
        'chains': 4,
        'iter': 2000
    }

    r_kwargs = kwargs_r(py_kwargs)
    # All values converted to R objects
    # Can now call: r_function(**r_kwargs)
    ```

    See Also
    --------
    py_to_r : Underlying conversion function for individual values
    brmspy.brms.fit : Uses this to prepare user kwargs for R
    """
    if kwargs is None:
        return {}
    return {k: py_to_r(v) for k, v in kwargs.items()}

py_to_r(obj)

Convert arbitrary Python objects to R objects via rpy2.

Comprehensive converter that handles nested structures (dicts, lists), DataFrames, arrays, and scalars. Uses rpy2's converters with special handling for dictionaries (→ R named lists) and lists of dicts.

Parameters:

Name Type Description Default
obj any

Python object to convert. Supported types: - None → R NULL - dict → R named list (ListVector), recursively - list/tuple of dicts → R list of named lists - list/tuple (other) → R vector or list - pd.DataFrame → R data.frame - np.ndarray → R vector/matrix - scalars (int, float, str, bool) → R atomic types

required

Returns:

Type Description
rpy2 R object

R representation of the Python object

Notes

Conversion Rules:

  1. None: → R NULL
  2. DataFrames: → R data.frame (via pandas2ri)
  3. Dictionaries: → R named list (ListVector), recursively converting values
  4. Lists of dicts: → R list with 1-based indexed names containing named lists
  5. Other lists/tuples: → R vectors or lists (via rpy2 default)
  6. NumPy arrays: → R vectors/matrices (via numpy2ri)
  7. Scalars: → R atomic values

Recursive Conversion:

Dictionary values are recursively converted, allowing nested structures:

{'a': {'b': [1, 2, 3]}}    list(a = list(b = c(1, 2, 3)))

List of Dicts:

Lists containing only dicts are converted to R lists with 1-based indexing:

[{'x': 1}, {'x': 2}]    list("1" = list(x = 1), "2" = list(x = 2))

Examples:

from brmspy.helpers.conversion import py_to_r
import numpy as np
import pandas as pd

# Scalars
py_to_r(5)        # R: 5
py_to_r("hello")  # R: "hello"
py_to_r(None)     # R: NULL

# Arrays
py_to_r(np.array([1, 2, 3]))  # R: c(1, 2, 3)

# DataFrames
df = pd.DataFrame({'x': [1, 2], 'y': [3, 4]})
py_to_r(df)  # R: data.frame(x = c(1, 2), y = c(3, 4))
See Also

r_to_py : Convert R objects back to Python kwargs_r : Convert keyword arguments dict for R function calls brmspy.brms.fit : Uses this for converting data to R

Source code in brmspy/helpers/_rpy2/_converters/_dispatch.py
def py_to_r(obj: PyObject) -> Sexp:
    """
    Convert arbitrary Python objects to R objects via rpy2.

    Comprehensive converter that handles nested structures (dicts, lists),
    DataFrames, arrays, and scalars. Uses rpy2's converters with special
    handling for dictionaries (→ R named lists) and lists of dicts.

    Parameters
    ----------
    obj : any
        Python object to convert. Supported types:
        - None → R NULL
        - dict → R named list (ListVector), recursively
        - list/tuple of dicts → R list of named lists
        - list/tuple (other) → R vector or list
        - pd.DataFrame → R data.frame
        - np.ndarray → R vector/matrix
        - scalars (int, float, str, bool) → R atomic types

    Returns
    -------
    rpy2 R object
        R representation of the Python object

    Notes
    -----
    **Conversion Rules:**

    1. **None**: → R NULL
    2. **DataFrames**: → R data.frame (via pandas2ri)
    3. **Dictionaries**: → R named list (ListVector), recursively converting values
    4. **Lists of dicts**: → R list with 1-based indexed names containing named lists
    5. **Other lists/tuples**: → R vectors or lists (via rpy2 default)
    6. **NumPy arrays**: → R vectors/matrices (via numpy2ri)
    7. **Scalars**: → R atomic values

    **Recursive Conversion:**

    Dictionary values are recursively converted, allowing nested structures:
    ```python
    {'a': {'b': [1, 2, 3]}}  →  list(a = list(b = c(1, 2, 3)))
    ```

    **List of Dicts:**

    Lists containing only dicts are converted to R lists with 1-based indexing:
    ```python
    [{'x': 1}, {'x': 2}]  →  list("1" = list(x = 1), "2" = list(x = 2))
    ```

    Examples
    --------

    ```python
    from brmspy.helpers.conversion import py_to_r
    import numpy as np
    import pandas as pd

    # Scalars
    py_to_r(5)        # R: 5
    py_to_r("hello")  # R: "hello"
    py_to_r(None)     # R: NULL

    # Arrays
    py_to_r(np.array([1, 2, 3]))  # R: c(1, 2, 3)

    # DataFrames
    df = pd.DataFrame({'x': [1, 2], 'y': [3, 4]})
    py_to_r(df)  # R: data.frame(x = c(1, 2), y = c(3, 4))
    ```

    See Also
    --------
    r_to_py : Convert R objects back to Python
    kwargs_r : Convert keyword arguments dict for R function calls
    brmspy.brms.fit : Uses this for converting data to R
    """
    import rpy2.robjects as ro

    if obj is None:
        return ro.NULL

    if isinstance(obj, ro.Sexp):
        return obj

    if isinstance(obj, RListVectorExtension) and isinstance(obj.r, ro.Sexp):
        return obj.r

    _type = type(obj)
    converter = None

    if _type in _registry._PY2R_CONVERTERS:
        # O(1) lookup first
        converter = _registry._PY2R_CONVERTERS[_type]
    else:
        for _type, _con in _registry._PY2R_CONVERTERS.items():
            if isinstance(obj, _type):
                converter = _con
                break

    assert len(_registry._PY2R_CONVERTERS) > 0, "NO PY2R CONVERTERS"
    assert (
        converter
    ), "object fallback must be in place in __init__.py! This is an issue with the library, not the user!"
    return converter(obj)

bf(*formulas, **formula_args)

Build a brms model formula.

This is the primary entrypoint for specifying the mean model and can be combined with other formula parts (e.g. lf, nlf, acformula) using +.

Parameters:

Name Type Description Default
*formulas str

One or more brms formula strings (e.g. "y ~ x + (1|group)"). Multiple formulas are commonly used for multivariate models.

()
**formula_args

Keyword arguments forwarded to R brms::brmsformula() (for example decomp="QR", center=True, sparse=True, nl=True, loop=True).

{}

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::brmsformula : R documentation

Examples:

Basic formula:

from brmspy.brms import bf

f = bf("y ~ x1 + x2 + (1|group)")

QR decomposition (often helps with collinearity):

from brmspy.brms import bf

f = bf("reaction ~ days + (days|subject)", decomp="QR")

Multivariate formula + residual correlation:

from brmspy.brms import bf, set_rescor

f = bf("mvbind(y1, y2) ~ x") + set_rescor(True)
Source code in brmspy/_brms_functions/formula.py
def bf(*formulas: str, **formula_args) -> FormulaConstruct:
    """
    Build a brms model formula.

    This is the primary entrypoint for specifying the mean model and can be
    combined with other formula parts (e.g. `lf`, `nlf`, `acformula`) using ``+``.

    Parameters
    ----------
    *formulas : str
        One or more brms formula strings (e.g. ``"y ~ x + (1|group)"``). Multiple
        formulas are commonly used for multivariate models.
    **formula_args
        Keyword arguments forwarded to R ``brms::brmsformula()`` (for example
        ``decomp="QR"``, ``center=True``, ``sparse=True``, ``nl=True``, ``loop=True``).

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::brmsformula : [R documentation](https://paulbuerkner.com/brms/reference/brmsformula.html)

    Examples
    --------
    Basic formula:

    ```python
    from brmspy.brms import bf

    f = bf("y ~ x1 + x2 + (1|group)")
    ```

    QR decomposition (often helps with collinearity):

    ```python
    from brmspy.brms import bf

    f = bf("reaction ~ days + (days|subject)", decomp="QR")
    ```

    Multivariate formula + residual correlation:

    ```python
    from brmspy.brms import bf, set_rescor

    f = bf("mvbind(y1, y2) ~ x") + set_rescor(True)
    ```
    """
    part = FormulaPart(_fun="bf", _args=list(formulas), _kwargs=formula_args)
    return FormulaConstruct._formula_parse(part)

lf(*formulas, flist=None, dpar=None, resp=None, center=None, cmc=None, sparse=None, decomp=None)

Add linear formulas for distributional / non-linear parameters.

This wraps R brms::lf() and is typically used to model distributional parameters such as sigma (heteroskedasticity) or to specify predictors for non-linear parameters.

Parameters:

Name Type Description Default
*formulas str | FormulaConstruct | FormulaPart | ProxyListSexpVector

One or more formulas such as "sigma ~ x".

()
flist

Optional list of formulas (advanced; mirrors brms).

None
dpar str or None

Distributional parameter name (e.g. "sigma", "phi").

None
resp str or None

Response name for multivariate models.

None
center bool | None

Forwarded to R brms::lf().

None
cmc bool | None

Forwarded to R brms::lf().

None
sparse bool | None

Forwarded to R brms::lf().

None
decomp bool | None

Forwarded to R brms::lf().

None

Returns:

Type Description
FormulaConstruct

A composable formula specification that can be combined using +.

See Also

brms::lf : R documentation

Examples:

Model mean + sigma:

from brmspy.brms import bf, lf

f = bf("y ~ x") + lf("sigma ~ x", dpar="sigma")
Source code in brmspy/_brms_functions/formula.py
def lf(
    *formulas: str | FormulaConstruct | FormulaPart | ProxyListSexpVector,
    flist=None,
    dpar: str | None = None,
    resp: str | None = None,
    center: bool | None = None,
    cmc: bool | None = None,
    sparse: bool | None = None,
    decomp: str | None = None,
) -> FormulaConstruct:
    """
    Add linear formulas for distributional / non-linear parameters.

    This wraps R ``brms::lf()`` and is typically used to model distributional
    parameters such as ``sigma`` (heteroskedasticity) or to specify predictors
    for non-linear parameters.

    Parameters
    ----------
    *formulas
        One or more formulas such as ``"sigma ~ x"``.
    flist
        Optional list of formulas (advanced; mirrors brms).
    dpar : str or None, default=None
        Distributional parameter name (e.g. ``"sigma"``, ``"phi"``).
    resp : str or None, default=None
        Response name for multivariate models.
    center, cmc, sparse, decomp
        Forwarded to R ``brms::lf()``.

    Returns
    -------
    FormulaConstruct
        A composable formula specification that can be combined using ``+``.

    See Also
    --------
    brms::lf : [R documentation](https://paulbuerkner.com/brms/reference/lf.html)

    Examples
    --------
    Model mean + sigma:

    ```python
    from brmspy.brms import bf, lf

    f = bf("y ~ x") + lf("sigma ~ x", dpar="sigma")
    ```
    """
    formula_args = {
        "flist": flist,
        "dpar": dpar,
        "resp": resp,
        "center": center,
        "cmc": cmc,
        "sparse": sparse,
        "decomp": decomp,
    }
    result = FormulaConstruct._formula_parse(
        FormulaPart("lf", list(formulas), formula_args)
    )
    return result

nlf(*formulas, flist=None, dpar=None, resp=None, loop=None)

Add non-linear formulas.

Wraps R brms::nlf(). This is used together with set_nl() and parameter definitions in lf() to specify non-linear models.

Parameters:

Name Type Description Default
*formulas str | FormulaConstruct | FormulaPart | ProxyListSexpVector

One or more non-linear formulas (e.g. "y ~ a * exp(b * x)").

()
flist

Optional list of formulas (advanced; mirrors brms).

None
dpar str or None

Distributional parameter name (optional).

None
resp str or None

Response name for multivariate models.

None
loop bool or None

Forwarded to R brms::nlf(loop=...).

None

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::nlf : R documentation

Examples:

from brmspy.brms import bf, nlf, set_nl

f = bf("y ~ 1") + nlf("y ~ a * exp(b * x)") + set_nl()
Source code in brmspy/_brms_functions/formula.py
def nlf(
    *formulas: str | FormulaConstruct | FormulaPart | ProxyListSexpVector,
    flist=None,
    dpar: str | None = None,
    resp: str | None = None,
    loop: bool | None = None,
) -> FormulaConstruct:
    """
    Add non-linear formulas.

    Wraps R ``brms::nlf()``. This is used together with `set_nl()` and parameter
    definitions in `lf()` to specify non-linear models.

    Parameters
    ----------
    *formulas
        One or more non-linear formulas (e.g. ``"y ~ a * exp(b * x)"``).
    flist
        Optional list of formulas (advanced; mirrors brms).
    dpar : str or None, default=None
        Distributional parameter name (optional).
    resp : str or None, default=None
        Response name for multivariate models.
    loop : bool or None, default=None
        Forwarded to R ``brms::nlf(loop=...)``.

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::nlf : [R documentation](https://paulbuerkner.com/brms/reference/nlf.html)

    Examples
    --------
    ```python
    from brmspy.brms import bf, nlf, set_nl

    f = bf("y ~ 1") + nlf("y ~ a * exp(b * x)") + set_nl()
    ```
    """
    formula_args = {
        "flist": flist,
        "dpar": dpar,
        "resp": resp,
        "loop": loop,
    }
    return FormulaConstruct._formula_parse(FormulaPart("nlf", formulas, formula_args))

acformula(autocor, resp=None)

Add an autocorrelation structure.

Wraps R brms::acformula().

Parameters:

Name Type Description Default
autocor str

One-sided autocorrelation formula (e.g. "~ arma(p = 1, q = 1)").

required
resp str or None

Response name for multivariate models.

None

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::acformula : R documentation

Examples:

from brmspy.brms import bf, acformula

f = bf("y ~ x") + acformula("~ arma(p = 1, q = 1)")
Source code in brmspy/_brms_functions/formula.py
def acformula(
    autocor: str,
    resp: str | None = None,
) -> FormulaConstruct:
    """
    Add an autocorrelation structure.

    Wraps R ``brms::acformula()``.

    Parameters
    ----------
    autocor : str
        One-sided autocorrelation formula (e.g. ``"~ arma(p = 1, q = 1)"``).
    resp : str or None, default=None
        Response name for multivariate models.

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::acformula : [R documentation](https://paulbuerkner.com/brms/reference/acformula.html)

    Examples
    --------
    ```python
    from brmspy.brms import bf, acformula

    f = bf("y ~ x") + acformula("~ arma(p = 1, q = 1)")
    ```
    """
    formula_args = {"resp": resp}
    return FormulaConstruct._formula_parse(
        FormulaPart("acformula", [autocor], formula_args)
    )

set_rescor(rescor=True)

Control residual correlations in multivariate models.

Wraps R brms::set_rescor().

Parameters:

Name Type Description Default
rescor bool

Whether to model residual correlations.

True

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::set_rescor : R documentation

Examples:

from brmspy.brms import bf, set_rescor

f = bf("y1 ~ x") + bf("y2 ~ z") + set_rescor(True)
Source code in brmspy/_brms_functions/formula.py
def set_rescor(rescor: bool = True) -> FormulaConstruct:
    """
    Control residual correlations in multivariate models.

    Wraps R ``brms::set_rescor()``.

    Parameters
    ----------
    rescor : bool, default=True
        Whether to model residual correlations.

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::set_rescor : [R documentation](https://paulbuerkner.com/brms/reference/set_rescor.html)

    Examples
    --------
    ```python
    from brmspy.brms import bf, set_rescor

    f = bf("y1 ~ x") + bf("y2 ~ z") + set_rescor(True)
    ```
    """
    formula_args = {
        "rescor": rescor,
    }
    return FormulaConstruct._formula_parse(FormulaPart("set_rescor", [], formula_args))

set_mecor(mecor=True)

Control correlations between latent me() terms.

Wraps R brms::set_mecor().

Parameters:

Name Type Description Default
mecor bool

Whether to model correlations between latent variables introduced by me().

True

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::set_mecor : R documentation

Examples:

from brmspy.brms import bf, set_mecor

f = bf("y ~ me(x, sdx)") + set_mecor(True)
Source code in brmspy/_brms_functions/formula.py
def set_mecor(mecor: bool = True) -> FormulaConstruct:
    """
    Control correlations between latent ``me()`` terms.

    Wraps R ``brms::set_mecor()``.

    Parameters
    ----------
    mecor : bool, default=True
        Whether to model correlations between latent variables introduced by ``me()``.

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::set_mecor : [R documentation](https://paulbuerkner.com/brms/reference/set_mecor.html)

    Examples
    --------
    ```python
    from brmspy.brms import bf, set_mecor

    f = bf("y ~ me(x, sdx)") + set_mecor(True)
    ```
    """
    formula_args = {
        "mecor": mecor,
    }
    return FormulaConstruct._formula_parse(FormulaPart("set_mecor", [], formula_args))

set_nl(dpar=None, resp=None)

Mark a model (or part of it) as non-linear.

Wraps R brms::set_nl().

Parameters:

Name Type Description Default
dpar str or None

Distributional parameter name (if only part of the model is non-linear).

None
resp str or None

Response name for multivariate models.

None

Returns:

Type Description
FormulaConstruct

A composable formula specification.

See Also

brms::set_nl : R documentation

Examples:

from brmspy.brms import bf, lf, set_nl

f = bf("y ~ a * inv_logit(x * b)") + lf("a + b ~ z") + set_nl()
Source code in brmspy/_brms_functions/formula.py
def set_nl(
    dpar: str | None = None,
    resp: str | None = None,
) -> FormulaConstruct:
    """
    Mark a model (or part of it) as non-linear.

    Wraps R ``brms::set_nl()``.

    Parameters
    ----------
    dpar : str or None, default=None
        Distributional parameter name (if only part of the model is non-linear).
    resp : str or None, default=None
        Response name for multivariate models.

    Returns
    -------
    FormulaConstruct
        A composable formula specification.

    See Also
    --------
    brms::set_nl : [R documentation](https://paulbuerkner.com/brms/reference/set_nl.html)

    Examples
    --------
    ```python
    from brmspy.brms import bf, lf, set_nl

    f = bf("y ~ a * inv_logit(x * b)") + lf("a + b ~ z") + set_nl()
    ```
    """
    formula_args = {
        "dpar": dpar,
        "resp": resp,
    }
    return FormulaConstruct._formula_parse(FormulaPart("set_nl", [], formula_args))

_execute_formula(formula)

Source code in brmspy/_brms_functions/formula.py
def _execute_formula(formula: FormulaConstruct | Sexp | str) -> Sexp:
    import rpy2.robjects as ro

    if isinstance(formula, Sexp):
        return formula
    if isinstance(formula, str):
        formula = FormulaConstruct._formula_parse(formula)

    # Must run for formula functions, e.g me() to register
    ro.r("library(brms)")

    fun_add = cast(Callable[[Sexp, Sexp], Sexp], ro.r("function (a, b) a + b"))

    result: Sexp | None = None
    for summand in formula:
        subresult: Sexp = py_to_r(summand[0])
        for part in summand[1:]:
            subresult = fun_add(subresult, py_to_r(part))

        if result is None:
            result = subresult
        else:
            result = fun_add(result, subresult)

    assert result is not None
    return result