diff --git a/.Rbuildignore b/.Rbuildignore index 5d97a0bf..d713df39 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -20,4 +20,5 @@ ^\\.git$ ^codecov\.yml$ ^vignettes/articles$ -^data_aux$ \ No newline at end of file +^data_aux$ +^python(/.*)?$ \ No newline at end of file diff --git a/.gitignore b/.gitignore index ea55e2da..32e172ac 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ markeR.Rproj inst/doc data_aux markeR.Rcheck +/python/.venv diff --git a/DESCRIPTION b/DESCRIPTION index f26c26a2..17cce5fd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: markeR Title: An R Toolkit for Evaluating Gene Signatures as Phenotypic Markers -Version: 1.1.1 +Version: 1.1.2 Authors@R: c( person("Rita", "Martins-Silva", diff --git a/NEWS.md b/NEWS.md index 16ad0ff4..4d66e94e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# markeR 1.1.2 (12 Mar, 2026) + +## Minor Changes +- Moved Python bridge scripts from `inst/python/` to a top-level `python/` + directory, as these are supplementary scripts not part of the R package itself. +- Added `requirements.txt` to the `python/` directory listing all needed + Python dependencies (`rpy2`, `pandas`, `numpy`, and optionally + `ipython` and `jupyter`) for easier environment setup. +- Removed redundant code snippets from the Python bridge scripts. + # markeR 1.1.1 (11 Mar, 2026) - Added `p.adjust.method` parameter across all functions performing or diff --git a/README.Rmd b/README.Rmd index 13d6d7a5..94f25d9c 100644 --- a/README.Rmd +++ b/README.Rmd @@ -30,7 +30,7 @@ knitr::opts_chunk$set( > **To cite `markeR` please use:** > -> Martins-Silva R, Kaizeler A, Barbosa-Morais NL (2025). _markeR: An R Toolkit for Evaluating Gene Signatures as Phenotypic Markers_. doi:10.18129/B9.bioc.markeR, R package version 1.1.1, https://bioconductor.org/packages/markeR. +> Martins-Silva R, Kaizeler A, Barbosa-Morais NL (2025). _markeR: An R Toolkit for Evaluating Gene Signatures as Phenotypic Markers_. doi:10.18129/B9.bioc.markeR, R package version 1.1.2, https://bioconductor.org/packages/markeR. The folder `inst/Paper/` is in the **paper** branch and contains all scripts and materials used in the original `markeR` paper to reproduce analyses and figures. You can browse it [here](https://github.com/DiseaseTranscriptomicsLab/markeR/tree/paper/inst/Paper). @@ -241,10 +241,10 @@ Filters can be applied based on similarity thresholds (e.g., minimum Jaccard, OR ## Python Bridge For users who prefer Python, a lightweight bridge is available in -`inst/python/` that allows calling any `markeR` function from a Python +`python/` that allows calling any `markeR` function from a Python environment via [`rpy2`](https://rpy2.github.io/). It includes a tutorial workflow script and a generic command-line wrapper. See -[`inst/python/README.md`](inst/python/README.md) for installation +[`python/README.md`](inst/python/README.md) for installation instructions and usage examples. diff --git a/README.md b/README.md index f2e267d6..0daabf81 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ across experimental and clinical phenotypes. > > Martins-Silva R, Kaizeler A, Barbosa-Morais NL (2025). *markeR: An R > Toolkit for Evaluating Gene Signatures as Phenotypic Markers*. -> , R package version 1.1.1, +> , R package version 1.1.2, > . The folder `inst/Paper/` is in the **paper** branch and contains all @@ -288,10 +288,10 @@ Jaccard, OR, or Fisher’s test p-value). ## Python Bridge For users who prefer Python, a lightweight bridge is available in -`inst/python/` that allows calling any `markeR` function from a Python +`python/` that allows calling any `markeR` function from a Python environment via [`rpy2`](https://rpy2.github.io/). It includes a tutorial workflow script and a generic command-line wrapper. See -[`inst/python/README.md`](inst/python/README.md) for installation +[`python/README.md`](inst/python/README.md) for installation instructions and usage examples. ## Contact diff --git a/inst/python/README.md b/python/README.md similarity index 89% rename from inst/python/README.md rename to python/README.md index c1f66512..a2f7b7b6 100644 --- a/inst/python/README.md +++ b/python/README.md @@ -12,14 +12,20 @@ Bioconductor R package **markeR** via `rpy2`. ## Prerequisites -* R (>=.4.5) installed and on your `PATH`. -* A Python virtual environment. Install dependencies with: - -```bash -python -m venv .venv -source .venv/bin/activate -pip install -r requirements.txt -``` +* R (>=4.5) installed and on your `PATH`. +* A Python virtual environment. A `requirements.txt` file is provided in + this folder listing the needed packages (`rpy2`, `pandas`, `numpy` plus + optional `ipython`/`jupyter` for notebook usage). + To set up the environment: + + ```bash + python -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + ``` + + After activation you can run the helper scripts using `python` from the + same environment. ## Quick start diff --git a/inst/python/markeR_to_python.py b/python/markeR_to_python.py similarity index 94% rename from inst/python/markeR_to_python.py rename to python/markeR_to_python.py index 83256c10..a2e3b09f 100644 --- a/inst/python/markeR_to_python.py +++ b/python/markeR_to_python.py @@ -60,12 +60,6 @@ # converting. helpers below wrap the recommended API. -def _to_r(obj): - """Convert a pandas object to an R object using the current converter.""" - with conversion.localconverter(ro.default_converter + pandas2ri.converter): - return conversion.py2rpy(obj) - - def _to_py(obj): """Convert an R object to a pandas/numpy equivalent.""" with conversion.localconverter(ro.default_converter + pandas2ri.converter): @@ -175,7 +169,6 @@ def plot_r_function(func_name: str, *args, width=800, height=600, filename=None, def ensure_bioc_installed() -> None: """Install Bioconductor's package manager if it is not already present.""" - utils = importr("utils") biocinstaller = "BiocManager" if not isinstalled(biocinstaller): ro.r('install.packages("{0}")'.format(biocinstaller)) @@ -189,14 +182,9 @@ def install_markeR() -> None: the package should be loadable via `importr("markeR")`. """ ensure_bioc_installed() - # use importr to check presence rather than only the isinstalled helper - try: - importr("markeR") - except Exception: - # attempt installation if import failed + if not isinstalled("markeR"): ro.r('BiocManager::install("markeR", ask=FALSE, update=FALSE)') - # load into namespace for side effects - ro.r('library(markeR)') + ro.r('library(markeR)') def get_markeR_functions() -> ro.Environment: @@ -204,7 +192,7 @@ def get_markeR_functions() -> ro.Environment: Example: mark = get_markeR_functions() - imputed = mark.rgImpute(...) + scores = mark.CalculateScores(data=counts, metadata=metadata, gene_sets=genesets, method="logmedian") """ install_markeR() # importing via importr is more reliable than accessing `ro.r['markeR']`. @@ -330,4 +318,4 @@ def tutorial_benchmark(output_file=None): print(" example: python markeR_to_python.py --tutorial --output my_plot.png") else: print("usage: python markeR_to_python.py --tutorial [--output FILENAME]") - print("See the module docstring for more details.") + print("See the module docstring for more details.") \ No newline at end of file diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 00000000..fdb0e44a --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,9 @@ +# install packages for markeR_to_python script: +# pip install -r requirements.txt + +rpy2>=3.6 +pandas +numpy +# optional (for notebook inline display) +ipython +jupyter diff --git a/inst/python/run_marker_function.py b/python/run_marker_function.py similarity index 95% rename from inst/python/run_marker_function.py rename to python/run_marker_function.py index 268fd552..5ca33375 100644 --- a/inst/python/run_marker_function.py +++ b/python/run_marker_function.py @@ -25,6 +25,7 @@ import argparse import json import os +import re # Check dependencies _missing = [] @@ -42,7 +43,6 @@ def ensure_bioc_installed() -> None: """Install Bioconductor's package manager if it is not already present.""" - utils = importr("utils") biocinstaller = "BiocManager" if not isinstalled(biocinstaller): ro.r('install.packages("{0}")'.format(biocinstaller)) @@ -52,11 +52,9 @@ def ensure_bioc_installed() -> None: def install_markeR() -> None: """Install the markeR package from Bioconductor if not already installed.""" ensure_bioc_installed() - try: - importr("markeR") - except Exception: + if not isinstalled("markeR"): ro.r('BiocManager::install("markeR", ask=FALSE, update=FALSE)') - ro.r('library(markeR)') + ro.r('library(markeR)') def load_example_data(): @@ -113,7 +111,8 @@ def parse_parameter(value: str): except (json.JSONDecodeError, ValueError): pass - # Default: treat as string + # Default: treat as string, escaping any internal quotes + value = value.replace('"', '\\"') return f'"{value}"' @@ -272,6 +271,12 @@ def main(): return func_name = sys.argv[1] + + # Validate function name to prevent code injection + if not re.match(r'^[A-Za-z][A-Za-z0-9_.]*$', func_name): + sys.exit(f"Error: invalid function name '{func_name}'. " + "Function names must start with a letter and contain only letters, digits, dots or underscores.") + output_file = None width = 800 height = 600 @@ -358,4 +363,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file