|
4 | 4 |
|
5 | 5 | # rds2py |
6 | 6 |
|
7 | | -Parse and save Python objects as **RDS or RData** files. `rds2py` supports various base classes from R, and Bioconductor's `SummarizedExperiment` and `SingleCellExperiment` S4 classes. **_For more details, check out [rds2cpp library](https://github.com/LTLA/rds2cpp)._** |
| 7 | +`rds2py` allows you to read and write R's native **RDS** and **RData** files directly in Python. Beyond standard R types, it provides integration with the [BiocPy](https://github.com/biocpy) ecosystem, allowing you to easily roundtrip complex S4 data structures like `SummarizedExperiment`, `SingleCellExperiment`, and `GenomicRanges`. **_For more details, check out [rds2cpp library](https://github.com/LTLA/rds2cpp)._** |
8 | 8 |
|
9 | 9 | ## Installation |
10 | 10 |
|
11 | 11 | Package is published to [PyPI](https://pypi.org/project/rds2py/) |
12 | 12 |
|
13 | 13 | ```shell |
14 | 14 | pip install rds2py |
| 15 | +``` |
| 16 | + |
| 17 | +To enable automatic conversion to Bioconductor/BiocPy classes, make sure to install the optional dependencies: |
15 | 18 |
|
16 | | -# or install optional dependencies |
| 19 | +```shell |
17 | 20 | pip install rds2py[optional] |
18 | 21 | ``` |
19 | 22 |
|
20 | | -By default, the package does not install packages to convert python representations to BiocPy classes. Please consider installing all optional dependencies. |
21 | 23 |
|
22 | | -## Usage |
| 24 | +## Quickstart |
23 | 25 |
|
24 | | -> [!NOTE] |
25 | | -> |
26 | | -> If you do not have an RDS object handy, feel free to download one from [single-cell-test-files](https://github.com/jkanche/random-test-files/releases). |
| 26 | +### 1. Reading RDS and RData files |
| 27 | + |
| 28 | +Reading an RDS or RData file is as simple as a single function call. `rds2py` automatically detects and maps known R/Bioconductor classes to their Python equivalents: |
27 | 29 |
|
28 | 30 | ```python |
29 | 31 | from rds2py import read_rds, read_rda |
30 | | -r_obj = read_rds("path/to/file.rds") # or read_rda("path/to/file.rda") |
| 32 | + |
| 33 | +# Read an RDS file (returns a Python/BiocPy object or dict) |
| 34 | +data = read_rds("path/to/file.rds") |
| 35 | + |
| 36 | +# Read objects from an RData workspace file (returns a dictionary of objects) |
| 37 | +workspace = read_rda("path/to/workspace.rda") |
31 | 38 | ``` |
32 | 39 |
|
33 | | -The returned `r_obj` either returns an appropriate Python class if a parser is already implemented or returns the dictionary containing the data from the RDS file. |
| 40 | +If `rds2py` encounters an S4 class or complex R structure it doesn't have a parser registered for, it falls back to returning a dictionary so you don't lose any data. |
34 | 41 |
|
35 | | -### Save RDS/RData files |
| 42 | +### 2. Saving to RDS and RData files |
36 | 43 |
|
37 | | -You can also construct RDS or RData files from Python objects. `rds2py` supports writing atomic types, generic dictionaries/lists, and **BiocPy objects**. |
| 44 | +You can serialize Python objects back to RDS or RData formats. This includes NumPy arrays, SciPy sparse matrices, standard dictionaries/lists, and BiocPy objects: |
38 | 45 |
|
39 | 46 | ```python |
40 | | -from rds2py import write_rds, write_rda |
41 | 47 | import numpy as np |
42 | | - |
43 | | -# Write atomic types |
44 | | -write_rds(np.array([1, 2, 3], dtype=np.int32), "path/to/file.rds") |
45 | | - |
46 | | -# Write complex objects |
| 48 | +from rds2py import write_rds, write_rda |
47 | 49 | from genomicranges import GenomicRanges |
48 | 50 | from iranges import IRanges |
49 | 51 |
|
50 | | -gr = GenomicRanges( |
51 | | - seqnames=["chr1", "chr2"], |
52 | | - ranges=IRanges(start=[1, 2], width=[10, 20]), |
53 | | - strand=["+", "-"] |
54 | | -) |
55 | | -write_rds(gr, "path/to/granges.rds") |
| 52 | +# 1. Write an atomic NumPy array |
| 53 | +write_rds(np.array([10, 20, 30], dtype=np.int32), "array.rds") |
| 54 | + |
| 55 | +# 2. Write a complex Bioconductor GenomicRanges object |
| 56 | +gr = GenomicRanges(seqnames=["chr1", "chr2"], ranges=IRanges(start=[1, 100], width=[10, 50]), strand=["+", "-"]) |
| 57 | +write_rds(gr, "genomic_ranges.rds") |
| 58 | + |
| 59 | +# 3. Write multiple Python objects into a single RData workspace |
| 60 | +objects = {"my_array": np.array([1.1, 2.2, 3.3]), "my_granges": gr} |
| 61 | +write_rda(objects, "workspace.rda") |
56 | 62 | ``` |
57 | 63 |
|
58 | | -### Write-your-own-reader |
| 64 | +### 3. Custom Extensions |
59 | 65 |
|
60 | | -Reading RDS or RData files as dictionary representations allows users to write their own custom readers into appropriate Python representations. |
| 66 | +If you have custom S4 representations or class mapping needs, you can parse the raw RDS structure into Python dictionary representations using `parse_rds`/`parse_rda` and apply your custom deserializers: |
61 | 67 |
|
62 | 68 | ```python |
63 | | -from rds2py import parse_rds, parse_rda |
| 69 | +from rds2py import parse_rds |
| 70 | +from rds2py.read_granges import read_genomic_ranges |
| 71 | + |
| 72 | +# 1. Parse into a raw dictionary representation of the RDS tree |
| 73 | +raw_dict = parse_rds("path/to/file.rds") |
| 74 | +print(raw_dict.keys()) # ['type', 'class_name', 'attributes', 'data', ...] |
64 | 75 |
|
65 | | -robject = parse_rds("path/to/file.rds") # or use parse_rda for rdata files |
66 | | -print(robject) |
| 76 | +# 2. Build or invoke custom parser logic |
| 77 | +if raw_dict.get("class_name") == "GRanges": |
| 78 | + gr = read_genomic_ranges(raw_dict) |
| 79 | + print(gr) |
67 | 80 | ``` |
68 | 81 |
|
69 | | -If you know this RDS file contains an `GenomicRanges` object, you can use the built-in reader or write your own reader to convert this dictionary. |
| 82 | +For writing custom objects, you can register your classes to `rds2py`'s serialization registry using the `save_rds` singledispatch generic: |
70 | 83 |
|
71 | 84 | ```python |
72 | | -from rds2py.read_granges import read_genomic_ranges |
| 85 | +from rds2py.generics import save_rds |
| 86 | + |
73 | 87 |
|
74 | | -gr = read_genomic_ranges(robject) |
75 | | -print(gr) |
| 88 | +class MyCustomClass: |
| 89 | + def __init__(self, value): |
| 90 | + self.value = value |
| 91 | + |
| 92 | + |
| 93 | +@save_rds.register(MyCustomClass) |
| 94 | +def _serialize_custom(x: MyCustomClass, path=None): |
| 95 | + # Construct the raw RDS dictionary representation expected by rds2cpp |
| 96 | + converted = { |
| 97 | + "type": "integer", |
| 98 | + "data": [x.value], |
| 99 | + "attributes": {"class": {"type": "string", "data": ["MyCustomRClass"]}}, |
| 100 | + } |
| 101 | + |
| 102 | + # Optionally save if path is provided, otherwise return representation |
| 103 | + if path is not None: |
| 104 | + from rds2py.lib_rds_parser import write_rds as write_rds_native |
| 105 | + |
| 106 | + write_rds_native(converted, path) |
| 107 | + return converted |
76 | 108 | ``` |
77 | 109 |
|
| 110 | + |
78 | 111 | ## Type Conversion Reference |
79 | 112 |
|
80 | | -| R Type | Python/NumPy Type | |
81 | | -| ---------- | ------------------------------------ | |
82 | | -| numeric | numpy.ndarray (float64) | |
83 | | -| integer | numpy.ndarray (int32) | |
84 | | -| character | list of str | |
85 | | -| logical | numpy.ndarray (bool) | |
86 | | -| factor | list | |
87 | | -| data.frame | BiocFrame | |
88 | | -| matrix | numpy.ndarray or scipy.sparse matrix | |
89 | | -| dgCMatrix | scipy.sparse.csc_matrix | |
90 | | -| dgRMatrix | scipy.sparse.csr_matrix | |
91 | | - |
92 | | -and integration with BiocPy ecosystem for Bioconductor classes |
93 | | - - SummarizedExperiment |
94 | | - - RangedSummarizedExperiment |
95 | | - - SingleCellExperiment |
96 | | - - GenomicRanges |
97 | | - - MultiAssayExperiment |
| 113 | +The table below describes how core R types are mapped to Python/NumPy/SciPy counterparts: |
| 114 | + |
| 115 | +| R Type / Class | Python / NumPy / SciPy Counterpart | |
| 116 | +| :--- | :--- | |
| 117 | +| **numeric** | `numpy.ndarray` (`float64`) | |
| 118 | +| **integer** | `numpy.ndarray` (`int32`) | |
| 119 | +| **logical** | `numpy.ndarray` (`bool`) | |
| 120 | +| **character** | `list` of `str` | |
| 121 | +| **factor** | `list` / representation levels | |
| 122 | +| **matrix (dense)** | `numpy.ndarray` | |
| 123 | +| **dgCMatrix** (Column-sparse) | `scipy.sparse.csc_matrix` | |
| 124 | +| **dgRMatrix** (Row-sparse) | `scipy.sparse.csr_matrix` | |
| 125 | +| **data.frame** / **DFrame** | `biocframe.BiocFrame` | |
| 126 | + |
| 127 | +### Supported Bioconductor Classes |
| 128 | +When `rds2py[optional]` is installed, the package fully translates R/S4 classes to their BiocPy equivalents: |
| 129 | +- **GenomicRanges** / **GRanges** <-> `genomicranges.GenomicRanges` |
| 130 | +- **GenomicRangesList** / **GRangesList** <-> `genomicranges.CompressedGenomicRangesList` |
| 131 | +- **SummarizedExperiment** <-> `summarizedexperiment.SummarizedExperiment` |
| 132 | +- **RangedSummarizedExperiment** <-> `summarizedexperiment.RangedSummarizedExperiment` |
| 133 | +- **SingleCellExperiment** <-> `singlecellexperiment.SingleCellExperiment` |
| 134 | +- **MultiAssayExperiment** <-> `multiassayexperiment.MultiAssayExperiment` |
| 135 | + |
| 136 | +--- |
98 | 137 |
|
99 | 138 | ## Developer Notes |
100 | 139 |
|
101 | | -This project uses pybind11 to provide bindings to the rds2cpp library. Please make sure necessary C++ compiler is installed on your system. |
| 140 | +- `rds2py` uses `pybind11` to bind the core C++ `rds2cpp` library. Compiling from source requires a compatible C++ compiler. |
| 141 | +- Tests can be run via `tox` or directly using `pytest`. |
102 | 142 |
|
103 | 143 | <!-- pyscaffold-notes --> |
104 | 144 |
|
|
0 commit comments