Np-Urのデータ分析教室

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

Shiny上で作ったファイルをダウンロード! downloadButtonメソッドの使い方

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


Shiny100本ノックも区切りの良い第20弾となりました!

第19弾の前回は、Shiny上でクラスタリングを行うアプリケーションを作ってみました。
www.randpy.tokyo
こんな風にShiny上で色々データをいじくる作業をしていると、そこだけで完結せずに手元にダウンロードして確認&深堀りしてみたいですよね?

ということで……。
今回は、Shiny上で作ったファイルをダウンロードする方法を紹介します。

前回クラスタリングを行ったアプリケーションをベースにして、csvダウンロード機能をつけてみます。
先に紹介しておきますが、ui.R側ではdownloadButtonメソッドを、server.R側ではdownloadHandlerメソッドを使います。

完成形イメージ

どんなShinyアプリケーションが作れるのか、スクショを用いて説明します。
f:id:Np-Ur:20171220072122p:plain
こちらが前回行ったクラスタリング後の画面です。

「Download」ボタンを押すと……
f:id:Np-Ur:20171220071905p:plain

このようにcsvファイルがダウンロードされます。

コードを紹介

それでは、ソースコードを紹介します。

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", "プロット"),
    downloadButton('downloadData', 'Download') # 追加部分
  ),
  mainPanel(
    tabsetPanel(type = "tabs",
                tabPanel("Table", tableOutput('table')),
                tabPanel("Plot", plotOutput("plot"))
    )
  )
))

追加したのは以下の一行だけです。

downloadButton('downloadData', 'Download')

あとはserver.R側で、downloadDataの中身を設定していけばOKです。

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)
    })
  })
  #以下が追加部分
  output$downloadData = downloadHandler(
    filename = "clustering.csv",
    content = function(file) {
      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)
      }
      else{ #select k-means
        clusters = kmeans(data, input$number)
        clusters = clusters$cluster
      }
      
      data_with_clusters = cbind(data, clusters)
      write.csv(data_with_clusters, file)
    }
  )
}

追加したのは以下の部分です。

output$downloadData = downloadHandler(
  filename = "clustering.csv",
  content = function(file) {
    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)
    }
    else{ #select k-means
      clusters = kmeans(data, input$number)
      clusters = clusters$cluster
    }
    
    data_with_clusters = cbind(data, clusters)
    write.csv(data_with_clusters, file)
  }
)

変更箇所が分かりやすいように、クラスタリングを行う処理を、ダウンロードボタンが押されたときも走らせてあります。しかし本当はこの処理は無駄なので、別で計算しておくのが良いです。まあ、今回は分かりやすさ重視ということで……。

ポイントは以下のような構成にすることです。

output$downloadData = downloadHandler(
  filename = "ファイル名",
  content = function(file) {
    #ファイル出力するためのコード
    ## write.csv()やwrite.tabel()やwriteDocなど。
  }

このように、downloadHandlerメソッドを使い、downloadData変数を作ってui.R側に返してあげると、ダウンロードすることができます。
本当に簡単ですね!

最後に

今回はファイルダウンロード機能を作成しました。

以前にパワーポイントダウンロードをさらっと紹介しているので、そちらも是非ご覧ください。
www.randpy.tokyo

前述した通り、今回紹介したコードは冗長な部分が多いので、もし実践する場合は修正してみてください!