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 60.0 Per-call timeout in seconds
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.6.0.tar.gz (89.7 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.6.0-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: r_bridge-0.6.0.tar.gz
  • Upload date:
  • Size: 89.7 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.6.0.tar.gz
Algorithm Hash digest
SHA256 b6f78c7829be24ef5a98732ac97e628c1c693aa25b8c34172eba116fd22d2dc1
MD5 32de278767f2b17e02caa10d852d880a
BLAKE2b-256 5360f3c4df8a49e4e19c1477fcefbecf213189683014b5540298380b845a6a4c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: r_bridge-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 20.2 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.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 256f8883aabd5994559bfdce9a29c5be8a4d5c24ec14f3ca8df0a82f87774534
MD5 9ef2664a68947209ece2796ba81b913d
BLAKE2b-256 2994927e491d89edf7e229bf6149062e1ac0ff73feb492ca67d539f017af6092

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