Skip to main content

🌉 r_bridge

Call a persistent R interpreter from Python — with a Pythonic API, full numpy/pandas support, and zero ceremony.

Pipeline Release PyPI Python License Coverage

📚 Documentation · 🔍 Example script

from r_bridge import RBridge

with RBridge() as r:
    r.x = [1, 2, 3, 4, 5]
    print(r.mean(r.x))   # 3.0

✨ Why r_bridge?

Python and R each have unique strengths. Rather than rewriting R code or shelling out to Rscript for every call, r_bridge keeps a single R process alive for the lifetime of your session. Calls are fast, state is shared, and the API feels natural on both sides.

  • 🚀 No subprocess-per-call overhead — R starts once, stays running
  • 🔄 Automatic type conversion — Python lists, numpy arrays, pandas DataFrames, datetimes all round-trip transparently
  • 🔒 Thread-safe — a single lock serialises concurrent calls; safe to use from threads
  • 🛡️ Robust — a sentinel-prefixed protocol means stray cat() output never corrupts the stream

📦 Installation

Requires Python ≥ 3.11 and R installed on your PATH.

pip install r-bridge

🪟 Windows note: R 4.2+ (UCRT build) is required for correct UTF-8 handling.


🚀 Quick start

from r_bridge import RBridge
import pandas as pd
import numpy as np

with RBridge() as r:
    # Set and get variables
    r.name = "world"
    print(r.paste("hello", r.name))   # "hello world"

    # Vectors and numpy arrays
    r.v = np.linspace(0, 1, 100)
    print(r.mean(r.v))                # 0.5

    # DataFrames
    r.df = pd.DataFrame({"x": [1, 2, 3], "y": [4.0, 5.0, 6.0]})
    result = r.df                     # returns a pandas DataFrame

    # Arbitrary R expressions
    r.eval("model <- lm(y ~ x, data=df)")
    coefs = r.coef(r.model)

    # Keyword arguments become named R arguments
    r.seq(1, 10, by=2)               # [1, 3, 5, 7, 9]

📖 API reference

RBridge(...) — constructor

Parameter Default Description
r_executable None Path to Rscript; auto-detected from PATH if omitted
startup_timeout 15.0 Seconds to wait for R ready signal
call_timeout None Per-call timeout in seconds (None = no timeout)
env {} Extra environment variables for the R subprocess
r_libs [] Paths prepended to R_LIBS_USER
log_level "WARNING" Python logging level for the r_bridge logger
verbose False Print all R calls and R output to stderr in real time
capture_output False Accumulate non-protocol R stdout (from cat() etc.) and print to stdout; access via last_stdout

Attribute access (primary interface)

r.x = value          # set a variable in R's global environment
value = r.x          # get a variable from R
r.mean([1, 2, 3])    # call an R function; returns a Python value

Explicit methods

r.set("x", value)                          # set with optional type_hint
r.get("x", result_type_hint="scalar")      # get with type conversion hint
r.call("mean", [1,2,3], timeout=5.0)       # call with per-call timeout
r.eval("x <- 1 + 1", result_type_hint=None)
r.ls()                                     # list names in R global env
r.ping()                                   # round-trip latency in seconds
r.last_stderr                              # accumulated R stderr as string
r.last_stdout                              # accumulated non-protocol R stdout as string

result_type_hint values

Hint Effect
"scalar" Return first element as a Python scalar
"numpy" Return as numpy.ndarray
"pandas" Return as pandas.Series or DataFrame
"list" Return as Python list
"raw" Return the raw tagged JSON dict, no conversion

🔀 Type conversion

Python → → R
int, float, bool, str, None scalar atomic / NULL
list (homogeneous) atomic vector
dict named list()
numpy.ndarray 1-D atomic vector
numpy.ndarray 2-D matrix
pandas.DataFrame data.frame
pandas.Categorical factor
datetime.datetime POSIXct
datetime.date Date

↕️ All conversions are bidirectional. Special R values (NA, NaN, Inf, factor, matrix, …) all round-trip correctly.


🔍 Verbose mode

Set verbose=True to trace every R call and see R's output in real time — great for debugging.

with RBridge(verbose=True) as r:
    r.x = [1, 2, 3]
    r.mean(r.x)
[R set]    x = {'__type__': 'integer_vector', 'value': [1, 2, 3]}
[R call]   mean([...])
[R stdout] ...    ← from cat() in R
[R stderr] ...    ← from message() or warnings in R

⚠️ Error handling

from r_bridge.exceptions import RError, RTimeoutError

try:
    r.eval("stop('something went wrong')")
except RError as e:
    print(e.message)    # "something went wrong"
    print(e.warnings)   # list of captured R warnings
    print(e.traceback)  # R traceback as list of strings

try:
    r.eval("Sys.sleep(99)", timeout=1.0)
except RTimeoutError:
    print("R took too long")

Exception hierarchy

RBridgeError          base class
├── RStartupError     R failed to start
├── RTimeoutError     call exceeded timeout
├── RError            R-side error (carries .message, .call, .traceback, .warnings)
└── RProtocolError    malformed response envelope

🔌 Custom type converters

Register serializers and deserializers without modifying the library:

from r_bridge import register_serializer, register_deserializer

register_serializer(MyType, lambda obj: {"__type__": "mytype", "value": obj.to_dict()})
register_deserializer("mytype", lambda d: MyType.from_dict(d["value"]))

⚙️ How it works

r_bridge spawns Rscript once and communicates over stdin/stdout using line-delimited JSON with a __RBRIDGE__: sentinel prefix on every protocol line. Stray cat() or print() output is captured and logged rather than corrupting the stream.

🐍 Python ──► __RBRIDGE__:{"id":"…","op":"call_func","payload":{…}}
📊 R      ──► __RBRIDGE__:{"id":"…","status":"ok","payload":{…},"warnings":[]}

A dedicated daemon thread drains R's stderr continuously to prevent pipe-buffer deadlocks. A single threading.Lock serialises concurrent Python calls.


🧪 Development

uv run pytest                          # all tests
uv run pytest tests/test_bridge.py -v  # integration tests (spawns real R)
uv run pytest tests/test_serializer.py # unit tests (no R needed)

📄 License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

r_bridge-0.8.0.tar.gz (93.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

r_bridge-0.8.0-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

File details

Details for the file r_bridge-0.8.0.tar.gz.

File metadata

  • Download URL: r_bridge-0.8.0.tar.gz
  • Upload date:
  • Size: 93.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.11

File hashes

Hashes for r_bridge-0.8.0.tar.gz
Algorithm Hash digest
SHA256 14074995789415c350a8b19cfaa5d3effeff0a04f6e4ca57a6d2e66e886c3b3d
MD5 d41fb2f519169919dff72b41c2ab76e1
BLAKE2b-256 c4f70e463abb16a6a3032174ad5e2ac716d32423fd5d27844bad6c78568b7c84

See more details on using hashes here.

File details

Details for the file r_bridge-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: r_bridge-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 20.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.11

File hashes

Hashes for r_bridge-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c5df681868846bcab0edf564cd0c1e65813d8af70a675713e40be09488beb9a2
MD5 0d6907a47fa101a75207622aac1b60fa
BLAKE2b-256 334992b72523176f9d6f5d46740f757fbcc46a1368b4ca13d287326c68917602

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 files

0.7.0

2 files

0.6.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page