トップ > 学習コンテンツ > 簡単 GUI

簡単 GUI

S-PLUS のグラフィカル・ユーザー・インターフェースを作成すれば、S 言語に不慣れな方でも、グラフ作成や統計解析を簡単に行うことができます。ここでは、お客様からの要望が多い、ROC曲線を描かせるボタンを作成するプログラムを作成したいと思います。

(この関数は Windows 版S-PLUS 6.0 以上で稼動することができます。検証はしていませんが、それより下のバージョンあるいは、UNIX版 S-PLUS でも、2.で定義した関数は動作することと思います。)

グラフィカル・ユーザー・インターフェース作成までの段取り

  1. どのようなデータを想定するか、どのようなメニューを作成するか、決める
  2. そのグラフを描かせる関数を通常のS言語で定義する
  3. ダイアログを作成する
  4. ダイアログを表示させて実行するためのボタンを作る
  5. ボタンが不要になったとき、削除する関数を作る

順を追って、作成方法を見てみます。

1. どのようなデータを想定するか、どのようなメニューを作成するか、決める

データはデータ・フレーム(Excel ファイルをインポートしたものはデータ・フレームになります)を想定、そのうち1つの列が疾患群、別の1列がノーマルの生データとします(この辺はいつも使うデータの種類にあわせて関数の書き換えを行ってください)。

また、ダイアログを作成するときのことも考えておく必要があります。ダイアログでは、通常のグラフィカルインターフェースからグラフを作成するときと同様、データの名前と列の名前を2列、それから、データを集約する間隔数を指定できるようにします。他のグラフダイアログなどとあまり異なるようだと使いにくいですよね。それから、結果として得られる統計量なども保存可能なようにします。

2. そのグラフを描かせる関数を通常のS言語で定義する

S-PLUS スクリプト・ウィンドウで通常通り、関数を定義します。コマンド・ウィンドウで定義してもいいのですが、定義が長いのと、最終的に他のユーザーに使ってもらうことを前提とすれば、スクリプトにしておいたほうが便利でしょう。

menuROC <- function(data, disease, normal, nint=80)
{
    disease <- data[[disease]]
    # 疾患群
    normal <- data[[normal]]
    # ノーマル群
    x <- c(disease, normal)
    min.x <- min(x)
    max.x <- max(x)
    brks <- pretty(x, nint=nint)
    # 集計区切り位置の計算
    disease.f <- table(cut(disease, brks, left.include=T))
    # 疾患群データを集計
    names(disease.f) <- NULL
    disease.f <- c(disease.f, 0)
    normal.f <- table(cut(normal, brks, left.include=T))
    # ノーマルデータを集計
    names(normal.f) <- NULL
    normal.f <- c(normal.f, 0)
    result <- ROCfun(brks, disease.f, normal.f)
    # 度数になったデータを使って、次の関数で計算
    result
}

# 度数分布表から計算するための関数

ROCfun <- function(x, disease, normal)
{
    distribution <- cbind(x, disease, normal)
    k <- length(x)
    n.disease <- sum(disease)
    n.normal <- sum(normal)
    Sensitivity <- c(1,(n.disease-cumsum(disease))/n.disease)
    False.Positive.Rate <- c(1, (n.normal - cumsum(normal))/n.normal)
    Specificity <- 1-False.Positive.Rate
    plot(False.Positive.Rate, Sensitivity, type="b")
    abline(h=c(0, 1), v=c(0, 1))
    c.index <- 0 # area under ROC curve
    for (i in 1:k) { 
        c.index <- c.index+(False.Positive.Rate[i]-False.Positive.Rate[i+1])*(Sensitivity[i+1]+Sensitivity[i])/2
    }
    result <- cbind(x, disease, normal, Sensitivity[-k-1], Specificity[-k-1], False.Positive.Rate[-k-1])
    row.names(result) <- as.character(1:k)
    names(result) <- c("Value", "Disease", "Normal", "Sensitivity", "Specificity", "F.P. rate")
    result
}

この2つの関数をスクリプト・ファイルに記述します。そして、そのスクリプトファイルを実行すれば、2つの関数menuROCとROCfunが定義され、利用可能になります。

ここでのポイントは列の指定方法です。データ名は普通に指定するのに対して、2つの列の名前は文字列で指定します。つまり、メニューを使わずコマンド実行させるなら、次のようになります。データ名を data、疾患群を疾患、normal群を非疾患という列の名前だったとして、

menuROC(data, "疾患群", "非疾患群")

です。この場合、データはデフォルトで、最小値から最大値までを80個の間隔に分けて、集計します。間隔数を変えるなら、nint で指定します。

result <- menuROC(data, "疾患群", "非疾患群", 50)

とすると、結果は result という名前で保存されます。

3. ダイアログを作成する

これで、関数からROC 曲線を描かせるようにすることができましたが、S言語を知らないユーザーに使ってもらうのはちょっと難しいですね。そこで、ダイアログを設計します。ダイアログでは、1で想定したとおり、データ名と列を2列名前で選び、間隔数を数値で入力できるようにします。それから、結果に名前を付けて保存できるようにします。ダイアログ作成には、S-PLUS for Windows のプロパティ機能を用います。既存のダイアログ画面や、メニューバー、ツールバーのボタンなどもその実体はすべて、定義された関数にプロパティを与えたものです。

まず、関数の引数1つ1つについて、プロパティを作成し、データ名を選択したり、引数名を指定できるようにします。
ここでは、引数は data (解析したいデータを指定)、disease(疾患群となる列を指定)、normal(ノーマル群となる列を指定)、nint(集計したいデータの間隔数)の4つですから、4つの引数すべてにプロパティを作成します。プロパティの作成には、関数 guiCreate という特殊な関数を用います。このプロパティは関数同様、一度作成されれば、削除あるいは上書きされるまで、継続して用いられます。

まず、データを選択するため(引数 data)のダイアログにおける準備をします。

guiCreate("Property",
 Name = "ROCdata",
 DialogPrompt = "Data", 
 DialogControl="List Box",
 CopyFrom="TXPROP_DataFrames",
 IsRequired=T)

引数 data に対する、入力準備のためのプロパティを ROCdata とします。DialogPrompt で指定される文字列は、ダイアログにおけるフィールドの見出しです。DialogContol は値選択をリストから行えるようにしたものです。それから、特殊な TXPROP_DataFrames ですが、これはデータ選択を今自分がデータベースに持っているデータ・フレームの中から選択できるように内部的な値を利用しています。IsRequired=T はこの値が選択必須であることを示しています。

続いて、疾患群とノーマル群を選択あるいは入力できる準備をします。

guiCreate("Property",
 Name = "ROCcolumn1",
 DialogPrompt = "Disease Data",
 DialogControl = "List Box",
 CopyFrom="TXPROP_DataFrameColumns",
 UseQuotes = T,
 IsRequired=T)

guiCreate("Property",
 Name = "ROCcolumn2",
 DialogPrompt = "Normal Data",
 DialogControl = "List Box",
 CopyFrom="TXPROP_DataFrameColumns",
 UseQuotes = T,
 IsRequired=T)

引数 disease と normal に対する、入力準備のためのプロパティをそれぞれ、ROCcolumn1ROCcolumn2 とします。指定する内容はほぼ、ROCdata と同じですが、CopyFromTXPROP_DataFrameColumns と、指定されたデータ・フレームの列がリストアップされる設定にします。それからもう1つ重要なのが、引数 UseQuotes = T の存在で、これは指定された値を文字列として取得することを表します。関数 menuROC の引数 disease と normal は文字列を指定することを前提としていますね。

引数4番目の nint に対しては、デフォルト値を表示したまま、必要ならばデータ入力できるよう、準備をします。

guiCreate("Property",
 Name = "ROCnint",
 DialogPrompt = "No. on intervals", 
 DialogControl = "String",
 DefaultValue = 80)

入力準備のためのプロパティを ROCnint とします。DefaultValue = 80 でデフォルト値を表示させ、変えるときは手入力できるようにしています。

以上で引数のプロパティ作成は完了です。今度は関数返り値に対するプロパティを作成します。この関数 menuROC は必ずROC曲線を表示させますが、menuROC(...) と単独で実行すると、結果の統計量はコマンド・ウィンドウあるいは、レポート・ウィンドウに表示され、result <- menuROC(...) と実行すると、結果の統計量は result に保存されます。この result に相当する、結果を保存したい名前をダイアログ上で指定できるようにします。

guiCreate("Property",
 Name = "ROCretval",
 DialogPrompt = "Save As", 
 DialogControl="String")

フィールドの見出しは他のダイアログ(統計関連のものなど)と同様、Save As とし、文字入力が可能なようにします。

これで、引数と返り値、いわばダイアログのパーツに当たるもののプロパティが作成できました。次は、これらパーツを集めて関数menuROC に結びつけるものが必要ですが、これが FuctionInfo です。

guiCreate("FunctionInfo", 
 Name="menuROC",
 Function="menuROC",
 DialogHeader = "ROC",
 PropertyList = "ROCretval, ROCdata, ROCcolumn1, ROCcolumn2, ROCnint",
 ArgumentList = "#0 = ROCretval,#1 = ROCdata,#2 = ROCcolumn1,#3 = ROCcolumn2,#4 = ROCnint")

menuROC という名前の FuctionInfo を関数 menuROC に結び付けます。DialogHeader = "ROC" はダイアログの見出しの文字を指定します。PropertyList はどの Property をダイアログで使うかのリストで、先ほど作成した5つのプロパティを指定します。ArgumentList はどのプロパティをどの引数に与えるかを示すもので、#0 は返り値に、#1 以降は引数順に当てはめられます。Property FuctionInfo を作成したところで、ダイアログの表示が可能になります。ダイアログの表示は関数 guiDisplayDialogを用います。

        guiDisplayDialog("Function", "menuROC")

コマンド・ウィンドウなどから実行すると、ごくシンプルなダイアログが表示され、データ名や列名の指定、間隔数の指定、結果を保存するデータ名の指定が可能となります。また、Save As を空欄のまま実行すると、結果はレポート・ウィンドウに表示されます。

4. ダイアログを表示させて実行するためのボタンを作る

では、これらのプログラムをまとめて配布すれば、ユーザーさんは自分でダイアログを表示させてROC曲線を描画することができるようになります。でも、ちょっと待って、ユーザーさんはいつも、guiDisplayDialog というコマンドを実行させなければなりません。これを実行するだけでも、コマンド・ウィンドウからの入力が必要になってしまいます。

では、どうしたらよいでしょうか?

関数実行のショートカットに当たる、ボタンを作ってツールバーに表示させれば、ユーザーさんはそのボタンをクリックするだけですから、便利ですね。新しいツールバーを作って、そこにボタンを表示させることもできますが、そのツールバーの表示/非表示という問題がありますから、ここはひとつ、安直に標準ツールバーにボタンを載せてしまいましょう。

guiCreate( "ToolbarButton", 
 Name = "Standard$menuROC",
 Action = "Function",
 Command = "menuROC", 
 TipText="ROC Graph")

やはり、関数 guiCreate を用います。Name = "Standard$menuROC" で、標準ツールバーにボタン menuROC を作成します。Action = "Function", Command = "menuROC" とすることによって、ツールバーの menuROC に関数 menuROC が関連付けられます。最後の TipText="ROC Graph" は、マウスをカーソルに近づけたときに表示される文字を指定するもので、このボタンが何だったか分からなくなってしまったときの手がかりにすることができます。

5. ボタンが不要になったとき、削除する関数を作る

5 まででボタンの作成と実行は可能です。最後に、ボタンやProperty などを削除するための関数をまとめておきます。これでユーザーさんはツールバーなどをいつでも元に戻すことができるようになります。

guiRemove( "ToolbarButton", Name = "Standard$menuROC",ShiftLeft = F)
guiRemove("FunctionInfo", Name = "menuROC")
guiRemove("Property", Name = "ROCretval")
guiRemove("Property", Name = "ROCdata")
guiRemove("Property", Name = "ROCcolumn1")
guiRemove("Property", Name = "ROCcolumn2")
guiRemove("Property", Name = "ROCnint")

すべてのボタンやプロパティ, FuntionInfo などを削除します。ダイアログをより便利なように作り変えたときなど、利用すると便利でしょう。