其他視覺化類型

The simple graph has brought more information to the data analyst’s mind than any other device.

John Tukey

基礎視覺化中我們暸解如何在 Python 與 R 語言中使用長條圖探索一組文字資料的相異觀測值數量與一組數值資料依類別分組摘要排序;使用直方圖探索一組數值資料的分佈;使用盒鬚圖探索一組數值資料依類別分組的分佈;使用散佈圖探索兩組數值資料的相關以及使用線圖探索數值資料隨著日期時間的變動趨勢。

除了這五種基礎視覺化,尚有許多其他較為亮麗的類型可以協助資料科學團隊方法進行探索性資料分析,有些非基礎的視覺化類型必須引用不同的模組或套件,記得在引用 Python 模組之前使用 pip install MODULENAME 指令在終端機進行安裝(如果開發環境在 Google Colaboratory 則在單元格中輸入 !pip install MODULE_NAME 然後執行;)在引用 R 語言套件之前使用 install.packages(PACKAGE_NAME) 指令在 R 的開發環境(我們使用 RStudio)安裝。

一組文字資料的相異觀測值數量

除了長條圖以外,樹狀圖(Treemap)也常用來探索文字資料相異值,觀察數量多寡與比例組成;它會將類別資料顯示為一組矩形,每個獨特分類都各自是一個矩形,用矩形的面積大小表示觀測值個數。例如想知道 1995 至 1996 年球季中的芝加哥公牛隊陣容組成。

Python

在 Python 中我們引用 squarify 模組來繪製樹狀圖。

# treemap
import pandas as pd
import matplotlib.pyplot as plt
import squarify

csv_url = "https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996.csv"
df = pd.read_csv(csv_url)
grouped = df.groupby("Pos")
pos = grouped["Pos"].count()
squarify.plot(sizes=pos.values, label=pos.index, color=["red", "green", "blue", "grey", "yellow"], alpha=0.4)
plt.axis('off')
plt.title("1995-1996 Chicago Bulls roster")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13

1995 至 1996 年球季中的芝加哥公牛隊陣容組成

R 語言

在 R 語言中我們引用 treemapify 套件中的 geom_treemap() 函數來繪製樹狀圖。

# treemap
library(treemapify)
library(ggplot2)
library(dplyr)

csv_url <- "https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996.csv"
df <- read.csv(csv_url)
pos <- df %>% 
  group_by(Pos) %>%
  summarise(freq = n())
ggplot(pos, aes(area = freq, label = Pos, fill = Pos)) +
  geom_treemap() +
  geom_treemap_text(fontface = "italic", colour = "white", place = "centre") +
  ggtitle("1995-1996 Chicago Bulls roster") +
  theme(legend.position = "none")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

1995 至 1996 年球季中的芝加哥公牛隊陣容組成

一組數值資料依類別分組摘要排序

除了長條圖以外,棒棒糖圖(Lollipop)也能用來將數值資料分組排序後的結果呈現,這是結合散佈圖(Scatter plot)與長條圖(Bar chart)的混合圖形,顯示數值與一個類別之間的關係。原則上使用水平的棒棒糖圖並且將數值最大的放在上方,依序往下遞減。

Python

在 Python 中我們使用 plt.plot() 完成散佈圖描繪數值的位置,再利用 plt.hline() 繪製水平線表現出高度。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

per_game_url = "https://storage.googleapis.com/ds_data_import/stats_per_game_chicago_bulls_1995_1996.csv"
player_info_url = "https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996.csv"
per_game = pd.read_csv(per_game_url)
player_info = pd.read_csv(player_info_url)
df = pd.merge(player_info, per_game[["Name", "PTS/G"]], left_on="Player", right_on="Name")
grouped = df.groupby("Pos")
points_per_game = grouped["PTS/G"].mean()
points_per_game = points_per_game.sort_values()
sns.set_style("white")
plt.plot(points_per_game, range(1, points_per_game.size + 1), 'o')
plt.hlines(y=range(1, points_per_game.size + 1), xmin=0, xmax=points_per_game, color='skyblue')
plt.yticks(range(1, points_per_game.size + 1), points_per_game.index)
plt.xlim(0, 33)
for i, v in enumerate(points_per_game):
  plt.text(v + 0.5, i + 0.95, "{:.1f}".format(v))
plt.title("1995-1996 Chicago Bulls PPG by positions")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1995 至 1996 年球季中的芝加哥公牛隊各位置的場均得分

R 語言

在 R 語言中我們使用 geom_point() 函數描繪數值的位置,再利用 geom_segment() 函數描繪高度。

library(dplyr)
library(ggplot2)

per_game_url <- "https://storage.googleapis.com/ds_data_import/stats_per_game_chicago_bulls_1995_1996.csv"
player_info_url <- "https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996.csv"
per_game <- read.csv(per_game_url)
player_info <- read.csv(player_info_url)
df <- merge(player_info, per_game[, c("Name", "PTS.G")], by.x = "Player", by.y = "Name")
df %>% 
  group_by(Pos) %>% 
  summarise(mean_pts = mean(PTS.G)) %>%
  arrange(mean_pts) %>% 
  mutate(x = 1:5) %>% 
  ggplot(aes(x=x, y=mean_pts)) +
  geom_segment(aes(x=x, xend=x, y=0, yend=mean_pts), color="grey") +
  geom_point(color="orange", size=4) +
  geom_text(aes(label = sprintf("%.1f", mean_pts), y= mean_pts),  hjust = -1) +
  theme_light() +
  scale_x_continuous(breaks = 1:5, labels = c("PF", "C", "PG", "SF", "SG")) +
  scale_y_continuous(limits = c(0, 35)) +
  coord_flip() +
  theme(
    panel.grid.major.x = element_blank(),
    panel.border = element_blank(),
    axis.ticks.x = element_blank()
  ) +
  ggtitle("1995-1996 Chicago Bulls PPG by positions") +
  xlab("") +
  ylab("")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

1995 至 1996 年球季中的芝加哥公牛隊各位置的場均得分

一組數值資料的分佈

與直方圖的呈現相似,密度圖(Density plot)亦常被用來顯示數值資料的分佈,與直方圖相同只需要輸入一組數值資料。

Python

在 Python 中我們可以使用 Seaborn 模組的 kdeplot() 繪製出密度圖。

from pyquery import PyQuery as pq
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def get_nba_salary():
  """
  Get NBA players' salary from SPORTRAC.COM
  """
  nba_salary_ranking_url = "https://www.spotrac.com/nba/rankings/"
  html_doc = pq(nba_salary_ranking_url)
  player_css = ".team-name"
  pos_css = ".rank-position"
  salary_css = ".info"
  players = [p.text for p in html_doc(player_css)]
  positions = [p.text for p in html_doc(pos_css)]
  salaries = [s.text.replace("$", "") for s in html_doc(salary_css)]
  salaries = [int(s.replace(",", "")) for s in salaries]
  df = pd.DataFrame()
  df["player"] = players
  df["pos"] = positions
  df["salary"] = salaries
  return df

nba_salary = get_nba_salary()
sns.kdeplot(nba_salary['salary'], shade=True)
plt.title("Salary of NBA players seems left-skewed.")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

美國職籃聯盟 NBA 球員的年薪分佈

R 語言

在 R 語言中可以使用 ggplot2 套件中的 geom_density() 函數繪製出密度圖。

library(rvest)
library(ggplot2)

get_nba_salary <- function() {
  nba_salary_ranking_url <- "https://www.spotrac.com/nba/rankings/"
  html_doc <- nba_salary_ranking_url %>% 
    read_html()
  player_css <- ".team-name"
  pos_css <- ".rank-position"
  salary_css <- ".info"
  players <- html_doc %>% 
    html_nodes(css = player_css) %>% 
    html_text()
  positions <- html_doc %>% 
    html_nodes(css = pos_css) %>% 
    html_text()
  salaries <- html_doc %>% 
    html_nodes(css = salary_css) %>% 
    html_text() %>% 
    gsub(pattern = "\\$", replacement = "", .) %>% 
    gsub(pattern = ",", replacement = "", .) %>% 
    as.numeric()
  df <- data.frame(player = players,
                   pos = positions,
                   salary = salaries,
                   stringsAsFactors = FALSE)
  return(df)
}
nba_salary <- get_nba_salary()
nba_salary %>% 
  ggplot(aes(x = salary)) +
    geom_density(alpha = 0.5, fill = "blue") +
    ggtitle("Salary of NBA players seems left-skewed.")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

美國職籃聯盟 NBA 球員的年薪分佈

一組數值資料依類別分組的分佈

當需要觀察不同類別分組數值資料的分佈來理解峰度(kurtosis)以及偏態(skewness)情況時,可以將前面我們使用的密度圖依照類別在同一個圖形軸上堆疊呈現或者使用 facet 的技法分組描繪。而另外一種與盒鬚圖極為相似的小提琴圖(Violin plot)也是資料科學團隊在探索這類型資料組合的一個熱門選項,小提琴圖是加入密度訊息的盒鬚圖。

Python

在 Python 中,我們可以呼叫多次 seaborn 模組的 kdeplot() 在同一個圖形軸上堆疊密度圖。

from pyquery import PyQuery as pq
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def get_nba_salary():
  """
  Get NBA players' salary from SPORTRAC.COM
  """
  nba_salary_ranking_url = "https://www.spotrac.com/nba/rankings/"
  html_doc = pq(nba_salary_ranking_url)
  player_css = ".team-name"
  pos_css = ".rank-position"
  salary_css = ".info"
  players = [p.text for p in html_doc(player_css)]
  positions = [p.text for p in html_doc(pos_css)]
  salaries = [s.text.replace("$", "") for s in html_doc(salary_css)]
  salaries = [int(s.replace(",", "")) for s in salaries]
  df = pd.DataFrame()
  df["player"] = players
  df["pos"] = positions
  df["salary"] = salaries
  return df

nba_salary = get_nba_salary()
wide_format = nba_salary.pivot(index='player', columns='pos', values='salary')
colors = ["r", "g", "b", "c", "m"]
positions = nba_salary["pos"].unique()
for color, pos in zip(colors, positions):
  sns.kdeplot(wide_format[pos][wide_format[pos].notna()], shade=True, color=color, alpha = 0.5)

plt.title("Salary of NBA players by positions.")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

亦可以使用 subplots() 將五個密度圖以子圖方式合併在一個畫布之上。

from pyquery import PyQuery as pq
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def get_nba_salary():
  """
  Get NBA players' salary from SPORTRAC.COM
  """
  nba_salary_ranking_url = "https://www.spotrac.com/nba/rankings/"
  html_doc = pq(nba_salary_ranking_url)
  player_css = ".team-name"
  pos_css = ".rank-position"
  salary_css = ".info"
  players = [p.text for p in html_doc(player_css)]
  positions = [p.text for p in html_doc(pos_css)]
  salaries = [s.text.replace("$", "") for s in html_doc(salary_css)]
  salaries = [int(s.replace(",", "")) for s in salaries]
  df = pd.DataFrame()
  df["player"] = players
  df["pos"] = positions
  df["salary"] = salaries
  return df

nba_salary = get_nba_salary()
fig, axes = plt.subplots(2, 3)
wide_format = nba_salary.pivot(index='player', columns='pos', values='salary')
colors = ["r", "g", "b", "c", "m"]
positions = nba_salary["pos"].unique()
ax_rows = [0, 0, 0, 1, 1]
ax_cols = [0, 1, 2, 0, 1]
for pos, color, ax_row, ax_col in zip(positions, colors, ax_rows, ax_cols):
  sns.kdeplot(wide_format[pos][wide_format[pos].notna()], shade=True, color=color, alpha = 0.5, ax=axes[ax_row, ax_col], legend=False)
  axes[ax_row, ax_col].set_title(pos)

axes[1, 2].set_visible(False) # hiding the sixth subplot
fig.suptitle("Salary of NBA players by positions")
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

或者使用 seaborn 模組的 violinplot() 函數繪製小提琴圖。

from pyquery import PyQuery as pq
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def get_nba_salary():
  """
  Get NBA players' salary from SPORTRAC.COM
  """
  nba_salary_ranking_url = "https://www.spotrac.com/nba/rankings/"
  html_doc = pq(nba_salary_ranking_url)
  player_css = ".team-name"
  pos_css = ".rank-position"
  salary_css = ".info"
  players = [p.text for p in html_doc(player_css)]
  positions = [p.text for p in html_doc(pos_css)]
  salaries = [s.text.replace("$", "") for s in html_doc(salary_css)]
  salaries = [int(s.replace(",", "")) for s in salaries]
  df = pd.DataFrame()
  df["player"] = players
  df["pos"] = positions
  df["salary"] = salaries
  return df

nba_salary = get_nba_salary()
sns.violinplot(x=nba_salary["pos"], y=nba_salary["salary"])
plt.title("Salary of NBA players by positions")
plt.xlabel("Positions")
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

R 語言

在 R 語言中,我們可以在 ggplot(aes()) 函數中將鋒衛位置指派給 colourfill 參數,如此一來密度圖就會以顏色堆疊不同位置的年薪密度。

library(rvest)
library(ggplot2)

get_nba_salary <- function() {
  nba_salary_ranking_url <- "https://www.spotrac.com/nba/rankings/"
  html_doc <- nba_salary_ranking_url %>% 
    read_html()
  player_css <- ".team-name"
  pos_css <- ".rank-position"
  salary_css <- ".info"
  players <- html_doc %>% 
    html_nodes(css = player_css) %>% 
    html_text()
  positions <- html_doc %>% 
    html_nodes(css = pos_css) %>% 
    html_text()
  salaries <- html_doc %>% 
    html_nodes(css = salary_css) %>% 
    html_text() %>% 
    gsub(pattern = "\\$", replacement = "", .) %>% 
    gsub(pattern = ",", replacement = "", .) %>% 
    as.numeric()
  df <- data.frame(player = players,
                   pos = positions,
                   salary = salaries,
                   stringsAsFactors = FALSE)
  return(df)
}
nba_salary <- get_nba_salary()
nba_salary %>% 
  ggplot(aes(x = salary, colour=pos, fill=pos)) +
    geom_density(alpha = 0.5) +
    ggtitle("Salary of NBA players by positions")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

亦可以使用 facet_wrap() 函數將不同鋒衛位置的密度圖合併在一個畫布之上。

library(rvest)
library(ggplot2)

get_nba_salary <- function() {
  nba_salary_ranking_url <- "https://www.spotrac.com/nba/rankings/"
  html_doc <- nba_salary_ranking_url %>% 
    read_html()
  player_css <- ".team-name"
  pos_css <- ".rank-position"
  salary_css <- ".info"
  players <- html_doc %>% 
    html_nodes(css = player_css) %>% 
    html_text()
  positions <- html_doc %>% 
    html_nodes(css = pos_css) %>% 
    html_text()
  salaries <- html_doc %>% 
    html_nodes(css = salary_css) %>% 
    html_text() %>% 
    gsub(pattern = "\\$", replacement = "", .) %>% 
    gsub(pattern = ",", replacement = "", .) %>% 
    as.numeric()
  df <- data.frame(player = players,
                   pos = positions,
                   salary = salaries,
                   stringsAsFactors = FALSE)
  return(df)
}
nba_salary <- get_nba_salary()
nba_salary %>% 
  ggplot(aes(x = salary, colour=pos, fill=pos)) +
    geom_density(alpha = 0.5) +
    ggtitle("Salary of NBA players by positions") +
    facet_wrap(~pos) +
    theme(legend.position = "none")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

或者使用 geom_violin() 函數繪製小提琴圖。

library(rvest)
library(ggplot2)

get_nba_salary <- function() {
  nba_salary_ranking_url <- "https://www.spotrac.com/nba/rankings/"
  html_doc <- nba_salary_ranking_url %>% 
    read_html()
  player_css <- ".team-name"
  pos_css <- ".rank-position"
  salary_css <- ".info"
  players <- html_doc %>% 
    html_nodes(css = player_css) %>% 
    html_text()
  positions <- html_doc %>% 
    html_nodes(css = pos_css) %>% 
    html_text()
  salaries <- html_doc %>% 
    html_nodes(css = salary_css) %>% 
    html_text() %>% 
    gsub(pattern = "\\$", replacement = "", .) %>% 
    gsub(pattern = ",", replacement = "", .) %>% 
    as.numeric()
  df <- data.frame(player = players,
                   pos = positions,
                   salary = salaries,
                   stringsAsFactors = FALSE)
  return(df)
}
nba_salary <- get_nba_salary()
nba_salary %>% 
  ggplot(aes(x = pos, y = salary, colour=pos, fill=pos)) +
    geom_violin(alpha = 0.5) +
    ggtitle("Salary of NBA players by positions") +
    theme(legend.position = "none") +
    scale_x_discrete(labels = c("C", "PG", "PF", "SG", "SF"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

美國職籃聯盟 NBA 球員依照不同的鋒衛位置年薪分佈

多組數值資料的相關

使用散佈圖(Scatter plot)描繪兩組數值資料的相關是最直觀且廣泛的方式;當資料科學團隊需要將多組數值資料兩兩成對觀察相關特徵的時候,會改而採用熱圖(Heatmap)將這些數值資料彼此的相關係數(從 -1 至 1)以色彩地圖(colormap)來表現。例如我們想探索 1995 至 1996 年球季中的芝加哥公牛隊球員陣容,各個球員的場均數據(例如上場時間、場均得分與場均籃板等)相關性,因為數據眾多的緣故可以採用熱圖作為視覺化。

在製作熱圖之前得先算出所有場均數據兩兩成對的相關係數,這個計算過程是取得相關矩陣(correlation matrix),在 Python 與 R 語言中計算相關矩陣都有可以直接使用的函數或方法,我們只需要注意輸入的資料是否都是數值類型即可。

Python

在 Python 中可以使用 df.corr() 方法取得相關矩陣,再利用 seaborn 模組的 heatmap() 函數即可繪製出熱圖。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996_per_game.csv")
df_numerics = df.iloc[:, 4:]
corr_matrix = df_numerics.corr()
sns.heatmap(corr_matrix)
plt.show()
1
2
3
4
5
6
7
8
9

1995 至 1996 年球季中的芝加哥公牛隊球員場均數據相關性

R 語言

在 R 語言中可以使用 cor() 函數取得相關矩陣,由於 ggplot2 並不能直接輸入相關矩陣,因此我們另外引用 reshape2 套件中的 melt() 函數將相關矩陣轉換為長表格,再以 geom_tile() 函數繪製出熱圖。

library(ggplot2)
library(reshape2)

df <- read.csv("https://storage.googleapis.com/ds_data_import/chicago_bulls_1995_1996_per_game.csv")
cor_matrix <- cor(df[, 5:ncol(df)])
melt_cor_matrix <- melt(cor_matrix)
ggplot(data = melt_cor_matrix, aes(x = Var1, y = Var2, fill = value)) + 
  geom_tile() +
  xlab("") +
  ylab("") +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red",
                       midpoint = 0, limits = c(-1, 1), name = "Corr")
1
2
3
4
5
6
7
8
9
10
11
12

1995 至 1996 年球季中的芝加哥公牛隊球員場均數據相關性

證券的 OHLC 趨勢

使用線圖(Line graph)觀察數值隨著日期時間變動趨勢已是該類型視覺化最直觀且廣泛的應用,但是在從事證券投資相關工作的資料科學團隊中,使用傳統線圖不足以描繪證券在交易日的開盤價(Open)、最高價(High)、最低價(Low)與收盤價(Close)四組數值資料,這時候會改而採用 Candlestick 圖形。

在繪製之前得先將交易日、開盤價(Open)、最高價(High)、最低價(Low)與收盤價(Close)的資料備妥,可以利用財經網站的 API 或者證券交易所擷取,我們選擇台灣證券交易所取得台積電(2330)在本月所有交易日的 OHLC 資訊。

Python

在 Python 中原本多數人在使用的 mpl_finance 模組已經沒有在維護,因此我們改使用 plotly 的 Candlestick() 函數來繪製,值得注意的是要繪製 plotly 圖形得使用該服務的 API 認證,所以得先去 Plotly 網站註冊一個帳號並產出 API 金鑰。

import requests
import datetime
import pandas as pd
import plotly.plotly as py
import plotly.graph_objs as go

def get_ohlc(twse_ticker):
  """
  Get ohlc data for current month
  """
  today = datetime.datetime.today().strftime('%Y%m%d')
  twse_url = "http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date={}&stockNo={}".format(today, twse_ticker)
  stock = requests.get(twse_url).json()
  trading_dates = []
  opens = []
  highs = []
  lows = []
  closes = []
  for i in range(len(stock["data"])):
    trading_dates.append(stock["data"][i][0])
    opens.append(float(stock["data"][i][3]))
    highs.append(float(stock["data"][i][4]))
    lows.append(float(stock["data"][i][5]))
    closes.append(float(stock["data"][i][6]))

  trading_dates_year = [str(int(td.split("/")[0]) + 1911) for td in trading_dates]
  trading_dates_month = [td.split("/")[1] for td in trading_dates]
  trading_dates_day = [td.split("/")[2] for td in trading_dates]
  trading_dates = ["{}-{}-{}".format(yr, m, d) for yr, m, d in zip(trading_dates_year, trading_dates_month, trading_dates_day)]
  trading_dates = pd.to_datetime(trading_dates)
  df = pd.DataFrame()
  df["trading_date"] = trading_dates
  df["open"] = opens
  df["high"] = highs
  df["low"] = lows
  df["close"] = closes
  df = df.set_index("trading_date")
  return df

tsmc = get_ohlc('2330')
py.sign_in('USERNAME', 'APIKEY') # Use your own plotly Username / API Key
trace = go.Candlestick(x=tsmc.index,
                       open=tsmc["open"],
                       high=tsmc["high"],
                       low=tsmc["low"],
                       close=tsmc["close"])
layout = go.Layout(
    xaxis = dict(
        rangeslider = dict(
            visible = False
        )
    )
)
data = [trace]
fig = go.Figure(data=data,layout=layout)
py.iplot(fig, filename='simple_candlestick')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

台積電(2330)在本月所有交易日的 OHLC 資訊

R 語言

在 R 語言可以使用 plotly 的 plot_ly() 函數,在其中指定 type = "candlestick"

library(jsonlite)
library(plotly)
library(magrittr)

get_ohlc <- function(twse_ticker) {
  today <- as.character(Sys.Date())
  today <- gsub(pattern = "-", replacement = "", today)
  twse_url <- sprintf("http://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=%s&stockNo=%s",today, twse_ticker)
  stock <- fromJSON(twse_url)
  data <- stock$data
  trading_dates <- data[, 1]
  trading_dates <- strsplit(trading_dates, split = "/")
  trading_dates_yr <- c()
  trading_dates_month <- c()
  trading_dates_day <- c()
  for (i in 1:length(trading_dates)) {
    trading_dates_yr <- c(trading_dates_yr, as.numeric(trading_dates[[i]][1]) + 1911)
    trading_dates_month <- c(trading_dates_month, trading_dates[[i]][2])
    trading_dates_day <- c(trading_dates_day, trading_dates[[i]][3])
  }
  trading_dates <- paste(trading_dates_yr, trading_dates_month, trading_dates_day,
                         sep = "-")
  trading_dates <- as.Date(trading_dates)
  opens <- as.numeric(data[, 4])
  highs <- as.numeric(data[, 5])
  lows <- as.numeric(data[, 6])
  closes <- as.numeric(data[, 7])
  ohlc <- data.frame(trading_date = trading_dates,
                     open = opens,
                     high = highs,
                     low = lows,
                     close = closes,
                     stringsAsFactors = FALSE)
  return(ohlc)
}

tsmc <- get_ohlc("2330")
p <- tsmc %>%
  plot_ly(x = ~trading_date,
          type="candlestick",
          open = ~open,
          close = ~close,
          high = ~high,
          low = ~low) %>%
  layout(xaxis = list(rangeslider = list(visible = FALSE)))
p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

台積電(2330)在本月所有交易日的 OHLC 資訊

數值資料依地理資訊的摘要

資料科學團隊有時會面臨數值資料對應地理區域的探索資料分析,入門的視覺化圖形稱之為 Choropleth map,協助使用者快速將數值摘要對應至地圖上,是一種被廣泛應用且效果極佳的視覺化圖形。

想要繪製 Choropleth map 我們需要兩份資料,一是地理區域的 geojson 檔案,另一則是欲映射至地理區域上的數值資料。假如想要將 2017 年 1 至 11 月台灣各縣市平均空氣中細懸浮微粒(PM 2.5)濃度以 Choropleth map 呈現,我們需要空氣品質資料(來自行政院環境保護署)以及台灣的 geojson 檔案(來自 g0v 零時政府)。

Python

在 Python 中我們可以引用 folium 模組的 Map() 函數先建立出以 OpenStreetMap 為地圖圖層的物件,然後指定台灣 geojson 檔案的 COUNTYNAME 作為 key_on= 的參數跟空氣品質資料的 county 變數關聯。

import pandas as pd
import folium

pm25_url = "https://storage.googleapis.com/ds_data_import/2017_avg_pm25.csv"
pm25 = pd.read_csv(pm25_url)
# 調整臺為台
pm25["county"] = pm25["county"].str.replace("臺", "台")
# 調整桃園市為桃園縣
pm25["county"] = pm25["county"].str.replace("桃園市", "桃園縣")
geojson = "twCounty2010.geo.json"

m = folium.Map(location=[24, 121], zoom_start=7)

m.choropleth(
    geo_data=geojson,
    name='choropleth',
    data=pm25,
    columns=['county', 'avg_pm25'],
    key_on='feature.properties.COUNTYNAME',
    fill_color='RdYlGn_r',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='PM2.5'
)

folium.LayerControl().add_to(m)
m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

2017 年 1 至 11 月台灣各縣市平均空氣中細懸浮微粒(PM 2.5)濃度

R 語言

在 R 語言中我們需要先引用 geojsonio 套件中的 geojson_read() 函數將 geojson 檔案讀取成為 spatial data 的清單;接著利用 tigris 套件中的 geo_join() 函數將空氣品質資料加入 spatial data;最後才是引用 leaflet 套件的 addPolygons() 函數完成 Choropleth map。

library(leaflet)
library(geojsonio)
library(tigris)

pm25_url <- "https://storage.googleapis.com/ds_data_import/2017_avg_pm25.csv"
pm_25 <- read.csv(pm25_url, stringsAsFactors = FALSE)
# 取代臺為台
pm_25$county <- gsub(pattern = "臺", replacement = "台", pm_25$county)
# 取代桃園市為桃園縣
pm_25$county <- gsub(pattern = "桃園市", replacement = "桃園縣", pm_25$county)
sp_data <- geojson_read("~/twgeojson/json/twCounty2010.geo.json", what = "sp")
my_pal <- colorNumeric(palette = "RdYlGn", domain = pm_25$avg_pm25, n = 5, reverse = TRUE)
merged <- geo_join(sp_data, pm_25, "COUNTYNAME", "county")

merged %>% 
  leaflet() %>% 
  addTiles() %>%
  setView(lat = 24, lng = 121, zoom = 7) %>%
  addPolygons(stroke = FALSE, smoothFactor = 0.2, fillOpacity = 0.6,
              fillColor = ~my_pal(avg_pm25)) %>%
  addLegend(position = "bottomright", pal = my_pal, values = ~avg_pm25,
            title = "PM2.5",
            opacity = 0.7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2017 年 1 至 11 月台灣各縣市平均空氣中細懸浮微粒(PM 2.5)濃度

小結

在這個小節中我們簡介如何在 Python 與 R 語言使用不同於基礎視覺化的圖形,像是樹狀圖、棒棒糖圖、密度圖、小提琴圖、熱圖、Candlestick 圖與 Choropleth Map 來探索不同資料型別的特徵,像是一組文字資料的相異觀測值數量、一組數值資料依類別分組摘要排序、一組數值資料的分佈、一組數值資料依類別分組的分佈、多組數值資料的相關、證券的 OHLC 趨勢以及數值資料依地理資訊的摘要。

延伸閱讀