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 |
|
|
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