Use tables effectively to show your results

Sadly we can't have everything as figures. Tables can also be useful for communicating certain types of results.

When are tables preferred?

Basically, whenever you can't use figures

When is it best to use tables? Basically, whenever you can't use figures.

Use tables when, for example, the units of measure are too dissimilar, when the items are distinct or comparison between them isn't important, when presenting multiple but different models, or when you want to show the raw numbers so it's easier to extract it.

Creating a table of participant characteristics

library(carpenter)
tidied_framingham %>%
    outline_table()
# A tibble: 0 x 0
tidied_framingham %>%
    outline_table() %>%
    add_rows("education_combined", stat = stat_nPct)
# A tibble: 4 x 2
  Variables          all         
  <chr>              <chr>       
1 education_combined NA          
2 - 0-11 years       4690 (41.4%)
3 - Post-Secondary   3232 (28.5%)
4 - High School      3410 (30.1%)

Presenting basic participant characteristics, as indicated by STROBE best practices, is a great example for using a table. Here you can show summary statistics of the outcomes, predictors, and other characteristics.

The carpenter package provides an easy way of creating these tables. We start by using outline_table(), piping in the data. This function also takes a argument to set the grouping variable, such as visit number, so they are arranged as the table columns. We haven't added rows, so it outputs nothing..

Let's use add_rows() and add a factor variable like combined education. A common statistic for factors is the count with percent of total, so let's set stat to stat_nPct().

Creating a table of participant characteristics

tidied_framingham %>%
    outline_table() %>%
    add_rows("education_combined", stat = stat_nPct) %>%
    add_rows("body_mass_index", stat = stat_meanSD) %>%
    add_rows(c("participant_age", "heart_rate"),
             stat = stat_medianIQR)
# A tibble: 7 x 2
  Variables          all             
  <chr>              <chr>           
1 education_combined NA              
2 - 0-11 years       4690 (41.4%)    
3 - Post-Secondary   3232 (28.5%)    
4 - High School      3410 (30.1%)    
5 body_mass_index    25.9 (4.1)      
6 participant_age    54.0 (48.0-62.0)
7 heart_rate         75.0 (69.0-85.0)

Now let's add some more rows to the table for BMI, using stat_meanSD for the mean and standard deviation, and participant age and heart rate, using stat_medianIQR for the median and interquartile range.

Renaming table headers

basic_char_table <- tidied_framingham %>%
    outline_table() %>%
    add_rows("education_combined", stat = stat_nPct) %>%
    add_rows("body_mass_index", stat = stat_meanSD) %>%
    add_rows(c("participant_age", "heart_rate"),
             stat = stat_medianIQR) %>%
    renaming("header", c("", "Characteristics"))
basic_char_table
# A tibble: 7 x 2
  ``                 Characteristics 
  <chr>              <chr>           
1 education_combined NA              
2 - 0-11 years       4690 (41.4%)    
3 - Post-Secondary   3232 (28.5%)    
4 - High School      3410 (30.1%)    
5 body_mass_index    25.9 (4.1)      
6 participant_age    54.0 (48.0-62.0)
7 heart_rate         75.0 (69.0-85.0)

Great! But the table headers aren't informative. We set them using the renaming function and the header argument, then set the new names for the columns. We'll name only one column as Characteristics.

Render data into actual table

build_table(basic_char_table)

| | Characteristics | |:-------------------|:----------------:| | education_combined | | | - 0-11 years | 4690 (41.4%) | | - Post-Secondary | 3232 (28.5%) | | - High School | 3410 (30.1%) | | body_mass_index | 25.9 (4.1) | | participant_age | 54.0 (48.0-62.0) | | heart_rate | 75.0 (69.0-85.0) |

If you use R Markdown, we can use build_table() to convert the output into a Markdown table. Now you have a basic characteristics table to use when presenting your cohort analysis!

Wrangling model results into table format

Example table showing model estimates and confidence interval:

|Predictors |Unadjusted |Adjusted | |:-----------------------|:-------------------|:-------------------| |Fasting blood glucose |1.55 (0.78 to 3.09) |1.52 (0.75 to 3.07) | |Systolic blood pressure |1.86 (0.8 to 4.29) |1.86 (0.78 to 4.43) |

How to get this?

At times you may need to present either your main or supplemental results as a table. Even if you present your main findings as a figure, providing the model estimates as text numbers could be helpful for other researchers who might use your findings as part of a meta-analysis of cohort studies. Here, we're showing the confidence interval.

So, how do we get our results into this form?

Wrangling data into table form

library(glue)
models %>%
    mutate_at(vars(estimate, conf.low, conf.high), round, digits = 2) %>%
    mutate(estimate_ci = glue("{estimate} ({conf.low} to {conf.high})")) %>%
    select(model, predictor, estimate_ci)
# A tibble: 4 x 3
  model      predictor               estimate_ci        
  <chr>      <chr>                   <S3: glue>         
1 unadjusted systolic_blood_pressure 1.86 (0.8 to 4.29) 
2 unadjusted fasting_blood_glucose   1.55 (0.78 to 3.09)
3 adjusted   systolic_blood_pressure 1.86 (0.78 to 4.43)
4 adjusted   fasting_blood_glucose   1.52 (0.75 to 3.07)

Most of these functions should be familiar, except for glue. As a reminder, mutate-at applies a function to each variable contained within the vars function. Here we are rounding the variable's values to two.

Next, we again use mutate but with the glue function. Glue helps create a character string that changes based on the variable given between the curly braces. We use glue to format a string with the estimate and confidence interval in brackets.

Finally, let's keep the most relevant variables and output the dataframe.

Spreading rows across to make table columns

table_models <- models %>%
    mutate_at(vars(estimate, conf.low, conf.high), round, digits = 2) %>%
    mutate(estimate_ci = glue("{estimate} ({conf.low} to {conf.high})")) %>%
    select(model, predictor, estimate_ci) %>%
    spread(model, estimate_ci)
table_models
# A tibble: 2 x 3
  predictor               adjusted            unadjusted         
  <chr>                   <S3: glue>          <S3: glue>         
1 fasting_blood_glucose   1.52 (0.75 to 3.07) 1.55 (0.78 to 3.09)
2 systolic_blood_pressure 1.86 (0.78 to 4.43) 1.86 (0.8 to 4.29) 

Next, by using the spread function from tidyr, the models will be represented as individual columns. The first argument to spread takes the model variable name that will represent the new columns while the second argument takes the estimate_ci variable that has the values that will make up the new columns.

So, with minimal code, we've gotten the results to appear very similar to our desired table.

Create R Markdown table with kable

library(knitr)
library(stringr)
table_models %>% 
    mutate(predictor = str_replace_all(predictor, "_", " ")) %>% 
    kable()

|predictor |adjusted |unadjusted | |:-----------------------|:-------------------|:-------------------| |fasting blood glucose |1.52 (0.75 to 3.07) |1.55 (0.78 to 3.09) | |systolic blood pressure |1.86 (0.78 to 4.43) |1.86 (0.8 to 4.29) |

We could continue to replace underscores with spaces using the str_replace_all() function from stringr. Like many search and replace functions, give arguments for the character vector, the string to search for, and the replacement string. If you use rmarkdown, the kable function from knitr can create nicely formatted table.

Time to create some tables!

Now let's try making some tables!



lwjohnst86/acdcourse documentation built on June 18, 2019, 8:26 p.m.