Skip to main content

Call a persistent R interpreter from Python

Project description

🌉 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

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

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

Project details


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.7.0.tar.gz (92.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.7.0-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: r_bridge-0.7.0.tar.gz
  • Upload date:
  • Size: 92.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.12 {"installer":{"name":"uv","version":"0.9.12"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for r_bridge-0.7.0.tar.gz
Algorithm Hash digest
SHA256 b5670faffb2638dc37043721cded6f887e65aa70eda18970e7c77559dfeafacf
MD5 46aff46a98a136c4c8219930064a15b7
BLAKE2b-256 f7fcd5550d40913896d84e551fda47ac313bb601cd1a07efc3fb2b94905453e8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: r_bridge-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 20.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.12 {"installer":{"name":"uv","version":"0.9.12"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for r_bridge-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a1176676fef9c68494dde3cf3c021293b8fcda4bb0b80d77b605fafa2ff0827e
MD5 9abfd68d68f74653a08ccd63080598cb
BLAKE2b-256 3c86ccc5ea9af117abe6867aa181f3dbce92cc0907a9481e5980673eb66b5282

See more details on using hashes here.

Supported by

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