終於來到我們主題的最後一站,資料視覺化(data visualization)了。什麼是資料視覺化呢?就是將我們的資料以圖表的方式讓相關的統計數字呈現出來。為什麼要做資料視覺化呢?先來看看經過了一次又一次的練習與實作,跨過了一個又一個的領域,到現在為止我們手上有了些什麼。第一個我們掌握了 Python 這個語言的基本操作,第二個我們透過 Python 教 LINE 聊天機器人說話哈啦,讓他變得幽默風趣又貼心。第三點,我們學了 SQL 語法,在網路上建立了資料庫,並瞭解如何新增、提取、更新、刪除資料庫裡的紀錄。第四點,我們學了 Flask,架構出了一個網站,供我們隨時可以用更方便且清楚的平台去瀏覽我們資料庫裡的紀錄。然後就是今天了。一切看起來這麼完美,直到我發現我的草泥馬訓練紀錄越疊越多,多到在瀏覽器中顯示每一筆資料變得龐大又沒有意義。如果我想知道'約翰'
總共進行了多久的'嚼食訓練'
?如果我想知道即將參加大賽的'法蘭克'
在9/16
之後的訓練量是否過多?如果我想知道我對'威廉'
安排的各種訓練課程是否平均?以我們目前的方式,單單調閱資料還是很難讓我能夠正確判斷上述問題的。這時候就需要用到資料視覺化的手段了,也就是將一筆一筆的資料,按照我們的需求,畫出相對應的圖表呈現在瀏覽器當中。
聽起來是不是很棒呢?是不是想馬上就來實作看看呢?當然好,不過在那之前,我們得些「略為」了解一下另一種程式語言:JavaScript。
怎麼這麼殘酷,學了 Python、學了 SQL、現在居然還想要凹我去學 JavaScript。原因是,不同語言有不同語言擅長的領域,Python相當擅長於資料處理分析、人工智慧神經網路開發,SQL 負責處理資料庫相關的操作,而 JavaScript 則幫我們在瀏覽器上作運算。當我們把資料用 Python 傳送到使用者端的瀏覽器,也就是所謂的前端,之後,剩下的所有工作,就交由 JavaScript 來處理。因此熟悉 JavaScript 能夠幫助我們在網頁開發與設計上更上一層樓。
接著說個好消息:因為我們已經對 Python 有一定程度的熟悉了,再學習另一種程式語言時,可以用觸類旁通的方式,讓我們能夠很快的掌握一些基本的要素。因此,今天的內容就是,為了要做好資料視覺化,讓我們一起來上一堂 JavaScript 速成班。
話先說再前面,所謂的速成班聽起來都不太可靠。我這堂當然也是。之所以能夠速成,其中有一個關鍵是我們已經學過 Python,可以用對照比較的方式,讓大家很快進入 JavaScript 的世界。另一個關鍵則是,我只介紹一些和接下來實作資料視覺化相關的基本概念和操作,所以可以在有限的篇幅說明完畢。其實我是相當推薦好好再學個 JavaScript 的。這次鐵人賽也有不少相關的主題➀➁,另外,想要再從基礎開始,有系統的了解 JavaScript 的人,可以下載這本教科書 Eloquent JavaScript➂。不要懷疑,免會員、免註冊,免費下載!
還記得我們是怎麼學 Python 的嗎?現在我們就這麼來學 JavaScript。
第一個問題:我們可以在哪裡編輯並且試跑 JavaScript 的程式碼呢?上面說過,將資料送到使用者端的瀏覽器,接下來就是由 JavaScript 來作運算了。因此每個瀏覽器都可以是 JavaScript 的集成開發環境!隨意開啟 Chrome、FireFox、或是 Edge,接著按下 "Ctrl" + "Shift" + "i",就可以看到一個開發人員工具,接著選 "Console",這就是我們的 JavaScript 集成開發環境囉!
圖一、用 Chrome 瀏覽器來編輯 JavaScript
圖二、用 Edge 瀏覽器來編輯 JavaScript
寫入 JavaScript 程式碼之後,"Enter" 就可以輸入指令看到結果。"Shift" + "Enter" 則是換行繼續編輯。
在 JavaScript 當中,基本的資料型態分三種:數值(Number)、字串(String)、布林(Boolean)。先來講講第一種,數值。
不像 Python 還分成整數跟浮點數,JavaScript 的數值直接霸氣的將這兩種都囊括了進來。數值的基本運算如下表:
表一、數值(Number)的運算方法
運算 | 運算符號 |
---|---|
加法 | + |
減法 | - |
乘法 | * |
除法 | / |
餘數 | % |
和 Python 相比,少了一個商數的計算方式,怎麼辦呢?我們可以用Math.floor()
,小於等於這個數值的最大整數,來計算我們的商數:
> 8 / 3
< 2.6666666666666665
> Math.floor( 8 / 3 )
< 2
> Math.ceil( 8 / 3 )
< 3
觸類旁通,Math.ceil()
則傳回大於等於這個數值的最小整數。
另外,也可以用Number()
的方式,將其他類型的資料轉換為數值資料:
> Number("2.5")
< 2.5
> Number(true)
< 1
第二種,字串資料,和 Python 相同,用單引號或是雙引號作為字串資料的開始跟結束。也有換行符號(\n
)、tab符號(\n
)。還記得 Python 的 F 字串(f-string)嗎?在 JavaScript 中,想要做到 F 字串的效果很簡單:
In [1]: word = "是這樣"
f"Python 中的 F 字串{word}!"
Out[1]: " Python 中的 F 字串是這樣!"
> let word = "是這樣"
`JavaScript 中的則${word}!`
< "JavaScript 中的則是這樣!"
用` `包住字串,並在需要放入變數的地方用${}
。相同的,可以用String()
來將其他類型的資料換為字串資料。
跟 Python 一樣,JavaScript 的布林資料只有兩種:true
跟false
。與布林資料相關的幾種邏輯運算子如表二所示:
表二、邏輯運算子
運算 | 運算符號 |
---|---|
等於 | == |
不等於 | != |
大於 | > |
小於 | < |
且 | && |
或 | ` |
全等於 | === |
不全等於 | !== |
在 JavaScript 裡比較麻煩的是,它給了我們兩種等於。因為 JavaScript 允許不同類型的資料擺在一起比較,當用==
將不同類型的資料擺在一起時,JavaScript 會(自做?)聰明的轉換資料類型:
> 1 == "1.0"
< true
> 1 == true
< true
如果你不希望發生這種事,就請用===
:
> 1 === "1.0"
< false
> 1 === true
< false
Python 當中的print()
在 JavaScript 當中是 console.log()
:
> let wordAgain = "是這樣"
console.log(`JavaScript 中的則${wordAgain}!`)
JavaScript 中的則是這樣!
< undefined
在 JavaScript 當中查詢資料的類型,請用typeof
:
> typeof("123");
< "string"
> typeof(123);
< "number"
接下來介紹一個 JavaScript 當中有趣的三元運算子?
,用法如下:
> let 如果正確 = true
如果正確 ? console.log("喊 1"): console.log("否則喊 2")
喊 1
< undefined
三元運算子?
會先判斷條件是否正確(true
),若是,則執行第一段程式碼,若否,則執行:
之後的第二段程式碼。
在 Python 當中,貼標籤非常簡單:
In [2]: 標籤 = 123
標籤
Out[2]: 123
然而在 JavaScript 當中,我們要先宣告標籤的類型var
、let
、const
,接下來才可以開始貼標籤,同一個標籤只需要宣告一次,也只能能宣告一次。
> let 標籤 = 123;
標籤;
< 123
> 標籤 = 456;
標籤;
< 456
> let 標籤 = 789;
Uncaught SyntaxError: Identifier '標籤' has already been declared
at <anonymous>:1:1
用var
和let
做成的標籤就像便利貼,隨時可以撕下來重新貼在另外的資料上。用const
做成的標籤則不行,一旦宣告完畢,貼上了資料,就必須跟隨到底。
> const 貼好貼滿 = 123
貼好貼滿
< 123
> 貼好貼滿 = 456
Uncaught TypeError: Assignment to constant variable.
at <anonymous>:1:6
在 JavaScript 當中,有兩種資料結構:序列(Array)跟物件(Object)。序列就像是 Python 當中的清單(List),而物件則像是 Python 當中的字典(Dict)。
> let anArray = [1, 3, "5", true];
anArray;
< [1, 3, "5", true]
> anArray.length
< 4
要在序列中加入/移出物件,就用push()
跟pop()
:
> anArray.push("new item");
anArray;
< [1, 3, "5", true, "new item"]
> anArray.pop();
"new item"
> anArray;
< [1, 3, "5", true]
將多個序列合併在一起則用concat()
:
> let anotherArray = [4, 7, "9"];
let theOtherArray = ["再來啊"];
let overallArray = anArray.concat(anotherArray, theOtherArray);
overallArray
< [1, 3, "5", true, 4, 7, "9", "再來啊"]
從序列中選取切片:
> overallArray.slice(2, 5);
< ["5", true, 4]
有沒有發現到 JavaScript 當中序列的編號方式跟 Python 是一樣的,也就是從 0 開始。查詢一下序列中某一個資料的編號:
> overallArray[1];
< 3
> overallArray.indexOf(7);
< 5
在 JavaScript 中,第二種資料結構叫物件(Object)。大家其實可以想成 Python 中的字典。
> let anObject = {keyOne: "valueOne", keyTwo: "valueTwo"};
anObject;
< {keyOne: "valueOne", keyTwo: "valueTwo"}
想當然爾,要調閱物件中,某一個鑰匙其相對應的資料:
> anObject.keyOne;
< "valueOne"
> anObject["keyOne"];
< "valueOne"
也能夠查詢物件中的所有鑰匙或是所有資料:
> Object.keys(anObject);
< ["keyOne", "keyTwo"]
> Object.values(anObject);
< ["valueOne", "valueTwo"]
> typeof(Object.keys(anObject));
< "object"
利用Object.keys()
可以得到一個包含了anObject
物件當中所有鑰匙的序列。而用Object.values()
則會得到包含了物件當中所有資料的序列(這邊會回傳"object"
,因為事實上,序列可以看做物件的其中一個特例,鑰匙是編號,而資料則是序列中的項目)。
擴充一個物件裡面的鑰匙:資料配對:
> Object.assign(anObject, {keyThree: "valueThree", keyMOre: "valueMore"});
< {keyOne: "valueOne", keyTwo: "valueTwo", keyThree: "valueThree", keyMOre: "valueMore"}
將{keyThree: "valueThree", keyMOre: "valueMore"}
成功擴充至anObject
裡面囉!
在 Python 中,有if
的判斷句,有while
跟for
迴圈,在 JavaScript 當中,也有相對應的句型,讓我們來看看該怎麼寫:
if
判斷句> let 狀況為真 = true
if (狀況為真) {console.log("執行這段程式碼囉!");}
執行這段程式碼囉!
< undefined
在 Python 當中,我們是藉由縮排來區分程式碼的段落。而在 JavaScript 當中,則需要用()
、{}
將程式碼的段落包裝起來,相反的,JavaScript 並不強求縮排(雖然這樣說,但習慣上為了可讀性還是會縮一下)。用剛才的if
為例,我們需要用()
將判斷句包裝起來,而用{}
將符合判斷時該執行的程式碼包裝起來。但是,為了讓程式碼看起來更乾淨,或是讓辛辛苦苦工作的碼農們可以輕鬆點,當程式碼只有簡簡單單一行的時候,{}
的包裝可以省略。
> if (狀況為真) console.log("只有一行,可以不用加{}!");
只有一行,可以不用加{}!
< undefined
當然,也可以做到更複雜的if
、else
:
> let 狀況一 = false; let 狀況二 = true;
if (狀況一) {
console.log("狀況一為真")
} else if(狀況二) {
console.log("狀況二為真")
} else {
console.log("以上狀況都不為真")
}
狀況二為真
< undefined
while
迴圈 除了須加上()
和{}
之外,其他寫起來真的跟 Python 很像呢:
> let numberForWhile = 0;
while(numberForWhile < 10) {
console.log(numberForWhile);
numberForWhile += 2;
}
0
2
4
6
8
< 10
JavaScript 還有另外一種形式的while
迴圈,寫作do {執行的內容} while (判斷)
,執行的效果基本上跟上面看到的while (判斷) {執行的內容}
迴圈相同,唯一的差別在do {執行的內容} while (判斷)
一定會將要執行的內容先執行一次,接著才進入判斷,若判斷成立,則繼續執行下一個迴圈,若判斷不成立,則結束迴圈。
> do { console.log("無論如何先印一個"); } while ( false )
無論如何先印一個
< undefined
for
迴圈 還記得在 Python 當中,我們這麼寫for
迴圈:
for i in range(6):
print(i)
在 JavaScript 當中,則是:
> for ( let i = 0; i < 6; i += 1 ) { console.log(i); }
或者也可以:
> for ( let i = 0; i < 6; i ++ ) { console.log(i); }
用i++
代表i += 1
。
另一種寫法:
> for ( let i of "POPCORN" ) { console.log(i); }
P
O
P
C
O
R
N
< undefined
想要在程式碼裡加上註解的話,JavaScript 用//
:
> console.log("comment") // 我是註解
Comment
< undefined
或是用/*
作為註解的開頭,而*/
作為註解的結尾:
> /*
這是一段
簡單的
小註解
*/
看到這裡,有沒有對 JavaScript 的寫法越來越有概念了呢?這篇文章要講的最後一種則是 JavaScript 當中的函數。還記得函數是什麼嗎?我們將一個或一組參數送進函數,函數接受參數並執行程式碼,最後回傳結果。在 JavaScript 中,函數是這樣寫的:
> let aFunction = function ( 參數 ) { 執行程式碼; return 結果; }
首先用function
來宣告我們要開始定義一個函數。接著用()
承接函數的參數,並將函數要執行的程式碼放入{}
當中,最後,用return
來返回執行結果。
> let multiplyTwoNumbers = function ( paraOne, paraTwo ) { let result = paraOne * paraTwo; return result; }
multiplyTwoNumbers(4, 5)
< 20
我們也可以這樣定義函數:
> function anotherFunction ( paraOne, paraTwo ){ let result = paraOne * paraTwo; return result; }
把函數的名字放在定義函數的關鍵字function
和(參數)
中間。除此之外,還有第三種寫法:
> let theOtherFunction = ( paraOne, paraTwo ) => { let result = paraOne * paraTwo; return result; }
這種方式叫做箭號函數(Arrow Function),用箭號(=>
)暗指一個函數,箭號前是參數,箭號後則是函數的執行內容。最後,因為我們的函數其實相當簡單,一行程式碼便可結束,因此我們的函數還可以再簡化成:
> let simpleFunction = ( paraOne, paraTwo ) => paraOne * paraTwo;
箭號前是函數的參數,箭號後則直接放入我們需要的回傳值。
此外,當我們想要放入多個數量不定的參數時,可以這樣做:
> function multiplyNumbers ( ...paras ) {
let result = 1;
for (let para of paras) {
result *= para;
}
return result;
}
> multiplyNumbers(4, 5, 6, 7);
< 840
既然我們對 JavaScript 的函數有一些初步的認識了,那麼就打鐵趁熱再介紹些 JavaScript 內建常用的序列(Array)函數吧:
forEach()
:對序列中的每個項目執行同一種操作。> let arrayExample = [2, 5, 8];
arrayExample.forEach(i => console.log(i));
2
5
8
再舉一例:
> let alpacaInfo = [{"吉姆": [100, 78]}, //草泥馬的名字: [身高(cm), 體重(kg)]
{"威廉": [102, 82]},
{"彼得": [93, 71]},
{"法蘭克": [98, 80]}];
alpacaInfo.forEach(x => {console.log(`${Object.keys(x)}的 BMI: ${Object.values(x)[0][1]*10000/Object.values(x)[0][0]/Object.values(x)[0][0]}`)})
吉姆的 BMI: 78
威廉的 BMI: 78.8158400615148
彼得的 BMI: 82.0904150768875
法蘭克的 BMI: 83.29862557267805
在forEach
當中放入一個函數,這個函數會對序列當中每一個項目執行相同的程式碼:
> anArray.forEach( (anArray當中的項目) => { 函數的程式碼; } );
map()
:對序列中的每個項目執行程式碼後,回傳新的序列> let alpacaHeightInMeters = alpacaHeight.map( x => x / 100 )
alpacaHeightInMeters
< [1, 1.02, 0.93, 0.98]
filter()
:回傳一個序列中符合判斷的項目> alpacaInfo.filter(x => Object.values(x)[0][0] >= 100)
< [{吉姆: [100, 78]}, {威廉: [102, 82]}]
some()
:檢查一個序列中是否有項目符合指定的判斷?> alpacaInfo.some(x => Object.values(x)[0][0] > 100)
< true
every()
:檢查一個序列中,是否每一個項目都符合指定的判斷?> alpacaInfo.every(x => Object.values(x)[0][0] > 100)
< false
reduce()
:將序列中的項目進行累積性的運算,並傳回最後的結果> alpacaInfo.map(x=>Object.values(x)[0][1]).reduce((totalWeight, x) => totalWeight + x, 0)
< 311
上面那段程式碼,我們首先用alpacaInfo.map(x=>Object.values(x)[0][1])
做出一份只有紀錄了草泥馬體重的序列,接著在這個序列上用.reduce((totalWeight, x) => totalWeight + x, 0)
把這個序列當中所的有項目加總起來,得到 4 隻草泥馬的體重總和。
我們讓totalWeight
在reduce()
當中扮演累積的結果,而x
則是代表序列中的項目。reduce()
在執行的時候,會先初始化一個totalWeight
。這個totalWeight
一開始的值,我們指定為0
。接著掃過序列中的所有項目,並在掃過項目的同時,執行totalWeight + x
這個程式碼,掃過序列中的所有項目之後,我們就得到體重的總和totalWeight
。
看個不那麼複雜的例子:
> ["吉姆", "威廉", "彼得", "法蘭克"].reduce((totalChar, x) => totalChar + x.length, 0)
< 9
上面那個例子,我們用reduce
算出序列中每個項目字串長度的總和。totalChar
是累積的結果,x
代表每一個項目,我們將totalChar
初始化為0
,接著掃過序列中的所有項目,並在每掃過一個項目時執行totalChar + x.length
。
想要看更多reduce()
的例子,或是更詳細的瞭解reduce()
運作的方式,可以到這個網站➃來參考看看。
好了,JavaScript 的速成班就到這結束了,若對 JavaScript 想更了解的朋友,記得把 Eloquent JavaScript➂ 翻來看一看,或是查看 FireFox 提供的線上資源➄。我們就介紹到這裡,下課囉!
➀ 好 Js 不學嗎 !?
➁ RE:從零開始的學習 JS 生活
➂ Eloquent JavaScript 官方
➃ 開發者開發資源網站 reduce()
在線說明
➄ 開發者開發資源官方網站
註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕