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
[email protected]
https://lists.sourceforge.net/lists/listinfo/rpy-list