How to get the name of the current variable inside a closure?

Please help solve the problem!
Is it possible to label the lines on the chart at the positions of the last values? As signatures, you need to use the index or name of the column instead of "x". The names are there!

plot(NULL, ylab="Values", xlab="Points",
     main="Title",
     xlim=c(1,28), ylim=c(0.0,1))
axis(1,at=seq(1,28,1))
apply(dataframe,2,function(v){
  c=8
  if(v[1]>v[14] && v[14]>v[28]){ c=4 }
  if(v[1]<v[14] && v[14]<v[28]){ c=3 }
  if(v[1]>v[14] && v[14]<v[28]){ c=2 }
  if(v[1]<v[14] && v[14]>v[28]){ c=5 }
  lines(seq(1,28),v,col=c)
  text(28,v[28],labels=c("x"),col=1)    # x=???
})

I don't think it is possible with apply(): the vector that is passed to the function is not named. That can be seen by running:

apply(dataframe,2,function(v){
  message("v:", print(v))
  message("names:", names(v))
  
})

you will see that the content of v gets printed properly, but the names are empty.

Instead, we can rely on the fact that a data.frame is a list of columns, so apply(dataframe , 2, FUN) is the same thing as lapply(dataframe, FUN), or what is practical in out case, the same thing as purrr::map(dataframe, FUN) ({purrr} is a tidyverse package, so it's likely you already it).

In that case, we can easily use the imap() or iwalk() variants that pass the names:

purrr::iwalk(dataframe,function(v, name_v){
  c=8
  if(v[1]>v[14] && v[14]>v[28]){ c=4 }
  if(v[1]<v[14] && v[14]<v[28]){ c=3 }
  if(v[1]>v[14] && v[14]<v[28]){ c=2 }
  if(v[1]<v[14] && v[14]>v[28]){ c=5 }
  lines(seq(1,28),v,col=c)
  text(28,v[28],labels=c(name_v),col=1)
})

If you want to stay with base R, you can do it with lapply() by looping on the column index, and passing all columns and all names as additional arguments:

lapply(seq_along(dataframe),
       function(i, all_cols, all_names){
         v <- all_cols[[i]]
         name_v <- all_names[[i]]
         
         c=8
         if(v[1]>v[14] && v[14]>v[28]){ c=4 }
         if(v[1]<v[14] && v[14]<v[28]){ c=3 }
         if(v[1]>v[14] && v[14]<v[28]){ c=2 }
         if(v[1]<v[14] && v[14]>v[28]){ c=5 }
         lines(seq(1,28),v,col=c)
         text(28,v[28],labels=c(name_v),col=1)
       },
       all_cols = dataframe,
       all_names = names(dataframe))
1 Like

Unfortunately it didn't work out. In the second variant
v<-all_cols[[i]] - is single value
v<-all_cols[,i] - out of range
v<-all_cols[,1] ... v<-all_cols[,36] - as a scalar it's worked var1 ... var36

name_v <- all_names [[i]] is NULL

and all_names is NULL

data_dens is dataframe
Screenshot from 2023-08-18 18-11-42

Looks to me like you were not using a data.frame, can you share a reprex? Check that it's really a data.frame with class(). From your screenshot, it looks like a matrix, you can convert it with as.data.frame().

For example, it works with this made-up data:

dataframe <- data.frame(a = 1:28,
                        b = 29:2)

plot(NULL, ylab="Values", xlab="Points",
     main="Title",
     xlim=c(1,28), ylim=c(0.0,29))

lapply(seq_along(dataframe),
       function(i, all_cols, all_names){
         v <- all_cols[[i]]
         name_v <- all_names[[i]]
         
         c=8
         if(v[1]>v[14] && v[14]>v[28]){ c=4 }
         if(v[1]<v[14] && v[14]<v[28]){ c=3 }
         if(v[1]>v[14] && v[14]<v[28]){ c=2 }
         if(v[1]<v[14] && v[14]>v[28]){ c=5 }
         lines(seq(1,28),v,col=c)
         text(28,v[28],labels=c(name_v),col=1)
       },
       all_cols = dataframe,
       all_names = names(dataframe))

out_plot

#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL

Created on 2023-08-18 with reprex v2.0.2

dataframe<-apply(data,2,function(c){
  vhd <- c()
  for(i in 1:28){    if(c[i] > 0){
      vhd <- c(vhd,rep(i,c[i]))
    }
  }
  W=1
  dens<-density(vhd,kernel='gaussian', adjust = 10, from=1, to=28, n=28, bw=W)$y
  return(dens/max(dens))
})

Yep, that's a matrix: density()$y is a vector, apply() assembles the vectors as a matrix.

Easiest is to convert with as.data.frame().

Or it should also work if you just add simplify = FALSE to apply():

dataframe<-apply(data,2,
                 function(c){
                   vhd <- c()
                   for(i in 1:28){    if(c[i] > 0){
                     vhd <- c(vhd,rep(i,c[i]))
                   }
                   }
                   W=1
                   dens<-density(vhd,kernel='gaussian', adjust = 10, from=1, to=28, n=28, bw=W)$y
                   return(dens/max(dens))
                 },
                 simplify = FALSE)

That way a list is returned, you can directly use it to loop on "columns".

Thanks a lot! Almost won! However, without this condition, an out-of-range error occurs.

lapply(seq_along(data_dens),function(i, all_cols, all_names){
 if(i<=36){ #  <- Patch  :((
  v <- all_cols[[i]]
  name_v <- all_names[[i]]
  c=8
  if(v[1]>v[12] && v[16]>v[28]){ c=4 }
  if(v[1]<v[12] && v[16]<v[28]){ c=3 }
  if(v[1]>v[12] && v[16]<v[28]){ c=2 }
  if(v[1]<v[12] && v[16]>v[28]){ c=5 }
  lines(seq(1,28),v,col=c)
  text(28,v[28],labels=c(name_v),col=1)
  }},
  all_cols=as.data.frame(data_dens),
  all_names=names(as.data.frame(data_dens))
)

I'm not sure what's happening just looking at the code. What's length(data_dens)? What's sapply(data_dens, length)? Which line of code is causing the error?

small sidenote: you could directly use colnames(data_dens) to avoid a conversion that's not really necessary.

Error in .subset2(x, i, exact = exact) : subgroup out of bounds

Line error
v <- all_cols[[i]]

Traceback:
9. (function(x, i, exact) if (is.matrix(i)) as.matrix(x)[[i]] else .subset2(x, i, exact = exact))(x, ..., exact = exact)
8. [[.data.frame(all_cols, i) at
density.r#37
7. all_cols[[i]] at
density.r#37
6. FUN(X[[i]], ...)
5. lapply(seq_along(data_dens), function(i, all_cols, all_names) { v <- all_cols[[i]] name_v <- all_names[[i]] c = 8 ... at
density.r#36
4. eval(ei, envir)
3. eval(ei, envir)
2. withVisible(eval(ei, envir))

  1. source("~/density.r")

While with the condition, it works

max(i) = 1008 = 28 * 36
length(data_dens) = 1008 = 28 * 36
length(all_cols) = 36
length(all_names) = 36

This solved the problem:
!!!
seq_along(as.data.frame(data_dens))
!!!

lapply(seq_along(as.data.frame(data_dens)),function(i, all_cols, all_names){
  v <- all_cols[,i]
  name_v <- all_names[[i]]
  c=8
  if(v[1]>v[12] && v[16]>v[28]){ c=4 }
  if(v[1]<v[12] && v[16]<v[28]){ c=3 }
  if(v[1]>v[12] && v[16]<v[28]){ c=2 }
  if(v[1]<v[12] && v[16]>v[28]){ c=5 }
  lines(seq(1,28),v,col=c)
  text(28,v[28],labels=c(name_v),col=1,cex=0.5)
  },
  all_cols=as.data.frame(data_dens),
  all_names=colnames(data_dens)
)
1 Like

Of course! this is relying on the fact that a data.frame has a length, which is the number of columns, whereas the length of a matrix is the total number of elements. Good catch!

Thanks Alexis!
You helped a lot!

1 Like

This topic was automatically closed 42 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.