rpy-bridge 0.5.1__py3-none-any.whl → 0.5.2__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/core.py +35 -1
- rpy_bridge/dataframe.py +24 -2
- {rpy_bridge-0.5.1.dist-info → rpy_bridge-0.5.2.dist-info}/METADATA +1 -7
- {rpy_bridge-0.5.1.dist-info → rpy_bridge-0.5.2.dist-info}/RECORD +7 -7
- {rpy_bridge-0.5.1.dist-info → rpy_bridge-0.5.2.dist-info}/WHEEL +1 -1
- {rpy_bridge-0.5.1.dist-info → rpy_bridge-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {rpy_bridge-0.5.1.dist-info → rpy_bridge-0.5.2.dist-info}/top_level.txt +0 -0
rpy_bridge/core.py
CHANGED
|
@@ -188,6 +188,16 @@ class RFunctionCaller:
|
|
|
188
188
|
except Exception:
|
|
189
189
|
pass
|
|
190
190
|
|
|
191
|
+
# Pass the current Python interpreter path to R so reticulate can use the same Python
|
|
192
|
+
# This is critical for nested Python→R→Python calls via reticulate
|
|
193
|
+
import sys
|
|
194
|
+
|
|
195
|
+
python_executable = sys.executable
|
|
196
|
+
venv_path = os.environ.get("VIRTUAL_ENV", "")
|
|
197
|
+
r(f'Sys.setenv(RPY_BRIDGE_PYTHON_EXECUTABLE = "{python_executable}")')
|
|
198
|
+
if venv_path:
|
|
199
|
+
r(f'Sys.setenv(VIRTUAL_ENV = "{venv_path}")')
|
|
200
|
+
|
|
191
201
|
self.ensure_r_package("withr")
|
|
192
202
|
|
|
193
203
|
if not hasattr(self, "_namespaces"):
|
|
@@ -357,9 +367,33 @@ class RFunctionCaller:
|
|
|
357
367
|
with localconverter(robjects.default_converter + pandas2ri.converter):
|
|
358
368
|
if is_na(obj):
|
|
359
369
|
return robjects.NULL
|
|
370
|
+
if isinstance(obj, pd.Timestamp):
|
|
371
|
+
# Convert single Timestamp to R Date (days since 1970-01-01)
|
|
372
|
+
days_since_epoch = (obj - pd.Timestamp("1970-01-01")).days
|
|
373
|
+
r = self.robjects.r
|
|
374
|
+
return r(f"as.Date({days_since_epoch}, origin='1970-01-01')")
|
|
360
375
|
if isinstance(obj, pd.DataFrame):
|
|
361
|
-
|
|
376
|
+
# Convert datetime columns to numeric (days since epoch) before R conversion
|
|
377
|
+
df_copy = obj.copy()
|
|
378
|
+
for col in df_copy.columns:
|
|
379
|
+
if pd.api.types.is_datetime64_any_dtype(df_copy[col]):
|
|
380
|
+
df_copy[col] = (df_copy[col] - pd.Timestamp("1970-01-01")).dt.days
|
|
381
|
+
r_df = pandas2ri.py2rpy(df_copy)
|
|
382
|
+
# Convert numeric date columns back to R Date class
|
|
383
|
+
r = self.robjects.r
|
|
384
|
+
for col in obj.columns:
|
|
385
|
+
if pd.api.types.is_datetime64_any_dtype(obj[col]):
|
|
386
|
+
r(f'class({r_df.rx2(col).r_repr()}) <- "Date"')
|
|
387
|
+
return r_df
|
|
362
388
|
if isinstance(obj, pd.Series):
|
|
389
|
+
if pd.api.types.is_datetime64_any_dtype(obj):
|
|
390
|
+
# Convert datetime Series to R Date vector
|
|
391
|
+
days = (obj - pd.Timestamp("1970-01-01")).dt.days.tolist()
|
|
392
|
+
r = self.robjects.r
|
|
393
|
+
days_vec = FloatVector(
|
|
394
|
+
[robjects.NA_Real if pd.isna(d) else float(d) for d in days]
|
|
395
|
+
)
|
|
396
|
+
return r("function(x) { class(x) <- 'Date'; x }")(days_vec)
|
|
363
397
|
return self._py2r(obj.tolist())
|
|
364
398
|
if isinstance(obj, (int, float, bool, str)):
|
|
365
399
|
return obj
|
rpy_bridge/dataframe.py
CHANGED
|
@@ -4,6 +4,8 @@ DataFrame cleaning and post-processing utilities for R ↔ Python workflows.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import re
|
|
8
|
+
|
|
7
9
|
import numpy as np
|
|
8
10
|
import pandas as pd
|
|
9
11
|
|
|
@@ -34,6 +36,21 @@ def normalize_single_df_dtypes(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
34
36
|
return df
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
# Token-based patterns to detect date/time columns; avoids substring matches like "endotoxin" or "runtime".
|
|
40
|
+
_DATE_TOKEN_RE = re.compile(
|
|
41
|
+
r"(?<![a-z0-9])"
|
|
42
|
+
r"(?:date|dt|time|dob|visit|start|end|created|updated|modified|timestamp|datetime|ts|dos)"
|
|
43
|
+
r"(?![a-z0-9])",
|
|
44
|
+
re.IGNORECASE,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _looks_like_date_column(col_name: str) -> bool:
|
|
49
|
+
"""Check if column name suggests it contains date values."""
|
|
50
|
+
col_lower = str(col_name).lower()
|
|
51
|
+
return bool(_DATE_TOKEN_RE.search(col_lower))
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
def fix_r_dataframe_types(df: pd.DataFrame) -> pd.DataFrame:
|
|
38
55
|
for col in df.columns:
|
|
39
56
|
series = df[col]
|
|
@@ -41,13 +58,18 @@ def fix_r_dataframe_types(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
41
58
|
df[col] = series.mask(series == -2147483648, pd.NA)
|
|
42
59
|
if pd.api.types.is_numeric_dtype(series):
|
|
43
60
|
values = series.dropna()
|
|
44
|
-
|
|
61
|
+
# Only convert to date if values are in R's date range AND column name suggests a date
|
|
62
|
+
if (
|
|
63
|
+
not values.empty
|
|
64
|
+
and values.between(10000, 40000).all()
|
|
65
|
+
and _looks_like_date_column(col)
|
|
66
|
+
):
|
|
45
67
|
try:
|
|
46
68
|
df[col] = pd.to_datetime("1970-01-01") + pd.to_timedelta(series, unit="D")
|
|
47
69
|
except Exception:
|
|
48
70
|
pass
|
|
49
71
|
if pd.api.types.is_datetime64tz_dtype(series):
|
|
50
|
-
df[col] = series.dt.
|
|
72
|
+
df[col] = series.dt.tz_convert(None)
|
|
51
73
|
return df
|
|
52
74
|
|
|
53
75
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rpy-bridge
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -52,12 +52,6 @@ Provides-Extra: r
|
|
|
52
52
|
Requires-Dist: rpy2>=3.5; extra == "r"
|
|
53
53
|
Provides-Extra: dev
|
|
54
54
|
Requires-Dist: ipykernel>=7.1.0; extra == "dev"
|
|
55
|
-
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
56
|
-
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
57
|
-
Requires-Dist: build>=1.0; extra == "dev"
|
|
58
|
-
Requires-Dist: twine>=4.0; extra == "dev"
|
|
59
|
-
Requires-Dist: certifi>=2025.0; extra == "dev"
|
|
60
|
-
Requires-Dist: ty>=0.0.1a34; extra == "dev"
|
|
61
55
|
Provides-Extra: docs
|
|
62
56
|
Requires-Dist: sphinx; extra == "docs"
|
|
63
57
|
Requires-Dist: myst-parser; extra == "docs"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
rpy_bridge/__init__.py,sha256=3UP1OOLuBaX7I9XsTaRsxr3EPYsGJere-Jh3xgc24QI,313
|
|
2
2
|
rpy_bridge/compare.py,sha256=pwNOYLMrm7zJlsxzTooPl7OSmhPw-dc7owngJvV-7-0,4129
|
|
3
3
|
rpy_bridge/convert.py,sha256=7DzdIGWl0eUYKzsfA5npL1DWKyhfk3gZ0CT2zAg2xfs,1952
|
|
4
|
-
rpy_bridge/core.py,sha256=
|
|
5
|
-
rpy_bridge/dataframe.py,sha256=
|
|
4
|
+
rpy_bridge/core.py,sha256=QqCSxrClz0uQ_I-U-pcVXfnYaNW8Xe4_6XfTskFWn08,20389
|
|
5
|
+
rpy_bridge/dataframe.py,sha256=L5ck9VTmkaWDRzhmsQSERM9avwfSwMhgdPoKUTvS6-4,3020
|
|
6
6
|
rpy_bridge/env.py,sha256=YN3tvl1eUp9IhbTmPgjSVKpRLTuf0OARgkMobQJd0Ks,3581
|
|
7
7
|
rpy_bridge/logging.py,sha256=9U2RJHmxLqrXEwS38TkrSqeETXxn1AAfL8BGTjnzSGY,1360
|
|
8
8
|
rpy_bridge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
rpy_bridge/renv.py,sha256=BYBFXP8SslKzD-GZ0yi7T-nMwqcIx2hYPY3BC_xSSZU,4511
|
|
10
10
|
rpy_bridge/rpy2_loader.py,sha256=rQEnAiJbzkTzb4UlcOsG8D4MXZ0LWtYLMP8DequBNzg,1874
|
|
11
|
-
rpy_bridge-0.5.
|
|
12
|
-
rpy_bridge-0.5.
|
|
13
|
-
rpy_bridge-0.5.
|
|
14
|
-
rpy_bridge-0.5.
|
|
15
|
-
rpy_bridge-0.5.
|
|
11
|
+
rpy_bridge-0.5.2.dist-info/licenses/LICENSE,sha256=JwbWVcSfeoLfZ2M_ZiyygKVDvhBDW3zbqTWwXOJwmrA,1276
|
|
12
|
+
rpy_bridge-0.5.2.dist-info/METADATA,sha256=uMlRJtLBBK3uVXjNBpQ2d2bqaWgHXcqy4mZnoJi3Avk,9997
|
|
13
|
+
rpy_bridge-0.5.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
14
|
+
rpy_bridge-0.5.2.dist-info/top_level.txt,sha256=z9UZ77ZuUPoLqMDQEpP4btstsaM1IpXb9Cn9yBVaHmU,11
|
|
15
|
+
rpy_bridge-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|