Aller au contenu

Nice cross-tabulated flextable

Required packages

We will use the following packages:

library(tidyverse)
library(flextable)
library(officer)
use_df_printer()

Sample dataset

The summary of a subset of ggplot2::diamonds will be used to define the content of the flextable.

The code below is filtering some data so that our illustration does not contain too many columns.

dat <- ggplot2::diamonds |>
  filter(
    cut %in% c("Good", "Very Good"),
    clarity %in% c("I1", "SI1", "VS2")
  ) |> 
  mutate(price = price / 1000)
dat

carat

cut

color

clarity

depth

table

price

x

y

z

numeric

ordered

ordered

ordered

numeric

numeric

numeric

numeric

numeric

numeric

0.3

Very Good

H

SI1

61.9

55

0.3

4.1

4.1

2.5

0.3

Good

J

SI1

64.0

55

0.3

4.2

4.3

2.7

0.3

Good

J

SI1

63.4

54

0.4

4.2

4.3

2.7

0.3

Good

J

SI1

63.8

56

0.4

4.2

4.3

2.7

0.3

Very Good

J

SI1

62.7

59

0.4

4.2

4.3

2.7

0.2

Very Good

E

VS2

63.8

55

0.4

3.9

3.9

2.5

0.3

Very Good

J

SI1

59.4

62

0.4

4.4

4.4

2.6

0.3

Very Good

J

SI1

58.1

62

0.4

4.4

4.5

2.6

0.3

Very Good

J

VS2

62.2

57

0.4

4.3

4.3

2.7

0.2

Very Good

D

VS2

60.5

61

0.4

4.0

4.0

2.4

n: 8549

Main aggregation

Now, let’s aggregate the filtered dataset and count observations. The created dataset will have three dimensions, cut, color and clarity that will be used as rows or columns in the final flextable.

summary_dat <- dat |>
  group_by(cut, color, clarity) |>
  summarise(
    y_mean = mean(price, na.rm = TRUE),
    y_sd = sd(price, na.rm = TRUE),
    .groups = "drop"
  )
summary_dat

cut

color

clarity

y_mean

y_sd

ordered

ordered

ordered

numeric

numeric

Good

D

I1

3.5

2.2

Good

D

SI1

3.0

2.8

Good

D

VS2

3.6

3.4

Good

E

I1

4.4

2.5

Good

E

SI1

3.2

3.1

Good

E

VS2

3.8

3.3

Good

F

I1

2.6

1.9

Good

F

SI1

3.3

3.0

Good

F

VS2

3.8

3.2

Good

G

I1

3.2

2.0

n: 42

Secondary aggregations

The following counts will be used to show the counts in the rows.

cut_counts <- count(dat, cut, name = "n_cut")
cut_counts

cut

n_cut

ordered

integer

Good

2,634

Very Good

5,915

n: 2

The following counts will be used to show the counts in the columns.

clarity_counts <- count(dat, clarity, name = "n_clarity")
clarity_counts

clarity

n_clarity

ordered

integer

I1

180

SI1

4,800

VS2

3,569

n: 3

Flextable default settings

This step is not mandatory, it defines some default values for the flextable to be produced later.

# Modify flextable defaults formatting properties ----
init_flextable_defaults()
set_flextable_defaults(
  theme_fun = theme_booktabs,
  big.mark = " ", font.color = "#333333",
  border.color = "#333333",
  padding = 3,
)

Using function tabulator

Now all datasets are ready to be used, let’s call function tabulator() that will prepare an object ready to be sent to as_flextable().

ftd <- fp_text_default(color = "#f24f26")

# create tabulator object to be used with `as_flextable()` ----
tab <- tabulator(
  x = summary_dat,
  rows = c("cut", "color"),
  columns = "clarity",
  hidden_data = cut_counts,
  row_compose = list(
    cut = as_paragraph(cut, as_chunk(x = paste0("\nn = ", n_cut), props = ftd))
  ),
  # defines the only cells to show in the result
  `y stats` = as_paragraph(y_mean, " (\u00B1 ", y_sd, ")")
)


## get colkeys corresponding to multiple "y stats" ----
colkeys <- tabulator_colnames(tab, columns = "y stats")
colkeys
## [1] "I1@y stats"  "SI1@y stats" "VS2@y stats"

The flextable

A flextable is produced with the following code:

ft <- as_flextable(tab, separate_with = "cut")
ft

cut

color

I1

SI1

VS2

Good
n = 2634

D

3.52.2)

3.02.8)

3.63.4)

E

4.42.5)

3.23.1)

3.83.3)

F

2.61.9)

3.33.0)

3.83.2)

G

3.22.0)

4.13.9)

4.13.3)

H

3.82.2)

4.23.8)

4.44.2)

I

4.22.9)

4.74.5)

6.05.1)

J

3.82.1)

4.63.9)

4.83.6)

Very Good
n = 5915

D

2.60.8)

3.22.9)

3.13.3)

E

3.42.2)

3.23.1)

3.33.5)

F

4.32.6)

3.63.2)

4.03.8)

G

3.22.1)

3.53.6)

4.44.0)

H

5.32.5)

4.94.4)

4.64.0)

I

6.04.9)

5.24.5)

5.85.1)

J

4.52.7)

5.04.1)

5.34.5)

Customise the table

We need to add details in the columns headers.

ft <- append_chunks(ft, 
  j = colkeys, i = 1, 
  part = "header",
  as_chunk(fmt_header_n(clarity_counts$n_clarity), props = ftd)
)
ft

cut

color

I1
(N=180)

SI1
(N=4 800)

VS2
(N=3 569)

Good
n = 2634

D

3.52.2)

3.02.8)

3.63.4)

E

4.42.5)

3.23.1)

3.83.3)

F

2.61.9)

3.33.0)

3.83.2)

G

3.22.0)

4.13.9)

4.13.3)

H

3.82.2)

4.23.8)

4.44.2)

I

4.22.9)

4.74.5)

6.05.1)

J

3.82.1)

4.63.9)

4.83.6)

Very Good
n = 5915

D

2.60.8)

3.22.9)

3.13.3)

E

3.42.2)

3.23.1)

3.33.5)

F

4.32.6)

3.63.2)

4.03.8)

G

3.22.1)

3.53.6)

4.44.0)

H

5.32.5)

4.94.4)

4.64.0)

I

6.04.9)

5.24.5)

5.85.1)

J

4.52.7)

5.04.1)

5.34.5)

Add a title in the header part:

ft <- add_header_lines(ft, "Subset of original dataset")
ft

Subset of original dataset

cut

color

I1
(N=180)

SI1
(N=4 800)

VS2
(N=3 569)

Good
n = 2634

D

3.52.2)

3.02.8)

3.63.4)

E

4.42.5)

3.23.1)

3.83.3)

F

2.61.9)

3.33.0)

3.83.2)

G

3.22.0)

4.13.9)

4.13.3)

H

3.82.2)

4.23.8)

4.44.2)

I

4.22.9)

4.74.5)

6.05.1)

J

3.82.1)

4.63.9)

4.83.6)

Very Good
n = 5915

D

2.60.8)

3.22.9)

3.13.3)

E

3.42.2)

3.23.1)

3.33.5)

F

4.32.6)

3.63.2)

4.03.8)

G

3.22.1)

3.53.6)

4.44.0)

H

5.32.5)

4.94.4)

4.64.0)

I

6.04.9)

5.24.5)

5.85.1)

J

4.52.7)

5.04.1)

5.34.5)

And add a special line so that when rendered in line the top of the flextable will indicate the page number corresponding to its position in the document.

ft <- add_header_lines(ft, "Page N°") |> 
  append_chunks(i = 1, part = "header", j = 1,
                as_word_field(x = "Page")) |> 
  align(part = "header", align = "right", i = 1) |> 
  set_caption(caption = "Prices of over 50 000 round cut diamonds")

ft
Table 1: Prices of over 50 000 round cut diamonds

Page N°

Subset of original dataset

cut

color

I1
(N=180)

SI1
(N=4 800)

VS2
(N=3 569)

Good
n = 2634

D

3.52.2)

3.02.8)

3.63.4)

E

4.42.5)

3.23.1)

3.83.3)

F

2.61.9)

3.33.0)

3.83.2)

G

3.22.0)

4.13.9)

4.13.3)

H

3.82.2)

4.23.8)

4.44.2)

I

4.22.9)

4.74.5)

6.05.1)

J

3.82.1)

4.63.9)

4.83.6)

Very Good
n = 5915

D

2.60.8)

3.22.9)

3.13.3)

E

3.42.2)

3.23.1)

3.33.5)

F

4.32.6)

3.63.2)

4.03.8)

G

3.22.1)

3.53.6)

4.44.0)

H

5.32.5)

4.94.4)

4.64.0)

I

6.04.9)

5.24.5)

5.85.1)

J

4.52.7)

5.04.1)

5.34.5)

See how it renders in Word

The following code is producing a Word document with package ‘officer’, the content is a fake content. The purpose is to show how the flextable is rendered in a Word document.

psum_txt <- "Lorem ipsum dolor sit amet, purus ut nullam nisl vehicula non ligula sem non. Egestas nascetur, eu sed nec mattis semper arcu auctor sagittis id consequat non? Facilisi vestibulum ac nec primis. Posuere sociis ligula tempor, mattis sed sed dapibus. Taciti nulla mattis aliquet dictumst, nisi aenean, pulvinar! Hendrerit porttitor quis praesent mi nisl lorem mauris ut nulla. Tincidunt in sit sit quisque id molestie. Eros, orci ligula phasellus sed erat vel vivamus penatibus aliquam, scelerisque turpis sociis erat."

landscape_two_columns <- block_section(
  prop_section(
    type = "continuous",
    section_columns = section_columns(widths = c(3, 3))
  )
)

read_docx(path = "template.docx") |> 
  body_add_par(value = "Lorem ipsum", style = "heading 1") |> 
  body_add_par(psum_txt) |> 
  body_add_par(value = "Tempor velit sed", style = "heading 2") |> 
  body_end_block_section(value = block_section(property = prop_section(type = "continuous"))) |> 
  body_add_par(psum_txt) |> 
  body_add_par(psum_txt) |> 
  body_end_block_section(value = landscape_two_columns) |> 
  body_add_par(psum_txt) |> 
  body_add_par(psum_txt) |> 
  body_add_break() |> 
  body_add_par(value = "Mattis potenti metus", style = "heading 2") |> 
  body_add_par(value = "") |> 
  body_add_flextable(value = ft, topcaption = FALSE, keepnext = FALSE) |> 
  body_add_par(psum_txt) |> 
  print(target = "illustration.docx")

Here is the produced Word document: illustration.docx

After updating the fields in the document, it has this rendering:

illustration
illustration