這篇文章是Julia的視覺化應用的集合,所有精華都集中在這了。我們將會涵蓋相當多的圖形,介紹各種圖形的概念,何時可以使用這些圖形,以及如何在拿到資料後將這些圖形繪製出來。我們也會介紹資料視覺化的概念,此外,還會回答一個重要問題, 為什麼要做資料視覺化?
整篇文章將會有
- 資料視覺化導論
- 長條圖(Bar Chart)
- 直方圖(Histogram)
- 機率密度圖(Density Plot)
- 盒形圖(Box Plot)
- 散佈圖及散佈圖矩陣(Scatter Matrix)
- 圓餅圖(Pie Chart)
- 分位圖(QQ Plot)
- 堆疊柱狀圖(Stake Plot)
- 時間序列圖(Time Series)
- 熱點圖(Heat Map)
1.資料視覺化導論
首先,讓我們先以一個問題開啟這篇文章。
資料視覺化其實就是所謂的「繪圖」,將手上有的資料繪製成各式各樣的圖表(長條圖、直方圖、盒型圖…),相信各位在課本上亦或是現實生活中都可以發現這些圖表的存在,從將民調以圓餅圖呈現到把日均溫、降雨量繪製成直方圖…。這些都是資料視覺化的應用。
然而你有思考過,為什麼需要將這些資料繪製成一個又一個的圖形嗎?
資料視覺化最主要的目的就是藉助於圖形化手段,清晰有效地傳達與溝通資訊。為了清晰有效地傳遞資訊,我們使用統計圖形、圖表、資訊圖表和其他工具。有效的視覺化可以幫助用戶分析和推理資料和證據。 它使複雜的資料更容易理解、理解和使用。
簡單來說,資料視覺化就是幫助我們 「降低資料的理解門檻」 ,讓我們用簡單的方式去理解複雜的資料。更有研究顯示,人腦對於圖像的反應速度比起數字快上好幾倍。比起生硬的統計數據,圖像對於大多數人而言好理解非常多。
不論是我們本身需要去探索資料、挖掘數據;亦或是資料分析後,我們需要把分析結果呈現,使人容易理解。資料視覺化都是不可或缺的一項重要技能!
2.長條圖(Bar Chart)
長條圖(Bar chart)是一個非常實用的統計圖表。和
直方圖非常相似,都是以長條來呈現資料的分布情形,因此很多人會把長條圖和直方圖搞混。
事實上,長條圖和直方圖確實有些差別,只要注意看便可以發現長條圖的長條與長條之間是 互不相連的,而直方圖的長條與長條之間是 互相連接 的。
這是外觀上直接的差別,但其實兩種圖形適用的資料也不同。 長條圖適合用來呈現 離散變數 的資料,而直方圖則是用來呈現連續變數的分配情形。
Julia程式碼
註:為方便大家自己練習,程式中使用的資料另存於
using CSV, CategoricalArrays, StatsPlots; pyplot()
# 匯入資料
df = CSV.read(".../data/companyData.csv")
# 前五筆資料
│ Row │ Dividend │ StockPrice │ Type │ MarketCap │ Year │
│ │ Float64 │ Float64 │ String │ Float64 │ Int64 │
├─────┼──────────┼────────────┼────────┼───────────┼───────┤
│ 1 │ 3.0 │ 1.5 │ A │ 0.5 │ 2012 │
│ 2 │ 4.2 │ 2.1 │ A │ 0.85 │ 2013 │
│ 3 │ 4.9 │ 2.6 │ A │ 1.5 │ 2014 │
│ 4 │ 5.3 │ 3.2 │ A │ 1.9 │ 2015 │
│ 5 │ 5.5 │ 4.1 │ A │ 2.1 │ 2016 │
# 將Year轉成levels,代表著不重複的Type種類
# 並以years儲存
years = levels(df.Year)
# 將資料reshape,從一個array轉成一個5X3的matrix
# 讓列代表年分,欄代表公司
data = reshape(df.MarketCap, 5, 3)
# 繪製長條圖
# 值得一提的是,長條圖有許多種變化形態
# 根據不同的bar_position的設定,可以是堆疊的、分開的...
p1 = groupedbar(years, data, bar_position=:stack)
p2 = groupedbar(years, data, bar_position=:dodge)
plot(p1, p2, bar_width=0.7, fill=[:blue :red :green], label=["A" "B" "C"],
ylims=(0,6), xlabel="Year", ylabel="Market Cap (MM)",
legend=:topleft, size=(800,400))
3.直方圖(Histogram)
直方圖是一種描述數據分布情形的工具,直方圖直接展示出資料的分布情況,專門用來繪 連續變數 ,在各個領域都被廣泛利用。例如用直方圖來描述身高或年齡的分布甚至是大家都看過的金字塔圖表實際上可以被理解成把兩張直方圖組在一起。
在資料分析中直方圖是一個頻繁被使用的工具。但直方圖有一個小問題,那就是當我們樣本數過小的時候,利用直方圖去看分布可能會受 分隔點的位置影響 而看到非常不同的結果。如果我們手上有10筆年齡(假設年齡來自常態分配,但我們不知道)的資料,且我們想要知道年齡的來自何種分配,因此選用直方圖來判斷。
理論上,我們會期望看到直方圖呈現常態分配的樣子。但當我們把10筆年齡的資料繪製成直方圖時,可能無意間由於分隔點之間距離過大,使直方圖呈現的分配看起來一點也不像常態分配,而是像Uniform Distribution。
所以在繪製直方圖時,要多留意分隔點的位置設定,特別是處理小資料集時。
就讓我們一起了解如何Julia使用套件繪製直方圖和手刻直方圖吧!
Julia程式碼
using Plots, Distributions, Random; pyplot()
Random.seed!(0)
# 預計生成的資料筆數
n = 2000
# 生成2000筆服從標準常態分配的資料
data = rand(Normal(),n)
# 為了繪製直方圖,找出資料的最大值及最小值
l, m = minimum(data), maximum(data)
# 設定每一個bin的大小
# 也就是每一個長條的寬度
delta = 0.3;
# 找出長條與長條之間的分隔點
bins = [(x,x+delta) for x in l:delta:m-delta]
# 如果原先bins(長條與長條之間的分隔點)中數值最大的元素
# 小於最大值的話(這代表我們長條與長條之間的分隔點切得還不夠好)
if last(bins)[2] < m
# 把原先bins中數值最大的元素和m(最大值)插入bins
# 也就是說,新增一個分隔點
push!(bins,(last(bins)[2],m))
end
L = length(bins)
# 定義函數inBin()檢查資料是否落在兩間隔分隔點之間
inBin(x,j) = first(bins[j]) <= x && x < last(bins[j])
# 定義函數回傳bin的關渡
sizeBin(j) = last(bins[j]) - first(bins[j])
# 計算所有資料inBin()為true的數量,並除上資料個數
# 也就是說落在這個bin之內的資料的比例
f(j) = sum([inBin(x,j) for x in data])/n
# 計算長條圖的高度
h(x) = sum([f(j)/sizeBin(j) * inBin(x,j) for j in 1:L])
xGrid = -4:0.01:4
# 內建長條圖
histogram(data,normed=true, bins=L,
label="Built-in histogram",
c=:blue, la=0, alpha=0.6)
# 手刻長條圖
plot!(xGrid,h.(xGrid), lw=3, c=:red, label="Manual histogram",
xlabel="x",ylabel="Frequency")
# 機率密度圖
plot!(xGrid,pdf.(Normal(),xGrid),label="True PDF",
lw=3, c=:green, xlims=(-4,4), ylims=(0,0.5))
4.機率密布圖(Density Plot)
和直方圖相比,另一個更美觀(當然美醜還是很主觀的)且更fancy的圖就是機率密布圖了。
機率密布圖和直方圖一樣都是用在描述連續變數的分布情形,但差別在於直方圖使用長條的形式來展示資料分布情形,然而機率密布圖是用平滑曲線來描繪。
不過平滑的曲線並不代表機率密度圖比直方圖更精確地描述出了連續變數的分配情形,機率密度圖只是將直方圖的長條轉換成曲線而已。
那機率密布圖是怎麼資料分布情形轉換成平滑曲線呢?這其實沒有聽起來的那麼容易,為了轉換成平滑曲線,需要使用 kernel density estimation 。
此外,機率密度圖的繪製難度也較高,由於在繪製時需要用kernel density estimation把直方圖的估計值轉成平滑曲線,其中牽扯到一些較複雜的數學,使得自行繪製的難度大大增高。但如果都使用套件來達成的話,其實並不困難。
在這邊我們就只介紹使用套件繪製機率密度圖,但去思考、了解統計學家是如何把一根一根的直方圖轉成平滑曲線也是蠻有趣的。有想進一步研究的同學可以自行了解kernel density estimation~
Julia程式碼
using Random, Distributions, StatsPlots; pyplot()
Random.seed!(0)
# 設定參數
mu1, sigma1 = 10, 5
mu2, sigma2 = 40, 12
# 定義兩個平均數及變異數不同的獨立常態分配
dist1, dist2 = Normal(mu1,sigma1), Normal(mu2,sigma2)
p = 0.3
# 和之前有些許不一樣,不再使用單一分配產出的資料進行繪圖
# 這裡使用dist1和dist2所組建出來的mixture distribution
# 有一部分資料(約30%)來自dist1,其餘則來自dist2
mixRv() = (rand() <= p) ? rand(dist1) : rand(dist2)
# 生成資料
n = 2000
data = [mixRv() for _ in 1:n]
# 繪製機率密布圖
density(data, c=:blue, label="Density via StatsPlots",
xlims=(-20,80), ylims=(0,0.035))
# 直方圖
stephist!(data, bins=50, c=:black, norm=true,
label="Histogram", xlabel="x", ylabel = "Density")
5.盒形圖(Box Plot)
盒形圖(Box plot),又稱為箱型圖,因型狀如盒子而得名。
盒型圖可以用來觀察連續變數的分布,也用來描述離散變數與連續變數之間的關係,顯示不同離散變數下連續變數的分布情形。盒子最上界及最下界分別代表第一四分位數及第三四分位數。而盒子所延伸出的鬚鬚最上界及最下界則分別是Q1(第一四分位數) − 1.5*IQR(四分位距)及Q3(第三四分位數) + 1.5*IQR(四分位距)。
盒型圖也可以藉鬚鬚的最上界及最下界判斷資料中是否有離群值。那些超出上下界的資料,可能就是我們需要特別留意的地方。
值得再次強調的是 — 盒形圖很常被使用來觀察 離散變數與連續變數 之間的關係。
Julia程式碼
using CSV, StatsPlots; pyplot()
# 匯入資料並擷取其中一欄位的資料
data1 = CSV.read(".../data/machine1.csv", header=false)[:,1]
data2 = CSV.read(".../data/machine2.csv", header=false)[:,1]
data3 = CSV.read(".../data/machine3.csv", header=false)[:,1]
# 繪製盒形圖
boxplot([data1,data2,data3], c=[:blue :red :green], label="",
xticks=([1:1:3;],["1", "2", "3"]), xlabel="Machine type",
ylabel="Pipe Diameter (mm)")
從圖中可以看出Machine1的數值普遍高於Machine2&Machine3,而Machine1在上下各有一點超出鬚鬚的最上界及最下界,我們有合理的證據”懷疑”這兩點可能是離群值。
6.散佈圖及散佈圖矩陣(Scatter Matrix)
散佈圖可以說是最為簡單且直觀的統計圖表之一了。利用點點來直接描述連續變數和連續變數的關係,在各種場合都能夠看到。散佈圖更是被廣泛的應用於探討兩變數之間的關係,是我們在做回歸模型之前很常使用的圖形。
散佈圖的限制
散佈圖雖然好用,但本身也有其限制。像是散佈圖最多就只能夠呈現2變數間的關係,當有3個變數時我們或許還可以用 點雲 來呈現,但三維度的圖表已經喪失二維散佈圖的可解釋性以及易讀性了,更何況若是有7、8個變數時,想要利用散佈圖或其他圖形探討彼此之間的相關就變得極其困難。
散佈圖矩陣
不過,既然散佈圖那麼好用,那我們也可以嘗試保留散佈圖的特性。當我們擁有>2個變數要探討時,不彷把散佈圖做個延伸。把多個散佈圖組成散佈圖矩陣來呈現多變數之間的關係就是一個很好的切入方法。既能保有散佈圖直觀好解釋的特性,同時也能一次看多變數之間的關係!
那要如何在Julia中繪製散佈圖矩陣呢?其實我們也只要繪製出多個散佈圖然後在一個一個組起來就行了!
Julia程式碼
using RDatasets, Plots, Measures; pyplot()
# 匯入資料iris
data = dataset("datasets", "iris")
println("Number of rows: ", nrow(data))
# 把變數名稱取出
featureNames = string.(names(data))[1:4]
println("Names of features:\n\t", featureNames)
# 花的種類
speciesNames = unique(data.Species)
# 種類的數量
speciesFreqs = [sn => sum(data.Species .== sn) for sn in speciesNames]
println("Frequency per species:\n\t", speciesFreqs)
# 圖形參數設置
default(msw = 0, ms = 3)
# 定義一個散佈圖矩陣,我們需要使用for迴圈來完成
scatters = [
scatter(data[:,i], data[:,j], c=[:blue :red :green], group=data.Species,
xlabel=featureNames[i], ylabel=featureNames[j], legend = i==1 && j==1)
for i in 1:4, j in 1:4 ]
# 畫出散佈圖,...代表依序將圖畫出
plot(scatters..., size=(1200,800), margin = 4mm)
7.圓餅圖(Pie Chart)
圓餅圖(Pie chart)將類別變數的分配狀況以圓形中的扇形面積大小來呈現,是非常常用的統計圖表。簡單、清楚即能有效率傳達資訊的優點使它熱門。
Julia程式碼
註:為方便大家自己練習,程式中使用的資料另存於
using CSV, CategoricalArrays, Plots; pyplot()
df = CSV.read(".../data/companyData.csv")
# 將Type轉成levels,代表著不重複的Type種類
# 並以companies儲存
companies = levels(df.Type)
# 抓取2012年及2016年時的市值(MarketCap)
year2012 = df[df.Year .== 2012, :MarketCap]
year2016 = df[df.Year .== 2016, :MarketCap]
# 將公司及市值畫成圓餅圖
# 藉由兩張圓餅圖可以很清楚的看到在這四年各公司市值占比的變化
p1 = pie(companies, year2012, title="2012 Market Cap \n by company")
p2 = pie(companies, year2016, title="2016 Market Cap \n by company")
plot(p1, p2, size=(800, 400))
8.分位圖(QQ Plot)
分位圖(QQ plot)Q代表分位數(Quantile)。分位數以白話一點的方式說明,就是描述資料分配情形的數值。
QQ plot透過比較兩筆資料的數值的分位數幫助我們辨別兩筆資料分佈的差別。 若兩筆資料繪製出來的QQ plot趨近於一條線性的斜線,則我們可能會猜測兩筆資料的分配類似或相等,反之亦然 。
QQ plot是做資料分析時一個相當常見的工具,最常被使用在某一筆資料 和理論常態分配 的比較,檢查某一筆資料的分布是否呈現常態分配。
之所以需要和理論常態分配比較的原因是因為大多數常見的統計工具都是建構在母體分配屬於常態分配的假設底下,如果母體分配屬於常態分配,我們才能夠使用基礎統計學所學到的技巧。而如果經由QQ plot確認後,發現母體分配不屬於常態分配,那就需要搬出其他統計工具來分析了!
但除了和理論常態分配比較之外,QQ plot也可以被用來比較手上的資料和 其他分配或其他資料 的差別,只不過在實務上比較少如此應用。
Julia程式碼
using Random, Distributions, StatsPlots, Plots, Measures; pyplot()
Random.seed!(0)
# 設定係數
b1, b2 = 0.5 , 2
# 定義兩個分配dist1、dist2,分別是有著不同係數的beta分配
dist1, dist2, = Beta(b1,b1), Beta(b2,b2)
# 生成分配
n = 2000
data1 = rand(dist1,n)
data2 = rand(dist2,n)
# 利用直方圖比較兩分配
stephist(data1, bins=15, label = "beta($b1,$b1)", c = :red, normed = true)
p1 = stephist!(data2, bins=15, label = "beta($b2,$b2)",
c = :blue, xlabel="x", ylabel="Density",normed = true)
# 利用QQ plot比較兩分配,
# 而我們知道data1、data2並不是屬於同個分配
# 從QQ plot來看,QQ plot也很忠實地呈現了
# 兩分配不相同的事實
p2 = qqplot(data1, data2, c=:black, ms=1, msw =0,
xlabel="Quantiles for beta($b1,$b1) sample",
ylabel="Quantiles for beta($b2,$b2) sample",
legend=false)
plot(p1, p2, size=(800,400), margin = 5mm)
9.堆疊柱狀圖(Stake Plot)
Stack plot在某些情況特別有用。Stack plot經常被用來呈現隨著時間的推移,不同事物、離散變數的組成的差別。
例如對經濟、投資有些研究的人或許就常常會看到SP500指數內各公司市值占比隨著時間推移的變化,又或是我們會看到隨著時間推移某地區人口組成的變化,這些都是以Stack plot來呈現的。
Julia程式碼
註:為方便大家自己練習,程式中使用的資料另存於
using CSV, CategoricalArrays, Plots; pyplot()
df = CSV.read(".../data/companyData.csv")
# 將資料reshape,從一個array轉成一個5X3的matrix
# 讓列代表年分,欄代表公司
mktCap = reshape(df.MarketCap, 5, 3)
# 將Year轉成levels,代表著不重複的Type種類
# 並以years儲存
years = levels(df.Year)
# Stack plot
# 從圖中就可以很清楚發現
# 各公司中以B公司的成長最為兇猛
# 而C公司市值的占比在2012年時曾市最高的
# 接著市值一路縮水,到2016年反而變成市值最小的公司了。
# 此外,A公司在C公司衰退的過程中則是維持緩慢成長
areaplot(years, mktCap,
c=[:blue :red :green], labels=["A" "B" "C"],
xlims=(minimum(years),maximum(years)), ylims=(0,6.5),
legend=:topleft, xlabel="Years", ylabel="MarketCap")
從圖中就可以很清楚發現各公司中以B公司的成長最為兇猛,而C公司市值的占比在2012年時曾市最高的,接著市值一路縮水,到2016年反而變成市值最小的公司了。
此外,A公司在C公司衰退的過程中則是維持緩慢成長
10.時間序列圖(Time Series)
時間序列圖是按照時間發生先後順序繪製的圖表。
它可以幫助我們辨別整體資料的走勢。例如日均溫,若我們將日均溫繪製成時間序列圖便可從時間序列圖看出在夏季時均溫普遍較高,接著一路向下,到冬季1、2月時日均溫來到最低點,再緩慢上升。
和堆疊柱狀圖相彷,都是包含著時間的資訊的圖表,但堆疊柱狀圖一般看的是離散變數的組成隨時間的變化,而時間序列圖則是看連續變數隨時間的變化。
Julia程式碼
註:為方便大家自己練習,程式中使用的資料另存於
using DataFrames, CSV, Statistics, Dates, Plots, Measures; pyplot()
data = CSV.read(.../temperatures.csv")
brisbane = data.Brisbane
goldcoast = data.GoldCoast
# 兩地區日均溫之差
diff = brisbane - goldcoast
# 對年、月、日的資料做處理
# 生成新變數date(yyyy-mm-dd)
dates = [Date(
Year(data.Year[i]),
Month(data.Month[i]),
Day(data.Day[i])
) for i in 1:nrow(data)]
# 將某兩星期的資料抓出來,分別定義為brisFortnight及goldFortnight
fortnightRange = 250:263
brisFortnight = brisbane[fortnightRange]
goldFortnight = goldcoast[fortnightRange]
# 繪圖基本參數設定
default(xlabel="Time", ylabel="Temperature")
default(label=["Brisbane" "Gold Coast"])
# 繪製兩地區均溫的時間序列圖
p1 = plot(dates, [brisbane goldcoast],
c=[:blue :red])
# 繪製兩地區均溫在某兩星期內的時間序列圖
p2 = plot(dates[fortnightRange], [brisFortnight goldFortnight],
c=[:blue :red], m=(:dot, 5, Plots.stroke(1)))
# 繪製兩地區均溫差的時間序列圖
p3 = plot(dates, diff,
c=:black, ylabel="Temperature Difference",legend=false)
# 繪製兩地區均溫差的直方圖
p4 = histogram(diff, bins=-4:0.5:6,
ylims=(0,140), legend = false,
xlabel="Temperature Difference", ylabel="Frequency")
plot(p1,p2,p3,p4, size = (800,500), margin = 5mm)
11.熱點圖(Heat Map)
熱點圖(Heat Map)是一個比較進階一點的圖像。
熱點圖適合用來呈現高密度的點資料。如上圖所示,它可用於識別點資料中的高度密集區域。每一個點,根據顏色的不同,各代表不同數值,這個數值可能是數量、比例、機率…。一般來說,顏色愈暖代表這個地方的數值愈大。
不過,Heat Map除了單純用來表示資料點的密集程度外,在資料分析的實作上,更常被使用的是呈現變數間的相關係數。我們可以利用Heat Map來表示變數之間相關係數的大小,若兩變數之間的相關係數越大,在Heat Map上就會有越深的顏色,如此可以幫助我們輕鬆的了解變數和變數之間的相關性。
在資料分析project的一開始,更是適合用Heat map來呈現變數間的共變異數,幫助我們瀏覽整筆資料的特性。
Julia程式碼
註:為了方便大家練習,資料分析使用的資料另存於
using StatsPlots, Distributions, CSV, DataFrames, Measures; pyplot()
realData = CSV.read(".../temperatures.csv")
default(c=cgrad([:blue, :red]),
xlabel="Brisbane Temperature",
ylabel="Gold Coast Temperature")
# 使用marginalhist()函數繪製熱點圖
# 這個內建函數會一同繪製X軸資料及Y軸資料的直方圖
p1 = marginalhist(realData.Brisbane, realData.GoldCoast, bins=10:0.4:45)
# 生成圖片,面對這樣高密度的點資料,熱點圖可使我們很輕易的識別點資料中的高度密集區域
plot(p1, size = (500,500), margin = 10mm)