Chapter 3 Table design

3.1 Table parts

A flextable object is composed of 3 parts:

  • header: by default, there is only one header row containing the names of the data.frame.
  • body: the body part is containing data from the data.frame.
  • footer: the footer part is not implemented by default but can contains footnotes or any content.

ft <- flextable(head(airquality))
ft
Table 3.1: default flextable

Ozone

Solar.R

Wind

Temp

Month

Day

41

190

7.4

67

5

1

36

118

8.0

72

5

2

12

149

12.6

74

5

3

18

313

11.5

62

5

4

<na>

<na>

14.3

56

5

5

28

<na>

14.9

66

5

6

The parts can be augmented with new lines.

ft <- add_header_row(ft, values = c("air quality", "time"), colwidths = c(4, 2))
ft <- add_footer_lines(ft, "hello note")
ft
Table 3.2: default flextable with headers and a footer

air quality

time

Ozone

Solar.R

Wind

Temp

Month

Day

41

190

7.4

67

5

1

36

118

8.0

72

5

2

12

149

12.6

74

5

3

18

313

11.5

62

5

4

<na>

<na>

14.3

56

5

5

28

<na>

14.9

66

5

6

hello note

3.2 Visible columns

A flextable is an object that will produce a reporting table from a data.frame object.

Parameter col_keys of function flextable define the variables to show as columns and their order. Visible columns are by default all columns of the data.frame.

myft <- flextable(
  data = head(airquality), 
  col_keys = c("Ozone", "Solar.R", "Wind", "Temp", 
               "Month", "Day"))
myft

Ozone

Solar.R

Wind

Temp

Month

Day

41

190

7.4

67

5

1

36

118

8.0

72

5

2

12

149

12.6

74

5

3

18

313

11.5

62

5

4

<na>

<na>

14.3

56

5

5

28

<na>

14.9

66

5

6

They can be choosen from a subset of existing columns and also non-existent columns. Non-existent columns contain blanks by default ("").

When parameter col_keys has names that are not existing in the dataset, they will be considered as blank columns and no mapping exists with the data.

These blank columns can be used as separators.

myft <- flextable(
  data = head(airquality), 
  col_keys = c("Ozone", "Solar.R", "col1", "Month", "Day")) |> 
  width(j = "col1", width = .2) |> 
  empty_blanks()
myft

Ozone

Solar.R

Month

Day

41

190

5

1

36

118

5

2

12

149

5

3

18

313

5

4

<na>

<na>

5

5

28

<na>

5

6

Their content can also be defined with functions such as mk_par() or append_chunks().

In the following example we show how to highlight some rows according to a condition; if the condition is true, a colored text is added in the blank column.

library(grid)
myft |> 
  append_chunks(
    i = ~ Ozone < 30, j = "col1", 
    as_chunk(";)", props = fp_text_default(color = "#ec11c2")))

Ozone

Solar.R

Month

Day

41

190

5

1

36

118

5

2

12

149

;)

5

3

18

313

;)

5

4

<na>

<na>

5

5

28

<na>

;)

5

6

By default, the cells are filled with the content of the data.frame cells, the values are formatted with the function format. The displayed content of the cells can be updated, i.e. you can define the decimal separator, the text representing missing values, a text to be used as a suffix or prefix.

With flextable, a cell is made of one single paragraph of text. Paragraphs can contain several chunks of text with different formatting but also images and equations. It is possible to create composite content, i.e. composed of several pieces of text, images and even ggplot graphics.

# data prep ----
z <- as.data.table(ggplot2::diamonds)
z <- z[, list(
  price = mean(price, na.rm = TRUE),
  list_col = list(.SD$x)
), by = "cut"]

# flextable ----
ft <- flextable(data = z) %>%
  mk_par(j = "list_col", value = as_paragraph(
    plot_chunk(value = list_col, type = "dens", col = "#ec11c2", 
               width = 1.5, height = .4, free_scale = TRUE)
  )) %>%
  colformat_double(big.mark = " ", suffix = " $") %>% 
  set_header_labels(box = "composite content", density = "density") %>% 
  autofit()
ft
Table 3.3: mixing text and plots

cut

price

list_col

Ideal

3 457.54 $

Premium

4 584.26 $

Good

3 928.86 $

Very Good

3 981.76 $

Fair

4 358.76 $

3.3 Selectors

Selectors can be used to specify the rows and columns where an operation should happened. Many flextable functions have selectors i and j: bg(), bold(), hline(), color(), padding(), fontsize(), italic(), align(), compose().

It makes conditional formatting easy and multiple operations can be seamlessly piped (with magrittr::%>% or |>).

dat <- head(ggplot2::diamonds, n = 10)
qflextable(dat) %>% 
  color(~ price < 330, color = "orange", ~ price + x + y + z ) %>% 
  color(~ carat > .24, ~ cut, color = "red")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

Default values for i and j are NULL, NULL value is interpreted as all rows or all columns.

3.3.1 Usage

i for rows selection and j for columns selection can be expressed in different ways:

3.3.1.1 as a formula

Use i = ~ col %in% "xxx" to select all rows where values from column ‘col’ are “xxx”.

The argument expression is ~ and then an R expression. There is no need to quote variable, when the formula will be evaluated, values from the corresponding dataset (to the part) will be used.

To express multiple conditions, use operator & or |:

i = ~ col1 < 5 & col2 %in% "zzz"

The columns can be selected with a formula also. Use operator + for multiple columns. To select columns col1 and col2: j = ~ col1 + col2

ft <- qflextable(dat)

color(
  ft, i = ~ cut %in% "Premium", 
  j = ~ x + y, color = "red")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

3.3.1.2 as a character vector

Argument j supports simple character vector containing the col_key names.

ft %>% 
  color(j = c("x", "y"), color = "orange", part = "all") %>% 
  bold(j = c("price", "x"), bold = TRUE)

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

3.3.1.3 as a integer vector

Each element is the row number or col_key number:

color(ft, i = 1:3, j = 1:3, color = "orange")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

3.3.1.4 as a logical vector

color(ft, i = rep(c(TRUE, FALSE), 5), color = "orange")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

3.3.2 Selectors and flextable parts

Several operations (bold, color, padding, compose) accept part="all". In this case all mean to apply to each part of the flextable (header, body and footer if any). That’s useful for many cases:

  • add a vertical line (vline) to one or several column in header and body part, use part="all", j = c('col1', 'col2') as selector.
border <- fp_border_default()
vline(ft, j = c('clarity', 'price'), border = border, part = "all")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

  • change color only on the first row of the header part, use part="header", i = 1 as selector.
color(ft, i = 1, color = "red", part = "header")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

  • change color in column ‘col1’ and ‘col3’ in body part when values of ‘col2’ are negative, use part="body", i = ~ col2 < 0, j = c('col1', 'col3) as selector.
bg(ft, i = ~ price < 335, 
  j = c('x', 'y', 'z'), 
  bg = "orange", part = "body")

carat

cut

color

clarity

depth

table

price

x

y

z

0.23

Ideal

E

SI2

61.5

55

326

3.95

3.98

2.43

0.21

Premium

E

SI1

59.8

61

326

3.89

3.84

2.31

0.23

Good

E

VS1

56.9

65

327

4.05

4.07

2.31

0.29

Premium

I

VS2

62.4

58

334

4.20

4.23

2.63

0.31

Good

J

SI2

63.3

58

335

4.34

4.35

2.75

0.24

Very Good

J

VVS2

62.8

57

336

3.94

3.96

2.48

0.24

Very Good

I

VVS1

62.3

57

336

3.95

3.98

2.47

0.26

Very Good

H

SI1

61.9

55

337

4.07

4.11

2.53

0.22

Fair

E

VS2

65.1

61

337

3.87

3.78

2.49

0.23

Very Good

H

VS1

59.4

61

338

4.00

4.05

2.39

The most efficient selector for rows is the formula expression. The most efficient selector for columns is the character vector.

Selectors expressed as formula connot be used everywhere:

  • Rows selector expressed as formula cannot be used with header part. The header part contains only character values.
  • A common value for argument part is “all”. In this case, a
    row selector expressed as a formula is not supported (it can not as header dataset is made of only character columns and body dataset is the original dataset).