Revision: 697 http://rpy.svn.sourceforge.net/rpy/?rev=697&view=rev Author: lgautier Date: 2008-11-19 22:12:22 +0000 (Wed, 19 Nov 2008)
Log Message: ----------- robjects: - new module conversion (useful for user-defined additional conversion function) - new module numpy2ri (adapted from the recent contributed patch by Nathaniel Smith) doc: edits Modified Paths: -------------- rpy2/branches/version_2.0.x/NEWS rpy2/branches/version_2.0.x/doc/source/robjects.rst rpy2/branches/version_2.0.x/doc/source/rpy_classic.rst rpy2/branches/version_2.0.x/rpy/robjects/__init__.py rpy2/branches/version_2.0.x/rpy/robjects/tests/__init__.py rpy2/branches/version_2.0.x/rpy/robjects/tests/testRobjects.py Added Paths: ----------- rpy2/branches/version_2.0.x/rpy/robjects/conversion.py rpy2/branches/version_2.0.x/rpy/robjects/numpy2ri.py rpy2/branches/version_2.0.x/rpy/robjects/tests/testNumpyConversions.py Modified: rpy2/branches/version_2.0.x/NEWS =================================================================== --- rpy2/branches/version_2.0.x/NEWS 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/NEWS 2008-11-19 22:12:22 UTC (rev 697) @@ -1,15 +1,33 @@ SVN === +New features +------------ + +- New module :mod:`rpy2.robjects.conversion`. + +- New module :mod:`rpy2.robjects.numpy2ri` to convert :mod:`numpy` objects + into :mod:`rpy2` objects. + # adapted from a patch contributed by Nathaniel Smith + + Changes ------- +:mod:`rpy2.rpy_classic`: + - :meth:`rpy_classic.RObj.getSexp` moved to a property :attr:`rpy_classic.Robj.sexp`. +:mod:`rpy2.robjects`: + - :meth:`RObject.__repr__` moved to :meth:`RObject.r_repr` +- :meth:`ri2py`, :meth:`ro2py`, and :meth:`py2ri` moved to the new module + :mod:`conversion`. Adding the prefix `conversion.` to calls + to those functions will be enough to update existing code + Bugs fixed ---------- Modified: rpy2/branches/version_2.0.x/doc/source/robjects.rst =================================================================== --- rpy2/branches/version_2.0.x/doc/source/robjects.rst 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/doc/source/robjects.rst 2008-11-19 22:12:22 UTC (rev 697) @@ -70,7 +70,7 @@ * '.' (dot) is syntactically valid in names for R objects, but not for python objects. -That last limitation can partly be removed by using :mod:`rpy.rpy_classic` if +That last limitation can partly be removed by using :mod:`rpy2.rpy_classic` if this feature matters most to you. >>> robjects.r.as_null @@ -80,11 +80,12 @@ >>> rpy.r.as_null # R function as.null() returned -.. warning:: - In the case there are R objects which name only differ by '.' and '_' - (e.g., 'my_variable' and 'my.variable'), setting :attr:`_dotter` to True - can result in confusing results at runtime. +.. note:: + The section :ref:`rpy_classic-mix` outlines how to integrate + :mod:`rpy2.rpy_classic` code. + + Behind the scene, the steps for getting an attribute of `r` are rather straightforward: @@ -93,13 +94,33 @@ 2. Check if the attribute is can be accessed in R, starting from `globalEnv` -When safety matters most, or when getting extraordinary funds for a bailout -is unlikely, we recommed using :meth:`__getitem__` to get -a given R object (and store it in a python variable if wanted): +When safety matters most, we recommend using :meth:`__getitem__` to get +a given R object. >>> as_null = robjects.r['as.null'] +Storing the object in a python variable will protect it from garbage +collection, even if deleted from the objects visible to an R user. +>>> robjects.globalEnv['foo'] = 1.2 +>>> foo = robjects.r['foo'] +>>> foo[0] +1.2 + +Here we `remove` the symbol `foo` from the R Global Environment. + +>>> robjects.r['rm']('foo') +>>> robjects.r['foo'] +LookupError: 'foo' not found + +The object itself remains available, and protected from R's +garbage collection until `foo` is deleted from Python + +>>> foo[0] +1.2 + + + Strings as R code ----------------- @@ -525,15 +546,15 @@ while an higher-level mapping is done between low-level objects and higher-level objects using the functions: -:meth:`ri2py` +:meth:`conversion.ri2py` :mod:`rpy2.rinterface` to Python. By default, this function is just an alias for the function :meth:`default_ri2py`. -:meth:`py2ri` +:meth:`conversion.py2ri` Python to :mod:`rpy2.rinterface`. By default, this function is just an alias for the function :meth:`default_py2ri`. -:meth:`py2ro` +:meth:`conversion.py2ro` Python to :mod:`rpy2.robjects`. That one function is merely a call to :meth:`py2ri` followed by a call to :meth:`ri2py`. @@ -554,7 +575,7 @@ res = res[0] return res - robjects.ri2py = my_ri2py + robjects.conversion.ri2py = my_ri2py Once this is done, we can verify immediately that this is working with: @@ -565,7 +586,7 @@ The default behavoir can be restored with: ->>> robjects.ri2py = default_ri2py +>>> robjects.conversion.ri2py = default_ri2py The docstrings for :meth:`default_ri2py`, :meth:`default_py2ri`, and :meth:`py2ro` are: Modified: rpy2/branches/version_2.0.x/doc/source/rpy_classic.rst =================================================================== --- rpy2/branches/version_2.0.x/doc/source/rpy_classic.rst 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/doc/source/rpy_classic.rst 2008-11-19 22:12:22 UTC (rev 697) @@ -94,6 +94,7 @@ >>> rpy.r.plot(pca, main = "PCA") >>> +.. _rpy_classic-mix: Partial use of :mod:`rpy_classic` ================================== Modified: rpy2/branches/version_2.0.x/rpy/robjects/__init__.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/__init__.py 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/rpy/robjects/__init__.py 2008-11-19 22:12:22 UTC (rev 697) @@ -11,9 +11,11 @@ import itertools import rpy2.rinterface as rinterface import rpy2.rlike.container as rlc +import rpy2.robjects.conversion #FIXME: close everything when leaving (check RPy for that). + def default_ri2py(o): """ Convert :class:`rpy2.rinterface.Sexp` to higher-level objects, without copying the R objects. @@ -50,7 +52,7 @@ res = RObject(o) return res -ri2py = default_ri2py +conversion.ri2py = default_ri2py def default_py2ri(o): @@ -84,14 +86,14 @@ elif isinstance(o, unicode): res = rinterface.SexpVector([o, ], rinterface.STRSXP) elif isinstance(o, list): - res = r.list(*[ri2py(py2ri(x)) for x in o]) + res = r.list(*[conversion.ri2py(conversion.py2ri(x)) for x in o]) elif isinstance(o, complex): res = rinterface.SexpVector([o, ], rinterface.CPLXSXP) else: raise(ValueError("Nothing can be done for the type %s at the moment." %(type(o)))) return res -py2ri = default_py2ri +conversion.py2ri = default_py2ri def default_py2ro(o): @@ -102,7 +104,7 @@ res = default_py2ri(o) return default_ri2py(res) -py2ro = default_py2ro +conversion.py2ro = default_py2ro def repr_robject(o, linesep=os.linesep): @@ -211,7 +213,7 @@ def __init__(self, o): if not isinstance(o, rinterface.SexpVector): - o = py2ri(o) + o = conversion.py2ri(o) super(RVector, self).__init__(o) self.r = RVectorDelegator(self) @@ -231,9 +233,9 @@ - an index is itself a vector of elements to select """ - args = [py2ro(x) for x in args] + args = [conversion.py2ro(x) for x in args] for k, v in kwargs.itervalues(): - args[k] = py2ro(v) + args[k] = conversion.py2ro(v) res = r["["](*([self, ] + [x for x in args]), **kwargs) return res @@ -241,15 +243,15 @@ def assign(self, index, value): if not (isinstance(index, rlc.TaggedList) | \ isinstance(index, rlc.ArgsDict)): - args = rlc.TaggedList([py2ro(index), ]) + args = rlc.TaggedList([conversion.py2ro(index), ]) else: for i in xrange(len(index)): - index[i] = py2ro(index[i]) + index[i] = conversion.py2ro(index[i]) args = index - args.append(py2ro(value)) + args.append(conversion.py2ro(value)) args.insert(0, self) res = r["[<-"].rcall(args.items()) - res = ri2py(res) + res = conversion.ri2py(res) return res def __add__(self, x): @@ -259,11 +261,11 @@ def __getitem__(self, i): res = super(RVector, self).__getitem__(i) if isinstance(res, rinterface.Sexp): - res = ri2py(res) + res = conversion.ri2py(res) return res def __setitem__(self, i, value): - value = py2ri(value) + value = conversion.py2ri(value) res = super(RVector, self).__setitem__(i, value) def getnames(self): @@ -312,11 +314,11 @@ def getdim(self): res = r.dim(self) - res = ri2py(res) + res = conversion.ri2py(res) return res def setdim(self, value): - value = py2ro(value) + value = conversion.py2ro(value) res = r["dim<-"](self, value) #FIXME: not properly done raise(Exception("Not yet implemented")) @@ -387,7 +389,7 @@ :rtype: SexpVector """ res = baseNameSpaceEnv["rownames"](self) - return ri2py(res) + return conversion.ri2py(res) def colnames(self): """ Column names @@ -395,7 +397,7 @@ :rtype: SexpVector """ res = baseNameSpaceEnv["colnames"](self) - return ri2py(res) + return conversion.ri2py(res) class RFunction(RObjectMixin, rinterface.SexpClosure): @@ -404,12 +406,12 @@ """ def __call__(self, *args, **kwargs): - new_args = [py2ri(a) for a in args] + new_args = [conversion.py2ri(a) for a in args] new_kwargs = {} for k, v in kwargs.iteritems(): - new_kwargs[k] = py2ri(v) + new_kwargs[k] = conversion.py2ri(v) res = super(RFunction, self).__call__(*new_args, **new_kwargs) - res = ri2py(res) + res = conversion.ri2py(res) return res @@ -423,20 +425,20 @@ def __getitem__(self, item): res = super(REnvironment, self).__getitem__(item) - res = ri2py(res) + res = conversion.ri2py(res) return res def __setitem__(self, item, value): - robj = py2ro(value) + robj = conversion.py2ro(value) super(REnvironment, self).__setitem__(item, robj) def get(self, item): """ Get a object from its R name/symol :param item: string (name/symbol) - :rtype: object (as returned by :func:`ri2py`) + :rtype: object (as returned by :func:`conversion.ri2py`) """ res = super(REnvironment, self).get(item) - res = ri2py(res) + res = conversion.ri2py(res) return res @@ -444,7 +446,7 @@ def __getattr__(self, attr): res = self.do_slot(attr) - res = ri2py(res) + res = conversion.ri2py(res) return res @@ -461,7 +463,7 @@ def getenvironment(self): res = self.do_slot(".Environment") - res = ri2py(res) + res = conversion.ri2py(res) return res def setenvironment(self, val): @@ -500,7 +502,7 @@ def __getitem__(self, item): res = rinterface.globalEnv.get(item) - res = ri2py(res) + res = conversion.ri2py(res) return res #FIXME: check that this is properly working @@ -523,6 +525,6 @@ r = R() -globalEnv = ri2py(rinterface.globalEnv) -baseNameSpaceEnv = ri2py(rinterface.baseNameSpaceEnv) -emptyEnv = ri2py(rinterface.emptyEnv) +globalEnv = conversion.ri2py(rinterface.globalEnv) +baseNameSpaceEnv = conversion.ri2py(rinterface.baseNameSpaceEnv) +emptyEnv = conversion.ri2py(rinterface.emptyEnv) Added: rpy2/branches/version_2.0.x/rpy/robjects/conversion.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/conversion.py (rev 0) +++ rpy2/branches/version_2.0.x/rpy/robjects/conversion.py 2008-11-19 22:12:22 UTC (rev 697) @@ -0,0 +1,12 @@ + + +def ri2py(obj): + raise RuntimeError("Conversion function undefined") + +def py2ri(obj): + raise RuntimeError("Conversion function undefined") + +def py2ro(obj): + raise RuntimeError("Conversion function undefined") + + Property changes on: rpy2/branches/version_2.0.x/rpy/robjects/conversion.py ___________________________________________________________________ Added: svn:eol-style + native Added: rpy2/branches/version_2.0.x/rpy/robjects/numpy2ri.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/numpy2ri.py (rev 0) +++ rpy2/branches/version_2.0.x/rpy/robjects/numpy2ri.py 2008-11-19 22:12:22 UTC (rev 697) @@ -0,0 +1,53 @@ +import rpy2.robjects as ro +import rpy2.rinterface as rinterface +import numpy + +def numpy2ri(o): + if isinstance(o, numpy.ndarray): + if not o.dtype.isnative: + raise(ValueError("Cannot pass numpy arrays with non-native byte orders at the moment.")) + + # The possible kind codes are listed at + # http://numpy.scipy.org/array_interface.shtml + kinds = { + # "t" -> not really supported by numpy + "b": rinterface.LGLSXP, + "i": rinterface.INTSXP, + # "u" -> special-cased below + "f": rinterface.REALSXP, + "c": rinterface.CPLXSXP, + # "O" -> special-cased below + "S": rinterface.STRSXP, + "U": rinterface.STRSXP, + # "V" -> special-cased below + } + # Most types map onto R arrays: + if o.dtype.kind in kinds: + # "F" means "use column-major order" + vec = rinterface.SexpVector(o.ravel("F"), kinds[o.dtype.kind]) + dim = rinterface.SexpVector(o.shape, rinterface.INTSXP) + res = ro.r.array(vec, dim=dim) + # R does not support unsigned types: + elif o.dtype.kind == "u": + raise(ValueError("Cannot convert numpy array of unsigned values -- R does not have unsigned integers.")) + # Array-of-PyObject is treated like a Python list: + elif o.dtype.kind == "O": + res = ro.conversion.py2ri(list(o)) + # Record arrays map onto R data frames: + elif o.dtype.kind == "V": + if o.dtype.names is None: + raise(ValueError("Nothing can be done for this numpy array type %s at the moment." % (o.dtype,))) + df_args = [] + for field_name in o.dtype.names: + df_args.append((field_name, + ro.conversion.py2ri(o[field_name]))) + res = ro.baseNameSpaceEnv["data.frame"].rcall(tuple(df_args)) + # It should be impossible to get here: + else: + raise(ValueError("Unknown numpy array type.")) + else: + res = ro.default_py2ri(o) + return res + + +ro.conversion.py2ri = numpy2ri Property changes on: rpy2/branches/version_2.0.x/rpy/robjects/numpy2ri.py ___________________________________________________________________ Added: svn:eol-style + native Modified: rpy2/branches/version_2.0.x/rpy/robjects/tests/__init__.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/tests/__init__.py 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/rpy/robjects/tests/__init__.py 2008-11-19 22:12:22 UTC (rev 697) @@ -9,6 +9,9 @@ import testREnvironment import testRobjects +# wrap this nicely so a warning is issued if no numpy present +import testNumpyConversions + def suite(): suite_RObject = testRObject.suite() suite_RVector = testRVector.suite() @@ -18,6 +21,7 @@ suite_REnvironment = testREnvironment.suite() suite_RFormula = testRFormula.suite() suite_Robjects = testRobjects.suite() + suite_NumpyConversions = testNumpyConversions.suite() alltests = unittest.TestSuite([suite_RObject, suite_RVector, suite_RArray, @@ -25,7 +29,9 @@ suite_RFunction, suite_REnvironment, suite_RFormula, - suite_Robjects ]) + suite_Robjects, + suite_NumpyConversions + ]) return alltests def main(): Added: rpy2/branches/version_2.0.x/rpy/robjects/tests/testNumpyConversions.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/tests/testNumpyConversions.py (rev 0) +++ rpy2/branches/version_2.0.x/rpy/robjects/tests/testNumpyConversions.py 2008-11-19 22:12:22 UTC (rev 697) @@ -0,0 +1,94 @@ +import unittest +import rpy2.robjects as robjects +r = robjects.r + +import numpy +import rpy2.robjects.numpy2ri as rpyn + + +class NumpyConversionsTestCase(unittest.TestCase): + + def setUp(self): + robjects.conversion.py2ri = rpyn.numpy2ri + + def tearDown(self): + robjects.conversion.py2ri = robjects.default_py2ri + + def checkHomogeneous(self, obj, mode, storage_mode): + converted = robjects.conversion.py2ri(obj) + self.assertEquals(r["mode"](converted)[0], mode) + self.assertEquals(r["storage.mode"](converted)[0], storage_mode) + self.assertEquals(list(obj), list(converted)) + self.assertTrue(r["is.array"](converted)[0]) + + def testVector(self): + + b = numpy.array([True, False, True], dtype=numpy.bool_) + self.checkHomogeneous(b, "logical", "logical") + + i = numpy.array([1, 2, 3], dtype="i") + self.checkHomogeneous(i, "numeric", "integer") + + f = numpy.array([1, 2, 3], dtype="f") + self.checkHomogeneous(f, "numeric", "double") + + c = numpy.array([1j, 2j, 3j], dtype=numpy.complex_) + self.checkHomogeneous(c, "complex", "complex") + + s = numpy.array(["a", "b", "c"], dtype="S") + self.checkHomogeneous(s, "character", "character") + + u = numpy.array([u"a", u"b", u"c"], dtype="U") + self.checkHomogeneous(u, "character", "character") + + def testArray(self): + + i2d = numpy.array([[1, 2, 3], [4, 5, 6]], dtype="i") + i2d_r = robjects.conversion.py2ri(i2d) + + self.assertEquals(r["storage.mode"](i2d_r)[0], "integer") + self.assertEquals(tuple(r["dim"](i2d_r)), (2, 3)) + + # Make sure we got the row/column swap right: + self.assertEquals(i2d_r.subset(1, 2)[0], i2d[0, 1]) + + f3d = numpy.arange(24, dtype="f").reshape((2, 3, 4)) + f3d_r = robjects.conversion.py2ri(f3d) + + self.assertEquals(r["storage.mode"](f3d_r)[0], "double") + self.assertEquals(tuple(r["dim"](f3d_r)), (2, 3, 4)) + + # Make sure we got the row/column swap right: + self.assertEquals(f3d_r.subset(1, 2, 3)[0], f3d[0, 1, 2]) + + def testObjectArray(self): + o = numpy.array([1, "a", 3.2], dtype=numpy.object_) + o_r = robjects.conversion.py2ri(o) + self.assertEquals(r["mode"](o_r)[0], "list") + self.assertEquals(r["[["](o_r, 1)[0], 1) + self.assertEquals(r["[["](o_r, 2)[0], "a") + self.assertEquals(r["[["](o_r, 3)[0], 3.2) + + def testRecordArray(self): + rec = numpy.array([(1, 2.3), (2, -0.7), (3, 12.1)], + dtype=[("count", "i"), ("value", numpy.double)]) + rec_r = robjects.conversion.py2ri(rec) + self.assertTrue(r["is.data.frame"](rec_r)[0]) + self.assertEquals(tuple(r["names"](rec_r)), ("count", "value")) + count_r = r["$"](rec_r, "count") + value_r = r["$"](rec_r, "value") + self.assertEquals(r["storage.mode"](count_r)[0], "integer") + self.assertEquals(r["storage.mode"](value_r)[0], "double") + self.assertEquals(count_r[1], 2) + self.assertEquals(value_r[2], 12.1) + + def testBadArray(self): + u = numpy.array([1, 2, 3], dtype=numpy.uint32) + self.assertRaises(ValueError, robjects.conversion.py2ri, u) + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(NumpyConversionsTestCase) + +if __name__ == '__main__': + unittest.main() + Property changes on: rpy2/branches/version_2.0.x/rpy/robjects/tests/testNumpyConversions.py ___________________________________________________________________ Added: svn:eol-style + native Modified: rpy2/branches/version_2.0.x/rpy/robjects/tests/testRobjects.py =================================================================== --- rpy2/branches/version_2.0.x/rpy/robjects/tests/testRobjects.py 2008-11-16 21:13:42 UTC (rev 696) +++ rpy2/branches/version_2.0.x/rpy/robjects/tests/testRobjects.py 2008-11-19 22:12:22 UTC (rev 697) @@ -119,7 +119,7 @@ if inherits(pyobj, classname)[0]: pyobj = Density(pyobj) return pyobj - robjects.ri2py = f + robjects.conversion.ri2py = f x = robjects.r.rnorm(100) d = robjects.r.density(x) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------- This SF.Net email is sponsored by the Moblin Your Move Developer's challenge Build the coolest Linux based applications with Moblin SDK & win great prizes Grand prize is a trip for two to an Open Source event anywhere in the world http://moblin-contest.org/redirect.php?banner_id=100&url=/ _______________________________________________ rpy-list mailing list rpy-list@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/rpy-list