From 4c8fa6b60fcfd0d2c1539c24e7406d334d189524 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 25 Jun 2026 22:02:58 -0700 Subject: [PATCH 1/3] fix bulk array assignments --- lib/src/rdswrapper.cpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/src/rdswrapper.cpp b/lib/src/rdswrapper.cpp index 74c93a0..49ea5af 100644 --- a/lib/src/rdswrapper.cpp +++ b/lib/src/rdswrapper.cpp @@ -289,12 +289,12 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect // bool arrays if (dtype.is(py::dtype::of())) { auto buf = arr.cast>(); - auto r = buf.unchecked<1>(); auto vec = std::make_unique(); - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) { - vec->data.push_back(r(i) ? 1 : 0); + vec->data.reserve(buf.size()); + const bool* ptr = buf.data(); + for (size_t i = 0; i < buf.size(); ++i) { + vec->data.push_back(ptr[i] ? 1 : 0); } return vec; @@ -306,14 +306,8 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect py::isinstance>(arr) || py::isinstance>(arr)) { auto buf = arr.cast>(); - auto r = buf.unchecked<1>(); auto vec = std::make_unique(); - - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) { - vec->data.push_back(r(i)); - } - + vec->data.assign(buf.data(), buf.data() + buf.size()); return vec; } @@ -321,13 +315,8 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect if (py::isinstance>(arr) || py::isinstance>(arr)) { auto buf = arr.cast>(); - auto r = buf.unchecked<1>(); auto vec = std::make_unique(); - - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) { - vec->data.push_back(r(i)); - } + vec->data.assign(buf.data(), buf.data() + buf.size()); return vec; } From 8dc8d1ad05dcf3ae831d195cc5be8bab2ef97533 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 25 Jun 2026 22:11:32 -0700 Subject: [PATCH 2/3] similar changes --- lib/src/rdswrapper.cpp | 16 +++++++--------- src/rds2py/PyRdsReader.py | 19 +++++++++++++++---- tests/test_atomics.py | 3 +++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/src/rdswrapper.cpp b/lib/src/rdswrapper.cpp index 49ea5af..2f9a840 100644 --- a/lib/src/rdswrapper.cpp +++ b/lib/src/rdswrapper.cpp @@ -365,9 +365,7 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect auto data_obj = d["data"]; if (py::isinstance(data_obj)) { auto arr = data_obj.cast>(); - auto r = arr.unchecked<1>(); - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) vec->data.push_back(r(i)); + vec->data.assign(arr.data(), arr.data() + arr.size()); } else { auto seq = data_obj.cast(); vec->data.reserve(py::len(seq)); @@ -398,9 +396,7 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect auto data_obj = d["data"]; if (py::isinstance(data_obj)) { auto arr = data_obj.cast>(); - auto r = arr.unchecked<1>(); - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) vec->data.push_back(r(i)); + vec->data.assign(arr.data(), arr.data() + arr.size()); } else { auto seq = data_obj.cast(); vec->data.reserve(py::len(seq)); @@ -431,9 +427,11 @@ std::unique_ptr py_to_robject(const py::object& obj, std::vect auto data_obj = d["data"]; if (py::isinstance(data_obj)) { auto arr = data_obj.cast>(); - auto r = arr.unchecked<1>(); - vec->data.reserve(r.shape(0)); - for (ssize_t i = 0; i < r.shape(0); ++i) vec->data.push_back(r(i) ? 1 : 0); + vec->data.reserve(arr.size()); + const bool* ptr = arr.data(); + for (size_t i = 0; i < arr.size(); ++i) { + vec->data.push_back(ptr[i] ? 1 : 0); + } } else { auto seq = data_obj.cast(); vec->data.reserve(py::len(seq)); diff --git a/src/rds2py/PyRdsReader.py b/src/rds2py/PyRdsReader.py index 238ecf5..32c8a1e 100644 --- a/src/rds2py/PyRdsReader.py +++ b/src/rds2py/PyRdsReader.py @@ -122,9 +122,12 @@ def _handle_r_special_cases(self, data: np.ndarray, rtype: str, size: int) -> Un """Handle special R data representations.""" try: # Special handling for R integer containing NA - if size != 2: - if any(data == self.R_MIN): - return np.array([np.nan if x == self.R_MIN else x for x in data]) + if rtype == "integer" and size != 2: + mask = data == self.R_MIN + if mask.any(): + float_data = data.astype(np.float64) + float_data[mask] = np.nan + return float_data # Special handling for R integer sequences if rtype == "integer" and size == 2 and data[0] == self.R_MIN and data[1] < 0: @@ -140,7 +143,15 @@ def _get_numeric_data(self, obj: RdsReader, rtype: str) -> np.ndarray: try: data = obj.get_numeric_data() if rtype == "boolean": - return data.astype(bool) + mask = data == self.R_MIN + if mask.any(): + res = data.astype(object) + res[mask] = None + non_na_mask = ~mask + res[non_na_mask] = res[non_na_mask].astype(bool) + return res + else: + return data.astype(bool) return data except Exception as e: diff --git a/tests/test_atomics.py b/tests/test_atomics.py index 57d9176..cbc5deb 100644 --- a/tests/test_atomics.py +++ b/tests/test_atomics.py @@ -34,6 +34,8 @@ def test_read_atomic_logical_na(): assert arr is not None assert isinstance(arr, BooleanList) assert len(arr) > 0 + assert None in arr + assert sum(1 for x in arr if x is None) == 10 ## Doubles/Floats @@ -123,6 +125,7 @@ def test_save_names_directly(): def test_save_numpy_scalars(): import numpy as np + from rds2py import save_rds # Test integer scalar From 9ccfe79248d7342907bd7a2ad73359dcc16f2ee5 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sat, 27 Jun 2026 21:07:37 -0700 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc25e22..dc489a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Version 0.10.0 - 0.10.2 +## Version 0.10.0 - 0.10.3 - Added methods to write to RDS/RData files. - Supports atomic types, generic dictionaries/lists, and **BiocPy objects**.