2014/08/23

R OOP - a little privacy please?

As of late, I’ve been making heavy use of Reference Classes in R. They are easier for me to wrap my mind around since they adopt a usage style more like “traditional” OOP languages like Java. Primarily, object methods are part of the class definition and accessed via the instantiated object.

For instance:
With S3/S4 classes, you define an object. Then you define separate generic functions that operate on the object:
# class and object method definition
myClass = setClass('myClass', ...)
print.myClass = function(x){...}

# so then ...
obj = myClass(...)
print(obj)
With Reference classes, you define an object and therein the methods the object employs:
# class and object method definition
myClass = setRefclass('myClass', 
    fields  = list(), 
    methods = list(print=function(){...}))

# so then ...
obj = myClass(...)
obj$print()

In the grand scheme of things, both ways of defining objects and their methods are pretty much equivalent. From a coding perspective, the S3/S4 style allows for object methods to be defined separately of the object class (e.g. in separate files if one prefers). The appeal of Reference Classes is that the objects they define know what methods they have.

Privacy issues

The one aspect of OOP in R that I’ve been trying to work out is how to implement private methods and fields - i.e. only visible/usable from within the scope of the object, thus not user callable/mutable. There is (currently) no official way to specify these in base R.

A little research reveals that the best one can do is obfuscate.

Roxygen2 will not specify the existence of a RefClass method if it lacks a docstring, but it will still be available to the user if they introspect the object interactively (ala the tab key if using RStudio).
The best suggestions I’ve come across are:
  1. build a package around your RefClass definition and use non-exported package functions for private methods
  2. define private methods as functions within the public methods they are used in
Option 1 is probably the better of the two, as it lets R’s namespace rules do the dirty work. However, it does require writing functions of the form:

privateFun = function(obj, ...) {
    # do stuff
    obj$field <<- newValue
}

Option 2 would likely require much code replication or, at the very least source()-ing the requisite code where ever a private function is required. A very far from ideal development/debugging situation.

From a high level view, Reference Classes are environments with added bells and whistles. What’s interesting is say I defined a class like so:

myClass = setRefClass('myClass',
  fields = list(
     pubField = 'character',
    .prvField = 'character'
  ),
  methods = list(
     pubMethod = function(){print('public')},
    .prvMethod = function(){print('private')}
  )
)

Notice, that the “prvField” field and “prvMethod” use a . to prefix the name. In R this is a way of creating a “hidden” variable – akin to hidden files on a *nix OS.

When I try to ls() the resultant object, I get:

> obj = myClass()
> ls(env = obj)
[1] "getClass" "pubField"

So, it is within the realm of possibilities!

Another alternative that I thought of was to make a field of the object an environment and then place private elements there. Again, making things private via obfuscation (and more typing). However, users could still access said elements by:

obj$private$field

R6 - a new hope

As I was putting the finishing touches on this post I read a great post by Romain Francois entitled “Pro Grammar and Devel Hoper” (kudos on the pun). Towards the end he links to an Rpub posted by Winston Chang entitled “Introduction to R6” which piqued my interest.

R6 is a new OOP system provided by the R6 package - posted to CRAN just 4 days ago about a month ago. While similar to the existing Reference Class system (objects are specially wrapped environments), it also provides separation of public and private elements - exactly what I was looking for! Performance tests also show that R6 is faster and more memory efficient than Reference Classes.

Suffice it to say, I’ll be checking R6 out.

Written with StackEdit.

2014/08/20

Optimizing with R expressions

I recently discovered a powerful use for R expression()’s

Say you are trying to fit some experimental data to the following nonlinear equation:

Ky0eu(xtl)K+y0(eu(xtl)1)+b1+(b0b1)ekx+b2x

with the independent variable x using nlminb() as the minimization optimizer.

This sort of work is significantly improved (i.e. faster with better convergence) if an analytical gradient vector and a Hessian matrix for the objective function are provided. This means a lot of partial differentiation of the model equation with respect to each parameter.
To get these partial derivatives one could:
  1. Review some calculus and derive them by hand
  2. Feed the equation into an online engine like Wolfram Alpha and copy/paste the results
The discovery I made is that there is a purely R solution via the combination of expression()’s and the functions D() and all.vars().

The all.vars() function extracts all variable and parameter names from an expression as a character vector. For example:

> all.vars(expression(b1 + (b0 - b1)*exp(-k*x) + b2*x))
[1] "b1" "b0" "k"  "x"  "b2"

The D() function takes two arguments: an expression to differentiate and a character specifying the variable term to differentiate by:

> D(expression(b1 + (b0 - b1)*exp(-k*x) + b2*x), 'x')
b2 - (b0 - b1) * (exp(-k * x) * k)

The following code produces a list of the partial derivatives of the above equation with respect to each variable/parameter.

# the model equation
expr = expression(
    (K*y0*exp(u*(x-tl)))/(K + y0*(exp(u*(x-tl))-1)) + 
        b1 + (b0 - b1)*exp(-k*x) + b2*x
)

sapply(all.vars(expr), function(v){
  D(expr, v)
})

# returns:
# $K
# y0 * exp(u * (x - tl))/(K + y0 * (exp(u * (x - tl)) - 1)) - (K * 
#     y0 * exp(u * (x - tl)))/(K + y0 * (exp(u * (x - tl)) - 1))^2
# 
# $y0
# K * exp(u * (x - tl))/(K + y0 * (exp(u * (x - tl)) - 1)) - (K * 
#     y0 * exp(u * (x - tl))) * (exp(u * (x - tl)) - 1)/(K + y0 * 
#     (exp(u * (x - tl)) - 1))^2
# 
# $u
# K * y0 * (exp(u * (x - tl)) * (x - tl))/(K + y0 * (exp(u * (x - 
#     tl)) - 1)) - (K * y0 * exp(u * (x - tl))) * (y0 * (exp(u * 
#     (x - tl)) * (x - tl)))/(K + y0 * (exp(u * (x - tl)) - 1))^2
# ...

Each element of the list returned by the sapply() statement above is itself an expression. Evaluation of each will give rows of the Jacobian matrix, which we’ll subsequently need to compute the gradient:

p0 = c(y0=0.01, u=0.3, tl=5, K=2, b0=0.01, b1=1, b2=0.001, k=0.1)
x = seq(0,10)

# notice that t() is applied to put parameters on rows
J = t(sapply(all.vars(expr), function(v, env){
  eval(D(expr, v), env)
}, env=c(as.list(p0), list(x=x))))

J
# returns:
#             [,1]          [,2]          [,3]          [,4] ...
# K  -4.367441e-06 -5.298871e-06 -6.067724e-06 -6.218461e-06 ...
# y0  2.248737e-01  3.033101e-01  4.089931e-01  5.512962e-01 ...
# u  -1.118747e-02 -1.207174e-02 -1.220845e-02 -1.097079e-02 ...
# x   1.006712e-01  9.148428e-02  8.327519e-02  7.598662e-02 ...
# tl -6.712481e-04 -9.053805e-04 -1.220845e-03 -1.645619e-03 ...
# b1  0.000000e+00  9.516258e-02  1.812692e-01  2.591818e-01 ...
# b0  1.000000e+00  9.048374e-01  8.187308e-01  7.408182e-01 ...
# k   0.000000e+00  8.957890e-01  1.621087e+00  2.200230e+00 ...
# b2  0.000000e+00  1.000000e+00  2.000000e+00  3.000000e+00 ...

Since x is the independent variable, the row corresponding to it can be safely removed from the Jacobian:

J = J[names(p0),,drop=F]
J
# returns:
#             [,1]          [,2]          [,3]          [,4] ...
# y0  2.248737e-01  3.033101e-01  4.089931e-01  5.512962e-01 ...
# u  -1.118747e-02 -1.207174e-02 -1.220845e-02 -1.097079e-02 ...
# tl -6.712481e-04 -9.053805e-04 -1.220845e-03 -1.645619e-03 ...
# K  -4.367441e-06 -5.298871e-06 -6.067724e-06 -6.218461e-06 ...
# b0  1.000000e+00  9.048374e-01  8.187308e-01  7.408182e-01 ...
# b1  0.000000e+00  9.516258e-02  1.812692e-01  2.591818e-01 ...
# b2  0.000000e+00  1.000000e+00  2.000000e+00  3.000000e+00 ...
# k   0.000000e+00  8.957890e-01  1.621087e+00  2.200230e+00 ...

The gradient vector is simply the inner product of the Jacobian and a vector of residuals:

gr = -J %*% r

For the Hessian, the full form in Gibbs-Einstein notation is:

Hjk=fipjfipk+ri2ripjpk


The first term is simply JTJ. The second term is typically called the “Hessian of the residuals” and is referred to as a matrix B. I’m still trying to wrap my head around what it actually is in vector notation.

Thankfully, in optimization cases where the initial guess is near the optimum, the behavior of the system should be “linear enough” that one can ignore the second term:

HJTJ


The equivalent R code would be:

H = J %*% t(J)

(because linear algebra in R is a little strange, the transpose is applied to the second Jacobian)

Putting it all together:

# the model equation
expr = expression(
    (K*y0*exp(u*(x-tl)))/(K + y0*(exp(u*(x-tl))-1)) + 
        b1 + (b0 - b1)*exp(-k*x) + b2*x
)

p0 = c(y0=0.01, u=0.3, tl=5, K=2, b0=0.01, b1=1, b2=0.001, k=0.1)
x = seq(0,48,by=0.25)

# let's say these are the residuals
r = runif(length(x))

# magic syntax that converts an equation expression into a jacobian matrix
J = t(sapply(all.vars(expr), function(v, env){
  eval(D(expr, v), env)
}, env = c(as.list(p0), list(x=x))))

# and then a gradient vector
gr = -J %*% r

# and then an approximate Hessian matrix
H = J %*% t(J)

Extending this further, one can now write a generic model object like so:

ModelObject = setRefClass('ModelObject', 
  fields = list(
    name = 'character',
    expr = 'expression'
  ),
  methods = list(
    value = function(p, data){
      eval(.self$expr, c(as.list(p), as.list(data)))
    },
    jacobian = function(p, data){
      J = t(sapply(all.vars(.self$expr), function(v, p, data){
              eval(D(.self$expr, v), c(as.list(p), as.list(data)))
            }, p=p, data=data))

      return(J[names(p),,drop=F])
    },
    gradient = function(p, data){
        r = data$y - value(p, data)
        return(-jacobian(p, data) %*% r)
    },
    hessian = function(p, data){
      J = jacobian(p, data)
      return(J %*% t(J))
    }
  )
)

which is instantiated with simply an expression and can be used to provide gradient and Hessian functions to nlminb():

> xy = list(x=seq(0,10,by=0.25), y=dnorm(seq(0,10,by=0.25),10,2)) # test data
> p0 = c(y0=0.01, u =0.2, l=5, A=log(1.5/0.01))
> mo = ModelObject(
         name = 'gompertz', 
         expr = expression( y0*exp(A*exp(-exp((u*exp(1)/A)*(l-x)+1))) )
       )

> fit = nlminb(p0, function(p, data){
        r = data$y - mo$value(p,data)
        return(r %*% r)
    }, gradient = mo$gradient, hessian = mo$hessian, data=xy)

> fit$par
         y0           u           l           A 
0.001125766 1.345796890 3.646340494 5.408138500 

> fit$message
[1] "relative convergence (4)"

> plot(xy, main='Fit Results'); lines(xy$x, mo$value(fit$par, xy))

Painless!

Written with StackEdit.

2014/04/01

Deploying Desktop Apps with R

(Update 4: 2016-08-19) I made many significant updates / improvements to this deployment method which are documented in a more recent blog post. TL;DR -- The deployment framework is separated (as much as possible) from the application that uses it, making it easier to configure, update, and deploy. The framework - called DesktopDeployR - is available on GitHub.


(Update 3: 2015-03-24) In response to a comment by Edward Kwartler, I’ve also added extra details for how to construct an InnoSetup script.


(Update 2: 2014-04-07) Today I encounted a few issues during a deployment that warranted a few extra notes during deployment setup and changing some of the code in runShinyApp.R. These are noted below.


(Update) Despite the original publish date (Apr 1), this post was not and April Fools joke. I’ve also shortened the title a bit.


As part of my job, I develop utility applications that automate workflows that apply more involved analysis algorithms. When feasible, I deploy web applications as it lowers installation requirements to simply a modern (standards compliant) web-browser, and makes pushing updates relatively transparent. Occasionally though, I need to deploy a desktop application, and when I do I rely on either Python or Matlab since both offer easy ways to make executables via freezing or compiling code.

I’ve long searched for a similarly easy way to make self-contained desktop applications using R, where “self-contained” means users do not have to navigate installing R and package dependencies on their system (since most live under IT lockdown).

Since the start of this year (2014), I’ve been jotting notes on how to do this:

  1. Use R-Portable as a self-contained R engine
  2. Use the shiny R package as the UI building framework
  3. Use GoogleChromePortable in “app” mode as the UI display engine

Apparently, I wasn’t the first person to have this idea. Just last week I found a blog post by ZJ Dia at Analytixware describing this very strategy. Kudos to ZJ for providing a great starting point! I tested this out and documented my additions, challenges, solutions, and thoughts.

Before going on, I first should mention that the process is Windows specific. I don’t doubt that something similar could be achieved under OS X, but I lack a Mac to test on.

Step 1: Create a deployment skeleton

Because this technique uses portable apps, you can save yourself some time by creating a skeleton desktop deployment. This skeleton can then be copied and customized for every new desktop app you create (Step 2). This is very much like using virtualenv with Python, in that each deployment is its own stand alone R environment and packages.

Step 1.1: Create the skeleton folder and engine framework

Create a folder called dist/.

Download:

and install both into dist/.

Create a folder called dist/shiny/. This is where the files for your Shiny app (e.g. ui.R and server.R) will reside.

The heirarchy of dist/ should now be:

dist/
+- GoogleChromePortable/
+- R-Portable/
+- shiny/

Step 1.2: Install Core R Packages

Before doing anything, add to the bottom of R-Portable/App/R-Portable/etc/Rprofile.site:

.First = function(){
    .libPaths(.Library)
}

This will force R-Portable to only use its local library (specified in the hidden global variable .Library) for installing/loading packages. Without this, if you have the non-portable version of R installed on your system, R-portable will detect your user library and include it in its .libPaths(). Then, any packages you install will be installed in your system or user R library, and not in R-Portable!

(In fact R-Portable detects just about all user settings - e.g. will read .RProfile in your user profile if you have one, so be sure there are no potential conflicts there)

Start R-Portable and install core package dependencies. At minimum this should be the package shiny and all its dependencies.

.libPaths() # verify that only the local R-Portable library path is available
install.packages('shiny')

UPDATE: I had a couple of instances where a package would appear to install but would end with a warning such as:

Warning: unable to move temporary installation 'XYZ' to 'ZYX'

From what I could find, this is likely due to the Windows Search indexing service or an antivirus program. Attempting the install a second (or third) time would sometimes work. Often, I would simply resort to copying the package from the temporarily stored .zip file, the path to which is also printed at the end of an attempted package install.

Step 1.3: Create Application Launch Scripts

To launch your application you will need two scripts:

  • runShinyApp.R : an R-script that loads the shiny package and launches your app via runApp()
  • A shell script (either a *.bat or *.vbs file) that invokes R-portable

Step 1.3.1: Create a Shiny app R launch script (runShinyApp.R)

In this script you need to do the following:

  • Set .libPaths() to only point to the local R-Portable library
  • Set the default web browser to the local GoogleChromePortable in “app” mode
  • Launch your Shiny app

Setting .libPaths() to point to the local R-Portable library should be handled by the modification to Rprofile.site made above. It’s a good idea to at least print a verification that the correct library location is being used:

# this message is printed on several lines (one per path) to make multiple paths
# easier to spot
message('library paths:\n', paste('... ', .libPaths(), sep='', collapse='\n'))

Setting the default web browser is not so straight forward. I could not get GoogleChromePortable to work with a GoogleChromePortable.ini file defining AdditionalParameters as previously described. The browser would hang with a blank page and eventually crash. So I needed another way to launch GoogleChromePortable with the --app commandline option to enable “app” mode.

Inspecting the code and docs for shiny::runApp() I found that the launch.browser= argument can be a function that performs special browser launching operations, whose first argument is the application’s URL (path and port).

In my case, I needed to use shell() to get Chrome to run with the --app option without error:

UPDATE: While shell() appeared to work on my main development workstation, it didn’t on my laptop where I performed a test deployment. The app would again hang at a blank but loading Chrome window. The issue was resolved when I switched to using system2(), which is specific to making command line calls in Windows. In addition, I used extra caution by first translating any forward slashes (/) to backslashes (\) in the command call using chartr().

One could further parameterize the function to allow for either the system or portable version of Chrome to be used:

# both chromes work!
chrome.sys = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe'
chrome.portable = file.path(getwd(),
                            'GoogleChromePortable/App/Chrome-bin/chrome.exe')

launch.browser = function(appUrl, browser.path=chrome.portable) {
    browser.path = chartr('/', '\\', browser.path)
    message('Browser path: ', browser.path)

    CMD = browser.path
    ARGS = sprintf('--app="%s"', appUrl)

    system2(CMD, args=ARGS, wait=FALSE)
    NULL
}

So if your users already have Google Chrome installed, you can potentially make the deployed installation smaller.

Finally, launching the app is straightforward:

shiny::runApp('./shiny/', launch.browser=launch.browser)

Notice that no port is specified in the call to runApp(), taking advantage of Shiny’s default behavior of finding a random unoccupied port to serve on. (I figured this out the hard way by originally specifying port=8888, on which I already had an IPython notebook running).

So in sum, your runShinyApp.R should have the following contents:

message('library paths:\n', paste('... ', .libPaths(), sep='', collapse='\n'))

chrome.portable = file.path(getwd(),
                            'GoogleChromePortable/App/Chrome-bin/chrome.exe')

launch.browser = function(appUrl, browser.path=chrome.portable) {
    browser.path = chartr('/', '\\', browser.path)
    message('Browser path: ', browser.path)

    CMD = browser.path
    ARGS = sprintf('--app="%s"', appUrl)

    system2(CMD, args=ARGS, wait=FALSE)
    NULL
}

shiny::runApp('./shiny/', launch.browser=launch.browser)

Step 1.3.2: Create a shell launch script

This is the script that your users will double-click on to launch the application. Herein, you need to call R-portable non-interactively (aka in BATCH mode) and have it run your runShinyApp.R script.

The simplest way to do this is to create a Windows Batch file called run.bat with the following contents:

@echo off
SET ROPTS=--no-save --no-environ --no-init-file --no-restore --no-Rconsole
R-Portable\App\R-Portable\bin\Rscript.exe %ROPTS% runShinyApp.R 1> ShinyApp.log 2>&1

Notice that Rscript.exe is called instead of R.exe CMD BATCH. In my testing, I found Rscript to load about an order of magnitude faster (i.e. 3s instead of 40s). Indeed, it seems that R CMD BATCH is somewhat of a legacy carryover and that Rscript should be used for commandline based scripting.

Here, I’ve set the following options:

--no-save --no-environ --no-init-file --no-restore --no-Rconsole

which is one flag short of --vanilla, to allow for Rprofile.site settings to be loaded. Otherwise the effect is the same; R loads without reading a user profile, or attempting to restore an existing workspace, and when the script is complete, the workspace is not saved. The last part of the command — 1> ShinyApp.log 2>&1 — specifies that all output from stdout (1>) and stderr (2>) be captured by the file ShinyApp.log.

It should be noted that all output generated by print() is sent to stdout. Everything else — message(), warning(), error() — is sent to stderr.

If your prefer to separate “results” output (i.e. print() statements) from “status” output (i.e. message() statements and the like), you can do so by specifying different files:

Rscript.exe %ROPTS% runShinyApp.R 1> ShinyAppOut.log 2> ShinyAppMsg.log

or leave the “results” output in the console and only capture messages/errors:

Rscript.exe %ROPTS% runShinyApp.R 2> ShinyAppMsg.log

Using a *.bat file will leave a system console window for users to look at. They can minimize it (or you can setup a shortcut *.lnk to start it minimized) to get it out of the way, but it will still be in their taskbar. If a user closes it by accident, the app will terminate in a possibly ungraceful manner.

If you don’t want users to see this, you can use a *.vbs script which provides more options on how to display the console window. Such a script would have the following contents:

Rexe           = "R-Portable\App\R-Portable\bin\Rscript.exe"
Ropts          = "--no-save --no-environ --no-init-file --no-restore --no-Rconsole"
RScriptFile    = "runShinyApp.R"
Outfile        = "ShinyApp.log" 
strCommand     = Rexe & " " & Ropts & " " & RScriptFile & " 1> " & Outfile & " 2>&1"

intWindowStyle = 0     ' Hide the window and activate another window.'
bWaitOnReturn  = False ' continue running script after launching R   '

' the following is a Sub call, so no parentheses around arguments'
CreateObject("Wscript.Shell").Run strCommand, intWindowStyle, bWaitOnReturn

So now the hierarchy in dist/ should be:

dist/
+- GoogleChromePortable/
+- R-Portable/
+- shiny/
+- run.(vbs|bat)
+- runShinyApp.R

and completes building the deployment skeleton.

Step 2: Clone and customize

To prepare a new app for deployment, copy and rename the dist/ folder — e.g. copy ./dist ./TestApp. There are a lot of files in the underlying R-portable (a few thousand in fact), so this will take a minute or so.

Step 2.1: Load required dependencies

If your app requires packages other than shiny and what comes with base R, this is the time to install them. As before, when building the skeleton, launch the new copy of R-portable, and install any packages that your app requires.

Copy your Shiny app files (e.g. ui.R, server.R) into TestApp/shiny/.

Step 2.2: Prepare for termination

Before testing your application, you need to ensure that it will stop the websocket server started by shiny::runApp() and the underlying R process when the browser window is closed. To do this, you need to add the following to server.R:

shinyServer(function(input, output, session){
    session$onSessionEnded(function() {
        stopApp()
    })
})

Here, session is a Reference Class object that handles data and methods to manipulate and communicate with the browser websocket client. Unfortunately, documentation for this object is not provided via the typical R help system (likely related to the shifting landscape of how best to document Reference Classes). Thankfully, the source code has a well written docstring:

onSessionEnded = function(callback) {
      "Registers the given callback to be invoked when the session is closed
      (i.e. the connection to the client has been severed). The return value
      is a function which unregisters the callback. If multiple callbacks are
      registered, the order in which they are invoked is not guaranteed."
      return(.closedCallbacks$register(callback))
    }

Thus, onSessionEnded is a method that stores functions to be executed when the connection between the websocket server and client is severed — i.e. when the browser is closed.

Notice that explicitly stopping the R process with q() is unnecessary. The only thing keeping it alive is the blocking nature of shiny::runApp(). Once this completes via stopApp() the runShinyApp.R script will be complete and the R process spawned by Rscript will naturally terminate.

Step 2.3: Test!

Your application folder should have the following hierarchy:

TestApp/
+- GoogleChromePortable/
+- R-Portable/
+- shiny/
   +- ui.R
   +- server.R
+- run.(vbs|bat)
+- runShinyApp.R

and everything should be in place.

Double-clicking on either run.vbs or run.bat (whichever you created in Step 1) should launch your app. If it doesn’t, check the content in ShinyApp.log for any errors.

Step 3: Distribute

The simplest way to distribute your application is to zip up your application folder and instruct users to unzip and double-click on run.vbs (or run.bat). While easy on you the developer, it adds a bit of complexity to the user as they’ll need to remember where they unzipped your app and what file to double click to get it to run.

To distribute a bit more professionally, I agree with Analytixware’s suggestion to pack your entire application folder into an installation executable using the freely available InnoSetup tool. This allows for control of where the application is installed (e.g. preferably to a user’s LocalAppData folder without the need for admin rights escalation), as well as the generation of familiar Start Menu, Desktop, and QuickLaunch shortcuts.

The compiler script wizard in InnoSetup creates a good starting point. That said there are a couple parameters that need to be tweaked.

First, your “executable” will be either the *.bat or *.vbs script your created to launch your application such that you should have the following directive in your *.iss script:

#define MyAppExeName "run.vbs"

Next, you should limit what is deployed to just the necessary application files. This should recursively include everything in your R-portable directory, but exclude “development cruft” files - i.e. the local git repository, unit test directories, *.Rproj* files, etc. You can achieve this via a combination of an Excludes directive and a recursesubdirs flag in the [Files] section. Hence, your [Files section should look something like:

Source: "C:\path\to\app\*"; DestDir: "{app}"; Excludes: ".git*,.Rproj*,*.Rhistory,tests\*"; Flags: ignoreversion recursesubdirs

Finally, to allow users to install your application themselves, sans admin privileges, you need to specify “user” locations as opposed to “common” locations like C:\Program Files\ which tends to be read-only for non-admins:

[Setup]
... other setup directives
DefaultDirName={userpf}\{#MyAppName}

...

[Icons]
... other icon directives
Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

The above directives in the [Setup] and [Icons] sections of the compile script specify that the application will be installed in the current user’s Program Files directory ({localappdata}\Programs). That said, installing to user locations only works for Windows 7 and higher.

Final thoughts

This is still an application based on modern web standards. Therefore, how interactive/responsive your app can be, and how it looks, will be limited by what can be done with HTML5, CSS, and JavaScript. That said, with the majority of apps, I think a modern web browser should have enough power to provide a good user experience.

What about heavier deployments, like apps that use GTK based UIs via the RGtk2 package? There are many portable apps that use GTK libraries, so it stands to reason that the toolkit libraries can be packed and deployed with your R application.

Lastly, keep in mind that your source code is easily accessible. If this is a concern for you (e.g. if you are distributing to a client that should not have access to the code) the best you can do is impede access by first compiling the sensitive source code into a binary package. That said, any user who knows R (and has sufficient intent) can simply dump the code to the console.

Overall, I’m very satisfied with this deployment method for R based applications.

Written with StackEdit.