Dynamically accessing globals through string interpolation - julia

You can call variables through a loop in Python like this:
var1 = 1
var2 = 2
var3 = 3
for i in range(1,4):
print(globals()[f"var{i}"])
This results in:
1
2
3
Now I'm looking for the equivalent way to call variables using interpolated strings in Julia! Is there any way?
PS: I didn't ask "How to get a list of all the global variables in Julia's active session". I asked for a way to call a local variable using an interpolation in a string.

PS: this is dangerous.
Code:
var1 = 1
var2 = 2
var3 = 3
for i in 1:3
varname = Symbol("var$i")
println(getfield(Main, varname))
end

List of globals:
vars = filter!(x -> !( x in [:Base, :Core, :InteractiveUtils, :Main, :ans, :err]), names(Main))
Values of globals:
getfield.(Ref(Main), vars)
To display names and values you can either just use varinfo() or eg. do DataFrames(name=vars,value=getfield.(Ref(Main), vars)).

You don't. Use a collection like an array instead:
julia> values = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
julia> for v in values
println(v)
end
1
2
3
As suggested in earlier comments, dynamically finding the variable names really isn't the approach you want to go for.

Related

R: Make function change dataset [duplicate]

I am trying to write a function that will add a new column to a data frame, when I call it, without doing any explicit assignment.
i.e I just want to call the function with arguments and have it modify the data frame:
input_data:
x y
1 2
2 6
column_creator<-function(data,column_name,...){
data$column_name <- newdata ...}
column_creator(input_data,new_col,...)
x y new_col
1 2 5
2 6 9
As opposed to:
input_data$new_col <- column_creator(input_data,new_col,...)
However doing assignment inside the function is not modifying the global variable.
I am working around this by having the function return a statement of assignment (temp in the function below), however is there another way to do this?
Here is my function for reference, it should create a column of 1s inbetween the supplied start and end date with the name dummy_name.
dummy_creator<-function(data,date,dummy_name,start,end){
temp<-paste(data,"['",dummy_name,"'] <- ifelse(",data,"['",date,"'] > as.Date (","'" , start,"'" , ", format= '%Y-%m-%d') & ",data,"['",date,"'] < as.Date(", "'", end,"'" ,",format='%Y-%m-%d') ,1,0)",sep="")
print(temp)
return()
}
Thanks
I also tried:
dummy_creator<-function(data,date,dummy_name,start,end){
data[dummy_name] <<- ifelse(data[,date] > as.Date (start, format= "%Y-%m-%d") & data[,date] < as.Date(end,format="%Y-%m-%d") ,1,0)
}
But that attempt gave me error object of type closure is not subsettable.
It’s generally a bad idea to modify global data or data passed into a function: R objects are immutable, and using tricks to modify them inside a function breaks the user’s expectations and makes it harder to reason about the program’s state.
It is good form to return the modified object instead:
input_data = column_creator(input_data, new_col, …)
That said, you have a few options. Generally, R has several mechanisms to allow modifiable objects. I recommend you look into R6 classes for this.
You could also use non-standard evaluation to capture the passed object and modify it at the caller’s site. However, this is rarely advisable. I’m posting an example of this here because the mechanism is interesting and worth knowing, but I’ll reiterate that you shouldn’t use it here.
function (df, new_col, new_data) {
# Get the unevaluated expression representing the data frame
df_obj = substitute(df)
new_col = substitute(new_col)
# Ensure that the input is valid
stopifnot(is.name(df_obj))
stopifnot(is.name(new_col))
stopifnot(is.data.frame(df))
# Add new column to data frame
df[[deparse(new_col)]] = new_data
# Assign back to object in caller scope
assign(deparse(df_obj), df, parent.frame())
invisible(df)
}
test = data.frame(A = 1 : 5, B = 1 : 5)
column_creator(test, C, 6 : 10)
test
# A B C
# 1 1 1 6
# 2 2 2 7
# 3 3 3 8
# 4 4 4 9
# 5 5 5 10

Using data.table function in lapply on a list with data.frames elements (Answer = setDT)

First question, let me know if more info or background is needed in the comments please.
Many answers on here and elsewhere deal with calling lapply in a data.table function. I want to do the opposite, which on paper should be easy lapply(list.of.dfs, fun(x) x) but I cant get it to work with data.table functions.
I have a list that contains several data.frames with the same columns but differing numbers of rows. This comes from the output of several simulation scenarios so they must be treated seperately and not rbind'ed.
#sample list of data.frames
scenarios <- replicate(5, data.frame(a=sample(letters[1:4],10,T),
b=sample(1:2,10,T),
x=sample(1:10, 10),
y =runif(10)), simplify = FALSE)
I want to add a column to every element that is the sum of x/y by a and b.
From the data.table documentation in the examples section the process to do this for one data.frame is the following (search: add new column by reference by group in the doc page):
test <- as.data.table(scenarios[[1]]) #must specify data.table class
test[, newcol := sum(x/y), by = .(a , b)][]
I want to use lapply to do the same thing to every element in the scenarios list and return the list.
My most recent attempt:
lapply(scenarios, function(i) {as.data.table(i[, z := sum(x/y), by=.(a,b)]); i})
but I keep getting the error unused argument (by = .a,b))
After pouring over the results of this and other sites I have been unable to solve this problem. Which I'm fairly sure means that there is something I dont understand about calling anonymous functions, and/or using the data.table function. Is this one of those cases where one you use the [ as the function? Or possibly my as.data.table is out of place.
This answer was a step in the right direction (I think), it covers the use of fun(x) {... ; x} to use an anonymous function and return x.
Thanks!
You can use setDT here instead.
scenarios <- lapply(scenarios, function(i) setDT(i)[, z := sum(x/y), by=.(a,b)])
scenarios[[1]]
a b x y z
1: c 2 2 0.87002174 2.298793
2: b 2 10 0.19720775 78.611837
3: b 2 8 0.47041670 78.611837
4: b 2 4 0.36705023 78.611837
5: a 1 5 0.78922686 12.774035
6: a 1 6 0.93186209 12.774035
7: b 1 3 0.83118438 3.609307
8: c 1 1 0.08248658 30.047494
9: c 1 7 0.89382050 30.047494
10: c 1 9 0.89172831 30.047494
Using as.data.table, the syntax would be
scenarios <- lapply(scenarios, function(i) {i <- as.data.table(i); i[, z := sum(x/y),
by=.(a,b)]})
but this wouldn't be recommended as it will create an additional copy, which is avoided by setDT.

Calling & creating new columns based on string

I have searched quite a bit and not found a question that addresses this issue--but if this has been answered, forgive me, I am still quite green when it comes to coding in general. I have a data frame with a large number of variables that I would like to combine & create new variables from based on names I've put in a 2nd data frame in a loop. The data frame formulas should create & call columns from the main data frame data
USDb = c(1,2,3)
USDc = c(4,5,6)
EURb = c(7,8,9)
EURc = c(10,11,12)
data = data.frame(USDb, USDc, EURb, EURc)
Now I'd like to create a new column data$USDa as defined by
data$USDa = data$USDb - data$USDc
and so on for EUR and other variables. This is easy enough to do manually, but I'd like to create a loop that pulls the names from formulas, something like this:
a = c("USDa", "EURa")
b = c("USDb", "EURb")
c = c("USDc", "EURc")
formulas = data.frame(a,b,c)
for (i in 1:length(formulas[,a])){
data$formulas[i,a] = data$formulas[i,b] - data$formulas[i,c]
}
Obviously data$formulas[i,a] this returns NULL, so I tried data$paste0(formulas[i,a]) and that returns Error: attempt to apply non-function
How can I get these strings to be recognized as variables in this way? Thanks.
There are simpler ways to do this, but I'll stick to most of your code as a means of explanation. Your code should work so long as you edit your for loop to the following:
for (i in 1:length(formulas[,"a"])){
data[formulas[i,"a"]] = data[formulas[i,"b"]] - data[formulas[i,"c"]]
}
formulas[,a] won't work because you have a variable defined as a already that is not appropriate inside an index. Use formulas[, "a"] instead if you want all rows from column "a" in data.frame formulas.
data$formulas is literally searching for the column called "formulas" in the data.frame data. Instead you want to write data[formulas](of course, knowing that you need to index formulas in order to make it a proper string)
logic : iterate through each of the formulae, using a apply which is a for loop internally, and do calculation based on the formula
x = apply(formulas, 1, function(x) data[[x[3]]] - data[[x[2]]])
colnames(x) = formulas$a
x
# USDa EURa
#[1,] 3 3
#[2,] 3 3
#[3,] 3 3
cbind(data, x)
# USDb USDc EURb EURc USDa EURa
#1 1 4 7 10 3 3
#2 2 5 8 11 3 3
#3 3 6 9 12 3 3
Another option is split with sapply
sapply(setNames(split.default(as.matrix(formulas[-1]),
row(formulas[-1])), formulas$a), function(x) Reduce(`-`, data[rev(x)]))
# USDa EURa
#[1,] 3 3
#[2,] 3 3
#[3,] 3 3

Modify global data from within a function in R

I am trying to write a function that will add a new column to a data frame, when I call it, without doing any explicit assignment.
i.e I just want to call the function with arguments and have it modify the data frame:
input_data:
x y
1 2
2 6
column_creator<-function(data,column_name,...){
data$column_name <- newdata ...}
column_creator(input_data,new_col,...)
x y new_col
1 2 5
2 6 9
As opposed to:
input_data$new_col <- column_creator(input_data,new_col,...)
However doing assignment inside the function is not modifying the global variable.
I am working around this by having the function return a statement of assignment (temp in the function below), however is there another way to do this?
Here is my function for reference, it should create a column of 1s inbetween the supplied start and end date with the name dummy_name.
dummy_creator<-function(data,date,dummy_name,start,end){
temp<-paste(data,"['",dummy_name,"'] <- ifelse(",data,"['",date,"'] > as.Date (","'" , start,"'" , ", format= '%Y-%m-%d') & ",data,"['",date,"'] < as.Date(", "'", end,"'" ,",format='%Y-%m-%d') ,1,0)",sep="")
print(temp)
return()
}
Thanks
I also tried:
dummy_creator<-function(data,date,dummy_name,start,end){
data[dummy_name] <<- ifelse(data[,date] > as.Date (start, format= "%Y-%m-%d") & data[,date] < as.Date(end,format="%Y-%m-%d") ,1,0)
}
But that attempt gave me error object of type closure is not subsettable.
It’s generally a bad idea to modify global data or data passed into a function: R objects are immutable, and using tricks to modify them inside a function breaks the user’s expectations and makes it harder to reason about the program’s state.
It is good form to return the modified object instead:
input_data = column_creator(input_data, new_col, …)
That said, you have a few options. Generally, R has several mechanisms to allow modifiable objects. I recommend you look into R6 classes for this.
You could also use non-standard evaluation to capture the passed object and modify it at the caller’s site. However, this is rarely advisable. I’m posting an example of this here because the mechanism is interesting and worth knowing, but I’ll reiterate that you shouldn’t use it here.
function (df, new_col, new_data) {
# Get the unevaluated expression representing the data frame
df_obj = substitute(df)
new_col = substitute(new_col)
# Ensure that the input is valid
stopifnot(is.name(df_obj))
stopifnot(is.name(new_col))
stopifnot(is.data.frame(df))
# Add new column to data frame
df[[deparse(new_col)]] = new_data
# Assign back to object in caller scope
assign(deparse(df_obj), df, parent.frame())
invisible(df)
}
test = data.frame(A = 1 : 5, B = 1 : 5)
column_creator(test, C, 6 : 10)
test
# A B C
# 1 1 1 6
# 2 2 2 7
# 3 3 3 8
# 4 4 4 9
# 5 5 5 10

How to sort a matrix by all columns

Suppose I have
arr = 2 1 3
1 2 3
1 1 2
How can I sort this into the below?
arr = 1 1 2
1 2 3
2 1 3
That is, first by column one, then by column two etc.
The function you're after is order (how I arrived at this conclusion -- my first thought was "well, sorting, what about sort?". Tried sort(arr) which looks like it sorts arr as a vector instead of row-wise. Looking at ?sort, I see in the "See Also: order for sorting on or reordering multiple variables.").
Looking at ?order, I see that order(x,y,z, ...) will order by x, breaking ties by y, breaking further ties by z, and so on. Great - all I have to do is pass in each column of arr to order to do this. (There is even an example for this in the examples section of ?order):
order( arr[,1], arr[,2], arr[,3] )
# gives 3 2 1: row 3 first, then row 2, then row 1.
# Hence:
arr[ order( arr[,1], arr[,2], arr[,3] ), ]
# [,1] [,2] [,3]
#[1,] 1 1 2
#[2,] 1 2 3
#[3,] 2 1 3
Great!
But it is a bit annoying that I have to write out arr[,i] for each column in arr - what if I don't know how many columns it has in advance?
Well, the examples show how you can do this too: using do.call. Basically, you do:
do.call( order, args )
where args is a list of arguments into order. So if you can make a list out of each column of arr then you can use this as args.
One way to do this is is to convert arr into a data frame and then into a list -- this will automagically put one column per element of the list:
arr[ do.call( order, as.list(as.data.frame(arr)) ), ]
The as.list(as.data.frame is a bit kludgy - there are certainly other ways to create a list such that list[[i]] is the ith column of arr, but this is just one.
This would work:
arr[do.call(order, lapply(1:NCOL(arr), function(i) arr[, i])), ]
What it is doing is:
arr[order(arr[, 1], arr[, 2], arr[ , 3]), ]
except it allows an arbitrary number of columns in the matrix.
I wrote this little func that does decreasing order as well
cols allows to choose which columns to order and their order
ord.mat = function(M, decr = F, cols = NULL){
if(is.null(cols))
cols = 1: ncol(M)
out = do.call( "order", as.data.frame(M[,cols]))
if (decr)
out = rev(out)
return(M[out,])
}
I had a similar problem, and solution seems to be simple and elegant:
t(apply(t(yourMatrix),2,sort))

Resources