Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Here’s the situation. Web applications, built using a framework (e.g. Rails, Django) are great for fetching data from a database and rendering it. They’re not so great for crunching and charting the data. Conversely, R is great for crunching and charting, but doesn’t make for a great web application.
The idea then, is to let each do what it does best and enable the passing of data between them. There isn’t a whole lot of literature on this topic, but there are a couple of guides:
- In these seminar slides (PDF), Jeroen Ooms describes how data can be passed between a web browser and R. Briefly, he uses client-side javascript to format the data as a JSON string. Server-side R (RApache) then parses the POST variable using fromJSON() (in the rjson package), formats the results of an R function as JSON using toJSON() and sends them back to the browser.
- Slide 46 of this presentation by Mike Driscoll of Dataspora illustrates a different approach, where a Django-based web application sends data to RApache in CSV format.
As a first step in understanding all of this, we can build a small demo application using Rails (version 2.3.5), which serves both JSON and CSV. We’ll see if we can get that into R, then see if R can return results back to Rails, via RApache. Baby steps, so we’ll avoid the AJAX stuff for now and just use Rails rendering methods to serve JSON from a controller.
1. Generate the Rails application
First, generate the skeleton. I’m calling the application “rapache” and scaffolding for a simple model, named Value, containing 2 integer fields named “x” and “y”:
rails rapache cd rapache rake db:create ./script/generate scaffold Value x:integer y:integer rake db:migrate
Next, edit config/environment.rb to contain these lines, required for Rails to render CSV:
config.gem "fastercsv" config.gem "comma"
Install those gems if you don’t have them and optionally, unpack them into your application tree. I like to freeze the Rails gems too:
rake gems:install rake gems:unpack:dependencies rake rails:freeze:gems
We also need to edit models/values.rb to provide the comma method:
class Value < ActiveRecord::Base comma do |f| f.x f.y end end
So far, so good. Now, we could seed the database with some test data but for just a few values, it’s as easy to use the forms generated by the scaffolding and enter some X and Y values. I made my Y = X, fired up ./script/server and then opened up “http://localhost:3000/values” in the browser, where I saw a view like that in the image, above-right in this blog post.
Generating the JSON and CSV end-points couldn’t be easier. Just edit the index method in controllers/values_controller.rb to look like this:
def index @values = Value.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @values } format.json { render :json => @values } format.csv { render :csv => @values } end end
That gives us JSON in the browser when we go to “http://localhost:3000/values.json” and a CSV file when we go to “http://localhost:3000/values.csv”. Depending on your browser setup, the browser may download the CSV, offer to download it or offer to open it in a spreadsheet application.
2. Experiments in R
With the Rails development environment still running, I opened an R console. First, fetching the data from the Rails application via JSON:
library(rjson) data.json <- fromJSON(readLines(url("http://localhost:3000/values.json"))) # Warning message: # In readLines(url("http://localhost:3000/values.json")) : # incomplete final line found on 'http://localhost:3000/values.json' length(data.json) [1] 6 data.json[[1]]$value$x [1] 0 data.json[[1]]$value$y [1] 0
Not too bad. The warning message arises because readLines() can’t detect an end of line character; the message could be hidden using suppressWarnings(). We could use either rjson or RJSONIO – both of them have the fromJSON() method, but neither can read directly from a URL connection, hence the requirement for readLines(url(…)).
R read the JSON into a list of lists. That could be transformed into a dataframe, but it’s a little unwieldy. Let’s have a look at the CSV approach:
data.csv <- read.csv("http://localhost:3000/values.csv", header = T) dim(data.csv) # [1] 6 2 data.csv # X Y # 1 0 0 # 2 1 1 # 3 2 2 # 4 3 3 # 5 4 4 # 6 5 5
Perfect – read.csv() can read the Rails-rendered CSV directly into a data frame.
3. Calling R from within Rails and displaying the results
The last step is to call R and retrieve results from within the Rails application. For this, you’ll need to have installed RApache. That’s beyond the scope of this post – it’s not difficult, under Ubuntu at least. I assume that R scripts will be served from /var/www/R, which is configured using the Apache configuration file /etc/apache2/conf.d/rapache.conf:
<Location "/R"> ROutputErrors SetHandler r-script RHandler sys.source REvalOnStartup "library(lattice)" # not used here </Location>
What we should really do is write a Rails controller method to retrieve the appropriate data and then write a generic R function to accept CSV from any URL and return the results. However, for demonstration purposes, I’m just going to write a test R script, /var/www/R/plot.R, to plot a scatterplot of the X versus Y data and then wrap the result inside a Rails image_tag. Here’s the R:
setContentType("image/png") d <- read.csv("http://localhost:3000/values.csv", header = T) t <- tempfile() png(t,type="cairo") plot(d, main = "Y = X!") dev.off() sendBin(readBin(t,'raw',n=file.info(t)$size)) unlink(t) DONE
And here’s the Rails – I just added this to the bottom of views/values/index.html.erb:
<div style="position:absolute; top:50px; left:400px;"> <%= image_tag "http://localhost/R/plot.R" %> </div>
The result – see image, right.
That covers the basics of serving JSON or CSV from Rails to RApache and sending a result back to Rails. Obviously, a real application would require more model or controller methods, views, R functions, error checks and prettier views, perhaps with a dash of AJAX thrown in. You’d probably also store your R scripts in the Rails directory tree and serve them from there. On the whole though, I’d say it’s surprisingly easy to create a RESTful API using Rails and use it to communicate with R.
Filed under: programming, R, rails, research diary, ruby, statistics
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.