galaaz 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -1
- data/README.md +90 -9
- data/bin/gknit +13 -10
- data/bin/gknit_Rscript +2 -4
- data/bin/gstudio_irb.rb +71 -22
- data/blogs/README.md +1 -1
- data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +8 -8
- data/blogs/galaaz_ggplot/galaaz_ggplot.md +6 -6
- data/blogs/gknit/gknit.Rmd +12 -11
- data/blogs/gknit/gknit.md +12 -11
- data/blogs/manual/manual.Rmd +120 -33
- data/blogs/manual/manual.md +90 -9
- data/blogs/oh_my/oh_my.Rmd +7 -7
- data/blogs/oh_my/oh_my.md +7 -7
- data/blogs/ruby_plot/ruby_plot.Rmd +13 -14
- data/blogs/ruby_plot/ruby_plot.md +13 -14
- data/examples/misc/ggplot.rb +1 -1
- data/lib/R_interface/new_bridge_adapter.rb +14 -1
- data/lib/R_interface/r.rb +60 -53
- data/lib/R_interface/r_job.rb +429 -0
- data/lib/galaaz/cli.rb +58 -11
- data/lib/gknit/diagnostics.rb +3 -1
- data/lib/new_bridge/session_client.rb +45 -0
- data/specs/gknit_install_timeout_report_spec.rb +27 -7
- data/specs/r_job_spec.rb +81 -0
- data/version.rb +1 -1
- metadata +4 -2
data/blogs/gknit/gknit.md
CHANGED
|
@@ -3,7 +3,7 @@ title: "How to do reproducible research in Ruby with gKnit"
|
|
|
3
3
|
author:
|
|
4
4
|
- "Rodrigo Botafogo"
|
|
5
5
|
- "Daniel Mossé - University of Pittsburgh"
|
|
6
|
-
tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz, gKnit, knitr]
|
|
6
|
+
tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz, gKnit, knitr]
|
|
7
7
|
date: "29/04/2019 (narrative updated for Galaaz 2.0, 2026)"
|
|
8
8
|
bibliography: stats.bib
|
|
9
9
|
output:
|
|
@@ -133,7 +133,7 @@ written both in Ruby and/or R and output it in any of the available formats of _
|
|
|
133
133
|
allows ruby developers to do literate programming and reproducible research by allowing them to
|
|
134
134
|
have in a single document, text and code.
|
|
135
135
|
|
|
136
|
-
gKnit runs with **JRuby**, **GNU R**, and **Galaaz** (the integration layer between Ruby and R—see below).
|
|
136
|
+
gKnit runs with **JRuby or CRuby**, **GNU R**, and **Galaaz** (the integration layer between Ruby and R—see below).
|
|
137
137
|
Knitr and **R Markdown** orchestrate the document; Galaaz’s engine keeps **Ruby state across chunks**
|
|
138
138
|
and talks to R through the **bridge**. Ruby chunks can read and update R variables (`~R[:name]`, `R.*`)
|
|
139
139
|
without GraalVM-style polyglot interop.
|
|
@@ -163,7 +163,7 @@ title: "How to do reproducible research in Ruby with gKnit"
|
|
|
163
163
|
author:
|
|
164
164
|
- "Rodrigo Botafogo"
|
|
165
165
|
- "Daniel Mossé - University of Pittsburgh"
|
|
166
|
-
tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
|
|
166
|
+
tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
|
|
167
167
|
date: "20/02/2019"
|
|
168
168
|
output:
|
|
169
169
|
html_document:
|
|
@@ -1166,7 +1166,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
|
|
|
1166
1166
|
|
|
1167
1167
|
A word of advice: the 'caret' package has lots of dependencies and installing
|
|
1168
1168
|
it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
|
|
1169
|
-
will install the package if it is not already installed
|
|
1169
|
+
will install the package if it is not already installed (via **`R::Job`**: a child
|
|
1170
|
+
`Rscript`, so the bridge stays free) and can take a while.
|
|
1170
1171
|
|
|
1171
1172
|
````
|
|
1172
1173
|
```{include model}
|
|
@@ -1245,7 +1246,7 @@ puts model.test.head
|
|
|
1245
1246
|
gKnit also allows developers to document and load files that are not in the same directory
|
|
1246
1247
|
of the '.Rmd' file.
|
|
1247
1248
|
|
|
1248
|
-
Here is an example of loading the 'find.rb' file from
|
|
1249
|
+
Here is an example of loading the 'find.rb' file from Ruby (via `$LOAD_PATH`). In this example, relative
|
|
1249
1250
|
is set to FALSE, so Ruby will look for the file in its $LOAD\_PATH, and the user does not
|
|
1250
1251
|
need to know its directory.
|
|
1251
1252
|
|
|
@@ -1360,9 +1361,9 @@ the Yaml header to generate this blog in PDF format instead of HTML:
|
|
|
1360
1361
|
|
|
1361
1362
|
```
|
|
1362
1363
|
---
|
|
1363
|
-
title: "gKnit - Ruby and R Knitting with Galaaz (JRuby + GNU R)"
|
|
1364
|
+
title: "gKnit - Ruby and R Knitting with Galaaz (JRuby or CRuby + GNU R)"
|
|
1364
1365
|
author: "Rodrigo Botafogo"
|
|
1365
|
-
tags: [Galaaz, Ruby, R, JRuby, "GNU R", knitr, gknit]
|
|
1366
|
+
tags: [Galaaz, Ruby, R, JRuby, CRuby, "GNU R", knitr, gknit]
|
|
1366
1367
|
date: "29 October 2018"
|
|
1367
1368
|
output:
|
|
1368
1369
|
pdf\_document:
|
|
@@ -1385,13 +1386,13 @@ be imported from files in the system. Ideally, in reproducible research, all th
|
|
|
1385
1386
|
needed to rebuild a report should be easily packed together (in the same zipped directory)
|
|
1386
1387
|
and distributed to peers for reexecution.
|
|
1387
1388
|
|
|
1388
|
-
**Galaaz 2.0** pairs **JRuby** with **GNU R**: you keep the full CRAN/Bioconductor world in R while
|
|
1389
|
+
**Galaaz 2.0** pairs **JRuby or CRuby** with **GNU R**: you keep the full CRAN/Bioconductor world in R while
|
|
1389
1390
|
writing orchestration, reuse, and application code in Ruby. The effort to wrap Ruby over R (Galaaz)
|
|
1390
1391
|
and to wrap Knitr as gKnit was tiny compared to reimplementing R’s ecosystem in Ruby—much like
|
|
1391
1392
|
Python’s investment in NumPy and Pandas, which no Ruby project is likely to duplicate.
|
|
1392
1393
|
|
|
1393
1394
|
An **earlier** prototype used Oracle’s **GraalVM** and Truffle interop; the **current** stack is
|
|
1394
|
-
deliberately **standard GNU R** plus the Galaaz **bridge
|
|
1395
|
+
deliberately **standard GNU R** plus the Galaaz **bridge** on JRuby or CRuby, documented in the project manual.
|
|
1395
1396
|
|
|
1396
1397
|
More interesting than wrapping the R libraries with Ruby, is that Ruby adds
|
|
1397
1398
|
value to R, by allowing developers to use powerful and modern constructs for code reuse that
|
|
@@ -1403,7 +1404,7 @@ readability.
|
|
|
1403
1404
|
|
|
1404
1405
|
## Prerequisites (Galaaz 2.0)
|
|
1405
1406
|
|
|
1406
|
-
* **JRuby** and a compatible **JDK**
|
|
1407
|
+
* **JRuby** and a compatible **JDK**, *or* **CRuby 3.3+**
|
|
1407
1408
|
* **GNU R** on your `PATH`
|
|
1408
1409
|
|
|
1409
1410
|
The following R packages will be automatically installed when necessary, but could be installed prior
|
|
@@ -1423,7 +1424,7 @@ the usual build tools are typically enough. On macOS, Xcode command-line tools
|
|
|
1423
1424
|
## Usage
|
|
1424
1425
|
|
|
1425
1426
|
* **`bin/gknit`** \<filename\> (from the Galaaz repo or your install layout); use **`--output_format all`** for HTML and PDF together.
|
|
1426
|
-
* Run Ruby with **`bin/galaaz-jruby`** when you need
|
|
1427
|
+
* Run Ruby with **`bin/galaaz-ruby`** (either engine) or **`bin/galaaz-jruby`** when you need JRuby JVM flags (see the manual).
|
|
1427
1428
|
|
|
1428
1429
|
|
|
1429
1430
|
# References
|
data/blogs/manual/manual.Rmd
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: "Galaaz Manual"
|
|
3
3
|
subtitle: "R-on-Rails: GNU R meets Ruby for the web"
|
|
4
4
|
author: "Rodrigo Botafogo"
|
|
5
|
-
tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
|
|
5
|
+
tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, CRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
|
|
6
6
|
date: "2026"
|
|
7
7
|
bibliography: "../../examples/Bibliography/stats.bib"
|
|
8
8
|
output:
|
|
@@ -66,18 +66,20 @@ of libraries for data analysis.
|
|
|
66
66
|
Until recently, there was no real perspective for Ruby to bridge this gap.
|
|
67
67
|
Implementing a complete scientific computing infrastructure would take too long.
|
|
68
68
|
|
|
69
|
-
**Galaaz 2.0**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
**Galaaz 2.0** supports **JRuby** and **CRuby** equally for the same NewBridge protocol.
|
|
70
|
+
Pick the Ruby that fits your app: JRuby when you want real multithreading for web and I/O;
|
|
71
|
+
CRuby when you prefer MRI. R remains the same **GNU R** you use interactively—including
|
|
72
|
+
compiled extensions and Bioconductor. Ruby and R run in **separate processes**; the
|
|
73
|
+
**Galaaz bridge** sends requests to R and returns results to Ruby. From your point of view
|
|
74
|
+
you still write Ruby: `R.c(...)`, `R.library('ggplot2')`, `~R[:mtcars]`, and dplyr-style
|
|
75
|
+
chains on R objects. You do not need to learn R syntax to get a lot done, though reading R
|
|
76
|
+
documentation for individual packages remains useful.
|
|
75
77
|
|
|
76
78
|
Earlier experiments with Galaaz used Oracle’s **GraalVM** with TruffleRuby and FastR so that
|
|
77
79
|
Ruby and R could share one runtime. That path is no longer the focus: **standard GNU R**
|
|
78
80
|
gives full compatibility with the R package ecosystem (including compiled extensions and
|
|
79
|
-
Bioconductor) while
|
|
80
|
-
|
|
81
|
+
Bioconductor) while either Ruby engine talks to R over NewBridge (JRuby for mature
|
|
82
|
+
multithreading on the application side; CRuby when you prefer MRI).
|
|
81
83
|
|
|
82
84
|
The bridge handles **communication and typing** between the two worlds; large tables can
|
|
83
85
|
also flow through **Apache Arrow** on the R side when you use the optional helpers described
|
|
@@ -241,14 +243,16 @@ For day-to-day **2.0** use, rely on **`bin/galaaz-ruby`** (or **`bin/galaaz-jrub
|
|
|
241
243
|
Typical development and CI targets:
|
|
242
244
|
|
|
243
245
|
* **Linux** — recent Ubuntu LTS or comparable distributions (x86_64).
|
|
244
|
-
* **macOS** — recent releases with JRuby and GNU R available.
|
|
246
|
+
* **macOS** — recent releases with JRuby or CRuby and GNU R available.
|
|
245
247
|
* **Windows** — use **WSL2** (same Linux stack as above); native Windows is not the primary target.
|
|
246
248
|
|
|
247
249
|
The native **gatekeeper** component under `ext/new_bridge` is built with `make` and a C++ toolchain; see the project `README` if compilation fails on your platform.
|
|
248
250
|
|
|
249
251
|
# Dependencies
|
|
250
252
|
|
|
251
|
-
* **
|
|
253
|
+
* **Ruby** — **JRuby** or **CRuby** (both supported for NewBridge). Tested with **JRuby 10.1.1.0**
|
|
254
|
+
(+ **JDK 21**) and **CRuby 3.3.12**. Use `bin/galaaz-ruby` (honors `GALAAZ_RUBY`) or plain
|
|
255
|
+
`gem install galaaz` under the Ruby you choose.
|
|
252
256
|
* **GNU R** — `R` and `Rscript` on your `PATH` (tested with **4.3.3**), plus a C++ toolchain (`g++`, `make`) and the **Rcpp** package to compile the gatekeeper.
|
|
253
257
|
* **galaaz gem** — runtime dependency `msgpack` is pulled in by `gem install`.
|
|
254
258
|
* Optional: **Docker** — if you run R in a container (common on WSL2); see bootstrap below.
|
|
@@ -258,29 +262,40 @@ The native **gatekeeper** component under `ext/new_bridge` is built with `make`
|
|
|
258
262
|
|
|
259
263
|
The supported install is **`gem install` + compile the gatekeeper**. You do not need a git clone.
|
|
260
264
|
|
|
261
|
-
1. Install **JRuby
|
|
265
|
+
1. Install **JRuby** (and a compatible **JDK**) **or CRuby 3.3+**, plus **GNU R** (with `Rscript` and a C++ compiler).
|
|
262
266
|
2. In R, install **Rcpp**: `install.packages("Rcpp")`.
|
|
263
|
-
3. Install the gem: `jruby -S gem install galaaz`
|
|
267
|
+
3. Install the gem: `jruby -S gem install galaaz` (or `gem install galaaz` under CRuby).
|
|
264
268
|
4. Compile the native gatekeeper from the installed gem:
|
|
265
269
|
|
|
266
270
|
```
|
|
267
|
-
gem_dir="$(
|
|
271
|
+
gem_dir="$(ruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
|
|
272
|
+
# under JRuby: gem_dir="$(jruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
|
|
268
273
|
make -C "${gem_dir}/ext/new_bridge" all
|
|
269
274
|
```
|
|
270
275
|
|
|
271
|
-
5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on Java 9
|
|
276
|
+
5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on **JRuby** (Java 9+), the child JVM needs `--add-opens=java.base/java.nio=ALL-UNNAMED` via **`JAVA_OPTS`** (from a checkout, `bin/galaaz-jruby` and `mise.toml` set this; a leading `jruby -J... -S bundle exec` does **not** pass `-J` to rspec). On **CRuby**, install Apache Arrow GLib (`libarrow-glib-dev` from the [Apache Arrow APT](https://arrow.apache.org/install/) repo) and `gem install red-arrow` matching `pkg-config --modversion arrow-glib`. Do not install the unrelated Rubygems package named `arrow`.
|
|
272
277
|
|
|
273
278
|
For **gKnit**, **knitr**, **rmarkdown**, and LaTeX (PDF output), install the corresponding R packages, **Pandoc**, and a TeX distribution if you need PDF; the repository includes helpers such as **`bin/install-tinytex`** where appropriate.
|
|
274
279
|
|
|
275
|
-
A **table of all `bin/` scripts** (bootstrap,
|
|
280
|
+
A **table of all `bin/` scripts** (bootstrap, Ruby launcher, gstudio, gknit, test runners, and which ones are legacy) is in the section **Command-line tools (`bin/`)** earlier in this manual.
|
|
276
281
|
|
|
277
282
|
### From a repository checkout (contributors)
|
|
278
283
|
|
|
279
|
-
1. Install **bundler** if needed, then run **`
|
|
284
|
+
1. Install **bundler** if needed, then run **`bundle install`** with your chosen Ruby
|
|
285
|
+
(`jruby -S bundle install` or CRuby `bundle install`) in the repository root.
|
|
280
286
|
2. Build the bridge native code: **`make -C ext/new_bridge all`** (or **`rake compile_gatekeeper`**).
|
|
281
|
-
3. Run scripts with **`bin/galaaz-
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
3. Run scripts with **`bin/galaaz-ruby`** (uses `ruby` on `PATH`; set **`GALAAZ_RUBY=jruby`** or
|
|
288
|
+
**`GALAAZ_RUBY=ruby`** to force an engine). Spec runners: **`bin/run_rspec`** /
|
|
289
|
+
**`bin/run_all_rspec`** (same `GALAAZ_RUBY` rule). **`bin/galaaz-jruby`** remains a thin
|
|
290
|
+
wrapper that forces JRuby.
|
|
291
|
+
|
|
292
|
+
A **gstudio** try image with Galaaz already installed is available for both engines:
|
|
293
|
+
**JRuby** — `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio` (or
|
|
294
|
+
`./docker/try-gstudio/run.sh` from a checkout); **CRuby** —
|
|
295
|
+
`docker run --rm -it ghcr.io/rbotafogo/galaaz-try:cruby` (or `./docker/try-cruby/run.sh`).
|
|
296
|
+
Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside
|
|
297
|
+
the container) with `./docker/cold-install/run.sh published-specs` (JRuby) or
|
|
298
|
+
`./docker/cold-install-cruby/run.sh published-specs` (CRuby).
|
|
284
299
|
|
|
285
300
|
## Windows + WSL2 (optional: Docker / R in a container)
|
|
286
301
|
|
|
@@ -315,8 +330,15 @@ WSL integration is enabled for the distro where Galaaz is installed.
|
|
|
315
330
|
```{ruby simple_example}
|
|
316
331
|
vec = R.c(1, 2, 3, 4)
|
|
317
332
|
puts vec
|
|
333
|
+
|
|
334
|
+
# R.foo(...) calls an R *function*. Datasets are objects — fetch with ~:
|
|
335
|
+
df = ~R[:mtcars]
|
|
336
|
+
puts R.summary(df)
|
|
318
337
|
```
|
|
319
|
-
|
|
338
|
+
|
|
339
|
+
(`R.mtcars` is wrong: it becomes `mtcars()` in R and fails. With
|
|
340
|
+
`using Galaaz::SymbolDSL`, the short form `~:mtcars` also works.)
|
|
341
|
+
|
|
320
342
|
* Run all specs
|
|
321
343
|
|
|
322
344
|
> galaaz specs:all
|
|
@@ -371,8 +393,13 @@ driver). For concurrency tests on the bridge itself, see `specs/bridge_concurren
|
|
|
371
393
|
|
|
372
394
|
## Long-running R calls and a completion block
|
|
373
395
|
|
|
374
|
-
For R work that can take a long time
|
|
375
|
-
scheduling the call and resuming in a **block** when the `RET` arrives.
|
|
396
|
+
For R work that can take a long time **on the bridge**, the bridge can avoid a Ruby-side
|
|
397
|
+
**wait timeout** by scheduling the call and resuming in a **block** when the `RET` arrives.
|
|
398
|
+
|
|
399
|
+
**Important distinction:** this keeps the **same** GNU R process busy. Other sync
|
|
400
|
+
`eval_r` / gknit chunks still wait on that R. For CRAN installs and other work that must
|
|
401
|
+
**not** monopolize the bridge (or that can OOM a small VM if abandoned mid-compile), use
|
|
402
|
+
**`R::Job`** in the next section instead.
|
|
376
403
|
|
|
377
404
|
- **`R.eval_r_async(code, timeout: nil) { |result| ... }`** — string eval; on success, `result.value`
|
|
378
405
|
is the same formatted string as **`R.eval_r`** (use `timeout: nil` for no Ruby-side limit).
|
|
@@ -420,6 +447,63 @@ the outcome to storage, and notify the client (poll, WebSocket, Turbo Stream, et
|
|
|
420
447
|
Ruby pattern above is only to show **when** the result exists (inside the block, or after data
|
|
421
448
|
written there is observed elsewhere). Runnable specs live in **`new_bridge_specs/eval_r_async_spec.rb`**.
|
|
422
449
|
|
|
450
|
+
## Background R jobs (`R::Job`)
|
|
451
|
+
|
|
452
|
+
`R::Async` / `R.eval_r_async` free the **Ruby** thread while the **same** bridge R process
|
|
453
|
+
runs your code. That is enough for Rails-style “don’t block the request thread,” but not
|
|
454
|
+
enough for heavy `install.packages` or multi-minute model fits: the gatekeeper R is still
|
|
455
|
+
busy, other chunks time out, and abandoning the wait can leave compile work burning RAM.
|
|
456
|
+
|
|
457
|
+
**`R::Job`** runs that work in a **child `Rscript` process**. The bridge stays free. Logs and
|
|
458
|
+
metadata live under `~/.local/share/galaaz/jobs/` (override with `GALAAZ_JOBS_DIR`).
|
|
459
|
+
|
|
460
|
+
### Package installs
|
|
461
|
+
|
|
462
|
+
`R.install_and_loads` / `R.install_rlibs` use `R::Job.install` and **await until the child
|
|
463
|
+
finishes** (default: no wall-clock limit). Optional limit: `GALAAZ_INSTALL_TIMEOUT_SEC` or
|
|
464
|
+
`install_timeout_sec:`. On timeout the child process group is killed so leftover
|
|
465
|
+
`make`/`gcc` cannot OOM the shell. Stale `00LOCK-*` dirs are cleared before the next install.
|
|
466
|
+
Only one install runs at a time (`install.lock`).
|
|
467
|
+
|
|
468
|
+
```{ruby r_job_install_note, eval=FALSE}
|
|
469
|
+
# May take a long time the first time (e.g. caret); the bridge is not used for compile.
|
|
470
|
+
R.install_and_loads 'caret'
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Long arbitrary R (`eval` / `script`)
|
|
474
|
+
|
|
475
|
+
Prefer the **block** form (like `File.open`): await the child, yield the job, return the
|
|
476
|
+
block’s value. Without a block, the methods still await by default and return the `Job`.
|
|
477
|
+
|
|
478
|
+
```{ruby r_job_eval_block}
|
|
479
|
+
coef = R::Job.eval(<<~R) { |job| job.load_rds }
|
|
480
|
+
fit <- lm(mpg ~ wt, data = mtcars)
|
|
481
|
+
saveRDS(unname(coef(fit)), result_path)
|
|
482
|
+
R
|
|
483
|
+
puts coef
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
```{ruby r_job_eval_no_block, eval=FALSE}
|
|
487
|
+
# Without a block: awaits (wait: true is the default) and returns the Job
|
|
488
|
+
job = R::Job.eval(code)
|
|
489
|
+
job = R::Job.eval(code, wait: false) # start only; call job.wait later
|
|
490
|
+
|
|
491
|
+
# Script file; trailing args → commandArgs(trailingOnly=TRUE) in the child
|
|
492
|
+
res = R::Job.script('train.R', '5') { |job| job.load_rds }
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
In the child: `setwd(job.dir)`, `.libPaths` includes the Galaaz user library,
|
|
496
|
+
`GALAAZ_JOB_DIR` is set, and **`result_path`** defaults to
|
|
497
|
+
`file.path(GALAAZ_JOB_DIR, "result.rds")`. Persist with `saveRDS(..., result_path)`, then
|
|
498
|
+
load on the bridge with **`job.load_rds`** (short sync `readRDS` → a normal Galaaz R object).
|
|
499
|
+
|
|
500
|
+
### Choosing async vs Job
|
|
501
|
+
|
|
502
|
+
| Need | Use |
|
|
503
|
+
|------|-----|
|
|
504
|
+
| Don’t freeze a Ruby thread; short/medium R on the bridge is OK | `R::Async` / `R.eval_r_async` |
|
|
505
|
+
| Install CRAN packages, or long R that must not block the bridge | `R::Job` / `R.install_and_loads` |
|
|
506
|
+
|
|
423
507
|
## Galaaz + Rails (R-on-Rails) integration baseline
|
|
424
508
|
|
|
425
509
|
This is the practical **R-on-Rails** starter: an R scientist’s analysis behind a small Rails
|
|
@@ -633,9 +717,9 @@ puts vec.map { |x| x + 2 }
|
|
|
633
717
|
|
|
634
718
|
This manual has been formatted using gKnit. gKnit uses knitr and R Markdown to knit
|
|
635
719
|
a document in Ruby or R and output it in any of the available formats for R Markdown.
|
|
636
|
-
gKnit runs with **JRuby**, **GNU R**, and Galaaz. In gKnit, Ruby variables are persisted between
|
|
637
|
-
chunks, making it an ideal solution for literate programming. Also, since it is based
|
|
638
|
-
on Galaaz, Ruby chunks can have access to R variables and combining Ruby with R in one
|
|
720
|
+
gKnit runs with **JRuby or CRuby**, **GNU R**, and Galaaz. In gKnit, Ruby variables are persisted between
|
|
721
|
+
chunks, making it an ideal solution for literate programming. Also, since it is based
|
|
722
|
+
on Galaaz, Ruby chunks can have access to R variables and combining Ruby with R in one
|
|
639
723
|
document is natural.
|
|
640
724
|
|
|
641
725
|
The idea of "literate programming" was first introduced by Donald Knuth in the
|
|
@@ -1163,7 +1247,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
|
|
|
1163
1247
|
|
|
1164
1248
|
A word of advice: the 'caret' package has lots of dependencies and installing
|
|
1165
1249
|
it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
|
|
1166
|
-
will install the package if it is not already installed
|
|
1250
|
+
will install the package if it is not already installed (via **`R::Job`**: a child
|
|
1251
|
+
`Rscript`, so the bridge stays free) and can take a while.
|
|
1167
1252
|
|
|
1168
1253
|
````
|
|
1169
1254
|
```{include model}`r ''`
|
|
@@ -2008,7 +2093,8 @@ a real **Arrow `Table` inside GNU R**. From there you use R’s **`arrow`** and
|
|
|
2008
2093
|
as usual: **`group_by`** on the Arrow table, **`summarise`** for aggregates, then **`collect()`** to
|
|
2009
2094
|
materialize a tibble when you need in-memory R rows.
|
|
2010
2095
|
|
|
2011
|
-
That pattern matches production use: **JRuby threads**
|
|
2096
|
+
That pattern matches production use: **JRuby threads** or a **multi-process CRuby** app
|
|
2097
|
+
(or sequential code) assemble many rows in
|
|
2012
2098
|
Ruby; you pay **one** bridge-heavy handoff to R; **dplyr** runs vectorised work on the Arrow table
|
|
2013
2099
|
in R.
|
|
2014
2100
|
|
|
@@ -2112,10 +2198,11 @@ pulls in several dependencies; the first install can take several minutes.
|
|
|
2112
2198
|
## Example: DESeq2 on the airway dataset
|
|
2113
2199
|
|
|
2114
2200
|
The script **`examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb`** is the canonical
|
|
2115
|
-
version in the repository. Run it from the **Galaaz repository root** with
|
|
2201
|
+
version in the repository. Run it from the **Galaaz repository root** with either engine, for example:
|
|
2116
2202
|
|
|
2117
2203
|
```text
|
|
2118
|
-
bin/galaaz-
|
|
2204
|
+
bin/galaaz-ruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
2205
|
+
# or: bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
2119
2206
|
```
|
|
2120
2207
|
|
|
2121
2208
|
The workflow in Ruby mirrors a standard DESeq2 vignette:
|
|
@@ -2143,7 +2230,7 @@ manual is knitted, because **DESeq2** is heavy and may be absent on the build ma
|
|
|
2143
2230
|
|
|
2144
2231
|
```{ruby deseq2_airway_full_listing, eval=FALSE}
|
|
2145
2232
|
# Canonical script: examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
2146
|
-
# Run: bin/galaaz-
|
|
2233
|
+
# Run: bin/galaaz-ruby examples/.../deseq2_airway_galaaz.rb (repo root).
|
|
2147
2234
|
|
|
2148
2235
|
require 'galaaz'
|
|
2149
2236
|
|
|
@@ -2665,8 +2752,8 @@ arguments.
|
|
|
2665
2752
|
|
|
2666
2753
|
* Fork it
|
|
2667
2754
|
* Create your feature branch (`git checkout -b my-new-feature`)
|
|
2668
|
-
* Write tests — use **`bin/run_rspec`** or **`bin/run_all_rspec`**
|
|
2669
|
-
the load path
|
|
2755
|
+
* Write tests — use **`bin/run_rspec`** or **`bin/run_all_rspec`** (JRuby or CRuby via
|
|
2756
|
+
**`GALAAZ_RUBY`**) so the load path matches **`docs/testing.md`**
|
|
2670
2757
|
* Commit your changes (`git commit -am 'Add some feature'`)
|
|
2671
2758
|
* Push to the branch (`git push origin my-new-feature`)
|
|
2672
2759
|
* Open a pull request
|
data/blogs/manual/manual.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: "Galaaz Manual"
|
|
3
3
|
subtitle: "R-on-Rails: GNU R meets Ruby for the web"
|
|
4
4
|
author: "Rodrigo Botafogo"
|
|
5
|
-
tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
|
|
5
|
+
tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, CRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
|
|
6
6
|
date: "2026"
|
|
7
7
|
bibliography: "../../examples/Bibliography/stats.bib"
|
|
8
8
|
output:
|
|
@@ -73,7 +73,7 @@ rewrite in another stack. **R-on-Rails** means:
|
|
|
73
73
|
1. **Keep your science in R** — packages, formulas, plots, Bioconductor, the same engine as RStudio.
|
|
74
74
|
2. **Learn enough Ruby/Rails** — routes, controllers, views, jobs, auth—not a second statistics career.
|
|
75
75
|
3. **Call R from the app** — Galaaz loads R behind the scenes; long jobs can complete asynchronously
|
|
76
|
-
while Rails stays responsive (see later sections on the bridge and `R::
|
|
76
|
+
while Rails stays responsive (see later sections on the bridge, `R::Async`, and `R::Job`).
|
|
77
77
|
4. **Ship alone when you need to** — one developer can own both the analysis and the product UI,
|
|
78
78
|
without waiting for a separate “stats engineer” and “Rails engineer.”
|
|
79
79
|
|
|
@@ -358,7 +358,13 @@ A **table of all `bin/` scripts** (bootstrap, Ruby launcher, gstudio, gknit, tes
|
|
|
358
358
|
**`bin/run_all_rspec`** (same `GALAAZ_RUBY` rule). **`bin/galaaz-jruby`** remains a thin
|
|
359
359
|
wrapper that forces JRuby.
|
|
360
360
|
|
|
361
|
-
A **gstudio** try image
|
|
361
|
+
A **gstudio** try image with Galaaz already installed is available for both engines:
|
|
362
|
+
**JRuby** — `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio` (or
|
|
363
|
+
`./docker/try-gstudio/run.sh` from a checkout); **CRuby** —
|
|
364
|
+
`docker run --rm -it ghcr.io/rbotafogo/galaaz-try:cruby` (or `./docker/try-cruby/run.sh`).
|
|
365
|
+
Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside
|
|
366
|
+
the container) with `./docker/cold-install/run.sh published-specs` (JRuby) or
|
|
367
|
+
`./docker/cold-install-cruby/run.sh published-specs` (CRuby).
|
|
362
368
|
|
|
363
369
|
## Windows + WSL2 (optional: Docker / R in a container)
|
|
364
370
|
|
|
@@ -394,11 +400,18 @@ WSL integration is enabled for the distro where Galaaz is installed.
|
|
|
394
400
|
``` ruby
|
|
395
401
|
vec = R.c(1, 2, 3, 4)
|
|
396
402
|
puts vec
|
|
403
|
+
|
|
404
|
+
# R.foo(...) calls an R *function*. Datasets are objects — fetch with ~:
|
|
405
|
+
df = ~R[:mtcars]
|
|
406
|
+
puts R.summary(df)
|
|
397
407
|
```
|
|
398
408
|
|
|
399
409
|
```
|
|
400
410
|
## [1] 1 2 3 4
|
|
401
411
|
```
|
|
412
|
+
|
|
413
|
+
(`R.mtcars` is wrong: it becomes `mtcars()` in R and fails. With
|
|
414
|
+
`using Galaaz::SymbolDSL`, the short form `~:mtcars` also works.)
|
|
402
415
|
|
|
403
416
|
* Run all specs
|
|
404
417
|
|
|
@@ -456,8 +469,13 @@ driver). For concurrency tests on the bridge itself, see `specs/bridge_concurren
|
|
|
456
469
|
|
|
457
470
|
## Long-running R calls and a completion block
|
|
458
471
|
|
|
459
|
-
For R work that can take a long time
|
|
460
|
-
scheduling the call and resuming in a **block** when the `RET` arrives.
|
|
472
|
+
For R work that can take a long time **on the bridge**, the bridge can avoid a Ruby-side
|
|
473
|
+
**wait timeout** by scheduling the call and resuming in a **block** when the `RET` arrives.
|
|
474
|
+
|
|
475
|
+
**Important distinction:** this keeps the **same** GNU R process busy. Other sync
|
|
476
|
+
`eval_r` / gknit chunks still wait on that R. For CRAN installs and other work that must
|
|
477
|
+
**not** monopolize the bridge (or that can OOM a small VM if abandoned mid-compile), use
|
|
478
|
+
**`R::Job`** in the next section instead.
|
|
461
479
|
|
|
462
480
|
- **`R.eval_r_async(code, timeout: nil) { |result| ... }`** — string eval; on success, `result.value`
|
|
463
481
|
is the same formatted string as **`R.eval_r`** (use `timeout: nil` for no Ruby-side limit).
|
|
@@ -514,6 +532,67 @@ the outcome to storage, and notify the client (poll, WebSocket, Turbo Stream, et
|
|
|
514
532
|
Ruby pattern above is only to show **when** the result exists (inside the block, or after data
|
|
515
533
|
written there is observed elsewhere). Runnable specs live in **`new_bridge_specs/eval_r_async_spec.rb`**.
|
|
516
534
|
|
|
535
|
+
## Background R jobs (`R::Job`)
|
|
536
|
+
|
|
537
|
+
`R::Async` / `R.eval_r_async` free the **Ruby** thread while the **same** bridge R process
|
|
538
|
+
runs your code. That is enough for Rails-style “don’t block the request thread,” but not
|
|
539
|
+
enough for heavy `install.packages` or multi-minute model fits: the gatekeeper R is still
|
|
540
|
+
busy, other chunks time out, and abandoning the wait can leave compile work burning RAM.
|
|
541
|
+
|
|
542
|
+
**`R::Job`** runs that work in a **child `Rscript` process**. The bridge stays free. Logs and
|
|
543
|
+
metadata live under `~/.local/share/galaaz/jobs/` (override with `GALAAZ_JOBS_DIR`).
|
|
544
|
+
|
|
545
|
+
### Package installs
|
|
546
|
+
|
|
547
|
+
`R.install_and_loads` / `R.install_rlibs` use `R::Job.install` and **await until the child
|
|
548
|
+
finishes** (default: no wall-clock limit). Optional limit: `GALAAZ_INSTALL_TIMEOUT_SEC` or
|
|
549
|
+
`install_timeout_sec:`. On timeout the child process group is killed so leftover
|
|
550
|
+
`make`/`gcc` cannot OOM the shell. Stale `00LOCK-*` dirs are cleared before the next install.
|
|
551
|
+
Only one install runs at a time (`install.lock`).
|
|
552
|
+
|
|
553
|
+
``` ruby
|
|
554
|
+
# May take a long time the first time (e.g. caret); the bridge is not used for compile.
|
|
555
|
+
R.install_and_loads 'caret'
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Long arbitrary R (`eval` / `script`)
|
|
559
|
+
|
|
560
|
+
Prefer the **block** form (like `File.open`): await the child, yield the job, return the
|
|
561
|
+
block’s value. Without a block, the methods still await by default and return the `Job`.
|
|
562
|
+
|
|
563
|
+
``` ruby
|
|
564
|
+
coef = R::Job.eval(<<~R) { |job| job.load_rds }
|
|
565
|
+
fit <- lm(mpg ~ wt, data = mtcars)
|
|
566
|
+
saveRDS(unname(coef(fit)), result_path)
|
|
567
|
+
R
|
|
568
|
+
puts coef
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
## [1] 37.285126 -5.344472
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
``` ruby
|
|
576
|
+
# Without a block: awaits (wait: true is the default) and returns the Job
|
|
577
|
+
job = R::Job.eval(code)
|
|
578
|
+
job = R::Job.eval(code, wait: false) # start only; call job.wait later
|
|
579
|
+
|
|
580
|
+
# Script file; trailing args → commandArgs(trailingOnly=TRUE) in the child
|
|
581
|
+
res = R::Job.script('train.R', '5') { |job| job.load_rds }
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
In the child: `setwd(job.dir)`, `.libPaths` includes the Galaaz user library,
|
|
585
|
+
`GALAAZ_JOB_DIR` is set, and **`result_path`** defaults to
|
|
586
|
+
`file.path(GALAAZ_JOB_DIR, "result.rds")`. Persist with `saveRDS(..., result_path)`, then
|
|
587
|
+
load on the bridge with **`job.load_rds`** (short sync `readRDS` → a normal Galaaz R object).
|
|
588
|
+
|
|
589
|
+
### Choosing async vs Job
|
|
590
|
+
|
|
591
|
+
| Need | Use |
|
|
592
|
+
|------|-----|
|
|
593
|
+
| Don’t freeze a Ruby thread; short/medium R on the bridge is OK | `R::Async` / `R.eval_r_async` |
|
|
594
|
+
| Install CRAN packages, or long R that must not block the bridge | `R::Job` / `R.install_and_loads` |
|
|
595
|
+
|
|
517
596
|
## Galaaz + Rails (R-on-Rails) integration baseline
|
|
518
597
|
|
|
519
598
|
This is the practical **R-on-Rails** starter: an R scientist’s analysis behind a small Rails
|
|
@@ -1873,7 +1952,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
|
|
|
1873
1952
|
|
|
1874
1953
|
A word of advice: the 'caret' package has lots of dependencies and installing
|
|
1875
1954
|
it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
|
|
1876
|
-
will install the package if it is not already installed
|
|
1955
|
+
will install the package if it is not already installed (via **`R::Job`**: a child
|
|
1956
|
+
`Rscript`, so the bridge stays free) and can take a while.
|
|
1877
1957
|
|
|
1878
1958
|
````
|
|
1879
1959
|
```{include model}
|
|
@@ -4194,10 +4274,11 @@ pulls in several dependencies; the first install can take several minutes.
|
|
|
4194
4274
|
## Example: DESeq2 on the airway dataset
|
|
4195
4275
|
|
|
4196
4276
|
The script **`examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb`** is the canonical
|
|
4197
|
-
version in the repository. Run it from the **Galaaz repository root** with
|
|
4277
|
+
version in the repository. Run it from the **Galaaz repository root** with either engine, for example:
|
|
4198
4278
|
|
|
4199
4279
|
```text
|
|
4200
|
-
bin/galaaz-
|
|
4280
|
+
bin/galaaz-ruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
4281
|
+
# or: bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
4201
4282
|
```
|
|
4202
4283
|
|
|
4203
4284
|
The workflow in Ruby mirrors a standard DESeq2 vignette:
|
|
@@ -4226,7 +4307,7 @@ manual is knitted, because **DESeq2** is heavy and may be absent on the build ma
|
|
|
4226
4307
|
|
|
4227
4308
|
``` ruby
|
|
4228
4309
|
# Canonical script: examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
|
|
4229
|
-
# Run: bin/galaaz-
|
|
4310
|
+
# Run: bin/galaaz-ruby examples/.../deseq2_airway_galaaz.rb (repo root).
|
|
4230
4311
|
|
|
4231
4312
|
require 'galaaz'
|
|
4232
4313
|
|
data/blogs/oh_my/oh_my.Rmd
CHANGED
|
@@ -3,7 +3,7 @@ title: "Extending R with classes, modules, procs, lambdas, oh my!"
|
|
|
3
3
|
author:
|
|
4
4
|
- "Rodrigo Botafogo"
|
|
5
5
|
- "Daniel Mossé - University of Pittsburgh"
|
|
6
|
-
tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
|
|
6
|
+
tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
|
|
7
7
|
date: "November 19th, 2018 (narrative updated for Galaaz 2.0, 2026)"
|
|
8
8
|
output:
|
|
9
9
|
html_document:
|
|
@@ -34,10 +34,10 @@ or similar solutions in Python,
|
|
|
34
34
|
such as [PypeR](https://pypi.python.org/pypi/PypeR/1.1.0), [rpy2](http://rpy2.bitbucket.org/)
|
|
35
35
|
and other similar solutions.
|
|
36
36
|
|
|
37
|
-
**Galaaz 2.0**
|
|
38
|
-
so Ruby code can create and manipulate R objects and call R
|
|
39
|
-
An earlier prototype used Oracle’s **GraalVM** with
|
|
40
|
-
historical and is **not** what current Galaaz targets.
|
|
37
|
+
**Galaaz 2.0** supports **[JRuby](https://www.jruby.org/)** or **CRuby** equally and drives
|
|
38
|
+
**GNU R** through a **bridge**, so Ruby code can create and manipulate R objects and call R
|
|
39
|
+
functions while staying idiomatic Ruby. An earlier prototype used Oracle’s **GraalVM** with
|
|
40
|
+
**TruffleRuby** and **FastR**; that stack is historical and is **not** what current Galaaz targets.
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
# Bases of Object Programming
|
|
@@ -1673,7 +1673,7 @@ For those interested in Ruby and science, we recommend:
|
|
|
1673
1673
|
|
|
1674
1674
|
* http://sciruby.com/
|
|
1675
1675
|
|
|
1676
|
-
**Galaaz 2.0** runs on **JRuby** and talks to **GNU R** through the bridge described in this
|
|
1676
|
+
**Galaaz 2.0** runs on **JRuby or CRuby** and talks to **GNU R** through the bridge described in this
|
|
1677
1677
|
series — the same integration model used throughout the examples above.
|
|
1678
1678
|
|
|
1679
1679
|
# Callbacks and R calling into Ruby
|
|
@@ -1695,7 +1695,7 @@ path in Galaaz 2.0.
|
|
|
1695
1695
|
|
|
1696
1696
|
# Conclusions II
|
|
1697
1697
|
|
|
1698
|
-
**JRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
|
|
1698
|
+
**JRuby or CRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
|
|
1699
1699
|
libraries, GNU R for statistics and the CRAN/Bioconductor ecosystem, and Galaaz as the bridge
|
|
1700
1700
|
between them. As always, choose the right tools for the job at hand — and when the job sits
|
|
1701
1701
|
between an R-only workflow and a broader polyglot application, Galaaz is designed to connect those
|
data/blogs/oh_my/oh_my.md
CHANGED
|
@@ -3,7 +3,7 @@ title: "Extending R with classes, modules, procs, lambdas, oh my!"
|
|
|
3
3
|
author:
|
|
4
4
|
- "Rodrigo Botafogo"
|
|
5
5
|
- "Daniel Mossé - University of Pittsburgh"
|
|
6
|
-
tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
|
|
6
|
+
tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
|
|
7
7
|
date: "November 19th, 2018 (narrative updated for Galaaz 2.0, 2026)"
|
|
8
8
|
output:
|
|
9
9
|
html_document:
|
|
@@ -32,10 +32,10 @@ or similar solutions in Python,
|
|
|
32
32
|
such as [PypeR](https://pypi.python.org/pypi/PypeR/1.1.0), [rpy2](http://rpy2.bitbucket.org/)
|
|
33
33
|
and other similar solutions.
|
|
34
34
|
|
|
35
|
-
**Galaaz 2.0**
|
|
36
|
-
so Ruby code can create and manipulate R objects and call R
|
|
37
|
-
An earlier prototype used Oracle’s **GraalVM** with
|
|
38
|
-
historical and is **not** what current Galaaz targets.
|
|
35
|
+
**Galaaz 2.0** supports **[JRuby](https://www.jruby.org/)** or **CRuby** equally and drives
|
|
36
|
+
**GNU R** through a **bridge**, so Ruby code can create and manipulate R objects and call R
|
|
37
|
+
functions while staying idiomatic Ruby. An earlier prototype used Oracle’s **GraalVM** with
|
|
38
|
+
**TruffleRuby** and **FastR**; that stack is historical and is **not** what current Galaaz targets.
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
# Bases of Object Programming
|
|
@@ -2148,7 +2148,7 @@ For those interested in Ruby and science, we recommend:
|
|
|
2148
2148
|
|
|
2149
2149
|
* http://sciruby.com/
|
|
2150
2150
|
|
|
2151
|
-
**Galaaz 2.0** runs on **JRuby** and talks to **GNU R** through the bridge described in this
|
|
2151
|
+
**Galaaz 2.0** runs on **JRuby or CRuby** and talks to **GNU R** through the bridge described in this
|
|
2152
2152
|
series — the same integration model used throughout the examples above.
|
|
2153
2153
|
|
|
2154
2154
|
# Callbacks and R calling into Ruby
|
|
@@ -2170,7 +2170,7 @@ path in Galaaz 2.0.
|
|
|
2170
2170
|
|
|
2171
2171
|
# Conclusions II
|
|
2172
2172
|
|
|
2173
|
-
**JRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
|
|
2173
|
+
**JRuby or CRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
|
|
2174
2174
|
libraries, GNU R for statistics and the CRAN/Bioconductor ecosystem, and Galaaz as the bridge
|
|
2175
2175
|
between them. As always, choose the right tools for the job at hand — and when the job sits
|
|
2176
2176
|
between an R-only workflow and a broader polyglot application, Galaaz is designed to connect those
|