Skip to content

Commit

Permalink
Upgrade rpy2 to >= 3.4.3 (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottgigante-immunai authored Jul 1, 2022
1 parent 3b1b5c3 commit 60c9aeb
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 59 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ jobs:
- name: Install system dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y pandoc gfortran libblas-dev liblapack-dev libedit-dev llvm-dev libcurl4-openssl-dev ffmpeg libhdf5-dev
sudo apt-get update -qq --allow-releaseinfo-change
sudo apt-get install -y --no-install-recommends \
pandoc gfortran libblas-dev liblapack-dev libedit-dev llvm-dev libcurl4-openssl-dev ffmpeg libhdf5-dev
- name: Set up Python
uses: actions/setup-python@v1
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dynamic = ['version']
requires-python = '>= 3.7'
dependencies = [
'get_version',
'rpy2 >= 3.4, < 3.4.3',
'rpy2 >= 3.4.3',
'tzlocal', # for pandas2ri
'anndata',
]
Expand Down
15 changes: 10 additions & 5 deletions src/anndata2ri/r2py.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
from anndata import AnnData
from rpy2.rinterface import IntSexpVector, NULLType, Sexp, SexpS4, baseenv
from rpy2.robjects import default_converter, pandas2ri
from rpy2.robjects import default_converter, numpy2ri, pandas2ri
from rpy2.robjects.conversion import localconverter
from rpy2.robjects.robject import RSlots

Expand Down Expand Up @@ -39,11 +39,16 @@ def rpy2py_vector(v):
return v
if isinstance(v, IntSexpVector):
assert v._R_SIZEOF_ELT == 4, 'R integer size changed away from 32 bit'
r = pd.array(v, dtype=pd.Int32Dtype())
v_is_na = numpy2ri.rpy2py(baseenv['is.na'](v)).astype(bool)
if 'factor' in v.rclass:
r = pandas2ri.rpy2py(v)
else:
r = pd.array(v, dtype=pd.Int32Dtype())
r[np.array(baseenv['is.na'](v), dtype=bool)] = pd.NA
levels = numpy2ri.rpy2py(baseenv['levels'](v))
codes = r.to_numpy() - 1
# temporarily set NA values to a valid index
codes[v_is_na] = 0
codes = codes.astype(int)
r = pd.array(levels[codes], dtype=pd.CategoricalDtype(levels))
r[v_is_na] = pd.NA
return r
return pandas2ri.rpy2py(v)

Expand Down
144 changes: 93 additions & 51 deletions src/anndata2ri/scipy2ri/py2r.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,91 @@
from functools import wraps
from typing import Any, Callable, Optional, Tuple, Type
from typing import Callable, Optional

import numpy as np
from rpy2.rinterface import Sexp
from rpy2.robjects import BoolVector, FloatVector, IntVector, Vector, baseenv, default_converter, numpy2ri
from rpy2.robjects import default_converter, numpy2ri
from rpy2.robjects.conversion import localconverter
from rpy2.robjects.packages import Package
from rpy2.robjects.packages import Package, SignatureTranslatedAnonymousPackage
from scipy import sparse

from ..rpy2_ext import importr
from .conv import converter


methods: Optional[Package] = None
as_logical: Optional[Callable[[Any], BoolVector]] = None
as_integer: Optional[Callable[[Any], IntVector]] = None
as_double: Optional[Callable[[Any], FloatVector]] = None
matrix: Optional[SignatureTranslatedAnonymousPackage] = None
base: Optional[Package] = None


def get_type_conv(dtype: np.dtype) -> Tuple[str, Callable[[np.ndarray], Sexp], Type[Vector]]:
def get_type_conv(dtype: np.dtype) -> Callable[[np.ndarray], Sexp]:
global base
if base is None:
base = importr('base')
if np.issubdtype(dtype, np.floating):
return 'd', as_double, FloatVector
return base.as_double
elif np.issubdtype(dtype, np.bool_):
return 'l', as_logical, BoolVector
return base.as_logical
else:
raise ValueError(f'Unknown dtype {dtype!r} cannot be converted to ?gRMatrix.')


def py2r_context(f):
@wraps(f)
def wrapper(obj):
global methods, as_logical, as_integer, as_double
if methods is None:
global as_logical, as_integer, as_double, matrix
if matrix is None:
importr('Matrix') # make class available
methods = importr('methods')
as_logical = baseenv['as.logical']
as_integer = baseenv['as.integer']
as_double = baseenv['as.double']

with localconverter(default_converter + numpy2ri.converter):
return f(obj)
matrix = SignatureTranslatedAnonymousPackage(
"""
sparse_matrix <- function(x, conv_data, dims, ...) {
Matrix::sparseMatrix(
...,
x=conv_data(x),
dims=as.integer(dims),
index1=FALSE
)
}
from_csc <- function(i, p, x, dims, conv_data) {
sparse_matrix(
i=as.integer(i),
p=as.integer(p),
x=x,
conv_data=conv_data,
dims=dims,
repr="C"
)
}
from_csr <- function(j, p, x, dims, conv_data) {
sparse_matrix(
j=as.integer(j),
p=as.integer(p),
x=x,
conv_data=conv_data,
dims=dims,
repr="R"
)
}
from_coo <- function(i, j, x, dims, conv_data) {
sparse_matrix(
i=as.integer(i),
j=as.integer(j),
x=x,
conv_data=conv_data,
dims=dims,
repr="T"
)
}
from_dia <- function(n, x, conv_data) {
Matrix::Diagonal(n=as.integer(n), x=conv_data(x))
}
""",
'matrix',
)

return f(obj)

return wrapper

Expand All @@ -48,56 +94,52 @@ def wrapper(obj):
@py2r_context
def csc_to_rmat(csc: sparse.csc_matrix):
csc.sort_indices()
t, conv_data, _ = get_type_conv(csc.dtype)
return methods.new(
f'{t}gCMatrix',
i=as_integer(csc.indices),
p=as_integer(csc.indptr),
x=conv_data(csc.data),
Dim=as_integer(list(csc.shape)),
)
conv_data = get_type_conv(csc.dtype)
with localconverter(default_converter + numpy2ri.converter):
return matrix.from_csc(i=csc.indices, p=csc.indptr, x=csc.data, dims=list(csc.shape), conv_data=conv_data)


@converter.py2rpy.register(sparse.csr_matrix)
@py2r_context
def csr_to_rmat(csr: sparse.csr_matrix):
csr.sort_indices()
t, conv_data, _ = get_type_conv(csr.dtype)
return methods.new(
f'{t}gRMatrix',
j=as_integer(csr.indices),
p=as_integer(csr.indptr),
x=conv_data(csr.data),
Dim=as_integer(list(csr.shape)),
)
conv_data = get_type_conv(csr.dtype)
with localconverter(default_converter + numpy2ri.converter):
return matrix.from_csr(
j=csr.indices,
p=csr.indptr,
x=csr.data,
conv_data=conv_data,
dims=list(csr.shape),
)


@converter.py2rpy.register(sparse.coo_matrix)
@py2r_context
def coo_to_rmat(coo: sparse.coo_matrix):
t, conv_data, _ = get_type_conv(coo.dtype)
return methods.new(
f'{t}gTMatrix',
i=as_integer(coo.row),
j=as_integer(coo.col),
x=conv_data(coo.data),
Dim=as_integer(list(coo.shape)),
)
conv_data = get_type_conv(coo.dtype)
with localconverter(default_converter + numpy2ri.converter):
return matrix.from_coo(
i=coo.row,
j=coo.col,
x=coo.data,
conv_data=conv_data,
dims=list(coo.shape),
)


@converter.py2rpy.register(sparse.dia_matrix)
@py2r_context
def dia_to_rmat(dia: sparse.dia_matrix):
t, conv_data, vec_cls = get_type_conv(dia.dtype)
conv_data = get_type_conv(dia.dtype)
if len(dia.offsets) > 1:
raise ValueError(
'Cannot convert a dia_matrix with more than 1 diagonal to a *diMatrix. '
f'R diagonal matrices only support 1 diagonal, but this has {len(dia.offsets)}.'
)
is_unit = np.all(dia.data == 1)
return methods.new(
f'{t}diMatrix',
x=vec_cls([]) if is_unit else conv_data(dia.data),
diag='U' if is_unit else 'N',
Dim=as_integer(list(dia.shape)),
)
with localconverter(default_converter + numpy2ri.converter):
return matrix.from_dia(
n=dia.shape[0],
x=dia.data,
conv_data=conv_data,
)

0 comments on commit 60c9aeb

Please sign in to comment.