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:
(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.
x <=5
is a vector filled with logicals (namelyc(TRUE, TRUE, TRUE, FALSE, FALSE, FALSE)
) whiley
is a vector containing the elements ofx
filtered by that vector (i.e.c(3,5,1)
), so in the first casex[y]
would refer to the 3rd, 5th and 1st elements ofx
whilex[x<=5]
would refer to the elements inferior or equal to 5 (i. e. the three first).y <- (x <= 5)
Alternatively the second would have produced the same as the first if its final line werex[x[x <= 5]] <- 0
and this is what I would have expected from substitution