I want to use tryCatch function in a loop which sometimes works but sometimes it does not, and I do not know where does it have errors to solve the problem and create a loop which always works. So I want it just to neglect the error and go to next round. To simplify the problem, for example, I have this x, and I want to have sqrt of each value. I write:
x <- c(1, 2, "a", 4)
for (i in x) {
y <- tryCatch(print(sqrt(i)) , error = function(e) { return(0) } )
if (y==0) {
print("NOT POSSIBLE")
next
}
}
I suppose this code should give me this answer:
[1] 1
[1] 1.414214
[1] "NOT POSSIBLE"
[1] 2
but it gives me this:
[1] "NOT POSSIBLE"
[1] "NOT POSSIBLE"
[1] "NOT POSSIBLE"
[1] "NOT POSSIBLE"
I could not find anywhere explaining that why this happens. Why this function does not apply to each round of the loop separately and what can I do about it?
The reason is a that one of the elements in the vector is character and a vector cannot have mixed types. So, it is coerced to character. Instead we should have a list where each element can have different types
x <- list(1,2,"a" , 4)
Now, running the OP' code gives
for (i in x) {
y <- tryCatch(print(sqrt(i)) , error= function(e) {return(0)} )
if (y==0) {print("NOT POSSIBLE")
next}
}
#[1] 1
#[1] 1.414214
#[1] "NOT POSSIBLE"
#[1] 2
If we can use only a vector, then there should be a provision to convert it to numeric within the loop, but it would also return an NA for the third element as
as.numeric('a')
#[1] NA
Warning message: NAs introduced by coercion
and ends the for loop
Related
rquote <- "r's internals are irrefutably intriguing"
chars <- strsplit(rquote, split = "")[[1]]
This question has been asked before on this forum and has one answer on it but I couldn't understand anything from that answer, so here I am asking this question again.
In the above code what is the meaning of [[1]] ?
The program that I'm trying to run:
rquote <- "r's internals are irrefutably intriguing"
chars <- strsplit(rquote, split = "")[[1]]
rcount <- 0
for (char in chars) {
if (char == "r") {
rcount <- rcount + 1
}
if (char == "u") {
break
}
}
print(rcount)
When I don't use [[1]] I get the following warning message in for loop and I get a wrong output of 1 for rcount instead of 5:
Warning message: the condition has length > 1 and only the first element will be used
strsplit is vectorized. That means it splits each element of a vector into a vectors. To handle this vector of vectors it returns a list in which a slot (indexed by [[) corresponds to a element of the input vector.
If you use the function on a one element vector (single string as you do), you get a one-slot list. Using [[1]] right after strsplit() selects the first slot of the list - the anticipated vector.
Unfortunately, your list chars works in a for loop - you have one iteration with the one slot. In if you compare the vector of letters against "r" which throws the warning. Since the first element of the comparison is TRUE, the condition holds and rcount is rised by 1 = your result. Since you are not indexing the letters but the one phrase, the cycle stops there.
Maybe if you run something like strsplit(c("one", "two"), split="") , the outcome will be more straightforward.
> strsplit(c("one", "two"), split="")
[[1]]
[1] "o" "n" "e"
[[2]]
[1] "t" "w" "o"
> strsplit(c("one", "two"), split="")[[1]]
[1] "o" "n" "e"
> strsplit(c("one"), split="")[[1]][2]
[1] "n"
We'll start with the below as data, without [[1]]:
rquote <- "r's internals are irrefutably intriguing"
chars2 <- strsplit(rquote, split = "")
class(chars2)
[1] "list"
It is always good to have an estimate of your return value, your above '5'. We have both length and lengths.
length(chars2)
[1] 1 # our list
lengths(chars2)
[1] 40 # elements within our list
We'll use lengths in our for loop for counter, and, as you did, establish a receiver vector outside the loop,
rcount2 <- 0
for (i in 1:lengths(chars2)) {
if (chars2[[1]][i] == 'r') {
rcount2 <- rcount2 +1
}
if (chars2[[1]][i] == 'u') {
break
}
}
print(rcount2)
[1] 6
length(which(chars2[[1]] == 'r')) # as a check, and another way to estimate
[1] 6
Now supposing, rather than list, we have a character vector:
chars1 <- strsplit(rquote, split = '')[[1]]
length(chars1)
[1] 40
rcount1 <- 0
for(i in 1:length(chars1)) {
if(chars1[i] == 'r') {
rcount1 <- rcount1 +1
}
if (chars1[i] == 'u') {
break
}
}
print(rcount1)
[1] 5
length(which(chars1 == 'r'))
[1] 6
Hey, there's your '5'. What's going on here? Head scratch...
all.equal(chars1, unlist(chars2))
[1] TRUE
That break should just give us 5 'r' before a 'u' is encountered. What's happening when it's a list (or does that matter...?), how does the final r make it into rcount2?
And this is where the fun begins. Jeez. break for coffee and thinking. Runs okay. Usual morning hallucination. They come and go. But, as a final note, when you really want to torture yourself, put browser() inside your for loop and step thru.
Browse[1]> i
[1] 24
Browse[1]> n
debug at #7: break
Browse[1]> chars2[[1]][i] == 'u'
[1] TRUE
Browse[1]> n
> rcount2
[1] 5
I am looking for a method in R to run the block inside the if statement only the first time the if statement is evaluated as TRUE, but the block would not be run again even if the if condition is TRUE again. Specifically, the method would be useful in a loop.
This would be the "once" statement (it is called so in some exotic languages).
Example:
for (id in id_list){ # runs over a list of several id's which are random
if (id == "snake"){ # I want to run this block only the first time and NOT each time id == "snake"
# now, do some calculations
# ...
}
# do some other calculations by default for all other runs inside the loop
# ...
}
I would be also curious to know how would this work in Python.
1) duplicated Using the test input shown in the first line iterate over an index and add a condition using duplicated. This avoids using a flag making it less error prone.
id_list <- c("a", "snake", "b", "snake") # test input
dup <- duplicated(id_list)
for(i in seq_along(id_list)) {
if (id_list[i] == "snake" && (!dup)[i]) print("snake")
print(i)
}
giving:
[1] 1
[1] "snake"
[1] 2
[1] 3
[1] 4
2) match Another approach to determine which iteration represents the first instance of snake and using that in the condition.
ix <- match("snake", id_list, nomatch = 0)
for(i in seq_along(id_list)) {
if (i == ix) print("snake")
print(i)
}
giving:
[1] 1
[1] "snake"
[1] 2
[1] 3
[1] 4
3) once
Another approach is to create a once function which returns TRUE the first time it is run and FALSE otherwise. This does use a mutable variable, x, (similar to a flag) but at least it is encapsulated. The genOnce function outputs a fresh once function.
It is important to use && in the condition to ensure that the right hand side of && is only run if the left hand side is TRUE. & does not have that short circuiting property.
genOnce <- function(x = 0) function() (x <<- x + 1) == 1
once <- genOnce()
for(id in id_list) {
if (id == "snake" && once()) print("***")
print(id)
}
giving:
[1] "a"
[1] "***"
[1] "snake"
[1] "b"
[1] "snake"
Suggested solution using a 'global' variable (i.e. 'flag') to denote first pass into if clause:
first <- TRUE
for (i in 1:5) {
if (first & i > 0) {
print("run this block only the first time")
first <- FALSE
}
print("do some other calculations")
}
Output:
[1] "run this block only the first time"
[1] "do some other calculations"
[1] "do some other calculations"
[1] "do some other calculations"
[1] "do some other calculations"
[1] "do some other calculations"
I have a vector
x <- c(1,90,233)
I need to convert this to a vector of the form:
result = c("001.csv","090.csv","233.csv")
This is the function that I wrote to perform this operation:
convert <- function(x){
for (a in 1:length(x)){
if (x[a]<10) {
x[a]<- paste("00",x[a],".csv",sep="")
}
else if (x[a] < 100) {
x[a]<- paste("0", x[a], ".csv",sep="")
}
else {
x[a]<-paste(x[a],".csv",sep="")
}
}
x
}
The output I got was:
[1] "001.csv","90.csv","233.csv"
So, a[2] is 90 was processed in the else part and not the else if part. Then I changed the else if condition to x[a]<=99
convert <- function(x){
for (a in 1:length(x)){
if (x[a]<10) {
x[a]<- paste("00",x[a],".csv",sep="")
}
else if (x[a] <= 99) {
x[a]<- paste("0", x[a], ".csv",sep="")
}
else {
x[a]<-paste(x[a],".csv",sep="")
}
}
x
}
I got this output:
[1] "001.csv" "090.csv" "0233.csv"
Now both x[2] and x[3] ie 90 and 233 are being processed in the ElseIf part. What am I doing wrong here? And how do I get the output I need?
This is a little bit more dynamic as you do not need to specify the number of places held by the largest number.
Step 1:
Obtain the maximum number of places held.
(nb = max(nchar(x)))
To get:
3
Step 2:
Paste the number into a sprintf() call that will automatically format the digit.
sprintf("%0*d.csv", nb, x)
To get:
[1] "001.csv" "090.csv" "233.csv"
The problem is that the first round of your loop makes a character, that converts the whole vector to type character. You can get around that using nchar
convert <- function(x){
for (a in 1:length(x)){
if (nchar(x[a]) == 1) {
x[a]<- paste("00",x[a],".csv",sep="")
}
else if (nchar(x[a]) == 2) {
x[a]<- paste("0", x[a], ".csv",sep="")
}
else {
x[a]<-paste(x[a],".csv",sep="")
}
}
x
}
sprintf("%03d", x)
[1] "001" "090" "233"
You can avoid a call to paste by including the ".csv" in the format string:
sprintf("%03d.csv", x)
[1] "001.csv" "090.csv" "233.csv"
The problem with the original code is the conversion to character, which happens on the first element.
Here's the conversion to character:
> x <- c(1, 90, 233)
> x
[1] 1 90 233
> x[1] <- "001.csv"
> x
[1] "001.csv" "90" "233"
Here's the resulting comparison of the second element:
> "90" <= 99
[1] TRUE
> "90" < 100
[1] FALSE
Similarly for the third:
> "233" < 100
[1] FALSE
> "233" <= 99
[1] TRUE
In all of these cases, the right-hand side is converted to character, then the comparison is made, as character strings.
Your code doesn't work as expected because the whole vector gets converted into a character vector after first assignment(conversion of numeric to character).
Please note that when a string is compared to digit, the characters are matched one by one. For eg. if you compare "90" to 100 then 9 is compared to 1, hence control goes to the else part and in the case of comparison of "233" to 99, 2 is compared 9.
You can get around this by assigning the changed values to another vector.Or, you could use the str_pad function from the stringr package.
library(stringr)
x=c(1,90,233)
padded_name= str_pad(x,width=3,side="left",pad="0")
file_name = paste0(padded_name, ".csv")
data <-c("001","002","103","119","129")
n1<- sapply(data,function(x){
x<-gsub(pattern="(\\d+)(\\d\\d)$","\\2",x)
if(gsub("(\\d)(\\d)","\\1",x)=="0")
x <- gsub("(\\d)(\\d)","\\2",x)
},USE.NAMES=FALSE)
n2<- sapply(data,function(x){
x<-gsub(pattern="(\\d+)(\\d\\d)$","\\2",x)
if(gsub("(\\d)(\\d)","\\1",x)=="0")
x <- gsub("(\\d)(\\d)","\\2",x)
print(x)},USE.NAMES=FALSE)
Why n2 can get a vector of "1" "2" "3" "19" "29" ,n1 can not?n2 is more one line print(x) than n1,what is the effect of print function here?
What is happening here exactly is a little easier to spot when we apply some better indentation and add some spaces:
n2 <- sapply(data, function(x) {
x <- gsub(pattern = "(\\d+)(\\d\\d)$", "\\2", x)
if (gsub("(\\d)(\\d)", "\\1", x) == "0") x <- gsub("(\\d)(\\d)", "\\2", x)
print(x)
}, USE.NAMES=FALSE)
If you do not use an explicit return statement, R will return the outcome of the last operation. In the first case, when the if statement fails, the last x <- will be skipped, and NULL will be returned. Adding print(x) both prints the number to the screen, and causes it to be returned from the function. This explains that the second case does always have a valid (non-NULL) return value.
In stead of print(x), I would use return(x), or simply x.
I am using the bit64 package in some R code. I have created a vector
of 64 bit integers and then tried to use sapply to iterate over these
integers in a vector. Here is an example:
v = c(as.integer64(1), as.integer64(2), as.integer64(3))
sapply(v, function(x){is.integer64(x)})
sapply(v, function(x){print(x)})
Both the is.integer64(x) and print(x) give the incorrect
(or at least) unexpected answers (FALSE and incorrect float values).
I can circumvent this by directly indexing the vector c but I have
two questions:
Why the type conversion? Is their some rule R uses in such a scenario?
Any way one can avoid this type conversion?
TIA.
Here is the code of lapply:
function (X, FUN, ...)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
Now check this:
!is.vector(v)
#TRUE
as.list(v)
#[[1]]
#[1] 4.940656e-324
#
#[[2]]
#[1] 9.881313e-324
#
#[[3]]
#[1] 1.482197e-323
From help("as.list"):
Attributes may be dropped unless the argument already is a list or
expression.
So, either you creaste a list from the beginning or you add the class attributes:
v_list <- lapply(as.list(v), function(x) {
class(x) <- "integer64"
x
})
sapply(v_list, function(x){is.integer64(x)})
#[1] TRUE TRUE TRUE
The package authours should consider writing a method for as.list. Might be worth a feature request ...