Developing a Dual R and Python Package: Monorepo vs. Separate Repositories
The Dilemma: One Repository or Two?
When developing a library that targets both the Python and R ecosystems, deciding on your repository architecture is one of the most critical early choices you will make. You might find yourself torn between keeping everything centralized in a monorepo or splitting the implementations into dedicated repositories to appease ecosystem-specific conventions.
Add in subtle naming mismatches—such as Python preferring snake_case (good_name) and R traditionally favoring period or camelCase separation (good.name)—and the decision becomes even trickier. Here is an in-depth breakdown of how industry leaders handle this problem, the technical trade-offs of both approaches, and how to choose the right strategy for your project.
Option 1: The Monorepo Approach
In a monorepo, both language packages live under the same GitHub repository, typically organized into distinct subdirectories:
good-name/\n├── python/\n│ ├── pyproject.toml\n│ └── src/good_name/\n├── r/\n│ ├── DESCRIPTION\n│ ├── NAMESPACE\n│ └── R/\n├── docs/\n└── README.mdWhen a Monorepo Makes Sense
- Shared Native Core: If your library is backed by a compiled engine (such as C++, C, or Rust) via tools like
pybind11andRcpp/extendr, a monorepo is almost mandatory. Projects like Apache Arrow, XGBoost, and DuckDB use this strategy so that changes to the core engine propagate to both language wrappers simultaneously. - Synchronized Logic and Algorithms: If changes in API design or algorithms must stay strictly parallel across both languages, a single repository allows you to update both implementations in a single pull request.
- Centralized Community Hub: Having a single issue tracker and documentation hub reduces fragmentation. Users do not need to hunt across multiple repositories to file bug reports or discuss algorithmic details.
Challenges with the Monorepo
- Installation Footprint: Installing directly from GitHub requires specifying subdirectories. While modern tooling supports this, it introduces friction:
- R:
remotes::install_github("user/good-name", subdir = "r") - Python:
pip install git+https://github.com/user/good-name.git#subdirectory=python
- R:
- Release Tagging: If version numbers desynchronize (e.g., you release a quick patch for Python
v1.0.1while R stays onv1.0.0), Git tags can become messy (e.g.,python-v1.0.1vsr-v1.0.0).
Option 2: Separate Repositories (Polyrepo)
In this approach, you maintain two entirely independent repositories: good_name for Python and good.name for R.
When Separate Repositories Make Sense
- Independent Implementations: If the packages are completely independent reimplementations written natively in pure Python and pure R without a shared compiled backend.
- Frictionless Ecosystem Tooling: Both CRAN and PyPI have distinct automation and CI/CD pipelines. Standard R packages expect root-level files (
DESCRIPTION,NAMESPACE), and tools likedevtools::install_github("user/good.name")work immediately out of the box without thesubdirargument. - Different Maintenance Cycles: If one language sees significantly higher development velocity, separate repositories prevent commit noise from polluting the other ecosystem's history.
Resolving the Naming Difference (good_name vs. good.name)
If you choose a monorepo, standard practice is to use a neutral repository name on GitHub using kebab-case:
github.com/organization/good-nameInside the subdirectories, adhere strictly to each community's packaging standards:
- In
python/pyproject.toml: declare the package asgood_name. - In
r/DESCRIPTION: declare the package asgood.name(orgoodname).
Note on R naming: While dots were historically common in R package names (e.g., data.table), the modern tidyverse and CRAN best practices increasingly recommend lower case without dots (e.g., goodname) to prevent confusion with S3 methods (where dots denote generic-method pairings).
The Verdict: Which Should You Choose?
- Choose a Monorepo if: You have a shared C++/Rust core, want a single source of truth for documentation and cross-language issues, or intend to maintain strict feature parity across both languages.
- Choose Separate Repositories if: The implementations are native and independent, you have separate maintainers for R and Python, or you prioritize standard zero-config installation directly from GitHub.