R and Spotfire S+ have many differences. Functions with the same names might have different arguments or defaults might be set differently. Other significant differences might exist. This paper discusses some of the important distinctions between R and Spotfire S+. If you see functions in R that you would like to see implemented in Spotfire S+, please contact Technical Support at http://spotfire.tibco.com/support.
Arithmetic
64-bit Arithmetic
On 64-bit systems, Spotfire S+ supports 64-bit integer arithmetic; R supports only 32-bit integer arithmetic, even on 64-bit systems. Both R and Spotfire S+ support 64-bit addressing on 64-bit systems.
Differences in Complex Arithmetic
The following differences occur between the results of certain R and Spotfire S+ complex math functions.
b2 <- c(-1+0i, 1+0i)
Z <- 1.25+0i
| Function |
Spotfire S+ |
R |
atanh(b2) |
NA NA |
-Inf+0i Inf+0i |
Arg(-b2) |
0.000000 3.141593 |
0.000000 -3.141593 |
sign(b2) |
-1+0i 1+0i |
Error in sign(b2) : unimplemented complex function |
asin(Z) |
1.570796-0.6931472i |
1.570796+0.693147i |
acosh(Z) |
-0.6931472+0i |
0.6931472+0i |
Notice that several of the differences are differences in sign (+/1)
C/Fortran Interface
Differences Between Spotfire S+ and R for the .Call() Function
There are differences between R and Spotfire S+ in how .Call() copies its arguments: The R version of .Call() never copies its arguments. The Spotfire S+ version of .Call() copies an argument if it is all or part of a named object. That is, .Call() cannot change any objects with names. Spotfire S+ lets you specify COPY=FALSE or to register the routine with setInterface to avoid the copy. Pointers to arguments will not remain valid from one .Call to another.
The first argument to .Call() can be unquoted in R, but it must be a quoted string in Spotfire S+. In R, the symbol name in the .Call() can be specified as unquoted:
arimaSS <- function(y, mod)
{
.Call(R_ARIMA_Like, y, mod$phi, mod$theta,
mod$Delta, mod$a, mod$P, mod$Pn, as.integer(0), TRUE)
}
In Spotfire S+, the symbol name must be a string, so the first argument is quoted:
arimaSS <- function(y, mod)
{
## next call changes objects a, P, Pn so beware!
.Call("R_ARIMA_Like", y, mod$phi, mod$theta, mod$Delta,
mod$a, mod$P, mod$Pn, as.integer(0), TRUE)
}
Porting C Code Using Rinternals.h and Rdefines.h
Some C code within R packages is written to manipulate internal R objects, such as SEXP structures. Typically, this code includes the files Rinternals.h or Rdefines.h and references structures and macros defined in these files. To make it easier to port C code from R to Spotfire S+, we have added the files Rinternals.h and Rdefines.h in the Spotfire S+ include directory. These files contain macro definitions that map some common R functions and macros to corresponding ones supported by Spotfire S+.
Even when you use these include files, most of the time, warnings and errors are generated when compiling the C code. One common problem is that R encodes integer values as C type int, whereas Spotfire S+ uses the C type long. Usually, these are the same on 32-bit architectures, but it is still important to examine warnings when one type is used instead of the other. For example, the R INTEGER(x) macro returns an int*, whereas Spotfire S+ returns a long*, and compiling the code
int* x = INTEGER(y)
could give the following warning:
warning: assignment from incompatible pointer type
There are many R functions and data names that do not have any meaning in Spotfire S+. For some such functions, Rinternals.h includes a macro definition to change the function call to an undefined variable, so it is easier for the user to understand the problem. For example, using R_GlobalEnv produces an error:
'unsupported_R_entry_R_GlobalEnv' undeclared
Graphics
Log Scale for x- and y-axis Specified Differently
In R, the xlog value in par() indicates whether the current plot has a log x-axis. In Spotfire S+, the xaxt value in par() is "l" if it is a log axis. Similarly, R has ylog, and Spotfire S+ has yaxt.
The following type of code makes the output the same:
if (is.R()){
par("xlog")
}
else {
(par("xaxt")=="l")
}
pairs() and dotchart() main Argument
The pairs() and dotchart() functions in R have a "main" argument. The same functions in Spotfire S+ do not.
I/O
T and F
In R, T and F are not reserved words (TRUE and FALSE are) and are global variables. Spotfire S+ reserves T, F, TRUE, and FALSE. It is strongly recommended that you not use T and F as variable names.
Differences in format.default()
In R, format.default() has the following arguments that are not present in format.default() in Spotfire S+:
- trim
- width
- na.encode
- zero.print
Differences in read.table
In R, the sep argument of read.table() cannot be a vector; in Spotfire S+ it can.
Language
Parsing Differences
In R, object names can include underscores ("_"), and the underscore is not used as an assignment operator; in Spotfire S+, an underscore means assignment (although this behavior is deprecated in Spotfire S+ 8.1).
You can set the default to use either the R or S parser with set.parse.mode() or parse(). See the Spotfire S+ help topic for set.parse.mode() for more information about the differences.
Example
set.parse.mode("R")
or
parse(mode="R", ...)
Use either mode="Splus" or 0 to parse in Spotfire S+ mode, and "R" or 1 to parse in R mode. The default mode is given by getenv("S_PARSER_MODE"). To see the current parse mode without changing it, type:
set.parse.mode(mode=-1).
Optionally, You can set the parser to parse in R mode to read in R functions and then set it back to Spotfire S+ mode to convert object names automatically. For example:
prev.parse.mode <- set.parse.mode("R")
source("my_fun.R") # assume this creates my_fun
set.parse.mode(prev.parse.mode)
dump("my_fun", "my_fun.q")
R parses numbers without decimal points as reals, unless you explicitly append an L. Spotfire S+ parses numbers without decimal points as integers (as long as they are less than or equal to 2147483647).
In R:
> class(1)
[1] "numeric"
> class(1L)
[1] "integer"
> class(2147483647)
[1] "numeric"
> class(2147483648)
[1] "numeric"
In Spotfire S+:
> class(1)
[1] "integer"
> class(1L)
[1] "integer"
> class(2147483647)
[1] "integer"
> class(2147483648)
[1] "numeric"
Spotfire S+ can be set to mimic R's behavior in this regard by the command
set.parse.mode(mode="R")
Namespaces
Spotfire S+ does not support namespaces, so the value of either package::name or package:::name is just name.
Data Object Storage
In Spotfire S+, objects are stored as files in the .Data directory. In R, objects are stored in memory and might be stored in one big file when you exit R.
as.data.frame Additional Arguments
The as.data.frame function in Spotfire S+ has additional arguments to control how arrays are converted to data frames. Using row.dims=1 in Spotfire S+ makes the output like R's output.
Vector Length Matching
1:5 + 1:2 succeeds with a warning in R, but it fails in Spotfire S+. The length of one argument must be a multiple of the length of the other for the arithmetic operators.
Assignment Operators
- In R, you use
x <<- newValue to find the current definition of x and put its new value in the same place. If x does not exist, put it in .GlobalEnv.
- In Spotfire S+, you use
x <<- newValue to put x on the working database (a permanent data set).
Functions Return the Results of an Assignment Invisibly
Consider the following function:
test<-function()
{
x <- 3
}
In Spotfire S+, running test() results in
[1] 3
In R, no return value is printed unless you wrap the call in print():
print(test())
String Manipulation Functions in Core R but not in Spotfire S+
These string manipulation functions are in core R, but not in Spotfire S+:
- agrep
- gregexpr
- strtrim
- substr
- substr<-
- toString
- toString.default
- chartr
- apropos
Differences in unique()
R has matrix and array methods for unique(). Spotfire S+ does not.
Spotfire S+ Does Not Let You Remove All Columns From data.frame with [ ] (bug)
The following code produces a 3x0 data frame in R. It produces an error in Spotfire S+:
x <- data.frame(a=1:3); x[,-1]
remove() Behaves Differently in R and Spotfire S+
In R:
> args(remove)
function (..., list = character(0), pos = -1, envir = as.environment(pos), inherits = FALSE)
In Spotfire S+:
> args(remove)
function(list, frame, where, meta = 0)
NULL
This difference results in the following behavior in Spotfire S+ (which works):
x <- 1
y <- 1
remove(c("x", "y"))
The following fails in R:
> x <- 1
> y <- 1
> remove(c("x", "y"))
Error in remove(c("x", "y")) :
... must contain names or character strings
The following code works in R:
> remove(x, y)
In Spotfire S+, it produces the following failure:
remove(x, y)
Problem in remove(x, y): need names of objects to remove, not the objects themselves
Use traceback() to see the call stack
The following code works in both Spotfire S+ and R:
remove(list=c("x", "y"))
This difference is a compatibility issue.
Differences in Missing Values
The missing value bit position differs between R and Spotfire S+. The missing value code is a NaN in both systems. To see the codes:
Spotfire S+: > writeBin(as.double(NA), pipe("od -x"))
Spotfire S+: 0000000 0001 7ff0 0001 7ff0
Spotfire S+: 0000010
R: > tfile<-tempfile()
R: > writeBin(as.double(NA), tfile)
R: > cat(system(paste("od -x < ", tfile)), sep="\\n")
# system replaces unix in R
R: 0000000 07a2 0000 0000 7ff8
Object Oriented Programming
Differences in class()
In R, class() can return a vector; in Spotfire S+, the result always has length 1.
typeof() in R
R has a typeof() function that returns S4 when appropriate, and otherwise acts something like class() or storage.mode().
Differences in S4
In R, S4 objects can have attributes without changing their classes.
Random Numbers
How to Get the Same Random Number Streams in Spotfire S+ and R
In general, the random number generators in R and in Spotfire S+ generate different streams of random numbers. The following code illustrates a method that you can use to generate the same stream of random numbers in R and in Spotfire S+ for a particular instance (that is, it works if you use runif, but not another random number generator).
In R:
> RNGkind("Super-Duper")
> set.seed(17)
> dput(.Random.seed[-1])
c(786472236L, -1895491523L)
> # Now generate 12 random uniform numbers:
> runif(12)
[1] 0.30746849 0.82811554 0.65195342 0.98243238 0.42425382 0.79464223
[7] 0.03540204 0.22236288 0.09750879 0.88091935 0.01319564 0.62546799
Use the above result ("c(786472236L, -1895491523L)") as an input to the function below to generate an equivalent .Random.seed for Spotfire S+'s random number generator:
In Spotfire S+:
# Define the function
"seed.RtoS" <- function(seed)
{
seed1 <- seed %% 2^16
seed2 <- seed/seed1
f <- function(k)
{
if(k < 0)
k <- k + 2^32
result <- rep(0, 6)
for(i in 1:6) {
result[i] <- k %% 64
k <- k %/% 64
}
result
}
as.integer(c(f(seed[2]), f(seed[1])))
}
Now use this function to set up Spotfire S+'s random number generator. Note that we use the argument generated in R as input.
> RNGkind("Super-Duper")
> set.seed(seed.RtoS(c(786472236L, -1895491523L)))
> # Now generate 12 random uniform numbers
> runif(12)
[1] 0.30746849 0.82811554 0.65195342 0.98243238 0.42425382 0.79464223
[7] 0.03540204 0.22236288 0.09750879 0.88091935 0.01319564 0.62546799
Observe that these are the same numbers generated in R.
For different seeds in R, we would use the same procedure to generate the equivalent seeds in Spotfire S+.
Statistics/Modeling
model.matrix
This was noted while comparing code in R vs. Spotfire S+:
d<-data.frame(f1=factor(rep(c("One","Two"),c(6,6)),
levels=c("One","Two")),
f2=factor(rep(c("I","II","III"),4),
levels=c("I","II","III")),
x=101:112, y=log(1:12))
f<-function(form, data){
z<-model.frame(form, data=data)
zz<-model.matrix(attr(z, "terms"), z)
invisible(dput(attr(zz, "assign")))
}
> f(y~., data=d)
In Spotfire S+, we get a list:
named("(Intercept)" = 1, "f1" = 2, "f2" = c(3, 4), "x" = 5)
and in R we get an integer vector:
c(0, 1, 2, 2, 3)
Here are the results for various formulae:
Spotfire S+ > f(y~f2+x-1, data=d)
named("f2" = c(1,2,3), "x" = 4)
R> f(y~f2+x-1, data=d)
c(1, 1, 1, 2)
and
Spotfire S+ > f(y~(f2+f1)^2, data=d)
named("(Intercept)" = 1
, "f2" = c(2, 3)
, "f1" = 4
, "f2:f1" = c(5, 6)
)
R > f(y~(f2+f1)^2, data=d)
c(0, 1, 1, 2, 3, 3)
and
Spotfire S+ > f(y~f1*f2-1, data=d)
named("f1" = c(1, 2)
, "f2" = c(3, 4)
, "f1:f2" = c(5, 6)
)
R > f(y~f1*f2-1, data=d)
c(1, 1, 2, 2, 3, 3)
They both seem to encode the same information but in different ways.
R's model.frame Adds predvars attribute
model.frame() in R returns information that model.frame() in Spotfire S+ does not. In particular, the terms attribute of the result has an attribute, predvars, that is not in the Spotfire S+ output. It is like the variables attribute, but it includes information that is needed to make predictions from new datasets. model.frame.default() calls makepredictcall() to create this attribute. help(makepredictcall) provides documentation on its use.
It is important to know about this when porting R code that deals with modeling functions. You might want to use a similar scheme to make predictions involving poly() or ns() valid.
The following is an example comparing predvars and variables in the output of R's model.frame():
> library("splines")
> d<-data.frame(y=log(1:12),
x=sqrt(1:12),
letter=factor(rep(c("a","b"),c(6,6))),
roman=factor(rep(c("I","II","III"),4)))
For "ordinary" terms in the model, predvars and variables are identical:
> attr(attr(model.frame(log(y)~sqrt(x+1),data=d), "terms"), "variables")
list(log(y), sqrt(x + 1))
> attr(attr(model.frame(log(y)~sqrt(x+1),data=d), "terms"), "predvars")
list(log(y), sqrt(x + 1))
But if your model has a term like bs(x) or poly(x) where there are other implied arguments whose default values depend on x, then predvars contains the calls you would need to use this with new data:
> attr(attr(model.frame(y~bs(x),d), "terms"), "variables")
list(y, bs(x))
> attr(attr(model.frame(y~bs(x),d), "terms"), "predvars")
list(y, bs(x, degree = 3, knots = numeric(0),
Boundary.knots = c(1, 3.46410161513775), intercept = FALSE))
The arguments that makepredictcall() adds to bs() come from bs(d$x):
> bs(d$x)
1 2 3
[1,] 0.00000000 0.00000000 0.000000000
...
[12,] 0.00000000 0.00000000 1.000000000
> attr(,"degree")
[1] 3
> attr(,"knots")
numeric(0)
> attr(,"Boundary.knots")
[1] 1.000000 3.464102
> attr(,"intercept")
[1] FALSE
> attr(,"class")
[1] "bs" "basis"
and trace(bs) shows how predict() uses the data in predvars:
> trace(bs)
> fit<-lm(y~bs(x), data=d)
trace: bs(x)
> predict(fit, newdata=data.frame(x=c(2,3)))
trace: bs(x, degree = 3, knots = numeric(0),
Boundary.knots = c(1, 3.46410161513775), intercept = FALSE)
1 2
1.391730 2.191604
Plotting the results shows that R outputs the correct prediction, and Spotfire S+ does not. (The predictions from new data are drawn as triangles, and they lie on the fitted data in R but not in Spotfire S+):
> plot(d$x, fitted(fit))
> points(c(2,3), predict(fit, newdata=data.frame(x=c(2,3))), pch=2, cex=2)
trace: bs(x, degree = 3, knots = numeric(0),
Boundary.knots = c(1, 3.46410161513775), intercept = FALSE)
In Spotfire S+, the trace shows just bs(x), with no extra arguments.
factanal()
The factanal() function has a covmat argument in R and covlist argument in Spotfire S+. To get code that works in both, specify just "cov" as the argument name:
Harman74.FA <- factanal(factors = 1, cov = Harman74.cor)
nls() and plot.formula() subset Argument
The nls() function has a subset argument in R and not in Spotfire S+. So R code of the form:
fm1 <- nls(circumference ~ SSlogis(age, Asym, xmid, scal),
data = Orange, subset = Tree == 3)
needs to use subscripting of the data argument instead:
fm1 <- nls(circumference ~ SSlogis(age, Asym, xmid, scal),
data = Orange[Orange$Tree == 3,])
The same is true for plot.formula():
plot(circumference ~ age, data = Orange, subset = Tree == 3,
xlab = "Tree age (days since 1968/12/31)",
ylab = "Tree circumference (mm)", las = 1,
main = "Orange tree data and fitted model
(Tree 3 only)")
Time/Date
Mixing timeSeries and matrix Classes Causes Errors
Some objects that use old classes such as timeSeries and matrix try to inherit from both. In this example, Y.ts tries to inherit from both:
x = c(1.0622, 1.0785, 1.0797, 1.0862, 1.1556, 1.1674,
1.1365, 1.1155, 1.1267, 1.1714, 1.1710, 1.2298)
y = c(1.5414, 1.5121, 1.4761, 1.4582, 1.3840, 1.3525,
1.3821, 1.3963, 1.3634, 1.3221, 1.3130, 1.3128)
Y.ts = ts(cbind(USDEUR = x, USDCAD = y))
plot(Y.ts)
Error in plot: Lengths of x and y must match
tsplot(Y.ts) does work as a possible workaround.
by="mon" in timeSequence Interpreted Differently
In the R version of timeSequence(), by="mon" gets partially matched with "month" so the sequence is by month:
> timeSequence(from="1/1/2001", to="1/1/2002", by="mon")
[1] "Zurich"
[1] [2001-01-01] [2001-02-01] [2001-03-01] [2001-04-01] [2001-05-01] [2001-06-01] [2001-07-01]
[8] [2001-08-01] [2001-09-01] [2001-10-01] [2001-11-01] [2001-12-01] [2002-01-01]
In the Spotfire S+ version of timeSequence(), by="mon" indicates the sequence should be by Mondays:
> timeSequence(from=timeDate("1/1/2001"),
to=timeDate("1/1/2002"), by="mon")
from: 01/01/2001
to: 01/01/2002
by: +1mon
[1] 01/01/2001 01/08/2001 01/15/2001 ... 12/31/2001