PyRlang
R-style syntax, running on pure Python. No R runtime, no compilation, zero dependencies.
PyRlang lets you write R the way R programmers actually type it — x <- c(1, 2), 1:10, df$col,
%>%, %in%, y ~ x — and runs it as ordinary Python underneath. It also ships the R-flavoured
data types as a normal Python library, usable from plain .py files.
- PyPI distribution name:
pyr4py(PyPI compares names with-/_stripped, so the whole family is blocked:pyris a 2013 REPL,pyrlangis an actively developed BEAM runtime, and thereforepyr-lang/PyRlangare rejected on upload too.pyr4py— "PyR for Python" — is the distribution name) - Import name:
PyRlang(import PyRlang,from PyRlang import *) - License: GPL-3.0-or-later
- Python: >= 3.10
Install
pip install pyr4py # zero-dependency core
pip install "pyr4py[all]" # + NumPy fast path, matplotlib graphics, pandas interop
pip install "pyr4py[accel]" # + NumPy only
Speed: when NumPy is installed the element-wise operations and statistics run
vectorised automatically (about 1.2–6× on real workloads; PYRLANG_NO_NUMPY=1
forces the pure-Python path). Set PYRLANG_NO_NUMPY=1 to compare.
Two ways to use it
1. R-style scripts (.pyr) — full syntax
Write hello.pyr:
x <- c(2, 4.5, 8, 1, 3.3, NA)
mean(x, na.rm = TRUE)
summary(x)
v <- 1:5
v[1] # one-based: the FIRST element
v[-1] # drops the first element, R-style
df <- data.frame(
name = c("ada", "bob"),
age = c(34, 47)
)
df$age
df[df$age > 35, ]
df$senior <- df$age > 40
df$age %>% mean() %>% round(2)
10 %in% c(4, 8, 10)
paste("R", "on", "Python", sep = "-") |> toupper()
f <- wage ~ age
Run it like an R script:
PyRlang hello.pyr # or: python -m PyRlang hello.pyr
Top-level expressions echo their results exactly like the R console,
vectors print as [1] 2 4.5 8 1 3.3 NA.
2. Interactive REPL
$ PyRlang
PyRlang 0.1.0 on Python 3.12 — R-style syntax, pure Python
Try: x <- c(1, 2, 3); mean(x). exit() or Ctrl-D to quit.
>> x <- c(1, 2, 3)
>> mean(x)
[1] 2
>> 1:5 %>% sum()
[1] 15
3. As a Python library
from PyRlang import *
v = c(1, 2, 3)
v[1] # 1 — one-based
mean(v) # 2.0
f = Formula("y ~ x")
df = data_frame(age=c(34, 47), wage=c(21.5, 33.0))
summary(df) # R-style six-number summaries
Syntax supported in transform mode
(.pyr files, the REPL, and PyRlang -e — plain .py files are never touched.)
| R syntax | becomes | notes |
|---|---|---|
x <- e |
x = e |
< and - must be adjacent, exactly like the real R parser |
x < -e |
unchanged | a space keeps it a Python comparison |
<<- |
not supported | clear error message |
a:b |
seq(a, b) |
outside []/{}; not after lambda, not annotations, not slices |
x ^ y |
x ** y |
|
x %% y |
x % y |
|
a && b |
a and b |
adjacent pair only, like R's scalar operators |
a || b |
a or b |
|
!x |
PyRlang.not_(x) |
element-wise negation — works on vectors |
a %in% b |
PyRlang.isin(a, b) |
vectorised membership |
a %>% f(b) |
PyRlang.pipe(a, f, b) |
magrittr; native R pipe a |> f(b) too |
y ~ x + z |
Formula('y ~ x + z') |
including inside call arguments: lm(y ~ x, data = d) style |
df$col |
df.col |
|
data.frame(…) |
DataFrame(…) |
also read.csv, write.csv, which.max, which.min, na.rm=, length.out=, names.arg= |
R block syntax
Braces become indentation, exactly where R uses them:
if (total > 10) {
msg <- "big"
} else if (total == 10) {
msg <- "ten"
} else {
msg <- "small"
}
for (i in 1:5) {
total <- total + i
}
repeat {
guard <- guard + 3
if (guard >= 9) {
break
}
}
next also works. Lines outside R braces are left byte-identical, so
plain Python code in .pyr files is never reshaped. Not supported:
R's function(x) {...} definitions (use Python def) and one-line
if (x) y <- 2 forms.
R console feel (REPL)
PyRlang with no arguments is a terminal in the spirit of R's console,
built like IPython over Python: TAB completes names, history persists at
~/.pyr_history, _ holds the last printed result, q() quits,
source("file.pyr") runs a script in-session:
>> x <- c(2, 4.5, 8, 1, 3.3, NA)
>> mean(x, na.rm = TRUE)
[1] 3.76
>> _
[1] 3.76
Graphics (optional)
With matplotlib installed (pip install "pyr4py[graphics]"):
plot(x, y, col = "steelblue") # scatter (type = "l" for a line)
hist(x, breaks = 20)
barplot(heights, names.arg = c("a", "b"))
Figures are saved as plot_01.png, hist_01.png, … and the path is printed,
so both scripts and the REPL stay non-blocking.
pandas interop (optional)
from PyRlang import data_frame, to_pandas, from_pandas
pdf = to_pandas(data_frame(age=c(34, 47))) # pandas DataFrame (NA -> NaN)
df = from_pandas(pdf) # back to PyRlang (NaN -> NA)
Semantics, by design
- One-based indexing.
v[1]is the first element; index 0 is an error with an R-style message.v[2:4]returns elements 2, 3, 4 (inclusive, like R), and out-of-range slices pad withNA. - Negative subscripts drop elements.
v[-1]removes the first element. (This differs deliberately from Python's negative indexing.) - Recycling.
c(1, 2) + c(1, 2, 3, 4)recycles the shorter vector, like R. - NA everywhere. A real
NAsentinel:NA + 1isNA,mean(c(1, NA))isNAunless you writena.rm = TRUE,if (NA)raises R's error message. - R-flavoured printing. Vectors print as
[1] 1 2 3, strings quoted, logicals asTRUE/FALSE, six-number summaries in R's column layout. - Division by zero is
Inf/NaN, not an exception — like R.
What ships in the library
- Constructors/inspection:
c,seq,rep,length,unique,sort,rev,head,tail,which,which.max,which.min,table - Statistics:
mean,median,sd,var,min,max,sum,prod,summary,sqrt,exp,log, all NA-aware - Strings:
paste,paste0,sprintf,nchar,toupper,tolower,substr,substring,cat - Data:
data.frame,$, R-style row/column subsetting,nrow,ncol,names,read.csv,write.csv - Logic:
ifelse,%in%, element-wise&,|,!, vectorised comparisons
Honest limitations (v0.1.2)
- R block syntax has limits:
function(x) {...}definitions are not translated (use Pythondef); one-lineif (x) y <- 2forms are not supported. ->(right assignment) is not yet transformed.lm,glm,ggplotand the modelling stack do not exist yet;Formulais data only and plotting coversplot,hist,barplot.- No packages/namespaces; Python modules are imported normally.
- Type annotations in
.pyrfiles are not supported (R mode treatsn:mas a range). - Only Python >= 3.10 is supported.
Versioning policy
Releases bump only the last digit of the version (0.1.1 → 0.1.2 → …);
the 0.1 prefix is fixed for this development line.
Development
pip install -e ".[dev]"
pytest
ruff format . && ruff check .
python -m build
twine check dist/*
Run the examples:
python -m PyRlang examples/basics.pyr
python -m PyRlang examples/dataframe.pyr
PyRlang
Publishing to PyPI
The PyPI name for this project is pyr4py (taken-name note above).
python -m build
twine check dist/*
twine upload dist/pyr4py-0.1.0*
Use --repository testpypi to rehearse on TestPyPI, or configure an API token:
# ~/.pypirc
[pypi]
username = __token__
password = pypi-xxxxxxxx
License
GPL-3.0-or-later. See LICENSE.
Metadata
Release files for pyr4py 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pyr4py-0.1.2.tar.gz | 51.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyr4py-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.1 kB
Release files / pyr4py-0.1.2.tar.gz
| Download URL | pyr4py-0.1.2.tar.gz |
|---|---|
| Size | 51.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0595b93db7fc829056d6eb1f5c4f4ab9a8a824e5191bbc6e6598bc0db981f259
|
|
BLAKE2b-256 checksum How to use checksums |
43136d7e9dc41b03b2983976ff5f0fac677de0f1c11044779e113a54f9448233
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / pyr4py-0.1.2-py3-none-any.whl
| Download URL | pyr4py-0.1.2-py3-none-any.whl |
|---|---|
| Size | 50.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c108a37fcc5294399ba2a49be5e726348f1ebe8f5a5dc1183ad4908c3500224
|
|
BLAKE2b-256 checksum How to use checksums |
8d785d1d07b53daa48f6695b4b3a1436fae14f78b36a914d6564189a8b530b56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Security for pyr4py 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.