6

スライダーを動かすか、テキスト ボックスに値を入力することでビンの幅を調整できる R でインタラクティブなヒストグラムを作成しようとしています。これに加えて、特定のビン幅のプロットを保存するオプションをユーザーに提供したいと思います。

このために、「aplpack」ライブラリの「gslider」機能が出発点として適していることがわかりました。Tcl/Tk コンストラクトについてさらに学ぶだけでなく、目的に合わせて変更しようとしました。しかし、主にスライダーの値がどのようにキャプチャされ、関数間で転送されるかを完全に理解していないため、立ち往生して先に進むことができません。

以下は、私がよく理解していないコードのスニペットです。これらは「gslider」関数のソース コードからのものです。

# What is the rationale behind using the 'assign' function here and at 
# other instances in the code?

  img <- tkrplot::tkrplot(gr.frame, newpl, vscale = 1, hscale = 1)
  tkpack(img, side = "top")
  assign("img", img, envir = slider.env)

# I understand the below lines when considered individually. But collectively,
# I am having a difficult time comprehending them. Most importantly, where 
# exactly is the slider movement captured here?

  sc <- tkscale(fr, from = sl.min, to = sl.max, 
              showvalue = TRUE, resolution = sl.delta, orient = "horiz")
  assign("sc", sc, envir = slider.env)
  eval(parse(text = "tkconfigure(sc, variable=inputbw1)"), envir = slider.env)
  sl.fun <- sl.function
  if (!is.function(sl.fun)) 
    sl.fun <- eval(parse(text = paste("function(...){", 
                                    sl.fun, "}")))
    fname <- 'tkrrsl.fun1'
    eval(parse(text = c(paste(fname, " <-"), " function(...){", 
                    "tkrreplot(get('img',envir=slider.env),fun=function()", 
                    deparse(sl.fun)[-1], ")", "}")))
    eval(parse(text = paste("environment(", fname, ")<-parent.env")))
    if (prompt) 
      tkconfigure(sc, command = get(fname))
    else tkbind(sc, "<ButtonRelease>", get(fname))

  if (exists("tkrrsl.fun1")) {
    get("tkrrsl.fun1")()
  } 
  assign("slider.values.old", sl.default, envir = slider.env)

さまざまな範囲の回答を寄せてくれた皆さんに感謝します。Juba と Greg の回答は、次のコードを作成するために使用できるものでした。

slider_txtbox <- function (x, col=1, sl.delta, title) 
{
  ## Validations
  require(tkrplot)
  pos.of.panel <- 'bottom'
  if(is.numeric(col))
    col <- names(x)[col]
  x <- x[,col, drop=FALSE]
  if (missing(x) || is.null(dim(x))) 
     return("Error: insufficient x values")
  sl.min <- sl.delta # Smarter initialization required
  sl.max <- max(x)
  xrange <- (max(x)-min(x))
  sl.default <- xrange/30
  if (!exists("slider.env")) {
    slider.env <<- new.env(parent = .GlobalEnv)    
  }
  if (missing(title)) 
    title <- "Adjust parameters"

  ## Creating initial dialogs
  require(tcltk)
  nt <- tktoplevel()
  tkwm.title(nt, title)
  if(.Platform$OS.type == 'windows')
    tkwm.geometry(nt, "390x490+0+10")
  else if(.Platform$OS.type == 'unix')
     tkwm.geometry(nt, "480x600+0+10")
  assign("tktop.slider", nt, envir = slider.env)
  "relax"
  nt.bak <- nt
  sl.frame <- tkframe(nt)
  gr.frame <- tkframe(nt)
  tx.frame <- tkframe(nt)
  tkpack(sl.frame, tx.frame, gr.frame, side = pos.of.panel)

  ## Function to create and refresh the plot
  library(ggplot2)
  library(gridExtra)
  makeplot <- function(bwidth, save) {
    if(bwidth <= 0) {
      df <- data.frame('x'=1:10, 'y'=1:10)
       histplot <- ggplot(df, aes(x=x, y=y)) + geom_point(size=0) + xlim(0, 10) +  ylim(0, 100) + 
    geom_text(aes(label='Invalid binwidth...', x=5, y=50), size=9)
    } else {

    histplot <- ggplot(data=x, aes_string(x=col)) +
  geom_histogram(binwidth=bwidth, aes(y = ..density..), fill='skyblue') + 
  theme(axis.title.x=element_text(size=15), axis.title.y=element_text(size=15), 
        axis.text.x=element_text(size=10, colour='black'),
        axis.text.y=element_text(size=10, colour='black'))
    }
    print(histplot)
    if(save){
  filename <- tkgetSaveFile(initialfile=paste('hist_bw_', bwidth, sep=''), 
                            filetypes='{{PNG files} {.png}} {{JPEG files} {.jpg .jpeg}}
                            {{PDF file} {.pdf}} {{Postscript file} {.ps}}')
  filepath <- as.character(filename)
  splitpath <- strsplit(filepath, '/')[[1]]
  flname <- splitpath[length(splitpath)]
  pieces <- strsplit(flname, "\\.")[[1]]
  ext <- tolower(pieces[length(pieces)])
  if(ext != 'png' && ext != 'jpeg' && ext != 'jpg' && ext != 'pdf' && ext != 'ps') {
    ext <- 'png'
    filepath <- paste(filepath, '.png', sep='')
    filename <- tclVar(filepath)
  }
  if(ext == 'ps')
    ext <- 'postscript'
  eval(parse(text=paste(ext, '(file=filepath)', sep='')))
  eval(parse(text='print(histplot)'))
  dev.off()
}
  }
  img <- tkrplot::tkrplot(gr.frame, makeplot(sl.default, FALSE), vscale = 1, hscale = 1)
  tkpack(img, side = "top")
  assign("img", img, envir = slider.env)

  ## Creating slider, textbox and labels
  parent.env <- sys.frame(sys.nframe() - 1)
  tkpack(fr <- tkframe(sl.frame), side = 'top')
  sc <- tkscale(fr, from = sl.min, to = sl.max, 
            showvalue = TRUE, resolution = sl.delta,
            orient = "horiz")
  tb <- tkentry(fr, width=4)
  labspace <- tklabel(fr, text='\t\t\t')
  tkpack(sc, labspace, tb, side = 'left')

  tkpack(textinfo <- tkframe(tx.frame), side = 'top')
  lab <- tklabel(textinfo, text = '                    Move slider', width = "20")
  orlabel <- tklabel(textinfo, text='          OR', width='10')
  txtboxmsg <- tklabel(textinfo, text = 'Enter binwidth', width='20')
  tkpack(txtboxmsg, orlabel, lab, side='right')

  tkpack(f.but <- tkframe(sl.frame))
  tkpack(tklabel(f.but, text=''))
  tkpack(tkbutton(f.but, text = "Exit", command = function() tkdestroy(nt)), 
     side='right')
  tkpack(tkbutton(f.but, text = "Save", command = function(...) {
    bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
    tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, TRUE); sync_slider()})
  }), side='right')

  ## Creating objects and variables associated with slider and textbox
  assign("sc", sc, envir = slider.env)
  eval(parse(text = "assign('inputsc', tclVar(sl.default), envir=slider.env)"))
  eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)

  assign("tb", tb, envir = slider.env)
  eval(parse(text = "assign('inputtb', as.character(tclVar(sl.default)),
         envir=slider.env)"))
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)

  ## Function to update the textbox value when the slider has changed
  sync_textbox <- function() {
  bwidth_sl <- tclvalue(get('inputsc', envir=slider.env))
  assign('inputtb', tclVar(bwidth_sl), envir=slider.env)
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
}

 ## Function to update the slider value when the textbox has changed
 sync_slider <- function() {
 bwidth_tb <- tclvalue(get('inputtb', envir=slider.env))
 assign('inputsc', tclVar(bwidth_tb), envir=slider.env)
 eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
}  

  ## Bindings : association of certain functions to certain events for the slider
  ## and the textbox

  tkbind(sc, "<ButtonRelease>", function(...) {
    bwidth <- as.numeric(tclvalue(get('inputsc', envir=slider.env)))
    tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, FALSE); sync_textbox()})
  })

  tkbind(tb, "<Return>", function(...) {
    bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
    if(bwidth > sl.max && !is.na(bwidth)) {
      bwidth <- sl.max
      assign('inputtb', tclVar(bwidth), envir=slider.env)
      eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
     } else
    if(bwidth < sl.min || is.na(bwidth)) {
      bwidth <- sl.min
      assign('inputtb', tclVar(bwidth), envir=slider.env)
      eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
     }
  tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, FALSE);    sync_slider()})
})

}

library(ggplot2)
slider_txtbox(movies, 'rating', 0.1, 'Adjust binwidth') 
4

4 に答える 4

2

これは、最初に送信した完全なコードに基づいた、コメント付きの最小限の作業例です。私は tcl/tk の専門家とはほど遠いので、よりクリーンな、またはより良い方法があるかもしれません。そして、それはかなり不完全です(たとえば、テキストボックスの値がスライダーの範囲内にあることを確認する必要があるなど):

library(ggplot2)
library(gridExtra)
title <- "Default title"
data(movies)

## Init dialog
require(tkrplot)
if (!exists("slider.env")) slider.env <<- new.env(parent = .GlobalEnv)
require(tcltk)
nt <- tktoplevel()
tkwm.title(nt, title)
tkwm.geometry(nt, "480x600+0+10")
assign("tktop.slider", nt, envir = slider.env)
"relax"
nt.bak <- nt
sl.frame <- tkframe(nt)
gr.frame <- tkframe(nt)
tx.frame <- tkframe(nt)
tkpack(sl.frame, tx.frame, gr.frame, side = "bottom")
## First default plot
newpl <- function(...) {
  dummydf <- data.frame('x'=1:10, 'y'=1:10)
  dummy <- ggplot(dummydf, aes(x=x, y=y)) + geom_point(size=0) + xlim(0, 10) + ylim(0, 100) + 
    geom_text(aes(label='Generating plot...', x=5, y=50), size=9)
  print(dummy)
  }
img <- tkrplot::tkrplot(gr.frame, newpl, vscale = 1, hscale = 1)
tkpack(img, side = "top")
assign("img", img, envir = slider.env)
tkpack(fr <- tkframe(sl.frame), side = 'top')

## Creating slider, textbox and labels
sc <- tkscale(fr, from = 0, to = 5, showvalue = TRUE, resolution = 0.1, orient = "horiz")
tb <- tkentry(fr, width=4)
lab <- tklabel(fr, text = 'Select binwidth ', width = "16")
orlabel <- tklabel(fr, text=' or ', width='4')
tkpack(lab, sc, orlabel, tb, side = 'left')
tkpack(textinfo <- tkframe(tx.frame), side = 'top')


## Creating objects and variables associated with slider and textbox
assign("sc", sc, envir = slider.env)
assign("tb", tb, envir = slider.env)
assign('inputsc', tclVar(2.5), envir=slider.env)
assign('inputtb', tclVar('2.5'), envir=slider.env)
eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)

## Function to update the textbox value when the slider has changed
sync_textbox <- function() {
  bwidth_sl <- tclvalue(get('inputsc', envir=slider.env))
  assign('inputtb', tclVar(bwidth_sl), envir=slider.env)
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
}

## Function to update the slider value when the textbox has changed
sync_slider <- function() {
  bwidth_tb <- tclvalue(get('inputtb', envir=slider.env))
  assign('inputsc', tclVar(bwidth_tb), envir=slider.env)
  eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
}

## Function to refresh the plot
refresh <- function(bwidth) {
  histplot <- ggplot(data=movies, aes_string(x="rating")) +
     geom_histogram(binwidth=bwidth, 
                    aes(y = ..density..), fill='skyblue') + 
                      theme(axis.title.x=element_text(size=15), axis.title.y=element_text(size=15), 
                            axis.text.x=element_text(size=10, colour='black'),
                            axis.text.y=element_text(size=10, colour='black'))
  print(histplot)
}

## Bindings : association of certain functions to certain events for the slider
## and the textbox

tkbind(sc, "<ButtonRelease>", function(...) {
  bwidth <- as.numeric(tclvalue(get('inputsc', envir=slider.env)))
  tkrreplot(get('img',envir=slider.env),fun=function() { refresh(bwidth); sync_textbox()})
})

tkbind(tb, "<Return>", function(...) {
  bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
  tkrreplot(get('img',envir=slider.env),fun=function() { refresh(bwidth); sync_slider()})
})
于 2013-01-31T13:08:33.820 に答える
2

If you do not insist on a local solution, you might give rapporter.net a try, which lets you specify such tasks easily with any number of tweakable sliders. Okay, enough of marketing :)

Here goes a quick demo: Interactive histogram on mtcars which looks like:

Interactive histogram demo on rapporter.net

There you could choose one of the well-know variables of mtcars, but of course you could provide any data frame to be used here or tweak the above form after a free registration.


How it's done? I have just created a quick rapport template and let it rapplicate. The body of the template is written in brew-style (please see the above "rapport" URL for more details):

<%=
evalsOptions('width', width)
evalsOptions('height', height)
%>

# Histogram

<%=
set.caption(paste('Histogram of', var.name))
hist(var, breaks=seq(min(var), max(var), diff(range(var))/round(binwidth)), main = paste('Histogram of', var.name), xlab = '')
%>

## Parameters

Provided parameters were:

  * variable: <%=var.name%> (<%=var.label%>)
  * bin-width of histogram: <%=binwidth%>
  * height of generated images: <%=height%>
  * width of generated images: <%=width%>

# Kernel density plot

<%=
set.caption('A kernel density plot')
plot(density(var), main = '', xlab = '')
%>

But a bare-minimal example of the task could be also addressed by a simple one-liner template:

<%=hist(var, breaks=seq(min(var), max(var), diff(range(var))/round(binwidth)))%>

There you would only need to create a new template, add two input types with a click (one numeric variable of any data set and a number input field which would hold the binwidth of the histogram), and you are ready to go.

于 2013-01-31T09:23:33.680 に答える
2

R パッケージ 'rpanel' を参照してください。内部では tcltk を使用していますが、使用するのははるかに簡単です。

パネル

rpanel リファレンス

于 2013-01-31T18:11:18.870 に答える
1

私は gslider の機能を知らず、そこであなたを助けることはできませんが、いくつかの代替手段があります:

1 つの簡単なオプションはtkexamp、TeachingDemos パッケージの関数を使用することです。1 つの方法を次に示します。

library(TeachingDemos)

myhist <- function(x, s.width, e.width, ...) {
    if( missing(e.width) || is.null(e.width) || is.na(e.width) ) {
        e.width<- s.width
    }
    b <- seq( min(x)-e.width/2, max(x)+e.width, by=e.width )
    hist(x, b, ...)
}

mylist <- list( s.width=list('slider', init=1, from=1, to=10, resolution=1),
    e.width=list('numentry', init='', width=7)
)

sampdata <- rnorm(100, 50, 5)
tkexamp(myhist(sampdata), mylist)

これにより、ヒストグラムとスライダーとエントリ ウィジェットを備えたクイック GUI が作成されます。バーの幅はエントリ ウィジェットの値によって決定され、それが空白 (デフォルト) の場合は、スライダーの値によって決定されます。残念ながら、スライダーとエントリ ウィジェットは相互に更新されません。現在の呼び出しを印刷するボタンがあるため、同じプロットをデフォルトまたは現在のプロット デバイスのコマンド ラインから再作成できます。mylist上記の変数を編集して、コントロールがデータにより適合するようにすることができます。

エントリとスライダーを相互に更新する場合は、それをより直接的にプログラムできます。を使用する基本的な関数を次に示しますtkrplot

mytkhist <- function(x, ...) {

    width <- tclVar()
    tclvalue(width) <- 1

    replot <- function(...) {
        width <- as.numeric(tclvalue(width))
        b <- seq( min(x) - width/2, max(x)+width, by=width )
        hist(x,b,...)
    }

    tt <- tktoplevel()
    img <- tkrplot(tt, replot)
    tkpack(img, side='top')

    tkpack( tkscale(tt, variable=width, from=1, to=10,
        command=function(...) tkrreplot(img),
        orient='horizontal'), side='top' )
    tkpack( e <- tkentry(tt, textvariable=width), side='top' )
    tkbind(e, "<KeyRelease>", function(...) tkrreplot(img))
}

mytkhist(sampdata)

スライダー (スケール) とエントリ ウィジェットの両方が同じ変数を使用するという事実により、それらは自動的に相互に更新されます (呼び出しはassign必要ありません)。command引数 intkscaleと呼び出しはtkbind、スライダーまたはエントリのいずれかを変更すると、プロットが更新されることを意味します。これには現在のプロットを保存するものは何もありませんが、その部分と使用したい他のコントロールを追加できるはずです。

于 2013-01-31T18:06:16.023 に答える