Retrieving R object attributes in JavaScript - Par

2019-07-06 09:09发布

问题:

I posted a similar question earlier (Retrieving R object attributes in JavaScript). In that earlier post, I oversimplified my MWE, and so the answer I rewarded unfortunately does not really apply to my real problem. Here, I am showing why I may need to retrieve R object attributes in JavaScript (unless there is another option that I am not aware of).

I have a 5-variable dataset with 100 observations. I used hexagon binning and created a scatterplot matrix. Each of the 10 scatterplots has somewhere between 12-18 hexagons. In order to save the rows of the 100 observations that are in each of the hexagon bins for all 10 scatterplots, I used the base::attr function in R. In the code below, this is done at:

attr(hexdf, "cID") <- h@cID

I am trying to create an interactive R Plotly object of the hexagon binning so that if a user were to click on a given hexagon bin (regardless of which scatterplot), they would obtain the rows of the 100 observations that were grouped into that bin. I have part of this goal completed. My MWE is below:

library(plotly)
library(data.table)
library(GGally)
library(hexbin)
library(htmlwidgets)

set.seed(1)
bindata <- data.frame(ID = paste0("ID",1:100), A=rnorm(100), B=rnorm(100), C=rnorm(100), D=rnorm(100), E=rnorm(100))
bindata$ID <- as.character(bindata$ID)

maxVal = max(abs(bindata[,2:6]))
maxRange = c(-1*maxVal, maxVal)

my_fn <- function(data, mapping, ...){
  x = data[,c(as.character(mapping$x))]
  y = data[,c(as.character(mapping$y))]
  h <- hexbin(x=x, y=y, xbins=5, shape=1, IDs=TRUE, xbnds=maxRange, ybnds=maxRange)
  hexdf <- data.frame (hcell2xy (h),  hexID = h@cell, counts = h@count)
  attr(hexdf, "cID") <- h@cID
  p <- ggplot(hexdf, aes(x=x, y=y, fill = counts, hexID=hexID)) + geom_hex(stat="identity")
  p
}

p <- ggpairs(bindata[,2:6], lower = list(continuous = my_fn))
pS <- p
for(i in 2:p$nrow) {
  for(j in 1:(i-1)) {
    pS[i,j] <- p[i,j] +
      coord_cartesian(xlim = c(maxRange[1], maxRange[2]), ylim = c(maxRange[1], maxRange[2]))
  }
}

ggPS <- ggplotly(pS)

myLength <- length(ggPS[["x"]][["data"]])
for (i in 1:myLength){
  item =ggPS[["x"]][["data"]][[i]]$text[1]
  if (!is.null(item))
    if (!startsWith(item, "co")){
      ggPS[["x"]][["data"]][[i]]$hoverinfo <- "none"
    }
}

ggPS %>% onRender("
          function(el, x, data) {
          el = el;
          x=x;
          var data = data[0];
          console.log(el)
          console.log(x)
          console.log(data)

          myLength = Math.sqrt(document.getElementsByClassName('cartesianlayer')[0].childNodes.length);
          console.log(myLength)

          el.on('plotly_click', function(e) {
            console.log(e.points[0])
            xVar = (e.points[0].xaxis._id).replace(/[^0-9]/g,'')
            if (xVar.length == 0) xVar = 1
            yVar = (e.points[0].yaxis._id).replace(/[^0-9]/g,'')
            if (yVar.length == 0) yVar = 1
            myX = myLength + 1 - (yVar - myLength * (xVar - 1))
            myY = xVar

            cN = e.points[0].curveNumber
            split1 = (x.data[cN].text).split(' ')
            hexID = (x.data[cN].text).split(' ')[2]
            counts = split1[1].split('<')[0]

            console.log(myX)
            console.log(myY)
            console.log(hexID)
            console.log(counts)
          })}
           ", data = pS[5,2]$data)

This creates an image as shown below:

As an example, if I click on the hexagon highlighted in the green box, I can determine which subplot it occurred in ("myX" and "myY"), the ID of the hexagon clicked ("hexID"), and the number of observation points that were binned into that hexagon ("counts"). For this particular hexagon, myX=5, myY=2, hexID=39, and counts=1. So, the user just clicked on hexagon with ID39 in the scatterplot on the fifth row and second column and there should be 1 data point that it binned.

If I leave the onRender() function, and simply type into R the following code:

myX <- 5
myY <- 2
hexID <- 39
obsns <- which(attr(pS[myX,myY]$data, "cID")==hexID)
dat <- bindata[obsns,]

Then, I can obtain the row of the data frame that contains the one observation that was binned into that clicked hexagon:

> dat
     ID        A         B        C          D        E
95 ID95 1.586833 -1.208083 1.778429 -0.1101588 3.810277

My problem is simply in this last step. I am unable to figure out how to use the base::attr() function from within the onRender() function in order to obtain the "obsns" object. Is there any workaround for this issue, or another possible approach I should consider taking? Thank you for any ideas/advice!

回答1:

I'm not sure you can access the hex IDs from plotly or whether it keeps this data somewhere so one option is to pass all the data needed for your purpose to the onRender function.

First you could add to your bindata dataframe a column per hexplot, called mX-mY (where you replace mX and mY by their value for each column), that would hold for each observation the hexbin it belongs to for that plot:

for(i in 2:5) {
  for(j in 1:4) {
    bindata[[paste(i,j,sep="-")]] <- attr(pS[i,j]$data, "cID")
  }
}

You can then pass bindata to the onRender function and whever you click on a hexagon in one of the plot, check in the corresponding column in bindata which observations belong to that hexbin:

ggPS %>% onRender("
              function(el, x, data) {

              myLength = Math.sqrt(document.getElementsByClassName('cartesianlayer')[0].childNodes.length);


              el.on('plotly_click', function(e) {
              xVar = (e.points[0].xaxis._id).replace(/[^0-9]/g,'')
              if (xVar.length == 0) xVar = 1
              yVar = (e.points[0].yaxis._id).replace(/[^0-9]/g,'')
              if (yVar.length == 0) yVar = 1
              myX = myLength + 1 - (yVar - myLength * (xVar - 1))
              myY = xVar

              cN = e.points[0].curveNumber
              split1 = (x.data[cN].text).split(' ')
              hexID = (x.data[cN].text).split(' ')[2]
              counts = split1[1].split('<')[0]

              var selected_rows = [];

              data.forEach(function(row){
                if(row[myX+'-'+myY]==hexID) selected_rows.push(row);
              });
              console.log(selected_rows);

              })}
              ", data = bindata)