35 Sơ đồ và biểu đồ
Chương này này bao gồm hướng dẫn sử dụng code để vẽ:
- Biểu đồ flow diagram bằng DiagrammeR và ngôn ngữ DOT
- Biểu đồ Alluvial/Sankey
- Chuỗi sự kiện theo thời gian
35.1 Chuẩn bị
Gọi packages
Đoạn code dưới đây hiển thị cách gọi các packages cần thiết cho các phân tích. Trong cuốn sách này, chúng tôi nhấn mạnh đến hàm p_load()
từ package pacman, cài đặt gói nếu cần và gọi nó ra để sử dụng. Bạn cũng có thể gọi các packages đã cài đặt với hàm library()
từ base R. Xem chương R cơ bản để biết thêm thông tin về các package R.
::p_load(
pacman# for flow diagrams
DiagrammeR, # For alluvial/Sankey diagrams
networkD3, # data management and visualization tidyverse)
Nhập dữ liệu
Hầu hết nội dung trong chương này không yêu cầu bộ dữ liệu. Tuy nhiên, trong phần sơ đồ Sankey, chúng ta sẽ sử dụng bộ dữ liệu linelist từ một vụ dịch Ebola mô phỏng. Để tiện theo dõi, hãy bấm để tải bộ dữ liệu linelist “đã làm sạch” ở đây (as .rds file). Nhập dữ liệu bằng hàm import ()
từ package rio (có thể xử lý nhiều loại tệp như .xlsx, .csv, .rds - Xem chương Nhập xuất dữ liệu để biết chi tiết).
# import the linelist
<- import("linelist_cleaned.rds") linelist
50 hàng đầu tiên của linelist được hiển thị bên dưới:
35.2 Sơ đồ flow
Chúng ta có thể sử dụng package R DiagrammeR để vẽ biểu đồ/sơ đồ flow. Chúng có thể là sơ đồ tĩnh hoặc có động dựa thay đổi theo những thay đổi trong tập dữ liệu.
Công cụ
Hàm grViz()
được sử dụng để tạo sơ đồ “Graphviz”. Hàm này chấp nhận một chuỗi ký tự đầu vào chứa các hướng dẫn để tạo sơ đồ. Trong chuỗi đó, các hướng dẫn được viết bằng một ngôn ngữ khác, được gọi là ngôn ngữ DOT - khá dễ dàng để học những điều cơ bản.
Cấu trúc cơ bản
- Mở hướng dẫn
grViz("
- Chỉ định hướng và tên của biểu đồ, đồng thời mở ngoặc, vd:
digraph my_flow_chart {
- Câu lệnh biểu đồ (bố cục, hướng sắp xếp các biến số)
- Câu lệnh nút (tạo nút)
- Câu lệnh Edges (cung cấp liên kết giữa các nút)
- Đóng các hướng dẫn
}")
Ví dụ đơn giản
Dưới đây là hai ví dụ đơn giản
Một ví dụ rất đơn giản:
# A minimal plot
::grViz("digraph {
DiagrammeR
graph[layout = dot, rankdir = LR]
a
b
c
a -> b -> c
}")
Một ví dụ khác áp dụng trong y tế công cộng:
grViz(" # All instructions are within a large character string
digraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name
# graph statement
#################
graph [layout = dot,
rankdir = TB,
overlap = true,
fontsize = 10]
# nodes
#######
node [shape = circle, # shape = circle
fixedsize = true
width = 1.3] # width of circles
Primary # names of nodes
Secondary
Tertiary
# edges
#######
Primary -> Secondary [label = ' case transfer']
Secondary -> Tertiary [label = ' case transfer']
}
")
Cú pháp
Cú pháp cơ bản
Tên nút hoặc biểu thức cạnh, có thể được phân tách bằng dấu cách, dấu chấm phẩy hoặc dòng mới.
Điều hướng
Một biểu đồ có thể được định hướng lại để di chuyển từ trái sang phải bằng cách điều chỉnh đối số rankdir
trong câu lệnh biểu đồ. Mặc định là TB (từ trên xuống dưới), nhưng nó có thể là LR (trái sang phải), RL hoặc BT.
Tên nút
Tên nút có thể là các từ đơn, như trong ví dụ đơn giản ở trên. Để sử dụng tên nhiều từ hoặc các ký tự đặc biệt (ví dụ: dấu ngoặc đơn, dấu gạch ngang), hãy đặt tên nút trong dấu ngoặc đơn (’ ’). Có thể dễ dàng hơn để có một tên nút ngắn và gán một nhãn, như được hiển thị bên dưới trong dấu ngoặc vuông [ ]. Nếu bạn muốn có một dòng mới trong tên của nút, bạn phải thực hiện điều đó thông qua một nhãn - sử dụng \n
trong nhãn của nút trong bên dấu ngoặc kép, như được trình bày bên dưới.
Nhóm phụ
Trong các biểu thức cạnh, nhóm phụ có thể được tạo ở hai bên của cạnh bằng dấu ngoặc nhọn ({ }). Sau đó, cạnh áp dụng cho tất cả các nút trong dấu ngoặc - nó là cách viết tắt.
Bố cục
- dot (đặt đối số
rankdir
cho một trong các giá trị sau TB, LR, RL, BT, ) - neato
- twopi
- circo
Nút - có thể chỉnh sửa
label
(ký tự, trong dấu ngoặc kép nếu nhiều từ)
fillcolor
(nhiều màu)
width of shape borderfontcolor
alpha
(độ trong suốt 0-1)
shape
(hình elip, hình bầu dục, kim cương, trứng, bản rõ, điểm, hình vuông, hình tam giác)
style
sides
peripheries
fixedsize
(h x w)
height
width
distortion
penwidth
(độ dày của đường viền)
x
(dịch chuyển trái/phải)
y
(dịch chuyển lên/xuống )
fontname
fontsize
icon
Các cạnh - có thể chỉnh sửa
arrowsize
arrowhead
(normal, box, crow, curve, diamond, dot, inv, none, tee, vee)
arrowtail
dir
(điều hướng, )
style
(gạch ngang, …)
color
alpha
headport
(văn bản phía trước đầu mũi tên )
tailport
(văn bản phía sau đuôi mũi tên)
fontname
fontsize
fontcolor
penwidth
(độ dày của mũi tên)
minlen
(chiều dài tối thiểu)
Tên màu: mã màu theo bảng mã hex hoặc tên màu ‘X11’, xem tại đây để biết thông tin chi tiết về X11
Ví dụ phức tạp
Ví dụ dưới đây mở rộng trên một sơ đồ giám sát, thêm các tên nút phức tạp, các cạnh được nhóm lại, màu sắc và style
DiagrammeR::grViz(" # All instructions are within a large character string
digraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name
# graph statement
#################
graph [layout = dot,
rankdir = TB, # layout top-to-bottom
fontsize = 10]
# nodes (circles)
#################
node [shape = circle, # shape = circle
fixedsize = true
width = 1.3]
Primary [label = 'Primary\nFacility']
Secondary [label = 'Secondary\nFacility']
Tertiary [label = 'Tertiary\nFacility']
SC [label = 'Surveillance\nCoordination',
fontcolor = darkgreen]
# edges
#######
Primary -> Secondary [label = ' case transfer',
fontcolor = red,
color = red]
Secondary -> Tertiary [label = ' case transfer',
fontcolor = red,
color = red]
# grouped edge
{Primary Secondary Tertiary} -> SC [label = 'case reporting',
fontcolor = darkgreen,
color = darkgreen,
style = dashed]
}
")
Các cụm biểu đồ phụ
Để nhóm các nút thành các cụm có khung, hãy đặt chúng trong cùng một đồ thị con được đặt tên (subgraph name {}
). Để xác định từng đồ thị con trong một khung giới hạn, hãy bắt đầu tên của đồ thị con bằng “cluster”, như được trình bày ở 4 khung bên dưới.
DiagrammeR::grViz(" # All instructions are within a large character string
digraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name
# graph statement
#################
graph [layout = dot,
rankdir = TB,
overlap = true,
fontsize = 10]
# nodes (circles)
#################
node [shape = circle, # shape = circle
fixedsize = true
width = 1.3] # width of circles
subgraph cluster_passive {
Primary [label = 'Primary\nFacility']
Secondary [label = 'Secondary\nFacility']
Tertiary [label = 'Tertiary\nFacility']
SC [label = 'Surveillance\nCoordination',
fontcolor = darkgreen]
}
# nodes (boxes)
###############
node [shape = box, # node shape
fontname = Helvetica] # text font in node
subgraph cluster_active {
Active [label = 'Active\nSurveillance']
HCF_active [label = 'HCF\nActive Search']
}
subgraph cluster_EBD {
EBS [label = 'Event-Based\nSurveillance (EBS)']
'Social Media'
Radio
}
subgraph cluster_CBS {
CBS [label = 'Community-Based\nSurveillance (CBS)']
RECOs
}
# edges
#######
{Primary Secondary Tertiary} -> SC [label = 'case reporting']
Primary -> Secondary [label = 'case transfer',
fontcolor = red]
Secondary -> Tertiary [label = 'case transfer',
fontcolor = red]
HCF_active -> Active
{'Social Media' Radio} -> EBS
RECOs -> CBS
}
")
Hình dạng nút
Ví dụ dưới đây, tham khảo từtài liệu trực tuyến này, hiển thị các hình dạng nút được áp dụng và cách viết tắt cho các kết nối cạnh nối tiếp.
::grViz("digraph {
DiagrammeR
graph [layout = dot, rankdir = LR]
# define the global styles of the nodes. We can override these in box if we wish
node [shape = rectangle, style = filled, fillcolor = Linen]
data1 [label = 'Dataset 1', shape = folder, fillcolor = Beige]
data2 [label = 'Dataset 2', shape = folder, fillcolor = Beige]
process [label = 'Process \n Data']
statistical [label = 'Statistical \n Analysis']
results [label= 'Results']
# edge definitions with the node IDs
{data1 data2} -> process -> statistical -> results
}")
Kết quả đầu ra
Cách xử lý và lưu kết quả đầu ra
- Kết quả đầu ra sẽ xuất hiện trong cửa sổ RStudio’s Viewer, theo mặc định ở phía dưới bên phải cùng với các mục Files, Plots, Packages, và Help.
- Để xuất, bạn có thể chọn “Save as image” để lưu dưới dạng ảnh hoặc “Copy to clipboard” để sao chép vào bộ nhớ tạm từ Viewer. Hình ảnh sẽ điều chỉnh theo kích thước được chỉ định.
Đồ thị được tham số hóa
Mục này được trích dẫn từ nguồn sau: https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/
“Các biểu đồ được tham số hóa: Lợi ích tuyệt vời của việc thiết kế các đồ thị trong R là chúng ta có thể kết nối các đồ thị trực tiếp với phân tích của mình bằng cách đọc các giá trị R trực tiếp vào flowchart của chúng ta. Ví dụ: giả sử bạn đã tạo một quy trình lọc để loại bỏ các giá trị sau mỗi giai đoạn của một quy trình, bạn có thể có một đồ thị hiển thị số lượng giá trị còn lại trong tập dữ liệu sau mỗi giai đoạn trong quy trình của bạn. Để làm điều này, chúng ta có thể sử dụng ký hiệu @@X trực tiếp trong đồ thị, sau đó tham chiếu tới footer của biểu đồ bằng cách sử dụng [X]:, trong đó X là chỉ số số duy nhất.”
Chúng tôi khuyến khích bạn xem lại hướng dẫn này nếu tham số hóa là điều bạn quan tâm.
35.3 Sơ đồ Alluvial/Sankey
Gọi packages
Đoạn code dưới đây hiển thị cách gọi các packages cần thiết cho các phân tích. Trong cuốn sách này, chúng tôi nhấn mạnh đến hàm p_load()
từ package pacman, cài đặt gói nếu cần và gọi nó ra để sử dụng. Bạn cũng có thể gọi các packages đã cài đặt với hàm library()
từ base R. Xem chương R cơ bản để biết thêm thông tin về các package R.
Chúng ta gọi package networkD3 để vẽ sơ đồ và package tidyverse cho các bước chuẩn bị dữ liệu.
::p_load(
pacman
networkD3, tidyverse)
Vẽ đồ thị từ một tập dữ liệu
Vẽ những mối liên quan trong một tập dữ liệu. Dưới đây, chúng tôi minh họa việc sử dụng package này với bộ số liệu linelist
. Hãy đọc thêm hướng dẫn trực tuyến sau..
Chúng ta sẽ bắt đầu bằng cách lấy số lượng các trường hợp theo sự kết hợp của nhóm tuổi và bệnh viện. Chúng ta cũng xóa các giá trị thiếu nhóm tuổi để làm sạch. Chúng ta cũng gắn lại nhãn các cột hospital
và cột age_cat
tương ứng là source
và target
. Đây sẽ là hai mặt của sơ đồ Alluvial.
# counts by hospital and age category
<- linelist %>%
links drop_na(age_cat) %>%
select(hospital, age_cat) %>%
count(hospital, age_cat) %>%
rename(source = hospital,
target = age_cat)
Tập dữ liệu bây giờ trông như thế này:
Bây giờ chúng ta tạo một data frame cho tất cả các nút của sơ đồ, dưới cột name
. Điều này bao gồm tất cả các giá trị cho cột hospital
và cột age_cat
. Lưu ý rằng chúng ta cần đảm bảo tất cả chúng đều có kiểu Ký tự trước khi kết hợp chúng và điều chỉnh cột ID thành dạng số thay vì dạng nhãn:
# The unique node names
<- data.frame(
nodes name=c(as.character(links$source), as.character(links$target)) %>%
unique()
)
# print nodes
name
1 Central Hospital
2 Military Hospital
3 Missing
4 Other
5 Port Hospital
6 St. Mark's Maternity Hospital (SMMH)
7 0-4
8 5-9
9 10-14
10 15-19
11 20-29
12 30-49
13 50-69
14 70+
Chúng ta tiếp tục chỉnh sửa data frame có tên links
mà chúng ta đã tạo ở trên với hàm count()
. Chúng ta thêm hai cột dạng số là cột IDsource
và IDtarget
để thực sự phản ánh/tạo liên kết giữa các nút. Các cột này sẽ giữ số thứ tự hàng (vị trí) của nút nguồn và nút đích. Số 1 sẽ bị trừ để các số vị trí này bắt đầu bằng 0 (không phải 1).
# match to numbers, not names
$IDsource <- match(links$source, nodes$name)-1
links$IDtarget <- match(links$target, nodes$name)-1 links
Tập dữ liệu link bây giờ trông như sau:
Bây giờ, chúng ta vẽ sơ đồ Sankey với hàm sankeyNetwork()
. Bạn có thể đọc thêm về từng đối số bằng cách chạy lệnh ?sankeyNetwork
trong bảng điều khiển. Lưu ý rằng trừ khi bạn đặt iterations = 0
, thứ tự các nút của bạn có thể sẽ không như bạn mong đợi.
# plot
######
<- sankeyNetwork(
p Links = links,
Nodes = nodes,
Source = "IDsource",
Target = "IDtarget",
Value = "n",
NodeID = "name",
units = "TWh",
fontSize = 12,
nodeWidth = 30,
iterations = 0) # ensure node order is as in data
p
Đây là một ví dụ trong đó Kết quả của bệnh nhân cũng được bao gồm. Lưu ý trong bước chuẩn bị dữ liệu, chúng ta phải tính toán số lượng các trường hợp giữa tuổi và bệnh viện, và phân biệt biệt giữa bệnh viện và outcome - sau đó liên kết tất cả các số lượng này với nhau bằng hàm bind_rows()
.
# counts by hospital and age category
<- linelist %>%
age_hosp_links drop_na(age_cat) %>%
select(hospital, age_cat) %>%
count(hospital, age_cat) %>%
rename(source = age_cat, # re-name
target = hospital)
<- linelist %>%
hosp_out_links drop_na(age_cat) %>%
select(hospital, outcome) %>%
count(hospital, outcome) %>%
rename(source = hospital, # re-name
target = outcome)
# combine links
<- bind_rows(age_hosp_links, hosp_out_links)
links
# The unique node names
<- data.frame(
nodes name=c(as.character(links$source), as.character(links$target)) %>%
unique()
)
# Create id numbers
$IDsource <- match(links$source, nodes$name)-1
links$IDtarget <- match(links$target, nodes$name)-1
links
# plot
######
<- sankeyNetwork(Links = links,
p Nodes = nodes,
Source = "IDsource",
Target = "IDtarget",
Value = "n",
NodeID = "name",
units = "TWh",
fontSize = 12,
nodeWidth = 30,
iterations = 0)
p
https://www.displayr.com/sankey-diagrams-r/
35.4 Chuỗi sự kiện trong thời gian
Để tạo dòng thời gian hiển thị các sự kiện cụ thể, bạn có thể sử dụng package vistime
.
Xem thêm vignette này
# load package
::p_load(vistime, # make the timeline
pacman# for interactive visualization
plotly )
Đây là tập dữ liệu mà chúng ta sẽ bắt đầu sử dụng:
<- vistime(data) # apply vistime
p
library(plotly)
# step 1: transform into a list
<- plotly_build(p)
pp
# step 2: Marker size
for(i in 1:length(pp$x$data)){
if(pp$x$data[[i]]$mode == "markers") pp$x$data[[i]]$marker$size <- 10
}
# step 3: text size
for(i in 1:length(pp$x$data)){
if(pp$x$data[[i]]$mode == "text") pp$x$data[[i]]$textfont$size <- 10
}
# step 4: text position
for(i in 1:length(pp$x$data)){
if(pp$x$data[[i]]$mode == "text") pp$x$data[[i]]$textposition <- "right"
}
#print
pp
35.5 Sơ đồ DAGs
Bạn có thể tạo sơ đồ DAG theo cách thủ công bằng cách sử dụng package DiagammeR và ngôn ngữ DOT như đã mô tả ở trên.
Ngoài ra, có các package như ggdag và daggity
35.6 Tài nguyên học liệu
Phần lớn nội dung ở trên liên quan đến ngôn ngữ DOT được tham khảo từ hướng dẫn này
Một tài liệu khác chuyên sâu hơn tại đây Tài liệu về DiagammeR
Xem thêm về Sơ đồ Sankey tại đây