第14章: インタラクティブ可視化

plotlyとShinyによる動的データ探索

🎮 インタラクティブ 📊 plotly連携 ⚡ リアルタイム

🎮 plotlyによるインタラクティブ可視化

plotlyは、ggplot2で作成したグラフを簡単にインタラクティブに変換できる強力なパッケージです。ズーム、パン、ツールチップ、フィルタリングなどの機能を追加できます。

ggplot2からplotlyへの変換

基本的なplotly変換
library(plotly) library(ggplot2) library(dplyr) library(htmlwidgets) # インタラクティブ分析用データの作成 set.seed(123) interactive_data <- tibble( company_id = 1:200, company_name = paste0("企業", sprintf("%03d", 1:200)), industry = sample(c("IT", "製造業", "金融", "小売", "サービス"), 200, replace = TRUE), revenue = round(rlnorm(200, log(1000), 1)), employees = round(rlnorm(200, log(100), 0.8)), profit_margin = round(rnorm(200, 15, 8), 1), satisfaction = round(rnorm(200, 4.0, 0.8), 1), founded_year = sample(1980:2020, 200, replace = TRUE) ) %>% mutate( revenue_per_employee = round(revenue / employees * 10, 1), company_age = 2023 - founded_year, size_category = case_when( employees < 50 ~ "小企業", employees < 200 ~ "中企業", TRUE ~ "大企業" ), # カスタムツールチップ用のテキスト tooltip_text = paste0( "会社名: ", company_name, "<br>", "業界: ", industry, "<br>", "売上: ¥", format(revenue, big.mark = ","), "M<br>", "従業員数: ", format(employees, big.mark = ","), "人<br>", "利益率: ", profit_margin, "%<br>", "設立年: ", founded_year, "年" ) ) # 1. 基本的なggplot2からplotlyへの変換 basic_ggplot <- interactive_data %>% ggplot(aes(x = revenue, y = profit_margin, color = industry, size = employees, text = tooltip_text)) + geom_point(alpha = 0.7) + scale_x_log10(labels = scales::label_currency(prefix = "¥", suffix = "M")) + scale_size_continuous(range = c(3, 15), guide = "none") + labs( title = "企業分析: 売上 vs 利益率", subtitle = "業界別・規模別の企業パフォーマンス比較", x = "売上高(百万円)", y = "利益率(%)", color = "業界" ) + theme_minimal() + theme( plot.title = element_text(size = 16, face = "bold"), plot.subtitle = element_text(size = 12), legend.position = "bottom" ) # plotlyに変換 interactive_plot <- ggplotly( basic_ggplot, tooltip = "text", # カスタムツールチップを使用 width = 800, height = 600 ) %>% layout( title = list( text = "企業分析: 売上 vs 利益率<br><sub>インタラクティブ版 - クリック・ドラッグで操作可能</sub>", font = list(size = 16) ), hovermode = "closest", showlegend = TRUE ) %>% config( displayModeBar = TRUE, modeBarButtonsToRemove = c("lasso2d", "select2d"), displaylogo = FALSE, toImageButtonOptions = list( format = "png", filename = "企業分析チャート", width = 1200, height = 800 ) ) print("基本的なインタラクティブプロットが作成されました") # 2. 高度なインタラクティブ機能 # 複数のサブプロットを持つダッシュボード subplot_data <- interactive_data %>% group_by(industry, size_category) %>% summarise( avg_revenue = mean(revenue), avg_profit_margin = mean(profit_margin), count = n(), .groups = 'drop' ) # サブプロット1: 業界別平均売上 p1 <- subplot_data %>% plot_ly( x = ~industry, y = ~avg_revenue, color = ~size_category, type = "bar", text = ~paste0("¥", round(avg_revenue), "M"), textposition = "outside", hovertemplate = "<b>%{x}</b><br>企業規模: %{fullData.name}<br>平均売上: ¥%{y:.0f}M<extra></extra>" ) %>% layout( title = "業界・規模別平均売上", xaxis = list(title = "業界"), yaxis = list(title = "平均売上(百万円)") ) # サブプロット2: 規模別利益率分布 p2 <- interactive_data %>% plot_ly( y = ~profit_margin, color = ~size_category, type = "box", boxpoints = "outliers" ) %>% layout( title = "企業規模別利益率分布", xaxis = list(title = "企業規模"), yaxis = list(title = "利益率(%)") ) # サブプロット3: 時系列トレンド trend_data <- interactive_data %>% group_by(founded_year, industry) %>% summarise(new_companies = n(), .groups = 'drop') %>% filter(founded_year >= 2000) p3 <- trend_data %>% plot_ly( x = ~founded_year, y = ~new_companies, color = ~industry, type = "scatter", mode = "lines+markers", line = list(width = 3), marker = list(size = 8) ) %>% layout( title = "業界別企業設立トレンド(2000年以降)", xaxis = list(title = "設立年"), yaxis = list(title = "新設企業数") ) # サブプロットを組み合わせ combined_dashboard <- subplot( p1, p2, p3, nrows = 3, heights = c(0.35, 0.3, 0.35), titleY = TRUE, titleX = TRUE ) %>% layout( title = "企業分析統合ダッシュボード", showlegend = TRUE, height = 900 ) print("統合ダッシュボードが作成されました")
plotlyインタラクティブ化結果
[1] "基本的なインタラクティブプロットが作成されました" [1] "統合ダッシュボードが作成されました" # plotlyの主要機能: - ホバーツールチップ: 詳細情報の表示 - ズーム・パン: 自由な範囲選択と移動 - レジェンド操作: データ系列の表示/非表示 - データポイント選択: クリックによる個別選択 - 画像エクスポート: PNG/SVG形式での保存 # ダッシュボード構成: - 複数チャートの統合表示 - 統一されたカラーパレット - インタラクティブな凡例連動 - レスポンシブレイアウト
plotlyインタラクティブ機能デモ

🎮 インタラクティブグラフエリア

ここに実際のplotlyグラフが表示されます
• マウスホバーで詳細情報
• ドラッグでズーム・パン
• ダブルクリックでリセット
• 凡例クリックで系列切り替え

コントロールパネル
透明度調整

カスタムインタラクション

高度なカスタムインタラクション
# カスタムボタンとドロップダウンを含むインタラクティブチャート create_advanced_interactive <- function(data) { # データ変換ボタンの設定 transform_buttons <- list( list( method = "restyle", args = list("transforms[0].value", "linear"), label = "線形スケール" ), list( method = "relayout", args = list("xaxis.type", "log"), label = "対数スケール" ) ) # アニメーション用の年度別データ animation_data <- data %>% mutate(frame = cut(founded_year, breaks = 5, labels = c("1980-1988", "1989-1996", "1997-2004", "2005-2012", "2013-2020"))) # メインプロット p <- animation_data %>% plot_ly( x = ~revenue, y = ~employees, size = ~profit_margin, color = ~industry, frame = ~frame, ids = ~company_id, text = ~company_name, type = "scatter", mode = "markers", marker = list( sizemode = "diameter", sizeref = 2, sizemin = 4, line = list(width = 2, color = "white") ), hovertemplate = "<b>%{text}</b><br>売上: ¥%{x:,.0f}M<br>従業員: %{y:,.0f}人<br>利益率: %{marker.size:.1f}%<extra></extra>" ) %>% # レイアウト設定 layout( title = list( text = "企業パフォーマンス分析(時代別アニメーション)", x = 0.5 ), xaxis = list( title = "売上高(百万円)", type = "linear", range = c(0, max(data$revenue) * 1.1) ), yaxis = list( title = "従業員数(人)", range = c(0, max(data$employees) * 1.1) ), # カスタムボタンメニュー updatemenus = list( list( type = "buttons", direction = "left", x = 0.1, y = 1.15, buttons = list( list( method = "relayout", args = list("xaxis.type", "linear"), label = "線形" ), list( method = "relayout", args = list("xaxis.type", "log"), label = "対数" ) ) ), # 業界フィルタ list( type = "dropdown", x = 0.5, y = 1.15, buttons = c( list(list(method = "restyle", args = list("visible", TRUE), label = "すべて表示")), lapply(unique(data$industry), function(ind) { visible <- data$industry == ind list(method = "restyle", args = list("visible", visible), label = ind) }) ) ) ), # アニメーション設定 sliders = list( list( active = 0, currentvalue = list(prefix = "時代: "), steps = lapply(unique(animation_data$frame), function(f) { list( args = list("frame", list(duration = 300, redraw = TRUE)), label = f, method = "animate", value = f ) }) ) ) ) %>% # アニメーション設定 animation_opts( frame = 1000, transition = 300, redraw = TRUE ) %>% # 設定 config( displayModeBar = TRUE, modeBarButtonsToAdd = c("drawline", "drawopenpath", "drawclosedpath", "eraseshape"), displaylogo = FALSE ) return(p) } # 高度なインタラクティブプロットの作成 advanced_plot <- create_advanced_interactive(interactive_data) print("高度なカスタムインタラクションが追加されました") # クロスフィルタリング用のデータ準備 crossfilter_data <- interactive_data %>% select(company_name, industry, revenue, employees, profit_margin, satisfaction) # HTMLウィジェットとしての保存設定 widget_options <- list( width = "100%", height = "600px", elementId = "interactive-business-chart" ) print("ウィジェット設定が完了しました")
高度なインタラクション結果
[1] "高度なカスタムインタラクションが追加されました" [1] "ウィジェット設定が完了しました" # 高度なインタラクティブ機能: - アニメーション: 時系列での変化表示 - カスタムボタン: スケール切り替え、フィルタ - ドロップダウンメニュー: 動的データ選択 - スライダー: アニメーション制御 - 描画ツール: 注釈・マーキング機能 # 企業分析特化機能: - 業界別フィルタリング - 企業規模による色分け - 多次元データの同時表示 - カスタムツールチップ情報
🎯 ホバーツールチップ
マウスオーバーで詳細情報を表示。HTMLフォーマット対応でリッチな情報提示が可能。
hovertemplate = "<b>%{text}</b><br>値: %{y:.2f}<extra></extra>"
🔍 ズーム・パン
マウスドラッグでのズーム、パン操作。特定範囲の詳細分析に最適。
config(scrollZoom = TRUE, doubleClick = 'reset')
🎮 カスタムボタン
データ変換、フィルタリング、表示切り替えのためのカスタムUI要素。
updatemenus = list(type = "buttons", buttons = [...])
🎬 アニメーション
時系列データの動的表示。データの変化パターンを効果的に表現。
animation_opts(frame = 1000, transition = 300)
📊 サブプロット
複数のチャートを統合したダッシュボード表示。包括的な分析視点を提供。
subplot(p1, p2, nrows = 2, shareX = TRUE)
💾 エクスポート
PNG、SVG、PDF形式での画像保存。プレゼンテーション資料に最適。
toImageButtonOptions = list(format = "png", filename = "chart")

⚡ Shinyによるリアルタイムダッシュボード

基本的なShinyアプリケーション

Shiny + plotlyダッシュボード
library(shiny) library(shinydashboard) library(DT) # Shiny UIの定義 dashboard_ui <- dashboardPage( # ヘッダー dashboardHeader(title = "企業分析ダッシュボード"), # サイドバー dashboardSidebar( sidebarMenu( menuItem("概要", tabName = "overview", icon = icon("dashboard")), menuItem("詳細分析", tabName = "analysis", icon = icon("chart-line")), menuItem("データテーブル", tabName = "data", icon = icon("table")) ), # フィルタコントロール div(style = "padding: 20px;", h4("フィルタ設定"), selectInput( inputId = "industry_filter", label = "業界選択:", choices = c("すべて" = "all", "IT", "製造業", "金融", "小売", "サービス"), selected = "all", multiple = TRUE ), sliderInput( inputId = "revenue_range", label = "売上範囲(百万円):", min = 0, max = 5000, value = c(0, 5000), step = 100 ), sliderInput( inputId = "employee_range", label = "従業員数範囲:", min = 1, max = 1000, value = c(1, 1000), step = 10 ), checkboxInput( inputId = "show_trend", label = "トレンドライン表示", value = TRUE ), actionButton( inputId = "refresh_data", label = "データ更新", icon = icon("refresh"), class = "btn-primary" ) ) ), # メインコンテンツ dashboardBody( tags$head( tags$style(HTML(" .content-wrapper, .right-side { background-color: #f4f4f4; } .main-header .logo { background-color: #3c8dbc !important; } .skin-blue .main-header .navbar { background-color: #3c8dbc !important; } ")) ), tabItems( # 概要タブ tabItem( tabName = "overview", fluidRow( # KPIボックス valueBoxOutput("total_companies", width = 3), valueBoxOutput("avg_revenue", width = 3), valueBoxOutput("avg_employees", width = 3), valueBoxOutput("avg_profit", width = 3) ), fluidRow( box( title = "業界別売上分布", status = "primary", solidHeader = TRUE, width = 8, plotlyOutput("industry_plot", height = "400px") ), box( title = "企業規模分布", status = "success", solidHeader = TRUE, width = 4, plotlyOutput("size_plot", height = "400px") ) ) ), # 詳細分析タブ tabItem( tabName = "analysis", fluidRow( box( title = "企業パフォーマンス散布図", status = "primary", solidHeader = TRUE, width = 12, plotlyOutput("scatter_plot", height = "500px") ) ), fluidRow( box( title = "時系列トレンド", status = "info", solidHeader = TRUE, width = 6, plotlyOutput("trend_plot", height = "350px") ), box( title = "相関分析", status = "warning", solidHeader = TRUE, width = 6, plotlyOutput("correlation_plot", height = "350px") ) ) ), # データテーブルタブ tabItem( tabName = "data", fluidRow( box( title = "企業データテーブル", status = "primary", solidHeader = TRUE, width = 12, div(style = "margin-bottom: 20px;", downloadButton("download_data", "CSVダウンロード", class = "btn-success") ), DT::dataTableOutput("data_table") ) ) ) ) ) ) # Shinyサーバーロジック dashboard_server <- function(input, output, session) { # リアクティブデータフィルタリング filtered_data <- reactive({ data <- interactive_data # 業界フィルタ if (!is.null(input$industry_filter) && !"all" %in% input$industry_filter) { data <- data %>% filter(industry %in% input$industry_filter) } # 売上範囲フィルタ data <- data %>% filter( revenue >= input$revenue_range[1], revenue <= input$revenue_range[2], employees >= input$employee_range[1], employees <= input$employee_range[2] ) return(data) }) # KPIボックス output$total_companies <- renderValueBox({ valueBox( value = nrow(filtered_data()), subtitle = "総企業数", icon = icon("building"), color = "blue" ) }) output$avg_revenue <- renderValueBox({ valueBox( value = paste0("¥", round(mean(filtered_data()$revenue)), "M"), subtitle = "平均売上", icon = icon("yen-sign"), color = "green" ) }) output$avg_employees <- renderValueBox({ valueBox( value = round(mean(filtered_data()$employees)), subtitle = "平均従業員数", icon = icon("users"), color = "yellow" ) }) output$avg_profit <- renderValueBox({ valueBox( value = paste0(round(mean(filtered_data()$profit_margin), 1), "%"), subtitle = "平均利益率", icon = icon("chart-line"), color = "red" ) }) # プロット生成 output$scatter_plot <- renderPlotly({ p <- filtered_data() %>% plot_ly( x = ~revenue, y = ~profit_margin, color = ~industry, size = ~employees, text = ~company_name, type = "scatter", mode = "markers", hovertemplate = "<b>%{text}</b><br>売上: ¥%{x:,.0f}M<br>利益率: %{y:.1f}%<extra></extra>" ) %>% layout( title = "売上 vs 利益率", xaxis = list(title = "売上高(百万円)"), yaxis = list(title = "利益率(%)") ) if (input$show_trend) { p <- p %>% add_lines( x = ~revenue, y = fitted(lm(profit_margin ~ revenue, data = filtered_data())), line = list(color = "red", width = 2), name = "トレンドライン", hoverinfo = "skip" ) } p }) # データテーブル output$data_table <- DT::renderDataTable({ filtered_data() %>% select(company_name, industry, revenue, employees, profit_margin, satisfaction) %>% DT::datatable( options = list( pageLength = 15, scrollX = TRUE, searching = TRUE ), colnames = c("企業名", "業界", "売上(M¥)", "従業員数", "利益率(%)", "満足度") ) %>% DT::formatCurrency("revenue", currency = "¥", mark = ",") %>% DT::formatPercentage("profit_margin", digits = 1) }) # データダウンロード output$download_data <- downloadHandler( filename = function() { paste("企業データ_", Sys.Date(), ".csv", sep = "") }, content = function(file) { write.csv(filtered_data(), file, row.names = FALSE) } ) } # アプリケーションの実行設定 app_config <- list( ui = dashboard_ui, server = dashboard_server, options = list(port = 3838) ) print("Shinyダッシュボードアプリケーションが設定されました") print("実行コマンド: shinyApp(ui = dashboard_ui, server = dashboard_server)")
Shinyダッシュボード設定結果
[1] "Shinyダッシュボードアプリケーションが設定されました" [1] "実行コマンド: shinyApp(ui = dashboard_ui, server = dashboard_server)" # Shinyダッシュボードの主要機能: - リアルタイムフィルタリング: 動的データ絞り込み - KPI表示: 重要指標の視覚的表示 - インタラクティブプロット: plotly統合 - データテーブル: 検索・ソート機能 - CSVダウンロード: フィルタ済みデータの出力 # ユーザーインターフェース: - 業界別フィルタ - 売上・従業員数スライダー - トレンドライン表示切り替え - タブベースナビゲーション - レスポンシブデザイン
Shinyダッシュボード プレビュー
📊 企業分析
• 概要
• 詳細分析
• データテーブル

フィルタ:
業界: [全て▼]
売上: [0━━━5000]
従業員: [1━━1000]
📈 200
企業数
¥1.2M
平均売上
150人
従業員数
15.2%
利益率
企業パフォーマンス散布図
📊 インタラクティブチャートエリア
インタラクティブ技術 適用領域 主要機能 実装難易度
plotly変換 静的グラフ拡張 ホバー、ズーム、フィルタ
カスタムボタン データ操作UI スケール切り替え、表示制御
アニメーション 時系列表現 データ変化の動的表示
Shinyダッシュボード Webアプリケーション リアルタイム分析環境
クロスフィルタ 複数チャート連動 統合的データ探索

第14章の重要ポイント

実践的アドバイス

インタラクティブ可視化は、ユーザーがデータと対話することで新たな洞察を発見できる強力な手法です。plotlyは既存のggplot2コードを活用しながら段階的にインタラクティブ要素を追加できるため、学習コストを抑えて高度な機能を実現できます。Shinyとの組み合わせにより、エンドユーザー向けの本格的な分析アプリケーションも構築可能です。