3

I'm finding R confusing because it has such a different notion of reference than I am used to in languages like C, Java, Javascript... Ruby, Python, C++, well, pretty much any language I have ever programmed in ever.

So one thing I've noticed is variable names are not irrelevant when passing them to something else. The reference can be part of the data. e.g. per this tutorial

a <- factor(c("A","A","B","A","B","B","C","A","C"))
results <- table(a)

Leads to $a showing up as an attribute as $dimnames$a.

We've also witnessed that calling a function like a <- foo(alpha=1, beta=2) can create attributes in a of names alpha and beta, or it can assign or otherwise compute on 1 and 2 to properties already existing. (Not that there's a computer science distinction here - it just doesn't really happen in something like Javascript, unless you want to emulate it by passing in the object and use key=value there.)

Functions like names(...) return lvalues that will affect the input of them.

And the one that most got me is this.

x <- c(3, 5, 1, 10, 12, 6)
y = x[x <= 5]
x[y] <- 0

is different from

x <- c(3, 5, 1, 10, 12, 6)
x[x <= 5] <- 0

Color me confused. Is there a consistent theory for what's going on here?

4
  • No. R is a hodgepodge of ideas. Jun 9 '14 at 3:58
  • Vis-à-vis your last example x <=5 is a vector filled with logicals (namely c(TRUE, TRUE, TRUE, FALSE, FALSE, FALSE)) while y is a vector containing the elements of x filtered by that vector (i.e. c(3,5,1)), so in the first case x[y] would refer to the 3rd, 5th and 1st elements of x while x[x<=5] would refer to the elements inferior or equal to 5 (i. e. the three first).
    – plannapus
    Jun 12 '14 at 13:47
  • Don't mutate objects in R. You should assign them to new things and they'll copy on write. Think of R as Haskell lite with regard to redefinitions. If you are finding yourself with large complex objects to work with, something's gone wrong.
    – Carbon
    Jul 10 '15 at 4:30
  • In your last example, you could adjust your code so the first part would have produced the same as the second with a middle line of y <- (x <= 5) Alternatively the second would have produced the same as the first if its final line were x[x[x <= 5]] <- 0 and this is what I would have expected from substitution
    – Henry
    Apr 5 '16 at 15:47
5

I'm not certain I can answer why things were created the way they were (and are), but I can provide some insight as to when they would be useful. But up front, R is a language originally intended for statistical analysis, so "models" are commonly in the lime-light. (This is by far not a current requirement, and there are many other reasons one would lean towards or against R.)

Creation of Result's Attributes

When creating a "model", whether it be regressing, PCA, factor analysis, etc, there are often times when you want to expand on the model results through a programmatic method. Perhaps this means to draw a regression/smooth line, print a summary, add or remove factors from a regression, etc. However, if you look at the return value from (say) a regression, it may not contain enough information to replicate the conditions necessary.

For example, consider a simple regression using the built-in dataset, mtcars:

lm1 <- lm(mpg ~ cyl, data=mtcars)

The output itself is rather terse (though can be more verbose)

lm1
## Call:
## lm(formula = mpg ~ cyl, data = mtcars)
## Coefficients:
## (Intercept)          cyl  
##      37.885       -2.876  

Let's say we want to provide a more detailed summary from this. We can look at:

summary(lm1)
## Call:
## lm(formula = mpg ~ cyl, data = mtcars)
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.9814 -2.1185  0.2217  1.0717  7.5186 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  37.8846     2.0738   18.27  < 2e-16 ***
## cyl          -2.8758     0.3224   -8.92 6.11e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Residual standard error: 3.206 on 30 degrees of freedom
## Multiple R-squared:  0.7262, Adjusted R-squared:  0.7171 
## F-statistic: 79.56 on 1 and 30 DF,  p-value: 6.113e-10

where it should be obvious that the majority of the information in the "summary" can not be derived from the original "output" of the linear regression. One could argue that this should be the default output from lm; I can't argue, but ...

If you want to look at other statistics-related analyses from this -- including Q-Q plots, scale-location plots, or residuals, the above output most certainly doesn't give you sufficient information, but simply typing plot(lm1) will give you four (with others available) often helpful plots:

enter image description here

(Simple, contrived, perhaps.) The use of out-of-the-way attributes make it possible to recreate the necessary conditions that built the linear model sufficiently for other related functions to do their jobs.

Could you use different functions to instead generate information to support this output versus the simpler output above? Sure, but it is certainly convenient.

Often, these attributes are strictly required for data manipulation. For instance, in R, the names of columns and/or rows are attributes, which are added either explicitly at variable creation, implicitly during model manipulation, or explicitly adjusted after-the-fact. Indexing and subsetting can be done by number or by name (if present). In python/pandas, these attributes are just as much part of the data.

Bottom line, though, is that implicit assignment of attributes based on passed arguments happens specifically with only some functions; I believe the majority of functions do not do this, though I haven't fished through them all to know for certain.

Variable Names' Relevance

The reason that a is in the dimnames is because you provided the variable named a to the function table, not because it was originally assigned to it. You get the helpful reference to the variable name when applicable to provide context and reference:

a <- factor(c("A","A","B","A","B","B","C","A","C"))
table(a)
## a
## A B C 
## 4 3 2 

table(factor(c("A","A","B","A","B","B","C","A","C")))
## 
## A B C 
## 4 3 2 

Occasionally this is just icing, to be honest, but often it can really help find perspective on what did what. For instance, in many languages when you call a function (whether pass-by-value or pass-by-reference), when inside the called function you may not be able to provide perspective in error messages, context, or change the process based on what was provided.

(Note: you can argue all day long that "change the process based on what was provided", aka polymorphism, can be problematic, and "explicit" is often better than "implicit". I'm not fighting that war, but the capability is there and used often.)

The creation of a list is a clear example: it takes its arguments and creates named entries. In Python, for example, a named list requires key/value pairs for its dictionaries, and the keys are provided as literal strings, ala { "key":2.0, "key2": 3.1415 }. In R, a similarly-intended list would be built as list(key=2.0, key2=3.1415). In this case, it is taking the names of the arguments being provided and including them for relevance in the output, so that you know which element is which. (This is a more direct example, but the argument names are being inserted into the attributes of the returned value.)

Take this one step further (and combine with my previous point about attributes), and you'll realize that the process of assigning direct return values (such as dictionary keys) or indirect attributes (such as what can be found with attributes(lm1)) is effectively for the same purpose: to provide relevance, context, and recreatability (at times) in the returned value.

Note that often, attributes survive variable mutation without affecting the mutation itself. For instance, a matrix (mtx <- matrix(1:6, nrow=2)) has as attributes its dimensions, column names, and row names. If I do simple math, such as mtx + 2, the math operation is done strictly on the data, and the attributes are unaffected. Operations that will have an affect on the attributes, such as taking the transpose, will adjust them accordingly.

names() and LHS Functions

Some functions operate solely on the right-hand side (RHS) of the equals (or <-) assignment. This is effectively the default for most programming languages out there. Some functions, however, allow for being on the left-hand side of the assignment operator. Think of the indexing function, [, which is really just a function. It indexes into a vector/array and either sets or gets the specific index. The fact that many languages treat this like an atomic "thing" in the language versus a "function" is one thing that sets R apart. (If you type in `[` on the R command prompt, it will tell you that it is merely a function ... a primitive function, perhaps, but other LHS-based functions are used, including names, colnames, and attr.)

Vector Indexing

Lastly, as a couple of comments have started discussing, your indexing has a little (commonly-hit) flaw in it:

x <- c(3, 5, 1, 10, 12, 6)
y = x[x <= 5]
y
## [1] 3 5 1
x[y]
## [1]  1 12  3

Notice that y is contains three integers: 3, 5, 1. The last command x[y] is saying to retrive the third, fifth, and first elements of the vector named x. So, you created a vector of index positions with which you would peer into x. If you typed in x[c(3,5,1)], would you not expect to get the response from this that you did?

Imagine, instead, that you did this instead:

x <- c(3, 5, 1, 10, 12, 6)
y = x[x <= 5] + 10
y
## [1] 13 15 11
x[y]
## [1] NA NA NA

Perhaps this provides some clarity as to why y in this case is not containing indices into x, but instead values derived (and adjusted) from x.

Your second example is a great example of subsetting vectors (and lists and data.frames and ...).

x <- c(3, 5, 1, 10, 12, 6)
x <= 5
## [1]  TRUE  TRUE  TRUE FALSE FALSE FALSE
x[x <= 5] <- 0
x
## [1]  0  0  0 10 12  6

Notice that x <= 5 is returning a logical vector of the same length as the original vector. Subsetting in R permits logical indices, so the logical vector is essentially saying "I want the first", "I want the second", ..., "I don't want the fourth", "I don't want the fifth", etc. If you run x[x <= 5] without the assignment, it would give you the first, second, and third elements, in order. The fact that it allows you to replace the three positions with a single value (recycled) is a convenience; other languages require the assigned value to have the same dimensionality (R occasionally requires this as well).

Bottom Line

R is certainly different in many aspects from other programming languages. It is loosely-typed, dynamic in so many ways to look, pass-by-value (most of the time), heavily influenced by scheme/lisp (ergo death-by-parentheses), and it appears to be heavily influenced to streamline statistical methods. That's not to say that other languages don't do it well; quite the opposite, many do it very well.

Are there things that, were we designing a new statistics-centric language, might be different? Certainly. Consider Julia; certainly not statistics-only, arguably faster in many ways. The bottom line is that it has nuances that are both differently-motivated as well as more-recently-defined, arguably with the lessons learned from so many other languages, including R.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.