29 Trình bày bảng

Chương này sẽ trình bày cách chuyển đổi một bảng tóm tắt dữ liệu thành các bảng sẵn sàng cho mục đích trình bày với package flextable. Các bảng này có thể được chèn vào slide powerpoint, trang HTML, tài liệu PDF hoặc Word, v.v.

Hãy hiểu rằng trước khi sử dụng flextable, bạn phải tạo bảng tóm tắt dữ liệu dưới dạng một data frame. Sử dụng các phương pháp trong các chương Bảng mô tảXoay trục dữ liệu như tạo bảng đơn, tạo bảng chéo, xoay trục, và tính toán các thống kê mô tả. Kết quả là một data frame sau đó có thể được chuyển đến flextable để định dạng hiển thị.

Có nhiều các R packages khác có thể được sử dụng để tạo bảng cho mục đích trình bày - trong chương này chúng tôi nhấm mạnh vào package flextable. Một ví dụ sử dụng knitr package và hàm của nó kable() có thể được tìm thấy trong chương Truy vết tiếp xúc. Tương tự như vậy, package DT cũng được nhấn mạnh trong chương Dashboards với Shiny. Các package khác như GThuxtable được đề cập trong chương Package đề xuất.

29.1 Chuẩn bị

Gọi packages

Hãy cài đặt và gọi package flextable. Trong sổ tay này chúng tôi nhấn mạnh việc sử dụng hàm p_load() từ package pacman, giúp cài đặt package nếu cần thiết gọi chúng ra để sử dụng. Bạn cũng có thể gọi package bằng lệnh library() từ base R. Xem thêm chương R cơ bản để biết thêm các thông tin về các package trong R.

pacman::p_load(
  rio,            # import/export
  here,           # file pathways
  flextable,      # make HTML tables 
  officer,        # helper functions for tables
  tidyverse)      # data management, summary, and visualization

Nhập dữ liệu

Để bắt đầu, chúng ta nhập bộ dữ liệu linelist đã được làm sạch về các ca bệnh Ebola mô phỏng. Để tiện theo dõi, bấm để tải dữ liệu linelist “đã làm sạch” (as .rds file). Nhập dữ liệu bằng hàm import() từ package rio (chấp nhận nhiều loại tập tin như .xlsx, .csv, .rds - xem thêm chương Nhập xuất dữ liệu để biết thêm chi tiết).

# import the linelist
linelist <- import("linelist_cleaned.rds")

50 hàng đầu tiên của bộ dữ liệu linelist được hiển thị như dưới đây.

Chuẩn bị bảng

Trước khi bắt đầu sử dụng flextable bạn cần phải tạo bảng của bạn dưới một data frame. Xem chương Bảng mô tảXoay trục dữ liệu để biết cách tạo một data frame sử dụng các packages như janitordplyr. Đầu tiên, bạn phải sắp xếp nội dung theo hàng và cột như cách bạn muốn nội dung hiển thị. Sau đó, data frame sẽ được chuyển đến flextable để hiển thị nó với màu sắc, tiêu đề, phông chữ, v.v.

Dưới đây là một ví dụ trong chương Bảng mô tả về cách biến đổi các trường hợp bệnh trong linelist thành một data frame để tóm tắt các outcomes của bệnh nhân và giá trị CT theo bệnh viện, với hàng Tổng ở cuối bảng. Đầu ra được lưu dưới dạng table.

table <- linelist %>% 
  
  # Get summary values per hospital-outcome group
  ###############################################
  group_by(hospital, outcome) %>%                      # Group data
  summarise(                                           # Create new summary columns of indicators of interest
    N = n(),                                            # Number of rows per hospital-outcome group     
    ct_value = median(ct_blood, na.rm=T)) %>%           # median CT value per group
  
  # add totals
  ############
  bind_rows(                                           # Bind the previous table with this mini-table of totals
    linelist %>% 
      filter(!is.na(outcome) & hospital != "Missing") %>%
      group_by(outcome) %>%                            # Grouped only by outcome, not by hospital    
      summarise(
        N = n(),                                       # Number of rows for whole dataset     
        ct_value = median(ct_blood, na.rm=T))) %>%     # Median CT for whole dataset
  
  # Pivot wider and format
  ########################
  mutate(hospital = replace_na(hospital, "Total")) %>% 
  pivot_wider(                                         # Pivot from long to wide
    values_from = c(ct_value, N),                       # new values are from ct and count columns
    names_from = outcome) %>%                           # new column names are from outcomes
  mutate(                                              # Add new columns
    N_Known = N_Death + N_Recover,                               # number with known outcome
    Pct_Death = scales::percent(N_Death / N_Known, 0.1),         # percent cases who died (to 1 decimal)
    Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)
  select(                                              # Re-order columns
    hospital, N_Known,                                   # Intro columns
    N_Recover, Pct_Recover, ct_value_Recover,            # Recovered columns
    N_Death, Pct_Death, ct_value_Death)  %>%             # Death columns
  arrange(N_Known)                                    # Arrange rows from lowest to highest (Total row at bottom)

table  # print
## # A tibble: 7 x 8
## # Groups:   hospital [7]
##   hospital                             N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death ct_value_Death
##   <chr>                                  <int>     <int> <chr>                  <dbl>   <int> <chr>              <dbl>
## 1 St. Mark's Maternity Hospital (SMMH)     325       126 38.8%                     22     199 61.2%                 22
## 2 Central Hospital                         358       165 46.1%                     22     193 53.9%                 22
## 3 Other                                    685       290 42.3%                     21     395 57.7%                 22
## 4 Military Hospital                        708       309 43.6%                     22     399 56.4%                 21
## 5 Missing                                 1125       514 45.7%                     21     611 54.3%                 21
## 6 Port Hospital                           1364       579 42.4%                     21     785 57.6%                 22
## 7 Total                                   3440      1469 42.7%                     22    1971 57.3%                 22

29.2 Cơ bản về flextable

Tạo một flextable

Để tạo và quản lý các đối tượng của flextable, đầu tiên chúng ta đẩy data frame vào hàm flextable(), sau đó lưu kết quả là my_table.

my_table <- flextable(table) 
my_table

Sau khi thực hiện việc này, chúng ta có thể pipe dần đối tượng my_table thông qua các hàm định dạng flextable khác.

Trong trang này để rõ ràng, chúng tôi sẽ lưu bảng ở các bước trung gian vào đối tượng my_table, thêm các hàm flextable theo từng bước. Nếu bạn muốn xem tất cả code từ đầu đến cuối được viết trong một đoạn, hãy xem mục Kết hợp tất cả các code phía dưới đây.

Cú pháp chung của mỗi dòng code flextable như sau:

  • function(table, i = X, j = X, part = "X"), where:
    • ‘function’ có thể là một trong số rất nhiều hàm khác nhau, ví dụ như width() để xác định độ rộng cột, bg() để thiết lập màu nền, align() để điều chỉnh văn bản căn giữa / phải / trái, v.v.
    • table = tên của data frame, có thể bỏ qua nếu như data frame được piping vào trong hàm.
    • part = đề cập đến phần nào của bảng mà hàm đang được áp dụng. Ví dụ. “tiêu đề”, “nội dung” hoặc “tất cả”.
    • i = chỉ định hàng mà hàm sẽ được áp dụng, trong đó ‘X’ là số thứ tự hàng. Nếu nhiều hàng được chọn, ví dụ: từ hàng đầu tiên đến hàng thứ ba, ta có thể viết: i = c (1: 3). Lưu ý nếu chọn ‘body’, hàng đầu tiên bắt đầu từ bên dưới phần tiêu đề.
    • j = chỉ định cột mà hàm sẽ được áp dụng, trong đó ‘X’ là số thứ tự cột hoặc tên cột. Nếu nhiều cột được chọn, ví dụ: từ hàng thứ năm đến hàng thứ sáu, ta có thể viết: j = c(5,6).

Bạn có thể tìm thấy danh sách đầy đủ các hàm định dạng trong package flextable tại đây hoặc xem tài liệu hướng dẫn bằng cách gõ ?flextable.

Độ rộng cột

Chúng ta có thể sử dụng hàm autofit() để điều chỉnh bảng sao cho mỗi ô chỉ có một hàng văn bản. Hàm qflextable() là một cách viết tắt thuận tiện cho flextable()autofit().

my_table %>% autofit()

Tuy nhiên, điều này có thể không phải lúc nào cũng phù hợp, đặc biệt nếu có các giá trị rất dài trong các ô, nghĩa là bảng có thể không vừa trong độ rộng của trang.

Thay vào đó, chúng ta có thể điều chỉnh độ rộng cột bằng hàm width(). Điều này có thể tốn một chút thời gian để tìm giá trị chiều rộng phù hợp cho các cột. Trong ví dụ dưới đây, chúng ta chỉ định các độ rộng khác nhau cho cột 1, cột 2 và cột 4 đến 8.

my_table <- my_table %>% 
  width(j=1, width = 2.7) %>% 
  width(j=2, width = 1.5) %>% 
  width(j=c(4,5,7,8), width = 1)

my_table

Tiêu đề cột

Một bảng có nhiều tiêu đề cột sẽ giúp giải thích nội dung bảng một cách dễ dàng hơn.

Đối với bảng này, chúng ta cần thêm một lớp tiêu đề thứ hai để các cột bao gồm các nhóm con giống nhau có thể được nhóm lại với nhau. Chúng ta thực hiện điều này bằng hàm add_header_row() với top = TRUE. Chúng ta cung cấp tên mới của mỗi cột bằng values =, bỏ trống "" đối với các cột chúng ta dự định sẽ ghép lại với nhau sau này.

Chúng ta cũng đổi tên các tên tiêu đề phụ ở hàng thứ hai bằng lệnh set_header_labels().

Cuối cùng, chúng ta sử dụng hàm merge_at () để hợp nhất các tiêu đề cột trong hàng tiêu đề trên cùng.

my_table <- my_table %>% 
  
  add_header_row(
    top = TRUE,                # New header goes on top of existing header row
    values = c("Hospital",     # Header values for each column below
               "Total cases with known outcome", 
               "Recovered",    # This will be the top-level header for this and two next columns
               "",
               "",
               "Died",         # This will be the top-level header for this and two next columns
               "",             # Leave blank, as it will be merged with "Died"
               "")) %>% 
    
  set_header_labels(         # Rename the columns in original header row
      hospital = "", 
      N_Known = "",                  
      N_Recover = "Total",
      Pct_Recover = "% of cases",
      ct_value_Recover = "Median CT values",
      N_Death = "Total",
      Pct_Death = "% of cases",
      ct_value_Death = "Median CT values")  %>% 
  
  merge_at(i = 1, j = 3:5, part = "header") %>% # Horizontally merge columns 3 to 5 in new header row
  merge_at(i = 1, j = 6:8, part = "header")     # Horizontally merge columns 6 to 8 in new header row

my_table  # print

Đường viền và nền

Bạn có thể điều chỉnh đường viền, đường bên trong, v.v. bằng các hàm khác nhau trong flextable. Để dễ dàng, thông thường đầu tiên bạn cần loại bỏ hết các đường viền trong bảng bằng hàm border_remove().

Sau đó, bạn có thể áp dụng các theme đường viền mặc định bằng cách đưa bảng tới hàm theme_box(), theme_booktabs(), hoặc theme_alafoli().

Bạn có thể thêm các đường dọc và ngang bằng nhiều hàm khác nhau. hline()vline() sẽ thêm các dòng vào một hàng hoặc cột cụ thể. Bên trong hàm, bạn cần chỉ định phần mà bảng sẽ áp dụng qua đối số part = với các tùy chọn “all”, “body”, hoặc “header”. Đối với các đường dọc, ghi rõ cột được áp dụng với j =, đối với các đường ngang, ghi rõ hàng được áp dụng với i =. Các hàm khác như vline_right(), vline_left(), hline_top(), và hline_bottom() chỉ thêm các đường viền ở bên ngoài.

Bên trong tất cả các hàm này, kiểu đường phải được định nghĩa thông qua đối số border = và phải là đầu ra của một lệnh riêng biệt bằng cách sử dụng hàm fp_border() từ package officer. Hàm này giúp bạn xác định độ rộng và màu sắc của đường. Bạn có thể định nghĩa các thông tin này phía trên trước khi thực hiện các lệnh liên quan tới bảng, như được trình bày dưới đây:

# define style for border line
border_style = officer::fp_border(color="black", width=1)

# add border lines to table
my_table <- my_table %>% 

  # Remove all existing borders
  border_remove() %>%  
  
  # add horizontal lines via a pre-determined theme setting
  theme_booktabs() %>% 
  
  # add vertical lines to separate Recovered and Died sections
  vline(part = "all", j = 2, border = border_style) %>%   # at column 2 
  vline(part = "all", j = 5, border = border_style)       # at column 5

my_table

Phông chữ và căn chỉnh

Chúng ta căn giữa tất cả các cột ngoại trừ cột ngoài cùng bên trái với tên các bệnh viện, bằng cách sử dụng hàm align() từ flextable.

my_table <- my_table %>% 
   flextable::align(align = "center", j = c(2:8), part = "all") 
my_table

Ngoài ra, chúng ta có thể tăng kích thước phông chữ tiêu đề và sau đó thay đổi thành in đậm. Chúng ta cũng có thể thay đổi hàng “Total” thành in đậm.

my_table <-  my_table %>%  
  fontsize(i = 1, size = 12, part = "header") %>%   # adjust font size of header
  bold(i = 1, bold = TRUE, part = "header") %>%     # adjust bold face of header
  bold(i = 7, bold = TRUE, part = "body")           # adjust bold face of total row (row 7 of body)

my_table

Chúng ta cũng có thể thiết lập để các cột tỷ lệ chỉ hiển thị một chữ số thập phân bằng cách sử dụng hàm colformat_num(). Lưu ý rằng điều này cũng có thể được thực hiện ở giai đoạn quản lý dữ liệu với hàm round().

my_table <- colformat_num(my_table, j = c(4,7), digits = 1)
my_table

Hợp nhất ô

Cũng giống như khi chúng ta hợp nhất các ô theo chiều ngang trong hàng tiêu đề, chúng ta cũng có thể hợp nhất các ô theo chiều dọc bằng cách sử dụng merge_at() và chỉ rõ các hàng (i) và cột (j). Ở đây chúng ta hợp nhất ô “Hospital” và “Total cases with known outcome” theo chiều dọc để cung cấp thêm không gian cho chúng.

my_table <- my_table %>% 
  merge_at(i = 1:2, j = 1, part = "header") %>% 
  merge_at(i = 1:2, j = 2, part = "header")

my_table

Màu nền

Để phân biệt nội dung của bảng với các tiêu đề, chúng ta có thể muốn thêm định dạng bổ sung, ví dụ như thay đổi màu nền. Trong ví dụ này, chúng ta sẽ thay đổi nội dung bảng thành màu xám.

my_table <- my_table %>% 
    bg(part = "body", bg = "gray95")  

my_table 

29.3 Định dạng có điều kiện

Chúng ta có thể highlight tất cả các giá trị trong một cột đáp ứng một quy tắc nhất định, ví dụ các ô có hơn 55% trường hợp tử vong. Đơn giản chỉ cần đặt điều kiện so sánh vào trong đối số i = hoặc j =, phía sau dấu ~. Bạn cần tham chiếu tới thứ tự cột cần highlight trong trong data frame, không phải tiêu đề cột.

my_table %>% 
  bg(j = 7, i = ~ Pct_Death >= 55, part = "body", bg = "red") 

Hoặc, chúng ta có thể highlight toàn bộ hàng đáp ứng một tiêu chí nhất định, chẳng hạn như tên một bệnh viện. Để làm điều này đơn giản chỉ cần không định danh thông số ở đối số (j), để các tiêu chí được áp dụng cho tất cả các cột.

my_table %>% 
  bg(., i= ~ hospital == "Military Hospital", part = "body", bg = "#91c293") 

29.4 Kết hợp tất cả các code

Dưới đây, chúng tôi ghép tất cả code từ các phần trên lại với nhau.

border_style = officer::fp_border(color="black", width=1)

pacman::p_load(
  rio,            # import/export
  here,           # file pathways
  flextable,      # make HTML tables 
  officer,        # helper functions for tables
  tidyverse)      # data management, summary, and visualization

table <- linelist %>% 

  # Get summary values per hospital-outcome group
  ###############################################
  group_by(hospital, outcome) %>%                      # Group data
  summarise(                                           # Create new summary columns of indicators of interest
    N = n(),                                            # Number of rows per hospital-outcome group     
    ct_value = median(ct_blood, na.rm=T)) %>%           # median CT value per group
  
  # add totals
  ############
  bind_rows(                                           # Bind the previous table with this mini-table of totals
    linelist %>% 
      filter(!is.na(outcome) & hospital != "Missing") %>%
      group_by(outcome) %>%                            # Grouped only by outcome, not by hospital    
      summarise(
        N = n(),                                       # Number of rows for whole dataset     
        ct_value = median(ct_blood, na.rm=T))) %>%     # Median CT for whole dataset
  
  # Pivot wider and format
  ########################
  mutate(hospital = replace_na(hospital, "Total")) %>% 
  pivot_wider(                                         # Pivot from long to wide
    values_from = c(ct_value, N),                       # new values are from ct and count columns
    names_from = outcome) %>%                           # new column names are from outcomes
  mutate(                                              # Add new columns
    N_Known = N_Death + N_Recover,                               # number with known outcome
    Pct_Death = scales::percent(N_Death / N_Known, 0.1),         # percent cases who died (to 1 decimal)
    Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)
  select(                                              # Re-order columns
    hospital, N_Known,                                   # Intro columns
    N_Recover, Pct_Recover, ct_value_Recover,            # Recovered columns
    N_Death, Pct_Death, ct_value_Death)  %>%             # Death columns
  arrange(N_Known) %>%                                 # Arrange rows from lowest to highest (Total row at bottom)

  # formatting
  ############
  flextable() %>%              # table is piped in from above
  add_header_row(
    top = TRUE,                # New header goes on top of existing header row
    values = c("Hospital",     # Header values for each column below
               "Total cases with known outcome", 
               "Recovered",    # This will be the top-level header for this and two next columns
               "",
               "",
               "Died",         # This will be the top-level header for this and two next columns
               "",             # Leave blank, as it will be merged with "Died"
               "")) %>% 
    set_header_labels(         # Rename the columns in original header row
      hospital = "", 
      N_Known = "",                  
      N_Recover = "Total",
      Pct_Recover = "% of cases",
      ct_value_Recover = "Median CT values",
      N_Death = "Total",
      Pct_Death = "% of cases",
      ct_value_Death = "Median CT values")  %>% 
  merge_at(i = 1, j = 3:5, part = "header") %>% # Horizontally merge columns 3 to 5 in new header row
  merge_at(i = 1, j = 6:8, part = "header") %>%  
  border_remove() %>%  
  theme_booktabs() %>% 
  vline(part = "all", j = 2, border = border_style) %>%   # at column 2 
  vline(part = "all", j = 5, border = border_style) %>%   # at column 5
  merge_at(i = 1:2, j = 1, part = "header") %>% 
  merge_at(i = 1:2, j = 2, part = "header") %>% 
  width(j=1, width = 2.7) %>% 
  width(j=2, width = 1.5) %>% 
  width(j=c(4,5,7,8), width = 1) %>% 
  flextable::align(., align = "center", j = c(2:8), part = "all") %>% 
  bg(., part = "body", bg = "gray95")  %>% 
  bg(., j=c(1:8), i= ~ hospital == "Military Hospital", part = "body", bg = "#91c293") %>% 
  colformat_num(., j = c(4,7), digits = 1) %>%
  bold(i = 1, bold = TRUE, part = "header") %>% 
  bold(i = 7, bold = TRUE, part = "body")
## `summarise()` has grouped output by 'hospital'. You can override using the `.groups` argument.
table

29.5 Lưu bảng của bạn

Có nhiều cách khác nhau mà bảng có thể được tích hợp vào kết quả đầu ra của bạn.

Lưu bảng đơn

Bạn có thể xuất các bảng ra file Word, PowerPoint hoặc HTML hoặc dưới tệp tin ảnh (PNG). Để thực hiện điều này, hãy sử dụng một trong các hàm sau:

  • save_as_docx()
  • save_as_pptx()
  • save_as_image()
  • save_as_html()

Ví dụ dưới đây, chúng ta sẽ lưu bảng dưới dạng tài liệu word. Lưu ý cú pháp của đối số đầu tiên - bạn chỉ có thể cung cấp tên của đối tượng flextable, ví dụ: my_table, hoặc bạn có thể gán một “tên” cho bảng (ví dụ đặt tên là “my table”). Nếu đặt tên thì tên này sẽ xuất hiện dưới dạng tiêu đề của bảng trong Word. Code để lưu bảng dưới dạng ảnh PNG cũng được minh họa như dưới đây.

# Edit the 'my table' as needed for the title of table.  
save_as_docx("my table" = my_table, path = "file.docx")

save_as_image(my_table, path = "file.png")

Lưu ý là bạn cần cài đặt package webshot hoặc webshot2 để lưu bảng từ flextable dưới dạng ảnh. Hình ảnh xuất ra sẽ có nền trong suốt.

Nếu bạn muốn xem thử kết quả đầu ra của bảng flextable , sử dụng lệnh print() và chỉ định định dạng muốn xem trước với preview =. Tài liệu sẽ được “mở lên” trên máy tính của bạn bằng phần mềm đã chỉ định, nhưng sẽ không được lưu. Điều này có thể hữu ích để kiểm tra xem bảng có vừa với một trang/slide hay không hoặc bạn có thể nhanh chóng copy kết quả sang một tài liệu khác. Bạn có thể sử dụng phương pháp này với đối số preview đặt là “pptx” hoặc “docx”.

print(my_table, preview = "docx") # Word document example
print(my_table, preview = "pptx") # Powerpoint example

In bảng trong R markdown

Bảng này có thể được tích hợp vào R markdown, một dạng báo cáo tự động của bạn, nếu đối tượng bảng được gọi trong phần code chunk của R markdown. Điều này có nghĩa là bảng có thể được cập nhật như một phần của báo cáo trong đó dữ liệu có thể thay đổi, do đó, các con số có thể được làm mới.

Xem thêm chi tiết trong chương Báo cáo với R Markdown của cuốn sổ tay này.

29.6 Nguồn

Sách đầy đủ về flextable có thể xem ở đây: https://ardata-fr.github.io/flextable-book/ Trang Github xem ở đây
Có thể tìm thấy sách hướng dẫn về tất cả các hàm flextableđây

Thư viên các ví dụ về mẫu bảng flextable cùng code có thể truy cập tại đây