rpy-bridge 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- rpy_bridge/__init__.py +1 -1
- rpy_bridge/rpy2_utils.py +60 -32
- {rpy_bridge-0.3.1.dist-info → rpy_bridge-0.3.3.dist-info}/METADATA +6 -1
- rpy_bridge-0.3.3.dist-info/RECORD +8 -0
- rpy_bridge-0.3.1.dist-info/RECORD +0 -8
- {rpy_bridge-0.3.1.dist-info → rpy_bridge-0.3.3.dist-info}/WHEEL +0 -0
- {rpy_bridge-0.3.1.dist-info → rpy_bridge-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {rpy_bridge-0.3.1.dist-info → rpy_bridge-0.3.3.dist-info}/top_level.txt +0 -0
rpy_bridge/__init__.py
CHANGED
|
@@ -10,13 +10,13 @@ from .rpy2_utils import (
|
|
|
10
10
|
activate_renv,
|
|
11
11
|
align_numeric_dtypes,
|
|
12
12
|
clean_r_dataframe,
|
|
13
|
+
clean_r_missing,
|
|
13
14
|
compare_r_py_dataframes,
|
|
14
15
|
fix_r_dataframe_types,
|
|
15
16
|
fix_string_nans,
|
|
16
17
|
normalize_dtypes,
|
|
17
18
|
normalize_single_df_dtypes,
|
|
18
19
|
postprocess_r_dataframe,
|
|
19
|
-
clean_r_missing,
|
|
20
20
|
r_namedlist_to_dict,
|
|
21
21
|
)
|
|
22
22
|
|
rpy_bridge/rpy2_utils.py
CHANGED
|
@@ -10,24 +10,36 @@ Ensure compatibility with your R project's renv setup (or other virtual env/base
|
|
|
10
10
|
# ruff: noqa: E402
|
|
11
11
|
# %%
|
|
12
12
|
# Import libraries
|
|
13
|
+
import importlib.util
|
|
13
14
|
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
14
17
|
import warnings
|
|
15
|
-
|
|
16
|
-
warnings.filterwarnings("ignore", message="Environment variable .* redefined by R")
|
|
17
|
-
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
import
|
|
20
|
-
import subprocess
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
21
20
|
|
|
22
|
-
import math
|
|
23
21
|
import numpy as np
|
|
24
22
|
import pandas as pd
|
|
25
23
|
|
|
24
|
+
warnings.filterwarnings("ignore", message="Environment variable .* redefined by R")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
import logging as logging_module
|
|
29
|
+
|
|
30
|
+
from loguru import Logger as LoguruLogger
|
|
31
|
+
|
|
32
|
+
LoggerType = LoggerType = Union[LoguruLogger, logging_module.Logger]
|
|
33
|
+
else:
|
|
34
|
+
LoggerType = None # runtime doesn’t need the type object
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
|
|
26
38
|
try:
|
|
27
|
-
from loguru import logger # type: ignore
|
|
28
|
-
except Exception:
|
|
29
|
-
import logging
|
|
39
|
+
from loguru import logger as loguru_logger # type: ignore
|
|
30
40
|
|
|
41
|
+
logger = loguru_logger
|
|
42
|
+
except ImportError:
|
|
31
43
|
logging.basicConfig()
|
|
32
44
|
logger = logging.getLogger("rpy-bridge")
|
|
33
45
|
|
|
@@ -35,41 +47,45 @@ except Exception:
|
|
|
35
47
|
# ---------------------------------------------------------------------
|
|
36
48
|
# R detection and rpy2 installation
|
|
37
49
|
# ---------------------------------------------------------------------
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
def ensure_rpy2_available() -> None:
|
|
51
|
+
"""
|
|
52
|
+
Ensure rpy2 is importable.
|
|
53
|
+
Do NOT attempt to install dynamically; fail with clear instructions instead.
|
|
54
|
+
"""
|
|
55
|
+
if importlib.util.find_spec("rpy2") is None:
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
"\n[Error] rpy2 is not installed. Please install it in your Python environment:\n"
|
|
58
|
+
" pip install rpy2\n\n"
|
|
59
|
+
"Make sure your Python environment can access your system R installation.\n"
|
|
60
|
+
"On macOS with Homebrew: brew install r\n"
|
|
61
|
+
"On Linux: apt install r-base (Debian/Ubuntu) or yum install R (CentOS/RHEL)\n"
|
|
62
|
+
"On Windows: install R from https://cran.r-project.org\n"
|
|
48
63
|
)
|
|
49
|
-
import rpy2 # noqa: F401
|
|
50
64
|
|
|
51
65
|
|
|
52
|
-
def find_r_home():
|
|
66
|
+
def find_r_home() -> str | None:
|
|
67
|
+
"""Detect system R installation."""
|
|
53
68
|
try:
|
|
54
69
|
r_home = subprocess.check_output(
|
|
55
70
|
["R", "--vanilla", "--slave", "-e", "cat(R.home())"],
|
|
56
71
|
stderr=subprocess.PIPE,
|
|
57
72
|
text=True,
|
|
58
73
|
).strip()
|
|
59
|
-
if r_home.endswith(">"):
|
|
74
|
+
if r_home.endswith(">"): # sometimes R console prints >
|
|
60
75
|
r_home = r_home[:-1].strip()
|
|
61
76
|
return r_home
|
|
62
77
|
except FileNotFoundError:
|
|
78
|
+
# fallback paths (Linux, macOS Homebrew, Windows)
|
|
63
79
|
possible_paths = [
|
|
64
80
|
"/usr/lib/R",
|
|
65
81
|
"/usr/local/lib/R",
|
|
66
|
-
"/opt/homebrew/Cellar/r/4.5.2/lib/R", # Homebrew
|
|
82
|
+
"/opt/homebrew/Cellar/r/4.5.2/lib/R", # macOS Homebrew
|
|
67
83
|
"C:\\Program Files\\R\\R-4.5.2", # Windows
|
|
68
84
|
]
|
|
69
85
|
for p in possible_paths:
|
|
70
86
|
if os.path.exists(p):
|
|
71
87
|
return p
|
|
72
|
-
|
|
88
|
+
return None
|
|
73
89
|
|
|
74
90
|
|
|
75
91
|
R_HOME = find_r_home()
|
|
@@ -78,7 +94,7 @@ if not R_HOME:
|
|
|
78
94
|
|
|
79
95
|
logger.info(f"R_HOME = {R_HOME}")
|
|
80
96
|
os.environ["R_HOME"] = R_HOME
|
|
81
|
-
|
|
97
|
+
ensure_rpy2_available()
|
|
82
98
|
|
|
83
99
|
# macOS dynamic library path
|
|
84
100
|
if sys.platform == "darwin":
|
|
@@ -107,6 +123,8 @@ def _require_rpy2(raise_on_missing: bool = True) -> dict | None:
|
|
|
107
123
|
try:
|
|
108
124
|
import rpy2.robjects as ro
|
|
109
125
|
from rpy2 import robjects
|
|
126
|
+
from rpy2.rinterface_lib.sexp import NULLType
|
|
127
|
+
from rpy2.rlike.container import NamedList
|
|
110
128
|
from rpy2.robjects import pandas2ri
|
|
111
129
|
from rpy2.robjects.conversion import localconverter
|
|
112
130
|
from rpy2.robjects.vectors import (
|
|
@@ -116,8 +134,6 @@ def _require_rpy2(raise_on_missing: bool = True) -> dict | None:
|
|
|
116
134
|
ListVector,
|
|
117
135
|
StrVector,
|
|
118
136
|
)
|
|
119
|
-
from rpy2.rinterface_lib.sexp import NULLType
|
|
120
|
-
from rpy2.rlike.container import NamedList
|
|
121
137
|
|
|
122
138
|
_RPY2 = {
|
|
123
139
|
"ro": ro,
|
|
@@ -146,6 +162,7 @@ def _ensure_rpy2() -> dict:
|
|
|
146
162
|
global _RPY2
|
|
147
163
|
if _RPY2 is None:
|
|
148
164
|
_RPY2 = _require_rpy2()
|
|
165
|
+
assert _RPY2 is not None, "_require_rpy2() returned None"
|
|
149
166
|
return _RPY2
|
|
150
167
|
|
|
151
168
|
|
|
@@ -318,7 +335,6 @@ class RFunctionCaller:
|
|
|
318
335
|
self._ensure_r_loaded()
|
|
319
336
|
robjects = self.robjects
|
|
320
337
|
pandas2ri = self.pandas2ri
|
|
321
|
-
IntVector = self.IntVector
|
|
322
338
|
FloatVector = self.FloatVector
|
|
323
339
|
BoolVector = self.BoolVector
|
|
324
340
|
StrVector = self.StrVector
|
|
@@ -470,13 +486,25 @@ class RFunctionCaller:
|
|
|
470
486
|
self._ensure_r_loaded()
|
|
471
487
|
|
|
472
488
|
# --- Find the function ---
|
|
489
|
+
func = None
|
|
473
490
|
try:
|
|
474
491
|
func = self.robjects.globalenv[func_name] # script-defined
|
|
475
492
|
except KeyError:
|
|
476
493
|
try:
|
|
477
494
|
func = self.robjects.r[func_name] # base or package function
|
|
478
495
|
except KeyError:
|
|
479
|
-
|
|
496
|
+
# --- Added: handle namespaced functions like stats::median ---
|
|
497
|
+
if "::" in func_name:
|
|
498
|
+
pkg, fname = func_name.split("::", 1)
|
|
499
|
+
try:
|
|
500
|
+
func = self.robjects.r(f"{pkg}::{fname}")
|
|
501
|
+
except Exception as e:
|
|
502
|
+
raise RuntimeError(
|
|
503
|
+
f"Failed to load R function '{func_name}' via namespace: {e}"
|
|
504
|
+
) from e
|
|
505
|
+
|
|
506
|
+
if func is None:
|
|
507
|
+
raise ValueError(f"R function '{func_name}' not found.")
|
|
480
508
|
|
|
481
509
|
# --- Convert Python args to R ---
|
|
482
510
|
r_args = [self._py2r(a) for a in args]
|
|
@@ -749,12 +777,12 @@ def compare_r_py_dataframes(
|
|
|
749
777
|
dict with mismatch diagnostics, preserving original indices in diffs.
|
|
750
778
|
"""
|
|
751
779
|
|
|
752
|
-
results = {
|
|
780
|
+
results: dict[str, Any] = {
|
|
753
781
|
"shape_mismatch": False,
|
|
754
782
|
"columns_mismatch": False,
|
|
755
783
|
"index_mismatch": False,
|
|
756
|
-
"numeric_diffs": {},
|
|
757
|
-
"non_numeric_diffs": {},
|
|
784
|
+
"numeric_diffs": {}, # type: dict[str, pd.DataFrame]
|
|
785
|
+
"non_numeric_diffs": {}, # type: dict[str, pd.DataFrame]
|
|
758
786
|
}
|
|
759
787
|
|
|
760
788
|
# --- Preprocessing: fix R-specific issues ---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rpy-bridge
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Python-to-R interoperability engine with environment management, type-safe conversions, data normalization, and safe R function execution.
|
|
5
5
|
Author-email: Victoria Cheung <victoriakcheung@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -31,12 +31,17 @@ License: MIT License
|
|
|
31
31
|
|
|
32
32
|
Project-URL: Homepage, https://github.com/vic-cheung/rpy-bridge
|
|
33
33
|
Project-URL: Issue Tracker, https://github.com/vic-cheung/rpy-bridge/issues
|
|
34
|
+
Keywords: python,r,rpy2,python-r,interoperability,data-science,statistics,bioinformatics
|
|
34
35
|
Classifier: License :: OSI Approved :: MIT License
|
|
35
36
|
Classifier: Programming Language :: Python
|
|
36
37
|
Classifier: Programming Language :: Python :: 3
|
|
37
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
38
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Intended Audience :: Developers
|
|
41
|
+
Classifier: Intended Audience :: Science/Research
|
|
39
42
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
45
|
Requires-Python: >=3.11
|
|
41
46
|
Description-Content-Type: text/markdown
|
|
42
47
|
License-File: LICENSE
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
rpy_bridge/__init__.py,sha256=1cyWVzhVnSqMRY6OkSo8RYjTKWjmaV9WR-otu4Y5dJc,829
|
|
2
|
+
rpy_bridge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
rpy_bridge/rpy2_utils.py,sha256=n58oSoqkZRv320dtgxEW597G8PrzCO8jCeGPZQH_5t8,29234
|
|
4
|
+
rpy_bridge-0.3.3.dist-info/licenses/LICENSE,sha256=JwbWVcSfeoLfZ2M_ZiyygKVDvhBDW3zbqTWwXOJwmrA,1276
|
|
5
|
+
rpy_bridge-0.3.3.dist-info/METADATA,sha256=Frw8qT49nSrWClRKCMKfU8cvLQVVKvpz3By99rTB_3A,9591
|
|
6
|
+
rpy_bridge-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
rpy_bridge-0.3.3.dist-info/top_level.txt,sha256=z9UZ77ZuUPoLqMDQEpP4btstsaM1IpXb9Cn9yBVaHmU,11
|
|
8
|
+
rpy_bridge-0.3.3.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
rpy_bridge/__init__.py,sha256=fXINFO0OFsSkxNccFPlSPIVsye5WGLVzu6Puf7o1Ao4,829
|
|
2
|
-
rpy_bridge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
rpy_bridge/rpy2_utils.py,sha256=D8hCv9axFbHGM71NFNO6E4oZIIBA5fBioOvzLDEVAHM,27842
|
|
4
|
-
rpy_bridge-0.3.1.dist-info/licenses/LICENSE,sha256=JwbWVcSfeoLfZ2M_ZiyygKVDvhBDW3zbqTWwXOJwmrA,1276
|
|
5
|
-
rpy_bridge-0.3.1.dist-info/METADATA,sha256=LeJLe_ETz_1bJxnjHUG-LJg3U7fG7Mq6O8YHccNZiVg,9267
|
|
6
|
-
rpy_bridge-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
rpy_bridge-0.3.1.dist-info/top_level.txt,sha256=z9UZ77ZuUPoLqMDQEpP4btstsaM1IpXb9Cn9yBVaHmU,11
|
|
8
|
-
rpy_bridge-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|