Thanks for Douglas Watson,the original English version is http://douglas-watson.github.io/post/gdocs_1_gdocs前端
對於物聯網應用,收集分佈式日誌數據到一箇中央服務器並作數據可視化是一項十分常見的工做,這一般須要部署和維護本身的服務器、數據庫和可視化界面。我對系統管理任務毫無樂趣,因此我找到了一種方法使用谷歌表做爲數據庫和ShinyApps.io做爲可視化平臺。上傳數據到Google docs是相對簡單的,但用shiny鏈接到Google docs卻須要一些技巧,這促使我寫這篇教程向其餘人展現如何打造一個相似的系統。python
在第一部分(原文)中,我將解釋如何設置 Google spreadsheet 做爲數據庫,而後使用它做爲一個基本的儀表板。react
在第二部分(原文),我將教你如何在R中取回數據,並用ggplot2庫作數據可視化。git
在第三部分(原文),我將帶您用 Shiny 製做一個簡單的交互式可視化應用,經過 ShinyApps.io 平臺發佈在網上。github
在本教程中,咱們將僞裝有一個由幾個溫度和溼度傳感器構成的傳感器網絡。每一個傳感器用它所在的位置名稱(如「臥室」或「客廳」)命名,並記錄每小時的溫度。
我假設您已經知道了物聯網硬件如何使用HTTP請求上傳數據;所以,教程中我提供了一個Python腳本上傳虛擬的溫度和溼度值。web
Google Sheets 能夠做爲一個簡單的服務器來存儲和檢索數據,這隻須要寫不多的代碼。咱們避免維護咱們本身的服務器,另外從容易得到原始數據中獲益。最重要的是,電子表格本身強大的分析工具,統計,數據透視表、過濾器,和交互式圖表能夠嵌入在外部網站。
數據庫
R是一種強大的語言專門爲數據分析,結合ggplot2圖形庫,R即可以作專業的數據可視化。一旦你找到了你想要展現什麼,shiny 則讓可互動的數據可視化圖表在網上發佈。Shiny 由 RStudio 公司開發,他們
甚至提供了免費方便的託管可視化服務。segmentfault
在本教程的第一部分中,您將學習如何用 Google Sheets 腳本從硬件設備發起一個 HTTP POST請求來上傳數據和使用一個 HTTP GET請求來獲取數據。在這個過程當中,您還將試驗表格的一些內置的分析工具:過濾器,數據透視表和圖表。安全
您也能夠跳到第2部分,學習如何在R中獲取和操做這些數據,或者學習第3部分:如何使用Shiny。服務器
咱們將爲傳感器上的每一個數據點日誌存儲起來,包括一個時間戳,一個設備ID,一個變量名(「溫度」,或「溼度」)以及訪問量。這意味着每次讀取的傳感器會產生兩條線:一個溫度和溼度。這種格式是R用戶所說的「長格式」。在下面的示例中,咱們以每三秒一次的頻率監控兩個房間:
timestamp | ID | variable | value |
---|---|---|---|
1448227096 | kitchen | temperature | 22.3 |
1448227096 | kitchen | humidity | 45 |
1448227096 | bedroom | temperature | 24.0 |
1448227096 | bedroom | humidity | 46 |
1448227099 | kitchen | temperature | 22.4 |
1448227099 | kitchen | humidity | 45 |
1448227099 | bedroom | temperature | 23.9 |
1448227099 | bedroom | humidity | 45 |
相比「寬」格式,每一個變量都有本身的一欄:
timestamp | kitchen temperature | kitchen humidity | bedroom temperature | bedroom humidity |
---|---|---|---|---|
1448227096 | 22.3 | 45 | 24.0 | 46 |
1448227099 | 22.4 | 45 | 23.9 | 45 |
寬格式是更加明顯和更緊湊的格式,但不容易擴展。若是咱們添加一個新的傳感器系統,咱們須要添加一個列到文件。而長格式,咱們只繼續插入新行。長格式也很適合R分析,可使用數據透視表電子表格轉換成寬格式。
最後,咱們將以逗號分隔值(CSV)格式交換數據,由於它從嵌入式設備生成很是容易,要儲存爲一個文本文件也很簡單,並且在任何文本編輯器或電子表格應用程序閱讀也很是方便。
若是你沒有一個 Google Drive 賬戶話能夠建立一個。而後:
在 Google Drive 建立一個新的 Google Sheet
重命名第一張工做表(表中一個標籤文檔),這就是數據將被上傳到這裏。
建立標題行。每一行的數據,將由unix時間戳(1970年1月1日以來的秒數)、「id」、「變量」和「閱讀量」構成。你能夠凍結的行,保持可見滾動時,用「視圖」>「凍結」>「一行」。
打開腳本編輯器,在「工具」>「腳本編輯器…」菜單。
如今您已經準備能夠開始寫代碼啦!
你如今應該看一下腳本編輯器,這個工具容許您編寫自定義電子表格函數,好比 =SUM(A1:A5)
或者是寫簡單的web應用程序。咱們的第一步是編寫一個函數,CSV數據表格插入一行。將這段代碼複製到腳本編輯器:
function appendLines(worksheet, csvData) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName(worksheet); var rows = Utilities.parseCsv(csvData); for ( var i = 0; i < rows.length; i++ ) { sheet.appendRow(rows[i]); } } function test() { Logger.log("Appending fake data"); appendLines("Raw", "12345, Monday, kitchen, temperature, 30\n12346, Tuesday, living room, humidity, 50"); }
咱們的 appendLine函數:
打開當前電子表格(整個文檔);
在文檔中選擇一個表,由第一個參數標識(咱們目前只有一個 Raw 表);
解析CSV數據做爲第二個參數;
將每一行的CSV數據,追加到表中。
咱們另外寫了一個小測試來測試appendLines
函數的調用,以確保一切正常。選擇工具欄中的test
函數並單擊run
按鈕能夠運行這個腳本。若是執行了該腳本,這應該有兩行添加到電子表格。您還能夠在腳本編輯器中知道輸出的「視圖」>「日誌」來查看日誌。
兩行數據將被添加到表中:
然而,咱們的代碼還有有一個重要缺陷。
由於稍後當咱們須要將數據做爲一個web服務時,相關聯的腳本將沒有激活電子表格。咱們須要更改代碼,以構建惟一ID和電子表格ID的關係,從其惟一的ID中找到獲取電子表格文檔ID:
而後修改appendLines
經過ID獲取文檔:
function appendLines(worksheet, csvData) { var ss = SpreadsheetApp.openById("13_BUd7WJlA8Z9B5Vc-5tyf3vyRUYmIx67sDz7ZmyPG4"); var sheet = ss.getSheetByName(worksheet); var rows = Utilities.parseCsv(csvData); for ( var i = 0; i < rows.length; i++ ) { sheet.appendRow(rows[i]); } }
再次運test
函數,它應該添加另外一個兩行「Raw」選項卡。
谷歌腳本接受兩個處理HTTP GET和POST請求的特殊功能:分別是doGet
和doPost
。
這些函數接受一個Event
參數類型,並且必須從ContentService
或HtmlService
返回一個特殊的對象。
咱們首先作一個函數響應POST請求,經過返回JSON格式的Event對象的內容繼續研究API。將下面的代碼添加到腳本編輯器:
function doPost(e) { var params = JSON.stringify(e); return ContentService.createTextOutput(params); }
如今點擊「發佈」>「部署爲web應用程序…」就能夠發佈了。給版本一個簡短描述而後執行,並容許匿名訪問,這樣你就能夠發佈到電子表格不須要身份驗證,接着部署:
複製URL:
最後你須要發送HTTP請求到這個新web服務器,而後分享公開的電子表格。返回到電子表格選項卡,打開「文件」>「分享…」。
點擊「獲取共享連接」,容許任何人編輯連接:
如今,打開你最喜歡的HTTP客戶端(好比 Postman就是一款好用的Chrome擴展)。或者在Terminal中使用CURL,向這個URl發送一個POST請求。若是使用CURL確保添加-l
參數遵循重定向以及--data
參數來添加數據:
$ curl --data "hello, world" "https://script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec" {"parameter":{"hello, world":""},"contextPath":"","contentLength":12,"queryString":null,"parameters":{"hello, world":[""]},"postData":{"length":12,"type":"application/x-www-form-urlencoded","contents":"hello, world","name":"postData"}}%
個人POST請求收到了JSON響應,能夠看到e
的結構參數傳遞給了doPost
。你將能夠看到 「hello, world」 POST 請求的數據將會存儲在e["postData"]["contents"]
中。
咱們可使用URL傳遞參數doPost
。重複相同的請求,但附加?sheet=Raw
(確保URL引用):
$ curl -L --data "hello, world" "https://script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec?sheet=Raw" {"parameter":{"sheet":"Raw","hello, world":""},"contextPath":"","contentLength":12,"queryString":"sheet=Raw","parameters":{"sheet":["Raw"],"hello, world":[""]},"postData":{"length":12,"type":"application/x-www-form-urlencoded","contents":"hello, world","name":"postData"}}%
URL參數出如今 e["parameter"]["sheet"]
。
既然咱們瞭解事件對象的結構,咱們就能夠修改doPost
爲:
在URL中找到一個"sheet"參數
從POST請求中提取CSV數據
附加在CSV數據到指定表的全部行。
function doPost(e) { var contents = e.postData.contents; var sheetName = e.parameter['sheet']; // Append to spreadsheet appendLines(sheetName, contents); var params = JSON.stringify(e); return ContentService.createTextOutput(params); }
發佈一個新版本的應用程序(「發佈」>「部署爲web應用程序…」,「項目版本」設置爲「新」,描述它,並點擊「更新」)。如今您能夠附加到電子表格行到一個HTTP Post請求!從新運行curl命令,並觀察最新的添加到您的電子表格行:
清除電子表格:刪除全部行除了標題。
更多關於doGet
和doPost
的資料: https://developers.google.com/apps-script/guides/web
更多關於Event
對象的資料: https://developers.google.com/apps-script/guides/triggers/events
在實踐中,您可使用任意語言發起POST請求上傳數據到物聯網平臺。本教程中,我使用一個python腳本建立了一些兩個房間每隔一個小時的隨機溼度和溫度數據並最終上傳電子表格。
本文因爲篇幅有,且本節內容並不影響大局,筆者放棄了對本小節其他部分的翻譯,請讀者諒解。
本節,咱們將討論:如何從網上下載CSV數據,處理日期字段,並使各類與ggplot2相關的細節。若是你閱讀教程時已經使用R且勾起了使用谷歌表數據的衝動這將是咱們的榮幸,若是沒有,我但願本文將激勵你深刻學習R!
若是你是一個經驗豐富的R用戶,你已經知道你喜歡開發環境可使用你本身的IDE。若是不是,我建議用RStudio,它能夠用於Linux,Mac和Windows多個平臺上。
RStudio是一個像樣的編輯器(還能夠支持Vim模式)而且將Shiny集成其中。下載地址
此外,咱們還須要在R中安裝ggplot2
包:
install.packages('ggplot2')
或者直接在Rstudio中點擊Install
按鈕,輸入ggplot2後安裝。
從網上抓取CSV數據很簡單:只要用read.csv
帶上url
參數,它會自動下載正確的數據,使數據幀頭的名字。
建立一個helpers.R
文件,並編寫下面的代碼:
getRaw <- function () { data <- read.csv( url("https://script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec?sheet=Raw"), strip.white = TRUE ) data }
咱們將在稍後的 Shiny App 中用到helpers.R
。getRaw
函數將會返回一個數據庫格式的數據,包括5個變量,而表頭就和Google Sheets裏面的電子表格的表頭同樣。
> data <- getRaw() > summary(data) timestamp date origin variable value Min. :1.448e+09 Mon Nov 23 2015 22:44:45 GMT+0100 (CET): 4 bedroom:120 humidity :120 Min. :23.00 1st Qu.:1.448e+09 Mon Nov 23 2015 23:44:45 GMT+0100 (CET): 4 kitchen:120 temperature:120 1st Qu.:23.88 Median :1.448e+09 Thu Nov 26 2015 00:44:45 GMT+0100 (CET): 4 Median :32.55 Mean :1.448e+09 Thu Nov 26 2015 01:44:45 GMT+0100 (CET): 4 Mean :34.34 3rd Qu.:1.448e+09 Thu Nov 26 2015 02:44:45 GMT+0100 (CET): 4 3rd Qu.:44.45 Max. :1.449e+09 Thu Nov 26 2015 03:44:45 GMT+0100 (CET): 4 Max. :49.90 (Other) :216 >
如今讓咱們把日期-時間數據轉化爲適當的R date對象。最簡單的方法是將UNIX時間戳列轉換爲POSIXct對象並取代「日期」列。自從咱們生成UTC日期,咱們將時區設置爲「格林尼治時間」。完成getRaw
函數:
getRaw <- function () { data <- read.csv( url("https://script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec?sheet=Raw"), strip.white = TRUE ) data$date = as.POSIXct(data$timestamp, tz="GMT", origin="1970-01-01") data }
萬事俱備,只欠東風!
ggplot2包
提供了機智的qplot
函數,一行代碼能夠繪製種類繁多的圖表,使R成爲我最喜歡的工具數據。
首先,咱們畫上全部的值。
import(ggplot2) source("helpers.R") data <- getRaw() qplot(date, value, data = data)
qplot(date, value, data = data, colour = origin)
咱們能夠根據傳感器的位置用顏色區分圖上的點:
咱們仍然不能區分溫度和溼度,讓咱們在不一樣的面板使用「facets」區分這些變量。咱們將添加「free_y」選項,容許每一個獨立y拉伸:
qplot(date, value, data = data, colour = origin) + facet_grid(variable ~ ., scales = "free_y")
由於有不少噪音,加入點線使圖表更容易閱讀:
qplot(date, value, data = data, colour = origin, geom = "line") + facet_grid(variable ~ ., scales = "free_y")
若是您添加一個傳感器在一個新的房間,一個新行顏色會自動出如今圖表上。若是你添加一個新的類型的數據(如「權力」功率計),第三個facets就會出現。試一試!
只是修改Python腳本發送數據並從新運行Python腳本,運行data<-- getRaw()
這行,
而且最新qplot
命令。
若是x軸上每一個點的日期範圍彷佛是錯的話,能夠嘗試添加scale_x_datetime
:
qplot(date, value, data = data, colour = origin, geom = "line") + scale_x_datetime() + facet_grid(variable ~ ., scales = "free_y")
爲了以後 Shiny 便於調用,咱們就把兩個qplot
封裝成一個函數。
咱們的helpers.R
文件如今看起來像這樣:
library(ggplot2) getRaw <- function () { data <- read.csv( url("https://script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec?sheet=Raw"), strip.white = TRUE ) data$date = as.POSIXct(data$timestamp, tz="GMT", origin="1970-01-01") data } timeseriesPlot <- function(data) { qplot(date, value, data = data, colour = origin, geom = "line") + scale_x_datetime() + facet_grid(variable ~ ., scales = "free_y") } boxPlot <- function(data) { qplot(origin, value, data = data, geom = "boxplot") + facet_grid(variable ~ ., scales = "free_y") }
在這部分, 咱們將建立真正的在線儀表盤。Shiny 將 Web 服務器和一個可交互式的Web前端融合在一塊兒,並嵌入 R 的代碼和圖表。咱們將使用它製做一個基於 Web 的儀表盤,換句話說就是一個能夠實時修改的傳感器數據展現網站。本節,咱們將用 Google Sheets 上的數據創建一個簡單的頁面來展現時間序列和箱線圖。
我建議你按照官方教程最新的安裝說明安裝。若是你有時間,跟隨整個教程的完整概述瞭解 Shiny 的思惟方式。
咱們第一次儀表盤只會顯示兩個從 Google Sheets 提取最新數據的圖表。Shiny 應用由兩部分組成:ui.R
和 server.R
。ui.R
決定了儀表盤的外觀:可視化的內容是什麼以及如何排版。在這個文件中咱們須要先規定好可視化的對象的名稱和類型,具體的可視化內容則在server.R
規定。
以下的實例代碼定義了一個容器: shinyUI(fluidPage(....))
。容器裏面包含了一個垂直佈局的盒子,將子類垂直堆疊。在佈局內部,咱們放置三個實體:一個標題欄和兩個畫圖實體。
# ui.R shinyUI(fluidPage( verticalLayout( titlePanel("Sensor data dashboard"), plotOutput("timeseries"), plotOutput("boxplot") ) ))
下一步是渲染圖形,在server.R
中用plotOutputs
選項控制顯示。server.R
中的代碼在以下時刻會被調用:
當服務器啓動時;
每次訪問一個頁面時;
每次一個交互式的部件改變時。
在咱們的例子中,當服務器啓動時,咱們只但願導入輔助代碼但不執行(數據加載和繪製函數)。每次頁面加載的時候,咱們想要獲取新數據並畫出情節。咱們沒有小部件,所以,沒有第三類中的代碼。
這是服務器代碼的初稿,下面,就完成了這個任務:shinyServer(...)
代碼塊以外的代碼只會被執行一次,而代碼塊裏面的部分每次訪問頁面都會被執行。
這最後一塊讀取原始數據,
建立了一個時間序列圖並將它放入一個咱們在ui.R
裏定義的名爲「timeseries」的plotOuput
中,而後渲染一個箱線圖放入名爲「boxplot」的plotOutput
中。
# server.R source("helpers.R") shinyServer(function(input, output) { # Load data when app is visited data <- getRaw() # Populate plots output$timeseries <- renderPlot({ timeseriesPlot(data) }) output$boxplot <- renderPlot({ boxPlot(data) }) })
像上節提到的helpers.R
文件同樣,在同一文件夾下建立ui.R
和server.R
文件後,RStudio 將自動檢測到這些文件是 Shiny 應用而且會在編輯器上方的工具欄中自動出現一個 「Run App」 的按鈕。
使用該按鈕來預覽應用程序,它應該彈出一個相似下面的窗口:
注意,咱們的沒有對谷歌文檔自動檢測數據變化的功能,你仍然須要刷新頁面來得到最新的數據。
如今讓咱們添加一些日期選擇小部件,限制顯示的日期範圍。咱們將首先從一個簡單的擴展布局垂直堆棧流體網格。遇到足夠寬的顯示,這種佈局顯示網格。遇到窄的顯示器,它返回一個垂直堆棧,以免橫向滾動。網格會以一個包含column
元素的fluidRow
元素。分配每一列的寬度(單位1⁄12屏幕的寬度)能夠包含另外一個小部件(一個輸入,一個圖表,或另外一個網格)。新的ui.R
文件下面會如今顯示嵌套的行和列的佈局。
第二個修改是添加輸入小部件,輸入容許用戶經過輸入文本,數量,日期,選擇按鈕…完成可視化交互。你也能夠在這裏瀏覽完整的素材庫。
咱們將使用兩種類型的輸入:
dateRangeInput
:一個下拉日曆選擇開始和結束日期。
numericInput
:一個只接受數字的輸入框,顯示了一個向上和向下箭頭來修改輸入值。
咱們使用dateRangeInput
指定日期範圍的數據繪製,和四個數字輸入指定開始一天的小時和分鐘,小時和分鐘的最後一天。
# ui.R shinyUI(fluidPage( verticalLayout( titlePanel("Sensor data dashboard"), fluidRow( column(3, dateRangeInput("dates", "Date Range", start="2015-11-20"), fluidRow( column(4, h3("From:")), column(4, numericInput("min.hours", "hours:", value=0)), column(4, numericInput("min.minutes", "minutes:", value=0)) ), fluidRow( column(4, h3("To:")), column(4, numericInput("max.hours", "hours:", value=23)), column(4, numericInput("max.minutes", "minutes:", value=59)) ) ), column(9, plotOutput("timeseries")) ), fluidRow( column(3), column(9, plotOutput("boxplot")) ) ) ))
經過ShinyServers
回調的input
參數,服務器端代碼能夠獲取用戶從輸入部件的值。當前端的輸入有任何改變時,shiny將自動從新執行服務器上全部的renderPlot
或者reactive
的代碼塊來獲取新的輸入值。reactive
是用來根據用戶輸入轉換數據的,咱們將使用一個應用日期範圍過濾器。在下面新代碼示例的reactive
代碼塊中轉換咱們輸入的兩個POSIXct日期對象,而後使用它們做爲日期的過濾條件來過濾數據框。
咱們將reactive
代碼塊分配給data.filt
而且修改renderPlot
調用繪製data.filt()
而不是原始data
。注意語法:data.filt
返回代碼塊的值傳遞給reactive
。正如上面介紹的那樣,每一次的輸入都會引起reactive
代碼塊的更新,而其餘調用data.filt()
的代碼塊也會跟着更新一次。在下面的例子中,兩個renderPlot
代碼塊都會被從新執行以更新數據。
警告:確保包括括號每次你調用data.filt()
!
# server.R source("helpers.R") shinyServer(function(input, output) { # Load data when app is visited data <- getRaw() # Filter by device ID / time range when options are updated data.filt <- reactive({ mindate <- as.POSIXct.Date(input$dates[1]) + (input$min.hours * 60 + input$min.minutes) * 60 maxdate <- as.POSIXct.Date(input$dates[2]) + (input$max.hours * 60 + input$max.minutes) * 60 subset(data, date > mindate & date < maxdate) }) # Populate plots output$timeseries <- renderPlot({ timeseriesPlot(data.filt()) }) output$boxplot <- renderPlot({ boxPlot(data.filt()) }) })
再次運行應用程序。新結果應該相似於下面個人例子:
ShinyApps.io 爲 Shiny 應用提供一個託管服務,而這個擴管服務同時也是 RStudio 的另外一個產品。他們的開發者在 RStudio 中集成了這個服務:簡單地在你 Shiny 應用的右上角點擊"發佈",跟着指引,建立一個免費的帳號而後上傳你的儀表盤。
一旦發佈應用程序,您將注意到一個問題是:在儀表板上沒有任何顯示。若是你檢查服務器日誌,你會注意到從 R 訪問 Google Sheets 的 HTTPS 請求失敗。下面,咱們的教程的最後一節將解釋如何解決。
不幸的是, ShinyApps (在撰寫本文時)不支持https url,從而阻礙了請求 Google Sheets。爲了解決這一問題,咱們須要經過外部路由請求服務器(代理),能夠經過HTTP、HTTPS能夠獲取目標頁面,並經過HTTP請求回到R的read.csv
返回相應的值。這樣作不利於發揮安全的HTTPS的優點,但因爲咱們是在作一項公共儀表板數據公開在谷歌,並且大多僅供演示,我不關心它。
保存你的工做,我創建了一個HTTPS-to-HTTP代理服務器。在server.R
代碼,在 script.google.com/以前添加shinyproxy.appspot.com/。在個人例子中,網址是:
# fragment of helpers.R data <- read.csv( url("https://shinyproxy.appspot.com/script.google.com/macros/s/AKfycbxOw-Tl_r0jDV4wcnixdYdUjcNipSzgufiezRKr28Q5OAN50cIP/exec?sheet=Raw"), strip.white = TRUE )
再次發佈儀表板,你成功了!
HTTPS代理是一個駐留在應用程序引擎的簡單代碼。相關代碼
原做者 Douglas Watson
英文原文地址 http://douglas-watson.github.io/post/gdocs_0_intro/做爲分享主義者(sharism),本人全部互聯網發佈的圖文均聽從CC版權,轉載請保留做者信息並註明做者 Harry Zhu 的 FinanceR專欄:https://segmentfault.com/blog/harryprince,若是涉及源代碼請註明GitHub地址:https://github.com/harryprince。微信號: harryzhustudio商業使用請聯繫做者。