【问题标题】:Retrieving R object attributes in JavaScript - Part 2在 JavaScript 中检索 R 对象属性 - 第 2 部分
【发布时间】:2017-02-25 18:37:47
【问题描述】:

我之前发布了一个类似的问题 (Retrieving R object attributes in JavaScript)。在之前的那篇文章中,我过度简化了我的 MWE,因此不幸的是,我得到的答案并不真正适用于我的真正问题。在这里,我展示了为什么我可能需要在 JavaScript 中检索 R 对象属性(除非有另一个我不知道的选项)。

我有一个包含 100 个观察值的 5 变量数据集。我使用六边形分箱并创建了一个散点图矩阵。 10 个散点图中的每一个都有 12-18 个六边形。为了保存所有 10 个散点图的每个六边形箱中的 100 个观察值的行,我使用了 R 中的 base::attr 函数。在下面的代码中,这是在以下位置完成的:

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

我正在尝试创建六边形分箱的交互式 R Plotly 对象,以便如果用户单击给定的六边形箱(无论哪个散点图),他们将获得 100 个观察值的行分组到那个箱子。我已经完成了这个目标的一部分。我的 MWE 如下:

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)

这将创建一个如下所示的图像:

例如,如果我点击绿色框中突出显示的六边形,我可以确定它出现在哪个子图中(“myX”和“myY”),点击的六边形的 ID(“hexID”),以及被分箱到该六边形中的观察点的数量(“计数”)。对于这个特定的六边形,myX=5,myY=2,hexID=39,counts=1。因此,用户只需在第五行第二列的散点图中单击 ID39 的六边形,应该有 1 个数据点被分箱。

如果我离开 onRender() 函数,只需在 R 中输入以下代码:

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

然后,我可以获得数据框的行,该行包含一个观察值,该观察值被分箱到单击的六边形中:

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

我的问题只是在最后一步。我无法弄清楚如何从 onRender() 函数中使用 base::attr() 函数来获取“obsns”对象。这个问题是否有任何解决方法,或者我应该考虑采取其他可能的方法?感谢您的任何想法/建议!

【问题讨论】:

    标签: javascript r plotly htmlwidgets onrender


    【解决方案1】:

    我不确定您是否可以从 plotly 访问十六进制 ID,或者它是否将此数据保存在某处,因此一种选择是将用于您的目的所需的所有数据传递给 onRender 函数。

    首先,您可以在您的bindata 数据框中为每个十六进制图添加一列,称为mX-mY(您将每列的 mX 和 mY 替换为它们的值),这将为每个观察保留它所属的 hexbin情节:

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

    然后您可以将bindata 传递给onRender 函数,无论您在其中一个图中单击六边形,都可以在bindata 中的相应列中检查哪些观察属于该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)
    

    【讨论】:

    • 感谢您向我指出这一点。我已经为此工作了很长时间,我从没想过我可以像您演示的那样使用 attr() ,即使在您之前为一个子图展示了它之后也是如此。令我惊讶的是,这种方法似乎不会导致 onRender() 函数出现延迟。我将数据框大小从示例 100 更改为 50,000,当我单击给定的六边形时,它可以立即解析 50,000 的大数据框以获取单个观察值。我想 JavaScript 允许这种情况发生的速度比我预期的要快(我主要使用 R)?
    猜你喜欢
    • 2017-07-11
    • 1970-01-01
    • 2013-09-24
    • 2019-05-30
    • 2012-12-24
    • 2021-12-21
    • 2020-08-02
    • 1970-01-01
    相关资源
    最近更新 更多