R Shiny : 外部データ CSVファイルの読み込み

Shinyで統計処理のUIを構築するときに必ず必要となると思われるのが、外部データの読み込み画面です。
今回は、外部データの読み込みから、読み込んだデータの表示までを取り扱います。実際に作っている際に気づいた点などをまとめます。

fileInput によるデータの取り込み

Shinyの公式リファレンスでもFile Upload Control としてこのfileInputの利用が紹介されています。

基本的な書式は以下の通りです。

fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL, buttonLabel = "Browse...", placeholder = "No file selected")

inputId:(必須)このコントロールの名称であり読み込まれるファイルの変数名(ID)を”ダブル(or シングル)コーテーション”で囲って指定します。例:inputId = "sampleInput" 格納されたこのファイルを取り出す時は、input$変数名とします。

label:(必須)コントロールの上に表示するラベルを指定します。表示しない(ブランク表示の)場合は label=""としてください。この項目は必須なので何らかの指定がないとエラーになります。

multiple:複数のファイルを同時にアップロードを許すかどうか。IE9以降などの新たらし目のブラウザーでしか有効になりません。

accept:どのようなタイプのファイルを受け付けるかをMIME形式*1で指定します。

width:ファイルインプットUIの幅を指定します。400pxなどピクセル幅での指定や、100%などでの幅指定も可能です。

buttonLabel:ボタンのラベルです。

placeholder:ファイルが選ばれる前に箱の中に表示されるコメント。

具体的には以下のような形となります。

fileInput(inputId = "file1", label = "CSVファイルを選んでください",
accept = c("text/csv")
,multiple = FALSE
,width = "80%"
,buttonLabel = "ホルダーを開く"
,placeholder = "ファイルが選択されていません"
)

TIPS

フィルタイプの指定にバグ?

ちなみに、このコントロールをテストしていて、acceptの指定がうまく機能していないので理由を調べていたら、どうやらRStudio内蔵ブラウザーにはacceptで指定した種類に関わらずどんなフィアルでも読み込んでしまうバグがあるようです。RStudioの付属ブラウザーで機能テストするときには注意してください。Chromeなどのブラウザーではきちんと指定したタイプ以外のファイルは選択できないように機能しますので一安心。

実際どのようにファイルタイプを指定すればよいの?

もう一つ気づいた点ですが、Shinyのオフィシャルリファレンスでは例えばCSVファイルの読み込みに次のように3つのスタイルを設定しています。ほかのサイトを見ても概ね同じようなスタイルで読み込みのファイルタイプを指定していることが多いようです。

accept = c( "text/csv",
 "text/comma-separated-values,text/plain",
 ".csv")

公式マニュアルはMIME形式で指定すればよいとしていますので"text/csv"だけで良さそうで、同じような3つのスタイルを指定するのはなんだか冗長な感じがします。
試しに一つだけを指定して読み込みをしてみましたが、3つのうちどれでも特に問題なくファイルタイプの指定ができることが確認できました。

ご心配な方は公式マニュアル通り指定いただくことでもよいと思いますが、もう少しすっきりとしたプログラムがお好みということであればどれか一つで十分かと思います。
このあたりは詳しい方からアドバイスいただけると参考になります。

ドラッグ&ドロップによる外部ファイルの読み込み

ネットでファイル選択でのドラッグ&ドロップを実現する方法を検索するとjavascriptにより実装する方法を紹介している記事を見かけます*2。実はこのfileInputはドラッグ&ドロップに標準で対応しています。ウインドウズならファイルエクスプローラーから、MACならFinderから読み込むファイルをドラッグしてfileInputのUIにドロップするだけで読み込むことが可能です。

この機能はShinyのバージョンアップで対応したものですが、今後もますます機能が改善されていくことを期待しています。RStudioの皆さんよろしくお願いします。

read.csvによるcsvファイルのテーブルへの読み込み

上記のfileInputでは、外部ファイルをShiny内部に取り込むこと(Upload)
ができましたが、この段階ではまだデータが分析できる形にはなっていませんので、次のステップでは、取り込んだファイルをデータ分析に使えるようにテーブル形式で読み込むことが必要になります。

このコマンドがread.csv()になります。この関数はShinyの組み込み関数ではなく、Rの関数として提供されています。

基本的な書式は以下の通りです。

df <- read.csv("yyy.csv", header = TRUE)

ファイルの一行目がカラム名の場合はheader = TRUEとします。

似たような関数にread.table()があります。こちらは基本的に上記read.csvと同じ機能がありますがより汎用的になっています。read.tableでcsvファイルを読み込むときはデータがカンマで区切られていることを指定する、sep=","
を指定すればOKです。

df <- read.table("yyy.csv", header = TRUE, sep = ',')

これでdfにcsvファイルがテーブルとして読み込まれました。これで各種分析を行う準備が整ったことになります。

チョット待った!

先ほどはcsvファイルの読み込みにはfileInputを使ったのでinputIdで指定した変数名にファイルが格納されているはずだが、上記のサンプルでは直接csvを読み込むようでどこにもinputIdで読み込んだファイルが使われてませんが・・・・? 先の説明では、input$変数名とするはずでは・・・?

ご理解通りです、Rの基本関数read.csv()ではcsvファイル名を 直接指定して(ShinyのUIでファイルを選ぶなどの画面がない状態で)読み込むことができます。ただし、先ほど読み込んだShinyのUI経由で読み込む場合には、input$変数名をread.csv()に渡す必要があります。

具体的には以下の通りに処理します。

  # csvファイルの読み込み
inFile1 <- input$file1
df <- read.csv(inFile1$datapath, header=TRUE)

少々解説しますと、fileInputinputId = "file1"と指定したとします。先ほど解説に記載した通り、読み込んだファイルはinput$file1input$がついた形で取り出します。2行目で、わかりやすくするためにこれをいったんinFile1という変数に取り込んであります。

ここでチョットややこしいのですが、input$file1(=inFile1)で取り込んだ変数はそのままではデータが取り出せず、その中にdatapathという変数として実際のデータが格納されています。
すなわち、inFile1$datapathとしてやっとcsvのデータが取り出すことができます。このあたり詳しく知りたい方は英語になりますが
fileInputの公式マニュアルをご参照ください。もちろんここをまとめて一行で以下のように書いてしまうことも可能です。

  # csvファイルの読み込み
df <- read.csv(input$file1$datapath, header=TRUE)

これでやっとデータの選択から分析がするためのテーブルとして取り出す準備が整いました。

まとめ

以上を踏まえて外部ファイルの読み込みから出力までのサンプルプログラムをご紹介します。以下のコードをShinyで実行してみてください。画面の左側(サイドバー)にファイル指定ができる箱(fileInput)が表示されますね。ここでファイルを指定して読み込むと画面の右側(メイン)に取り込んだcsvの内容がテーブル形式で表示されます。

library(shiny)
library(DT) # テーブルをきれいに出すパッケージ
ui <- fluidPage(
# Program title
titlePanel("FileInput と Drag & Dropのテスト画面です"),
sidebarLayout(
sidebarPanel(
# csv ファイルのアップロード
fluidRow(fileInput(inputId = "file1", label = "Choose CSV File",
accept = c("text/csv")
,multiple = FALSE
,width = "80%"
,buttonLabel = "ホルダーを開く"
,placeholder = "ファイルが選択されていません"
)
),
# レンダリングされたテーブルの画面への表示
mainPanel(
dataTableOutput("outInFile") # server側: renderDataTable とのセットで利用
)
)
)
server <- function(input, output, session) {
# csvファイルのテーブル読み込み
inFile <- reactive({
inFile1 <- input$file1
if (is.null(inFile1)){
return(NULL)
} else {
df <- read.csv(inFile1$datapath, header=TRUE)
return(df)
}
})
# 読み込んだデータを表示形式にレンダリング
output$outInFile <- DT::renderDataTable({ # DT:: はなくてもOK
data.frame(inFile())
})
}
shinyApp(ui, server)

CSVの表示が大きすぎて画面をはみ出してしまって見にくい・・・スクロールバーをつけられたらいいのに・・・と思われたかもしれません。実は簡単にテーブルをかっこよくスタイリングすることも可能なんです。テーブルの見栄えの変更なども今後ご説明できればと思います。一方でこのDTというパッケージを使えば何も指定していなくても、ページングやサーチ、ソートのボタンがついているので結構このままでも使えたりします。

Shinyに初見という方にはチョットわかりにくい部分もあったかもしれませんが、私がShinyを学び初めて気づいた点をまとめてみました。お役に立てれば幸いです。

おや?

ここまでに説明した内容よりもずいぶんと余分なコードがついているじゃないか
と感じられた方もいるのではないでしょうか。

はい・・・例えば、読み込んだファイルをテーブルに変更する部分では、このうち2行分しか説明をしておりませんでした。

以下のコードのうち4行目以降はアップロードされたファイルの存在確認(エラー処理)なのでやや実践的な部分です。

  # csvファイルのテーブル読み込み
inFile <- reactive({
inFile1 <- input$file1
if (is.null(inFile1)){
return(NULL)
} else {
df <- read.csv(inFile1$datapath, header=input$header)
return(df)
}
})

一方で、2行めのreactiveの部分がShinyの核心部分です。実はここがないとこのプログラムはエラーで動きません。
Shinyのプログラムを作る際に最も難しいのが「リアクティビティー」と呼ばれる部分です。UIコントロールを中心とした”入力に反応して動く部分”から得られた
情報を使う場合は「リアクティブ環境」の中で使うことが決まりです。(難しく考えずにいえば、UIパーツの中や、observeEvent(), reactive()などの中で使う必要があります。)

Shinyは入力に変更があると自動で反応してくれる点が素晴らしいところですが、一方で予期しないところが反応(「発火」と呼ばれます)したりしてその理由を探すのに苦労したりもします。
ここではこれ以上リアクティビティーに深入りしませんので、詳細はほかのサイトや書籍をご参照いただくか、公式チュートリアル
をご参照ください。このブログでも後ほどまとめてみたいと思います。

難しいことは考えたくない、動くものがほしいという(エンジニアリングな方は)このあたりのコードを適宜コピペして使ってください。
いくつかプログラムを作っていくうちにより深く理解できるようになりますので。

一点だけ補足させていただきますと、2行目の部分、inFile = reactive(…コード…)で取り出したデータテーブルですが、これを使う場合にはinFile()と変数名の後にカッコをつけることを忘れずに(プログラムの41行目です)。このあたりは作法的なものでリアクティブ変数を使うときはこのカッコをつける必要あるからです。このあたりを忘れてプログラムを組むとエラーが出てくるので気づくことが多いです。

それと39行目以降のデータのレンダリング部分ですが今回説明していませんが、、、よろしいですね。そろそろ疲れてきたのでまたの機会にします。


*1:MIMEの一覧は例えば、MIME Typeを参照。

*2:stackoverflow.comにいくつか見かけますし、日本語では情報豊富なここにあります。