##// END OF EJS Templates
rerunning notebook serially
rerunning notebook serially

File last commit:

r7215:0a1e2682
r7216:af4d975a
Show More
rmagic.py
257 lines | 7.7 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
"""
R related magics.
Author:
* Jonathan Taylor
"""
import sys
import tempfile
from glob import glob
from shutil import rmtree
from getopt import getopt
# numpy and rpy2 imports
import rpy2.rinterface as ri
import numpy as np
import rpy2.robjects as ro
from rpy2.robjects.numpy2ri import numpy2ri
ro.conversion.py2ri = numpy2ri
# IPython imports
from IPython.core.displaypub import publish_display_data
from IPython.core.magic import Magics, magics_class, cell_magic, line_magic
from IPython.testing.skipdoctest import skip_doctest
from IPython.core.magic_arguments import (
argument, magic_arguments, parse_argstring
)
@magics_class
class RMagics(Magics):
def __init__(self, shell, Rconverter=np.asarray,
pyconverter=np.asarray):
super(RMagics, self).__init__(shell)
ri.set_writeconsole(self.write_console)
# the embedded R process from rpy2
self.r = ro.R()
self.output = []
self.Rconverter = Rconverter
self.pyconverter = pyconverter
def eval(self, line):
try:
ri.baseenv['eval'](ri.parse(line))
except ri.RRuntimeError as msg:
self.output.append('ERROR parsing "%s": %s\n' % (line, msg))
pass
def write_console(self, output):
'''
A hook to capture R's stdout.
'''
self.output.append(output)
def flush(self):
value = ''.join(self.output)
self.output = []
return value
@line_magic
def Rpush(self, line):
'''
A line-level magic for R that pushes
variables from python to rpy2.
Parameters
----------
line: input
A white space separated string of
names of objects in the python name space to be
assigned to objects of the same name in the
R name space.
'''
inputs = line.split(' ')
for input in inputs:
self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
@line_magic
def Rpull(self, line):
'''
A line-level magic for R that pushes
variables from python to rpy2.
Parameters
----------
line: output
A white space separated string of
names of objects in the R name space to be
assigned to objects of the same name in the
python name space.
Notes
-----
Beware that R names can have '.' so this is not fool proof.
To avoid this, don't name your R objects with '.'s...
'''
outputs = line.split(' ')
for output in outputs:
self.shell.push({output:self.Rconverter(self.r(output))})
@line_magic
def Rinline(self, line):
'''
A line-level magic for R that executes
some code in R and stores the resulting
expression in the ipython shell.
Parameters
----------
line: str
A string of Rcode to be evaluted
with return value passed to self.Rconverter.
'''
result = ri.baseenv['eval'](ri.parse(line))
return self.Rconverter(result)
@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(
'-u', '--units', type=int,
help='Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"].'
)
@argument(
'-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.'
)
@cell_magic
def R(self, line, cell):
'''
A cell-level magic for R.
The contents of the cell are evaluated using rpy2 with stdout
and graphics being captured and returned in the output of the cell.
Parameters
----------
line: {input, output, width, height, units, pointsize, bg}
Optional long arguments recognized by the cell magic.
The first two relate to passing python objects
back and forth between rpy2 and python:
'--input' should be a comma separated list of
names of objects in the namespace of the shell;
'--output' are names of object in the R namespace
that are returned as arrays.
All other line are passed as arguments to
the png plotting device in R.
cell: str
String of R code to be executed by rpy2.
Anything written to stdout of R is captured,
as are plots (in the form of .png files) that
are published via ipython's frontend
publishing system.
'''
# need to get the ipython instance for assigning
args = parse_argstring(self.R, line)
if args.input:
for input in ','.join(args.input).split(','):
self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
png_argdict = dict([(n, getattr(args, n)) for n in ['units', 'height', 'width', 'bg', 'pointsize']])
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()
self.r('png("%s/Rplots%%03d.png",%s)' % (tmpd, png_args))
lines = cell.split('\n')
[self.eval(line) for line in lines]
self.r('dev.off()')
# read out all the saved .png files
images = [file(imgfile).read() for imgfile in glob("%s/Rplots*png" % tmpd)]
# 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
publish_display_data('Rmagic.cell_magic', {'text/plain':self.flush()})
# 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()
publish_display_data(
'Rmagic.cell_magic',
{mime : image}
)
value = {}
# 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(','):
# with self.shell, we assign the values to variables in the shell
self.shell.push({output:self.Rconverter(self.r(output))})
# kill the temporary directory
rmtree(tmpd)
_loaded = False
def load_ipython_extension(ip):
"""Load the extension in IPython."""
global _loaded
if not _loaded:
ip.register_magics(RMagics)
_loaded = True