Introduction
This article describes some aspects of how R works in Q that are different to how it works in some other programs, such as RStudio.
Method
1. R is stored on our servers
Instead of customers downloading R locally, R is already installed on our servers. When Q performs calculations in R, it does so by sending the data and code to our R servers (see Security and R), and then returns the results to Q.
This means customers don't need to download any R packages, as these are instead loaded on our R servers. Q has many packages pre-loaded:
- You can see which packages have been installed, and their versions, by right clicking on your Report tree, selecting Add R Output, and entering
installed.packages()
. - You can also see if a particular R package is present by entering
library(package_name)
. - If you wish to use a package that is not installed, please contact support@q-researchsoftware.com.
Additionally, as customers will be interacting with Q R servers, reading and writing files is a bit different. Q scans the R CODE before sending to the server and looks for any explicit filepaths in the code and will attempt to send the file along with the R CODE to the R server for processing. This enables you to load a data file from your computer into R via Q. However, Q is not able to save to your computer's local disk (C:) via R as Q displays all things returned from the R CODE in the R output in Q. Thus, you can use functions like read.csv() and read.spss() within R, but not functions like write.csv() and write.spss().
2. Only variable names and Output names that appear in code are accessible within code
When R code is run, both the code and any associated data is sent to the R server, which then returns the result. The associated data is identified by scanning through the code and looking for the names of Variables, Questions, and Outputs. If these names do not appear in the code, the data will not be sent to the R server and will not be accessible for the calculation.
As an example, consider two R Outputs. The first contains myVariable <- 10
. This causes an R Output to be created with a Name of myVariable
and a value of 10. The second contains the following code:
myVariable * eval(parse(text = "myVariable")) * eval(parse(text = paste0("my","Variable")))
This will return a value of 1,000, as it retrieves the value 10 three times, and multiplies them together. This is identical to how R normally works.
By contrast, if the second R Output contains:
eval(parse(text = "myVariable")) * eval(parse(text = paste0("my","Variable")))
we get the error that object myVariable not found
. This is because the R code does not contain myVariable
directly in the code. Rather, myVariable is a string (in quotes) and pasted into a string.
In situations where it is necessary to use code of this form, a solution is to have some irrelevant usage of the Variable Names at the beginning of the code block, which will cause the data to be uploaded. For example, the following code will return the correct result of 100:
tempVar = myVariable
eval(parse(text = "myVariable")) * eval(parse(text = paste0("my","Variable")))
3. References to public, external or other non-local variables in functions
Consider the following R Output:
f <- 1
b <- function()
{
print(f)
}
Where an R Output contains a function, or, ends with a function, that function can be used within other places that can run R code (i.e., a different R Output, R Variable, or when creating an R Data Set). However, if the function refers to a variable, such as f in this example, which is not defined within the function, or within its signature, an error will typically occur (the exception is described below). For example, an R Output containing b(1)
, will return an error of object 'f' not found
.
A similar error will occur if, for example, f <- 1
appears in a separate R Output with a name of f. In this case, a more voluminous error message is provided, saying, for example: An R Output called 'f' has been used in the R code. Although this does exist in this document, it is not available where it is referred to. A common cause of this is where referring to objects within a function. A solution is to change the function to accept 'f' (e.g., function(..., f))..
There are two ways to work around this:
- As described in the longer error message, the way to resolve this issue is to pass the variables into the function as arguments (e.g.,
b <- function(f)
). - When calling the function, you can refer to the variable elsewhere in the same R Output, as is done in the example below. The reason that this works is that any variables referred to within a R Output, other than within a function, are automatically loaded prior to computing a R Output.
z = f
b(1)
4. Recursive functions
a) Permitted recursions
In Q, it is fine to write recursive R functions. For example:
fib = function(i) {
if (i < 2)
i
else
fib(i-1) + fib(i-2)
}
It is also fine to write mutually recursive R functions. So, for example, the above function could have been written as:
fibsum = function(k, j) {
fib(k) + fib(j)
}
fib = function(i) {
if (i < 2)
i
else
fibsum(i-1, i-2)
}
b) Problematic Recursion
However, it is a requirement in Q to have these two functions defined within the same R item. That is, it is not fine to create two R items:
fibsum = function(k, j) {
fib(k) + fib(j)
}
and
fib = function(i) {
if (i < 2)
i
else
fibsum(i-1, i-2)
}
as they both require that the other one is defined before it is. If two (or more) functions are mutually defined, then Q will complain of mutual recursion, and suggest that you solve this by either rewriting the code to eliminate the recursion, or by defining all associated functions within the one R item.
c) Recursive Functions and Implications for S3 Classes
Q achieves S3 class functionality by making not only objects which are explicitly referenced in the code available to R for computation, but also objects which have the same initial prefix as objects referred to in the code. That is, if Q sees the code
plot(mydata)
and if there are functions which have been defined with the "plot." prefix, such as "plot.myclass", then these functions are made available for the R server to use when evaluating "plot(mydata)". If "mydata" is of class "myclass", then "plot.myclass" will be called, giving the functionality of S3 classes. On the other hand, if "mydata" is not of class "myclass", then "plot.myclass" will be ignored when evaluating "plot(mydata)".
This has an implication when it comes to recursion, because mutual recursion (above) may be present although not necessarily apparent. For example, two objects defined as
auxiliary_plotting = function(x) {
…
plot(x)
…
}
and
plot.myclass = function(x) {
…
auxiliary_plotting(x)
…
}
exhibit mutual recursion. As the definition of "auxiliary_plotting" refers to "plot", which may mean "plot.myclass", there is an implicit reference to "plot.myclass". For this reason, "auxiliary_plotting" implicitly requires "plot.myclass" and "plot.myclass" requires "auxiliary_plotting", resulting in mutual recursion.
See Also
How to Use Different Types of Data in R
How to Reference Different Items in Your Project in R
How to Work with Conditional R Formulas
How to Add a Custom R Output to your Report
How to Create a Custom R Variable