##// END OF EJS Templates
added use cases for rpush/rpull, tried fixing indents for examples in R docstring
Jonathan Taylor -
Show More
@@ -1,298 +1,314 b''
1
1 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
2 """
3 """
3 R related magics.
4 R related magics.
4
5
5 Author:
6 Author:
6 * Jonathan Taylor
7 * Jonathan Taylor
7
8
8 """
9 """
9
10
10 import sys
11 import sys
11 import tempfile
12 import tempfile
12 from glob import glob
13 from glob import glob
13 from shutil import rmtree
14 from shutil import rmtree
14 from getopt import getopt
15 from getopt import getopt
15
16
16 # numpy and rpy2 imports
17 # numpy and rpy2 imports
17
18
18 import numpy as np
19 import numpy as np
19
20
20 import rpy2.rinterface as ri
21 import rpy2.rinterface as ri
21 import rpy2.robjects as ro
22 import rpy2.robjects as ro
22 from rpy2.robjects.numpy2ri import numpy2ri
23 from rpy2.robjects.numpy2ri import numpy2ri
23 ro.conversion.py2ri = numpy2ri
24 ro.conversion.py2ri = numpy2ri
24
25
25 # IPython imports
26 # IPython imports
26
27
27 from IPython.core.displaypub import publish_display_data
28 from IPython.core.displaypub import publish_display_data
28 from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic,
29 from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic,
29 line_cell_magic)
30 line_cell_magic)
30 from IPython.testing.skipdoctest import skip_doctest
31 from IPython.testing.skipdoctest import skip_doctest
31 from IPython.core.magic_arguments import (
32 from IPython.core.magic_arguments import (
32 argument, magic_arguments, parse_argstring
33 argument, magic_arguments, parse_argstring
33 )
34 )
34
35
35 @magics_class
36 @magics_class
36 class RMagics(Magics):
37 class RMagics(Magics):
37
38
38 def __init__(self, shell, Rconverter=np.asarray,
39 def __init__(self, shell, Rconverter=np.asarray,
39 pyconverter=np.asarray):
40 pyconverter=np.asarray):
40 super(RMagics, self).__init__(shell)
41 super(RMagics, self).__init__(shell)
41 ri.set_writeconsole(self.write_console)
42 ri.set_writeconsole(self.write_console)
42
43
43 # the embedded R process from rpy2
44 # the embedded R process from rpy2
44 self.r = ro.R()
45 self.r = ro.R()
45 self.output = []
46 self.output = []
46 self.Rconverter = Rconverter
47 self.Rconverter = Rconverter
47 self.pyconverter = pyconverter
48 self.pyconverter = pyconverter
48
49
49 def eval(self, line):
50 def eval(self, line):
50 try:
51 try:
51 return ri.baseenv['eval'](ri.parse(line))
52 return ri.baseenv['eval'](ri.parse(line))
52 except (ri.RRuntimeError, ValueError) as msg:
53 except (ri.RRuntimeError, ValueError) as msg:
53 self.output.append('ERROR parsing "%s": %s\n' % (line, msg))
54 self.output.append('ERROR parsing "%s": %s\n' % (line, msg))
54 pass
55 pass
55
56
56 def write_console(self, output):
57 def write_console(self, output):
57 '''
58 '''
58 A hook to capture R's stdout.
59 A hook to capture R's stdout.
59 '''
60 '''
60 self.output.append(output)
61 self.output.append(output)
61
62
62 def flush(self):
63 def flush(self):
63 value = ''.join([s.decode('utf-8') for s in self.output])
64 value = ''.join([s.decode('utf-8') for s in self.output])
64 self.output = []
65 self.output = []
65 return value
66 return value
66
67
68 @skip_doctest
67 @line_magic
69 @line_magic
68 def Rpush(self, line):
70 def Rpush(self, line):
69 '''
71 '''
70 A line-level magic for R that pushes
72 A line-level magic for R that pushes
71 variables from python to rpy2.
73 variables from python to rpy2. The line should be made up
74 of whitespace separated variable names in the IPython
75 namespace.
76
77 In [7]: import numpy as np
78
79 In [8]: X = np.array([4.5,6.3,7.9])
72
80
73 Parameters
81 In [9]: X.mean()
74 ----------
82 Out[9]: 6.2333333333333343
75
83
76 line: input
84 In [10]: %Rpush X
77
85
78 A white space separated string of
86 In [11]: %R mean(X)
79 names of objects in the python name space to be
87 Out[11]: array([ 6.23333333])
80 assigned to objects of the same name in the
81 R name space.
82
88
83 '''
89 '''
84
90
85 inputs = line.split(' ')
91 inputs = line.split(' ')
86 for input in inputs:
92 for input in inputs:
87 self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
93 self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
88
94
95 @skip_doctest
89 @line_magic
96 @line_magic
90 def Rpull(self, line):
97 def Rpull(self, line):
91 '''
98 '''
92 A line-level magic for R that pulls
99 A line-level magic for R that pulls
93 variables from python to rpy2.
100 variables from python to rpy2::
101
102 In [18]: _ = %R x = c(3,4,6.7); y = c(4,6,7); z = c('a',3,4)
103
104 In [19]: %Rp
105 %Rpull %Rpush
106
107 In [19]: %Rpull x y z
94
108
95 Parameters
109 In [20]: x
96 ----------
110 Out[20]: array([ 3. , 4. , 6.7])
97
111
98 line: output
112 In [21]: y
113 Out[21]: array([ 4., 6., 7.])
99
114
100 A white space separated string of
115 In [22]: z
101 names of objects in the R name space to be
116 Out[22]:
102 assigned to objects of the same name in the
117 array(['a', '3', '4'],
103 python name space.
118 dtype='|S1')
104
119
105 Notes
120 Notes
106 -----
121 -----
107
122
108 Beware that R names can have '.' so this is not fool proof.
123 Beware that R names can have '.' so this is not fool proof.
109 To avoid this, don't name your R objects with '.'s...
124 To avoid this, don't name your R objects with '.'s...
110
125
111 '''
126 '''
112 outputs = line.split(' ')
127 outputs = line.split(' ')
113 for output in outputs:
128 for output in outputs:
114 self.shell.push({output:self.Rconverter(self.r(output))})
129 self.shell.push({output:self.Rconverter(self.r(output))})
115
130
116
131
132 @skip_doctest
117 @magic_arguments()
133 @magic_arguments()
118 @argument(
134 @argument(
119 '-i', '--input', action='append',
135 '-i', '--input', action='append',
120 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.'
136 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.'
121 )
137 )
122 @argument(
138 @argument(
123 '-o', '--output', action='append',
139 '-o', '--output', action='append',
124 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.'
140 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.'
125 )
141 )
126 @argument(
142 @argument(
127 '-w', '--width', type=int,
143 '-w', '--width', type=int,
128 help='Width of png plotting device sent as an argument to *png* in R.'
144 help='Width of png plotting device sent as an argument to *png* in R.'
129 )
145 )
130 @argument(
146 @argument(
131 '-h', '--height', type=int,
147 '-h', '--height', type=int,
132 help='Height of png plotting device sent as an argument to *png* in R.'
148 help='Height of png plotting device sent as an argument to *png* in R.'
133 )
149 )
134
150
135 @argument(
151 @argument(
136 '-u', '--units', type=int,
152 '-u', '--units', type=int,
137 help='Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"].'
153 help='Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"].'
138 )
154 )
139 @argument(
155 @argument(
140 '-p', '--pointsize', type=int,
156 '-p', '--pointsize', type=int,
141 help='Pointsize of png plotting device sent as an argument to *png* in R.'
157 help='Pointsize of png plotting device sent as an argument to *png* in R.'
142 )
158 )
143 @argument(
159 @argument(
144 '-b', '--bg',
160 '-b', '--bg',
145 help='Background of png plotting device sent as an argument to *png* in R.'
161 help='Background of png plotting device sent as an argument to *png* in R.'
146 )
162 )
147 @argument(
163 @argument(
148 'code',
164 'code',
149 nargs='*',
165 nargs='*',
150 )
166 )
151 @line_cell_magic
167 @line_cell_magic
152 def R(self, line, cell=None):
168 def R(self, line, cell=None):
153 '''
169 '''
154 Execute code in R, and pull some of the results back into the Python namespace.
170 Execute code in R, and pull some of the results back into the Python namespace.
155
171
156 In line mode, this will evaluate an expression and convert the returned value to a Python object.
172 In line mode, this will evaluate an expression and convert the returned value to a Python object.
157 The return value is determined by rpy2's behaviour of returning the result of evaluating the
173 The return value is determined by rpy2's behaviour of returning the result of evaluating the
158 final line. Multiple R lines can be executed by joining them with semicolons.
174 final line. Multiple R lines can be executed by joining them with semicolons::
159
175
160 In [9]: %R X=c(1,4,5,7); sd(X); mean(X)
176 In [9]: %R X=c(1,4,5,7); sd(X); mean(X)
161 Out[9]: array([ 4.25])
177 Out[9]: array([ 4.25])
162
178
163 As a cell, this will run a block of R code, without bringing anything back by default::
179 As a cell, this will run a block of R code, without bringing anything back by default::
164
180
165 In [10]: %%R
181 In [10]: %%R
166 ....: Y = c(2,4,3,9)
182 ....: Y = c(2,4,3,9)
167 ....: print(summary(lm(Y~X)))
183 ....: print(summary(lm(Y~X)))
168 ....:
184 ....:
169
185
170 Call:
186 Call:
171 lm(formula = Y ~ X)
187 lm(formula = Y ~ X)
172
188
173 Residuals:
189 Residuals:
174 1 2 3 4
190 1 2 3 4
175 0.88 -0.24 -2.28 1.64
191 0.88 -0.24 -2.28 1.64
176
192
177 Coefficients:
193 Coefficients:
178 Estimate Std. Error t value Pr(>|t|)
194 Estimate Std. Error t value Pr(>|t|)
179 (Intercept) 0.0800 2.3000 0.035 0.975
195 (Intercept) 0.0800 2.3000 0.035 0.975
180 X 1.0400 0.4822 2.157 0.164
196 X 1.0400 0.4822 2.157 0.164
181
197
182 Residual standard error: 2.088 on 2 degrees of freedom
198 Residual standard error: 2.088 on 2 degrees of freedom
183 Multiple R-squared: 0.6993,Adjusted R-squared: 0.549
199 Multiple R-squared: 0.6993,Adjusted R-squared: 0.549
184 F-statistic: 4.651 on 1 and 2 DF, p-value: 0.1638
200 F-statistic: 4.651 on 1 and 2 DF, p-value: 0.1638
185
201
186 In the notebook, plots are published as the output of the cell.
202 In the notebook, plots are published as the output of the cell.
187
203
188 %R plot(X, Y)
204 %R plot(X, Y)
189
205
190 will create a scatter plot of X bs Y.
206 will create a scatter plot of X bs Y.
191
207
192 If cell is not None and line has some R code, it is prepended to
208 If cell is not None and line has some R code, it is prepended to
193 the R code in cell.
209 the R code in cell.
194
210
195 Objects can be passed back and forth between rpy2 and python via the -i -o flags in line
211 Objects can be passed back and forth between rpy2 and python via the -i -o flags in line::
196
212
213 In [14]: Z = np.array([1,4,5,10])
197
214
198 In [14]: Z = np.array([1,4,5,10])
215 In [15]: %R -i Z mean(Z)
216 Out[15]: array([ 5.])
199
217
200 In [15]: %R -i Z mean(Z)
201 Out[15]: array([ 5.])
202
218
219 In [16]: %R -o W W=Z*mean(Z)
220 Out[16]: array([ 5., 20., 25., 50.])
203
221
204 In [16]: %R -o W W=Z*mean(Z)
222 In [17]: W
205 Out[16]: array([ 5., 20., 25., 50.])
223 Out[17]: array([ 5., 20., 25., 50.])
206
207 In [17]: W
208 Out[17]: array([ 5., 20., 25., 50.])
209
224
210 If the cell is None, the resulting value is returned,
225 If the cell is None, the resulting value is returned,
211 after conversion with self.Rconverter
226 after conversion with self.Rconverter
212 unless the line has contents that are published to the ipython
227 unless the line has contents that are published to the ipython
213 notebook (i.e. plots are create or something is printed to
228 notebook (i.e. plots are create or something is printed to
214 R's stdout() connection).
229 R's stdout() connection).
215
230
216 If the cell is not None, the magic returns None.
231 If the cell is not None, the magic returns None.
217
232
218 '''
233 '''
219
234
220 args = parse_argstring(self.R, line)
235 args = parse_argstring(self.R, line)
221
236
222 # arguments 'code' in line are prepended to
237 # arguments 'code' in line are prepended to
223 # the cell lines
238 # the cell lines
224 if not cell:
239 if not cell:
225 code = ''
240 code = ''
226 return_output = True
241 return_output = True
227 else:
242 else:
228 code = cell
243 code = cell
229 return_output = False
244 return_output = False
230
245
231 code = ' '.join(args.code) + code
246 code = ' '.join(args.code) + code
232
247
233 if args.input:
248 if args.input:
234 for input in ','.join(args.input).split(','):
249 for input in ','.join(args.input).split(','):
235 self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
250 self.r.assign(input, self.pyconverter(self.shell.user_ns[input]))
236
251
237 png_argdict = dict([(n, getattr(args, n)) for n in ['units', 'height', 'width', 'bg', 'pointsize']])
252 png_argdict = dict([(n, getattr(args, n)) for n in ['units', 'height', 'width', 'bg', 'pointsize']])
238 png_args = ','.join(['%s=%s' % (o,v) for o, v in png_argdict.items() if v is not None])
253 png_args = ','.join(['%s=%s' % (o,v) for o, v in png_argdict.items() if v is not None])
239 # execute the R code in a temporary directory
254 # execute the R code in a temporary directory
240
255
241 tmpd = tempfile.mkdtemp()
256 tmpd = tempfile.mkdtemp()
242 self.r('png("%s/Rplots%%03d.png",%s)' % (tmpd, png_args))
257 self.r('png("%s/Rplots%%03d.png",%s)' % (tmpd, png_args))
243 result = self.eval(code)
258 result = self.eval(code)
244 self.r('dev.off()')
259 self.r('dev.off()')
245
260
246 # read out all the saved .png files
261 # read out all the saved .png files
247
262
248 images = [open(imgfile, 'rb').read() for imgfile in glob("%s/Rplots*png" % tmpd)]
263 images = [open(imgfile, 'rb').read() for imgfile in glob("%s/Rplots*png" % tmpd)]
249
264
250 # now publish the images
265 # now publish the images
251 # mimicking IPython/zmq/pylab/backend_inline.py
266 # mimicking IPython/zmq/pylab/backend_inline.py
252 fmt = 'png'
267 fmt = 'png'
253 mimetypes = { 'png' : 'image/png', 'svg' : 'image/svg+xml' }
268 mimetypes = { 'png' : 'image/png', 'svg' : 'image/svg+xml' }
254 mime = mimetypes[fmt]
269 mime = mimetypes[fmt]
255
270
256 published = False
271 published = False
257 # publish the printed R objects, if any
272 # publish the printed R objects, if any
258 flush = self.flush()
273 flush = self.flush()
259 if flush:
274 if flush:
260 published = True
275 published = True
261 publish_display_data('RMagic.R', {'text/plain':flush})
276 publish_display_data('RMagic.R', {'text/plain':flush})
262
277
263 # flush text streams before sending figures, helps a little with output
278 # flush text streams before sending figures, helps a little with output
264 for image in images:
279 for image in images:
265 published = True
280 published = True
266 # synchronization in the console (though it's a bandaid, not a real sln)
281 # synchronization in the console (though it's a bandaid, not a real sln)
267 sys.stdout.flush(); sys.stderr.flush()
282 sys.stdout.flush(); sys.stderr.flush()
268 publish_display_data(
283 publish_display_data(
269 'RMagic.R',
284 'RMagic.R',
270 {mime : image}
285 {mime : image}
271 )
286 )
272 value = {}
287 value = {}
273
288
289 # kill the temporary directory
290 rmtree(tmpd)
291
274 # try to turn every output into a numpy array
292 # try to turn every output into a numpy array
275 # this means that output are assumed to be castable
293 # this means that output are assumed to be castable
276 # as numpy arrays
294 # as numpy arrays
277
295
278 if args.output:
296 if args.output:
279 for output in ','.join(args.output).split(','):
297 for output in ','.join(args.output).split(','):
280 # with self.shell, we assign the values to variables in the shell
298 # with self.shell, we assign the values to variables in the shell
281 self.shell.push({output:self.Rconverter(self.r(output))})
299 self.shell.push({output:self.Rconverter(self.r(output))})
282
300
283 # kill the temporary directory
284 rmtree(tmpd)
285
301
286 # if there was a single line, return its value
302 # if there was a single line, return its value
287 # converted to a python object
303 # converted to a python object
288
304
289 if return_output and not published:
305 if return_output and not published:
290 return self.Rconverter(result)
306 return self.Rconverter(result)
291
307
292 _loaded = False
308 _loaded = False
293 def load_ipython_extension(ip):
309 def load_ipython_extension(ip):
294 """Load the extension in IPython."""
310 """Load the extension in IPython."""
295 global _loaded
311 global _loaded
296 if not _loaded:
312 if not _loaded:
297 ip.register_magics(RMagics)
313 ip.register_magics(RMagics)
298 _loaded = True
314 _loaded = True
General Comments 0
You need to be logged in to leave comments. Login now