test = [1, 1, 1, 1]
ifelse(isassigned(test, 5), test[5], "nope")
To me, this should yield the string "nope", but instead I get
BoundsError: attempt to access 4-element Array{Int64,1} at index [5]
Is this a bug or expected functionality?
For now, I am using
if isassigned(test, 5) test[5] else "nope" end
but this isn't very legible inside of a list comprehension.
Use:
julia> isassigned(test, 5) ? test[5] : "nope"
"nope"
which should be more readable.
The ifelse evaluates all its arguments. The consequence of this behavior is best described by ifelse docstring:
This differs from ? or if in that it is an ordinary function, so all the arguments are evaluated first. In some cases, using ifelse instead of an if statement can eliminate the branch in generated code and provide higher performance in tight loops.
Related
I am supposed to write a function that, for the values of Pi and P* returns the α.
I am having trouble with the usage of sum in my function.
So far I have something like this:
sqrt((sum(x[i], i == 1, i == length(pstar)])*(p-pstar)^2)/n)/pstar)*100
A sum over a vector x in R is just sum(x), not sum(x[i], i == 1, i == length(x)) * x. (In fact, the latter doesn’t make much sense even if the syntax was correct, since there’s no multiplication involved in a sum.)
So, in your case:
sum((p - pstar) ^ 2)
I need a vector of UnitRanges as follows:
[2:5, 3:6, 4:7, 5:8]
when I try to run this (2:5):(5:8), I get an error of "ArgumentError: step cannot be zero."
Is there a way of creating a UnitRange array using UnitRange itself?
It sounds like you want to map or broadcast : over the elements in the two arguments. Just do it explicitly:
julia> map(:, 2:5, 5:8)
4-element Array{UnitRange{Int64},1}:
2:5
3:6
4:7
5:8
Now, ideally you'd also be able to write this as (2:5) .: (5:8) — you'd dot the : operator to broadcast it — but since : is used for so many things and since this isn't a very common use-case we've not enabled the dotting of :. You can, however, use the non-infix form and dot that:
julia> (:).(2:5, 5:8)
4-element Array{UnitRange{Int64},1}:
2:5
3:6
4:7
5:8
As for the error message you're getting, it's because the first thing : tries to do is determine the length, assuming its two arguments are scalars. To do this it subtracts the first argument from the second:
julia> (5:8) - (2:5)
ERROR: ArgumentError: step cannot be zero
That fails because it's trying to create a step range that's effectively 3:0:3 and has a length of 4.
I am trying to write a function that calls several functions that accept named parameters. I'd like the A function to be able to named parameters splatted together in args and pass along the matching arguments to the functions it calls.
function A(x, y; args...)
B(x; args...)
C(y; args...)
end
function B(x; a=0)
println(x,a)
end
function C(y; a=0, b=0)
println(y,a,b)
end
funcA(1, 2) # Works
funcA(1, 2, a=1) # Works
funcA(1, 2, a=1, b=1) # Error: unrecognized keyword argument "b"
What is the preferred way of getting this to work? Adding "args..." into the argument list of B fixes the error, but I'm not sure if it's a good idea (e.g. is there any performance hit).
Your solution is the preferred way
function A(x, y; args...)
B(x; args...)
C(y; args...)
end
function B(x; a=0, _...) # catch rest
println(x,a)
end
function C(y; a=0, b=0)
println(y,a,b)
end
A(1, 2) # Works
A(1, 2, a=1) # Works
A(1, 2, a=1, b=1) # Works
Theres no special meaning to _, use whatever feels best to you.
As to performance, I doubt it'd noticeable. Are you calling B many times in a hot loop and using the values of the keyword arguments in calculations? They aren't typed very well, so that could be only thing (although its not really relevant to the specific question).
I'm confused with when a value is treated as a variable, and when as a string in R. In Ruby and Python, I'm used to a string always having to be quoted, and an unquoted string is always treated as a variable. Ie.
a["hello"] => a["hello"]
b = "hi"
a[b] => a["hi"]
But in R, this is not the case, for example
a$b < c(1,2,3)
b here is the value/name of the column, not the variable b.
c <- "b"
a$c => column not found (it's looking for column c, not b, which is the value of the variable c)
(I know that in this specific case I can use a[c], but there are many other cases. Such as ggplot(a, aes(x=c)) - I want to plot the column that is the value of c, not with the name c)...
In other StackOverflow questions, I've seen things like quote, substitute etc mentioned.
My question is: Is there a general way of "expanding" a variable and making sure the value of the variable is used, instead of the name of the variable? Or is that just not how things are done in R?
In your example, a$b is syntatic sugar for a[["b"]]. That's a special feature of the $ symbol when used with lists. The second form does what you expect - a[[b]] will return the element of a whose name == the value of the variable b, rather than the element whose name is "b".
Data frames are similar. For a data frame a, the $ operator refers to the column names. So a$b is the same as a[ , "b"]. In this case, to refer to the column of a indicated by the value of b, use a[, b].
The reason that what you posted with respect to the $ operator doesn't work is quite subtle and is in general quite different to most other situations in R where you can just use a function like get which was designed for that purpose. However, calling a$b is equivalent to calling
`$`(a , b)
This reminds us, that in R, everything is an object. $ is a function and it takes two arguments. If we check the source code we can see that calling a$c and expecting R to evaluate c to "b" will never work, because in the source code it states:
/* The $ subset operator.
We need to be sure to only evaluate the first argument.
The second will be a symbol that needs to be matched, not evaluated.
*/
It achieves this using the following:
if(isSymbol(nlist) )
SET_STRING_ELT(input, 0, PRINTNAME(nlist));
else if(isString(nlist) )
SET_STRING_ELT(input, 0, STRING_ELT(nlist, 0));
else {
errorcall(call,_("invalid subscript type '%s'"),
type2char(TYPEOF(nlist)));
}
nlist is the argument you passed do_subset_3 (the name of the C function $ maps to), in this case c. It found that c was a symbol, so it replaces it with a string but does not evaluate it. If it was a string then it is passed as a string.
Here are some links to help you understand the 'why's and 'when's of evaluation in R. They may be enlightening, they may even help, if nothing else they will let you know that you are not alone:
http://developer.r-project.org/nonstandard-eval.pdf
http://journal.r-project.org/2009-1/RJournal_2009-1_Chambers.pdf
http://www.burns-stat.com/documents/presentations/inferno-ish-r/
In that last one, the most important piece is bullet point 2, then read through the whole set of slides. I would probably start with the 3rd one, then the 1st 2.
These are less in the spirit of how to make a specific case work (as the other answers have done) and more in the spirit of what has lead to this state of affairs and why in some cases it makes sense to have standard nonstandard ways of accessing variables. Hopefully understanding the why and when will help with the overall what to do.
If you want to get the variable named "b", use the get function in every case. This will substitute the value of b for get(b) wherever it is found.
If you want to play around with expressions, you need to use quote(), substitute(), bquote(), and friends like you mentioned.
For example:
x <- quote(list(a = 1))
names(x) # [1] "" "a"
names(x) <- c("", a)
x # list(foo = 1)
And:
c <- "foo"
bquote(ggplot(a, aes(x=.(c)))) # ggplot(a, aes(x = "foo"))
substitute(ggplot(a, aes(x=c)), list(c = "foo"))
Why do the if-else construct and the function ifelse() behave differently?
mylist <- list(list(a=1, b=2), list(x=10, y=20))
l1 <- ifelse(sum(sapply(mylist, class) != "list")==0, mylist, list(mylist))
l2 <-
if(sum(sapply(mylist, class) != "list") == 0){ # T: all list elements are lists
mylist
} else {
list(mylist)
}
all.equal(l1,l2)
# [1] "Length mismatch: comparison on first 1 components"
From the ifelse documentation:
‘ifelse’ returns a value with the same shape as ‘test’ which is
filled with elements selected from either ‘yes’ or ‘no’ depending
on whether the element of ‘test’ is ‘TRUE’ or ‘FALSE’.
So your input has length one so the output is truncated to length 1.
You can also see this illustrated with a more simple example:
ifelse(TRUE, c(1, 3), 7)
# [1] 1
if ( cond) { yes } else { no } is a control structure. It was designed to effect programming forks rather than to process a sequence. I think many people come from SPSS or SAS whose authors chose "IF" to implement conditional assignment within their DATA or TRANSFORM functions and so they expect R to behave the same. SA and SPSS both have implicit FOR-loops in there Data steps. Whereas R came from a programming tradition. R's implicit for-loops are built in to the many vectorized functions (including ifelse). The lapply/sapply fucntions are the more Rsavvy way to implement most sequential processing, although they don't succeed at doing lagged variable access, especially if there are any randomizing features whose "effects" get cumulatively handled.
ifelse takes an expression that builds a vector of logical values as its first argument. The second and third arguments need to be vectors of equal length and either the first of them or the second gets chosen. This is similar to the SPSS/SAS IF commands which have an implicit by-row mode of operation.
For some reason this is marked as a duplicate of
Why does ifelse() return single-value output?
So a work around for that question is:
a=3
yo <- ifelse(a==1, 1, list(c(1,2)))
yo[[1]]