rmagic.py
696 lines
| 22.1 KiB
| text/x-python
|
PythonLexer
|
r7214 | # -*- coding: utf-8 -*- | ||
""" | ||||
|
r7235 | ====== | ||
Rmagic | ||||
====== | ||||
|
r7214 | |||
|
r7235 | Magic command interface for interactive work with R via rpy2 | ||
|
r12534 | .. note:: | ||
The ``rpy2`` package needs to be installed separately. It | ||||
can be obtained using ``easy_install`` or ``pip``. | ||||
You will also need a working copy of R. | ||||
|
r7235 | Usage | ||
===== | ||||
|
r12534 | To enable the magics below, execute ``%load_ext rmagic``. | ||
|
r7235 | ``%R`` | ||
{R_DOC} | ||||
``%Rpush`` | ||||
{RPUSH_DOC} | ||||
``%Rpull`` | ||||
{RPULL_DOC} | ||||
|
r7214 | |||
|
r7276 | ``%Rget`` | ||
{RGET_DOC} | ||||
|
r7214 | """ | ||
|
r13348 | from __future__ import print_function | ||
|
r7214 | |||
|
r7235 | #----------------------------------------------------------------------------- | ||
# Copyright (C) 2012 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
|
r7214 | import sys | ||
import tempfile | ||||
from glob import glob | ||||
from shutil import rmtree | ||||
# numpy and rpy2 imports | ||||
import numpy as np | ||||
|
r7217 | |||
import rpy2.rinterface as ri | ||||
|
r7214 | import rpy2.robjects as ro | ||
|
r10017 | try: | ||
from rpy2.robjects import pandas2ri | ||||
pandas2ri.activate() | ||||
except ImportError: | ||||
pandas2ri = None | ||||
from rpy2.robjects import numpy2ri | ||||
numpy2ri.activate() | ||||
|
r7214 | |||
# IPython imports | ||||
from IPython.core.displaypub import publish_display_data | ||||
|
r11128 | from IPython.core.magic import (Magics, magics_class, line_magic, | ||
|
r8262 | line_cell_magic, needs_local_scope) | ||
|
r7214 | from IPython.testing.skipdoctest import skip_doctest | ||
from IPython.core.magic_arguments import ( | ||||
argument, magic_arguments, parse_argstring | ||||
) | ||||
|
r10017 | from IPython.external.simplegeneric import generic | ||
|
r13153 | from IPython.utils.py3compat import (str_to_unicode, unicode_to_str, PY3, | ||
unicode_type) | ||||
|
r13604 | from IPython.utils.text import dedent | ||
|
r7227 | |||
|
r7327 | class RInterpreterError(ri.RRuntimeError): | ||
|
r7318 | """An error when running R code in a %%R magic cell.""" | ||
def __init__(self, line, err, stdout): | ||||
self.line = line | ||||
self.err = err.rstrip() | ||||
self.stdout = stdout.rstrip() | ||||
def __unicode__(self): | ||||
|
r7327 | s = 'Failed to parse and evaluate line %r.\nR error message: %r' % \ | ||
|
r7318 | (self.line, self.err) | ||
if self.stdout and (self.stdout != self.err): | ||||
s += '\nR stdout:\n' + self.stdout | ||||
return s | ||||
if PY3: | ||||
__str__ = __unicode__ | ||||
else: | ||||
def __str__(self): | ||||
return unicode_to_str(unicode(self), 'utf-8') | ||||
|
r7214 | |||
|
r7276 | def Rconverter(Robj, dataframe=False): | ||
|
r7267 | """ | ||
Convert an object in R's namespace to one suitable | ||||
for ipython's namespace. | ||||
For a data.frame, it tries to return a structured array. | ||||
|
r7276 | It first checks for colnames, then names. | ||
If all are NULL, it returns np.asarray(Robj), else | ||||
it tries to construct a recarray | ||||
|
r7267 | |||
Parameters | ||||
---------- | ||||
Robj: an R object returned from rpy2 | ||||
""" | ||||
|
r7276 | is_data_frame = ro.r('is.data.frame') | ||
colnames = ro.r('colnames') | ||||
rownames = ro.r('rownames') # with pandas, these could be used for the index | ||||
names = ro.r('names') | ||||
if dataframe: | ||||
as_data_frame = ro.r('as.data.frame') | ||||
cols = colnames(Robj) | ||||
_names = names(Robj) | ||||
if cols != ri.NULL: | ||||
Robj = as_data_frame(Robj) | ||||
names = tuple(np.array(cols)) | ||||
elif _names != ri.NULL: | ||||
names = tuple(np.array(_names)) | ||||
else: # failed to find names | ||||
return np.asarray(Robj) | ||||
Robj = np.rec.fromarrays(Robj, names = names) | ||||
return np.asarray(Robj) | ||||
|
r7267 | |||
|
r10017 | @generic | ||
|
r9495 | def pyconverter(pyobj): | ||
|
r10017 | """Convert Python objects to R objects. Add types using the decorator: | ||
|
r9495 | |||
|
r10017 | @pyconverter.when_type | ||
""" | ||||
return pyobj | ||||
# The default conversion for lists seems to make them a nested list. That has | ||||
# some advantages, but is rarely convenient, so for interactive use, we convert | ||||
# lists to a numpy array, which becomes an R vector. | ||||
@pyconverter.when_type(list) | ||||
def pyconverter_list(pyobj): | ||||
|
r9495 | return np.asarray(pyobj) | ||
|
r10017 | if pandas2ri is None: | ||
# pandas2ri was new in rpy2 2.3.3, so for now we'll fallback to pandas' | ||||
# conversion function. | ||||
try: | ||||
from pandas import DataFrame | ||||
from pandas.rpy.common import convert_to_r_dataframe | ||||
@pyconverter.when_type(DataFrame) | ||||
def pyconverter_dataframe(pyobj): | ||||
return convert_to_r_dataframe(pyobj, strings_as_factors=True) | ||||
except ImportError: | ||||
pass | ||||
|
r7214 | @magics_class | ||
class RMagics(Magics): | ||||
|
r7236 | """A set of magics useful for interactive work with R via rpy2. | ||
""" | ||||
|
r7214 | |||
|
r7267 | def __init__(self, shell, Rconverter=Rconverter, | ||
|
r9495 | pyconverter=pyconverter, | ||
|
r7227 | cache_display_data=False): | ||
|
r7236 | """ | ||
Parameters | ||||
---------- | ||||
shell : IPython shell | ||||
|
r9495 | |||
Rconverter : callable | ||||
To be called on values taken from R before putting them in the | ||||
IPython namespace. | ||||
|
r7236 | |||
pyconverter : callable | ||||
To be called on values in ipython namespace before | ||||
assigning to variables in rpy2. | ||||
cache_display_data : bool | ||||
If True, the published results of the final call to R are | ||||
cached in the variable 'display_cache'. | ||||
""" | ||||
|
r7214 | super(RMagics, self).__init__(shell) | ||
|
r7227 | self.cache_display_data = cache_display_data | ||
|
r7214 | |||
self.r = ro.R() | ||||
|
r7267 | |||
|
r7235 | self.Rstdout_cache = [] | ||
|
r7214 | self.pyconverter = pyconverter | ||
|
r7267 | self.Rconverter = Rconverter | ||
|
r7214 | |||
|
r12172 | def eval(self, line): | ||
|
r7235 | ''' | ||
|
r12172 | Parse and evaluate a line of R code with rpy2. | ||
Returns the output to R's stdout() connection, | ||||
the value generated by evaluating the code, and a | ||||
boolean indicating whether the return value would be | ||||
visible if the line of code were evaluated in an R REPL. | ||||
|
r12164 | |||
|
r12172 | R Code evaluation and visibility determination are | ||
done via an R call of the form withVisible({<code>}) | ||||
|
r12164 | |||
|
r7235 | ''' | ||
|
r7227 | old_writeconsole = ri.get_writeconsole() | ||
ri.set_writeconsole(self.write_console) | ||||
|
r7214 | try: | ||
|
r16256 | res = ro.r("withVisible({%s\n})" % line) | ||
|
r12172 | value = res[0] #value (R object) | ||
visible = ro.conversion.ri2py(res[1])[0] #visible (boolean) | ||||
|
r7237 | except (ri.RRuntimeError, ValueError) as exception: | ||
|
r7281 | warning_or_other_msg = self.flush() # otherwise next return seems to have copy of error | ||
|
r7327 | raise RInterpreterError(line, str_to_unicode(str(exception)), warning_or_other_msg) | ||
|
r7227 | text_output = self.flush() | ||
ri.set_writeconsole(old_writeconsole) | ||||
|
r12172 | return text_output, value, visible | ||
|
r7214 | |||
def write_console(self, output): | ||||
''' | ||||
|
r7235 | A hook to capture R's stdout in a cache. | ||
|
r7214 | ''' | ||
|
r7235 | self.Rstdout_cache.append(output) | ||
|
r7214 | |||
def flush(self): | ||||
|
r7235 | ''' | ||
Flush R's stdout cache to a string, returning the string. | ||||
''' | ||||
value = ''.join([str_to_unicode(s, 'utf-8') for s in self.Rstdout_cache]) | ||||
self.Rstdout_cache = [] | ||||
|
r7214 | return value | ||
|
r7225 | @skip_doctest | ||
|
r8956 | @needs_local_scope | ||
|
r7214 | @line_magic | ||
|
r8956 | def Rpush(self, line, local_ns=None): | ||
|
r7214 | ''' | ||
A line-level magic for R that pushes | ||||
|
r7225 | variables from python to rpy2. The line should be made up | ||
of whitespace separated variable names in the IPython | ||||
|
r7235 | namespace:: | ||
|
r7225 | |||
|
r7235 | In [7]: import numpy as np | ||
|
r7225 | |||
|
r7235 | In [8]: X = np.array([4.5,6.3,7.9]) | ||
|
r7214 | |||
|
r7235 | In [9]: X.mean() | ||
Out[9]: 6.2333333333333343 | ||||
|
r7214 | |||
|
r7235 | In [10]: %Rpush X | ||
|
r7214 | |||
|
r7235 | In [11]: %R mean(X) | ||
Out[11]: array([ 6.23333333]) | ||||
|
r7214 | |||
''' | ||||
|
r8956 | if local_ns is None: | ||
local_ns = {} | ||||
|
r7214 | |||
inputs = line.split(' ') | ||||
for input in inputs: | ||||
|
r8956 | try: | ||
val = local_ns[input] | ||||
except KeyError: | ||||
|
r8959 | try: | ||
val = self.shell.user_ns[input] | ||||
except KeyError: | ||||
# reraise the KeyError as a NameError so that it looks like | ||||
# the standard python behavior when you use an unnamed | ||||
# variable | ||||
raise NameError("name '%s' is not defined" % input) | ||||
|
r8956 | self.r.assign(input, self.pyconverter(val)) | ||
|
r7214 | |||
|
r7225 | @skip_doctest | ||
|
r7276 | @magic_arguments() | ||
@argument( | ||||
'-d', '--as_dataframe', action='store_true', | ||||
default=False, | ||||
help='Convert objects to data.frames before returning to ipython.' | ||||
) | ||||
@argument( | ||||
'outputs', | ||||
nargs='*', | ||||
) | ||||
|
r7214 | @line_magic | ||
def Rpull(self, line): | ||||
''' | ||||
|
r7219 | A line-level magic for R that pulls | ||
|
r7225 | variables from python to rpy2:: | ||
In [18]: _ = %R x = c(3,4,6.7); y = c(4,6,7); z = c('a',3,4) | ||||
In [19]: %Rpull x y z | ||||
|
r7214 | |||
|
r7225 | In [20]: x | ||
Out[20]: array([ 3. , 4. , 6.7]) | ||||
|
r7214 | |||
|
r7225 | In [21]: y | ||
Out[21]: array([ 4., 6., 7.]) | ||||
|
r7214 | |||
|
r7225 | In [22]: z | ||
Out[22]: | ||||
array(['a', '3', '4'], | ||||
dtype='|S1') | ||||
|
r7214 | |||
|
r7235 | |||
|
r7276 | If --as_dataframe, then each object is returned as a structured array | ||
after first passed through "as.data.frame" in R before | ||||
being calling self.Rconverter. | ||||
This is useful when a structured array is desired as output, or | ||||
when the object in R has mixed data types. | ||||
See the %%R docstring for more examples. | ||||
|
r7214 | Notes | ||
----- | ||||
Beware that R names can have '.' so this is not fool proof. | ||||
To avoid this, don't name your R objects with '.'s... | ||||
''' | ||||
|
r7276 | args = parse_argstring(self.Rpull, line) | ||
outputs = args.outputs | ||||
|
r7214 | for output in outputs: | ||
|
r7276 | self.shell.push({output:self.Rconverter(self.r(output),dataframe=args.as_dataframe)}) | ||
@skip_doctest | ||||
@magic_arguments() | ||||
@argument( | ||||
'-d', '--as_dataframe', action='store_true', | ||||
default=False, | ||||
help='Convert objects to data.frames before returning to ipython.' | ||||
) | ||||
@argument( | ||||
'output', | ||||
nargs=1, | ||||
type=str, | ||||
) | ||||
@line_magic | ||||
def Rget(self, line): | ||||
''' | ||||
Return an object from rpy2, possibly as a structured array (if possible). | ||||
Similar to Rpull except only one argument is accepted and the value is | ||||
returned rather than pushed to self.shell.user_ns:: | ||||
In [3]: dtype=[('x', '<i4'), ('y', '<f8'), ('z', '|S1')] | ||||
In [4]: datapy = np.array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c'), (4, 5, 'e')], dtype=dtype) | ||||
In [5]: %R -i datapy | ||||
In [6]: %Rget datapy | ||||
Out[6]: | ||||
array([['1', '2', '3', '4'], | ||||
['2', '3', '2', '5'], | ||||
['a', 'b', 'c', 'e']], | ||||
dtype='|S1') | ||||
In [7]: %Rget -d datapy | ||||
Out[7]: | ||||
array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c'), (4, 5.0, 'e')], | ||||
dtype=[('x', '<i4'), ('y', '<f8'), ('z', '|S1')]) | ||||
''' | ||||
args = parse_argstring(self.Rget, line) | ||||
output = args.output | ||||
return self.Rconverter(self.r(output[0]),dataframe=args.as_dataframe) | ||||
|
r7214 | |||
|
r7225 | @skip_doctest | ||
|
r7214 | @magic_arguments() | ||
@argument( | ||||
'-i', '--input', action='append', | ||||
help='Names of input variable from shell.user_ns to be assigned to R variables of the same names after calling self.pyconverter. Multiple names can be passed separated only by commas with no whitespace.' | ||||
) | ||||
@argument( | ||||
'-o', '--output', action='append', | ||||
help='Names of variables to be pushed from rpy2 to shell.user_ns after executing cell body and applying self.Rconverter. Multiple names can be passed separated only by commas with no whitespace.' | ||||
) | ||||
@argument( | ||||
'-w', '--width', type=int, | ||||
help='Width of png plotting device sent as an argument to *png* in R.' | ||||
) | ||||
@argument( | ||||
'-h', '--height', type=int, | ||||
help='Height of png plotting device sent as an argument to *png* in R.' | ||||
) | ||||
@argument( | ||||
|
r7276 | '-d', '--dataframe', action='append', | ||
help='Convert these objects to data.frames and return as structured arrays.' | ||||
) | ||||
@argument( | ||||
|
r13153 | '-u', '--units', type=unicode_type, choices=["px", "in", "cm", "mm"], | ||
|
r7214 | help='Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"].' | ||
) | ||||
@argument( | ||||
|
r8805 | '-r', '--res', type=int, | ||
help='Resolution of png plotting device sent as an argument to *png* in R. Defaults to 72 if *units* is one of ["in", "cm", "mm"].' | ||||
) | ||||
@argument( | ||||
|
r7214 | '-p', '--pointsize', type=int, | ||
help='Pointsize of png plotting device sent as an argument to *png* in R.' | ||||
) | ||||
@argument( | ||||
'-b', '--bg', | ||||
help='Background of png plotting device sent as an argument to *png* in R.' | ||||
) | ||||
|
r7217 | @argument( | ||
|
r7227 | '-n', '--noreturn', | ||
help='Force the magic to not return anything.', | ||||
action='store_true', | ||||
default=False | ||||
) | ||||
@argument( | ||||
|
r7217 | 'code', | ||
nargs='*', | ||||
) | ||||
|
r8262 | @needs_local_scope | ||
|
r7217 | @line_cell_magic | ||
|
r8262 | def R(self, line, cell=None, local_ns=None): | ||
|
r7214 | ''' | ||
|
r7221 | Execute code in R, and pull some of the results back into the Python namespace. | ||
In line mode, this will evaluate an expression and convert the returned value to a Python object. | ||||
The return value is determined by rpy2's behaviour of returning the result of evaluating the | ||||
|
r7277 | final line. | ||
Multiple R lines can be executed by joining them with semicolons:: | ||||
|
r7221 | |||
|
r7225 | In [9]: %R X=c(1,4,5,7); sd(X); mean(X) | ||
Out[9]: array([ 4.25]) | ||||
|
r7221 | |||
|
r12172 | In cell mode, this will run a block of R code. The resulting value | ||
is printed if it would printed be when evaluating the same code | ||||
within a standard R REPL. | ||||
|
r12536 | Nothing is returned to python by default in cell mode:: | ||
|
r7221 | |||
|
r7225 | In [10]: %%R | ||
....: Y = c(2,4,3,9) | ||||
|
r12170 | ....: summary(lm(Y~X)) | ||
|
r12536 | |||
|
r7225 | Call: | ||
lm(formula = Y ~ X) | ||||
|
r7221 | |||
|
r7225 | Residuals: | ||
1 2 3 4 | ||||
0.88 -0.24 -2.28 1.64 | ||||
|
r7221 | |||
|
r7225 | Coefficients: | ||
Estimate Std. Error t value Pr(>|t|) | ||||
(Intercept) 0.0800 2.3000 0.035 0.975 | ||||
X 1.0400 0.4822 2.157 0.164 | ||||
|
r7221 | |||
|
r7225 | Residual standard error: 2.088 on 2 degrees of freedom | ||
Multiple R-squared: 0.6993,Adjusted R-squared: 0.549 | ||||
F-statistic: 4.651 on 1 and 2 DF, p-value: 0.1638 | ||||
|
r7221 | |||
|
r12536 | In the notebook, plots are published as the output of the cell:: | ||
|
r7221 | |||
|
r12536 | %R plot(X, Y) | ||
|
r7221 | |||
will create a scatter plot of X bs Y. | ||||
If cell is not None and line has some R code, it is prepended to | ||||
the R code in cell. | ||||
|
r7225 | Objects can be passed back and forth between rpy2 and python via the -i -o flags in line:: | ||
|
r7221 | |||
|
r7225 | In [14]: Z = np.array([1,4,5,10]) | ||
|
r7221 | |||
|
r7225 | In [15]: %R -i Z mean(Z) | ||
Out[15]: array([ 5.]) | ||||
|
r7221 | |||
|
r7225 | In [16]: %R -o W W=Z*mean(Z) | ||
Out[16]: array([ 5., 20., 25., 50.]) | ||||
|
r7221 | |||
|
r7225 | In [17]: W | ||
Out[17]: array([ 5., 20., 25., 50.]) | ||||
|
r7217 | |||
|
r7235 | The return value is determined by these rules: | ||
* If the cell is not None, the magic returns None. | ||||
* If the cell evaluates as False, the resulting value is returned | ||||
|
r12536 | unless the final line prints something to the console, in | ||
which case None is returned. | ||||
|
r7235 | |||
* If the final line results in a NULL value when evaluated | ||||
|
r12536 | by rpy2, then None is returned. | ||
|
r7217 | |||
|
r7277 | * No attempt is made to convert the final value to a structured array. | ||
|
r12536 | Use the --dataframe flag or %Rget to push / return a structured array. | ||
|
r7277 | |||
|
r7285 | * If the -n flag is present, there is no return value. | ||
* A trailing ';' will also result in no return value as the last | ||||
|
r12536 | value in the line is an empty string. | ||
|
r7285 | |||
|
r7277 | The --dataframe argument will attempt to return structured arrays. | ||
This is useful for dataframes with | ||||
|
r7276 | mixed data types. Note also that for a data.frame, | ||
if it is returned as an ndarray, it is transposed:: | ||||
In [18]: dtype=[('x', '<i4'), ('y', '<f8'), ('z', '|S1')] | ||||
In [19]: datapy = np.array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c'), (4, 5, 'e')], dtype=dtype) | ||||
In [20]: %%R -o datar | ||||
datar = datapy | ||||
....: | ||||
In [21]: datar | ||||
Out[21]: | ||||
array([['1', '2', '3', '4'], | ||||
['2', '3', '2', '5'], | ||||
['a', 'b', 'c', 'e']], | ||||
dtype='|S1') | ||||
In [22]: %%R -d datar | ||||
datar = datapy | ||||
....: | ||||
In [23]: datar | ||||
Out[23]: | ||||
array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c'), (4, 5.0, 'e')], | ||||
dtype=[('x', '<i4'), ('y', '<f8'), ('z', '|S1')]) | ||||
|
r7277 | The --dataframe argument first tries colnames, then names. | ||
If both are NULL, it returns an ndarray (i.e. unstructured):: | ||||
|
r7276 | |||
In [1]: %R mydata=c(4,6,8.3); NULL | ||||
In [2]: %R -d mydata | ||||
In [3]: mydata | ||||
Out[3]: array([ 4. , 6. , 8.3]) | ||||
In [4]: %R names(mydata) = c('a','b','c'); NULL | ||||
In [5]: %R -d mydata | ||||
In [6]: mydata | ||||
Out[6]: | ||||
array((4.0, 6.0, 8.3), | ||||
dtype=[('a', '<f8'), ('b', '<f8'), ('c', '<f8')]) | ||||
In [7]: %R -o mydata | ||||
In [8]: mydata | ||||
Out[8]: array([ 4. , 6. , 8.3]) | ||||
|
r7214 | ''' | ||
args = parse_argstring(self.R, line) | ||||
|
r7217 | # arguments 'code' in line are prepended to | ||
# the cell lines | ||||
|
r8262 | |||
if cell is None: | ||||
|
r7220 | code = '' | ||
|
r7217 | return_output = True | ||
|
r7227 | line_mode = True | ||
|
r7217 | else: | ||
|
r7220 | code = cell | ||
|
r7217 | return_output = False | ||
|
r7227 | line_mode = False | ||
|
r7217 | |||
|
r7220 | code = ' '.join(args.code) + code | ||
|
r7217 | |||
|
r8262 | # if there is no local namespace then default to an empty dict | ||
if local_ns is None: | ||||
local_ns = {} | ||||
|
r7214 | if args.input: | ||
for input in ','.join(args.input).split(','): | ||||
|
r8262 | try: | ||
val = local_ns[input] | ||||
except KeyError: | ||||
|
r8959 | try: | ||
val = self.shell.user_ns[input] | ||||
except KeyError: | ||||
raise NameError("name '%s' is not defined" % input) | ||||
|
r8262 | self.r.assign(input, self.pyconverter(val)) | ||
|
r7214 | |||
|
r8805 | if getattr(args, 'units') is not None: | ||
if args.units != "px" and getattr(args, 'res') is None: | ||||
args.res = 72 | ||||
args.units = '"%s"' % args.units | ||||
png_argdict = dict([(n, getattr(args, n)) for n in ['units', 'res', 'height', 'width', 'bg', 'pointsize']]) | ||||
|
r7214 | png_args = ','.join(['%s=%s' % (o,v) for o, v in png_argdict.items() if v is not None]) | ||
# execute the R code in a temporary directory | ||||
tmpd = tempfile.mkdtemp() | ||||
|
r8771 | self.r('png("%s/Rplots%%03d.png",%s)' % (tmpd.replace('\\', '/'), png_args)) | ||
|
r7227 | |||
|
r7233 | text_output = '' | ||
|
r11054 | try: | ||
if line_mode: | ||||
for line in code.split(';'): | ||||
|
r12172 | text_result, result, visible = self.eval(line) | ||
|
r11054 | text_output += text_result | ||
if text_result: | ||||
# the last line printed something to the console so we won't return it | ||||
return_output = False | ||||
else: | ||||
|
r12172 | text_result, result, visible = self.eval(code) | ||
|
r7227 | text_output += text_result | ||
|
r12173 | if visible: | ||
|
r12172 | old_writeconsole = ri.get_writeconsole() | ||
ri.set_writeconsole(self.write_console) | ||||
ro.r.show(result) | ||||
text_output += self.flush() | ||||
ri.set_writeconsole(old_writeconsole) | ||||
|
r11054 | |||
except RInterpreterError as e: | ||||
|
r13386 | print(e.stdout) | ||
|
r11054 | if not e.stdout.endswith(e.err): | ||
|
r13386 | print(e.err) | ||
|
r11054 | rmtree(tmpd) | ||
return | ||||
|
r7227 | |||
|
r7214 | self.r('dev.off()') | ||
# read out all the saved .png files | ||||
|
r7221 | images = [open(imgfile, 'rb').read() for imgfile in glob("%s/Rplots*png" % tmpd)] | ||
|
r7214 | |||
# now publish the images | ||||
# mimicking IPython/zmq/pylab/backend_inline.py | ||||
fmt = 'png' | ||||
mimetypes = { 'png' : 'image/png', 'svg' : 'image/svg+xml' } | ||||
mime = mimetypes[fmt] | ||||
# publish the printed R objects, if any | ||||
|
r7227 | |||
display_data = [] | ||||
if text_output: | ||||
display_data.append(('RMagic.R', {'text/plain':text_output})) | ||||
|
r7214 | |||
# flush text streams before sending figures, helps a little with output | ||||
for image in images: | ||||
# synchronization in the console (though it's a bandaid, not a real sln) | ||||
sys.stdout.flush(); sys.stderr.flush() | ||||
|
r7227 | display_data.append(('RMagic.R', {mime: image})) | ||
|
r7214 | |||
|
r7225 | # kill the temporary directory | ||
rmtree(tmpd) | ||||
|
r7214 | # try to turn every output into a numpy array | ||
# this means that output are assumed to be castable | ||||
# as numpy arrays | ||||
if args.output: | ||||
for output in ','.join(args.output).split(','): | ||||
|
r7276 | self.shell.push({output:self.Rconverter(self.r(output), dataframe=False)}) | ||
if args.dataframe: | ||||
for output in ','.join(args.dataframe).split(','): | ||||
self.shell.push({output:self.Rconverter(self.r(output), dataframe=True)}) | ||||
|
r7214 | |||
|
r7227 | for tag, disp_d in display_data: | ||
publish_display_data(tag, disp_d) | ||||
# this will keep a reference to the display_data | ||||
# which might be useful to other objects who happen to use | ||||
# this method | ||||
|
r7214 | |||
|
r7227 | if self.cache_display_data: | ||
self.display_cache = display_data | ||||
|
r7217 | |||
|
r7227 | # if in line mode and return_output, return the result as an ndarray | ||
if return_output and not args.noreturn: | ||||
if result != ri.NULL: | ||||
|
r7276 | return self.Rconverter(result, dataframe=False) | ||
|
r7214 | |||
|
r7235 | __doc__ = __doc__.format( | ||
|
r13604 | R_DOC = dedent(RMagics.R.__doc__), | ||
RPUSH_DOC = dedent(RMagics.Rpush.__doc__), | ||||
RPULL_DOC = dedent(RMagics.Rpull.__doc__), | ||||
RGET_DOC = dedent(RMagics.Rget.__doc__) | ||||
|
r7235 | ) | ||
|
r7214 | def load_ipython_extension(ip): | ||
"""Load the extension in IPython.""" | ||||
|
r8552 | ip.register_magics(RMagics) | ||
|
r9017 | # Initialising rpy2 interferes with readline. Since, at this point, we've | ||
# probably just loaded rpy2, we reset the delimiters. See issue gh-2759. | ||||
if ip.has_readline: | ||||
ip.readline.set_completer_delims(ip.readline_delims) | ||||