これで無理なら諦めて!世界一やさしいデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

Shinyでクラスタリングの楽しさを体感できるアプリケーション制作

本記事は、Shiny Advent Calendar 2017の19日目の記事です。


Shiny100本ノック第19弾!

今回は、データ分析のあまり経験が無い人に、クラスタリングの面白さを体験してもらえるようなアプリケーションを作ってみます。

shinyapps.ioに上げてみました。試してみてください!(無料プランなので停止していましたらすいません…。)
https://randpy-testserver.shinyapps.io/clustering/


作った機能としては、

  • ファイルをアップロード
  • どの列を元にクラスタリングするか選択
  • クラスタリングの手法を選択
  • プロット

という流れで、任意のファイルのデータを元にクラスタリングを行うことができます。

なお、ソースコードはGithubにも置いてあります。
github.com

完成形イメージ

まずは、完成形のイメージを共有します。

基本画面は以下の通りです。
f:id:Np-Ur:20171216180556p:plain

ここでファイルをアップロードすると、以下のようにテーブルが表示されるとともに、どの列をもってクラスタリングを行うか選択できるようになります。(図の例ではirisデータを使っています)
f:id:Np-Ur:20171216181038p:plain

次に、

  • 階層的クラスタリング(最長距離法)
  • 非階層的クラスタリング(k-means)

のどちらかからクラスタリングの種類、そしてクラスターの数を選択します。

すると、下図のようにクラスタリングの結果が色付けされて表示されます。
f:id:Np-Ur:20171216180725p:plain

クラスタリングの種類やクラスターの数を変えて再実行すると、表示がすぐに反映されるので、結構面白いアプリケーションなのではと思います!

それではどのようにこのShinyアプリケーションを作るのか、次節で見ていきましょう!

ソースコード解説

まずはui.Rのソースコードを貼ります。

ui.Rのソースコード

library(shiny)

shinyUI(fluidPage(
  sidebarPanel(
    h2("クラスタリング"),
    fileInput("file", "Choose CSV File",
              accept = c(
                "text/csv",
                "text/comma-separated-values,text/plain",
                ".csv")
    ),
    tags$hr(),
    h2("プロットするデータを選択"),
    htmlOutput("colname1"),
    htmlOutput("colname2"),
    selectInput("clustering_method", "クラスタリングの種類", 
                c("階層的(complete)" = "hclust", "非階層的(k-means)" = "k-means")),
    numericInput("number", "何個のクラスターに分類?", 5,
                 min = 1, max = 20),
    actionButton("submit", "プロット")
  ),
  mainPanel(
    tabsetPanel(type = "tabs",
                tabPanel("Table", tableOutput('table')),
                tabPanel("Plot", plotOutput("plot"))
    )
  )
))

まず、fileInputメソッドでファイルアップロード機能を作ることができます。fileInputメソッドについては、以下記事で紹介しているのでご覧ください。
www.randpy.tokyo
www.randpy.tokyo

その後、

selectInput("clustering_method", "クラスタリングの種類", 
            c("階層的(complete)" = "hclust", "非階層的(k-means)" = "k-means")),
numericInput("number", "何個のクラスターに分類?", 5,
             min = 1, max = 20),
actionButton("submit", "プロット")

こちらの部分にて、どんなクラスタリングを行うかを選択しています。

今回は、最長距離法とkmeansのみですが、ここを増やすともっと面白い機能になると思います。

また以下にて、

  • アップロードされたファイルをテーブル表示
  • クラスタリングの結果をプロット

を作っています。

mainPanel(
  tabsetPanel(type = "tabs",
              tabPanel("Table", tableOutput('table')),
              tabPanel("Plot", plotOutput("plot"))
  )
)


この辺りは非常によく出てくる書き方ですね。

server.Rのソースコード

次に、server,Rを紹介します。

library(shiny)

server = function(input, output, session) {
  
  observeEvent(input$file, {
    
    csv_file = reactive(read.csv(input$file$datapath))
    output$table = renderTable(csv_file())
    
    output$colname1 = renderUI({ 
      selectInput("x", "x軸方向", colnames(csv_file()))
    })
    output$colname2 = renderUI({ 
      selectInput("y", "y軸方向", colnames(csv_file()))
    })
  })
  
  observeEvent(input$submit, {
    cols = colorRampPalette(c("#0068b7","white","#f39800"))
    
    csv_file = reactive(read.csv(input$file$datapath))
    
    x = csv_file()[input$x]
    y = csv_file()[input$y]
    
    data = cbind(x, y)
    
    if(input$clustering_method == "hclust"){
      hc = hclust(dist(data))
      clusters = cutree(hc, input$number)
      color = clusters
    }
    else{ #select k-means
      clusters = kmeans(data, input$number)
      color = clusters$cluster
    }
    
    output$plot = renderPlot({
      plot(data, col = color, pch = 20, cex = 3)
    })
  })
}

前半部分は、以下で紹介した内容なので割愛させていただきます。
www.randpy.tokyo

こちらの部分では、ui.R側で選択されたクラスタリングの手法に基づいて、処理を分岐しています。
階層的クラスタリングを行うhclustメソッドと、kmeansでは若干返り値など異なるので、その辺りが少し面倒でした。

if(input$clustering_method == "hclust"){
  hc = hclust(dist(data))
  clusters = cutree(hc, input$number)
  color = clusters
}
else{ #select k-means
  clusters = kmeans(data, input$number)
  color = clusters$cluster
}

上で処理した内容を元に、以下でプロットをし、ui.R側に渡しています。

output$plot = renderPlot({
  plot(data, col = color, pch = 20, cex = 3)
})

まとめ

今回は、クラスタリングを簡単に行うことができるShinyアプリケーションを作ってみました。
https://randpy-testserver.shinyapps.io/clustering/

試してみると、「あーこんな風に結果が変わっていくのかー」と、結構面白いです。

構成としては、以前行ったファイルアップロードさえできれば非常にシンプルだと思います。
是非試してみてください!