library(tidyverse)
mdd_data <- read_csv("https://uoepsy.github.io/data/dapr1-mdd-data.csv")Formatting descriptives tables
The data
The data we will use come from a randomised controlled trial comparing treatments for Major Depressive Disorder (MDD) (DeRubeis et al., 2005)1. Patients with moderate to severe depression were randomly assigned to one of three groups: a placebo, anti-depressant medication (ADM), or cognitive therapy (CT). The outcome of interest, HDRS8, is each patient’s total score on the Hamilton Depression Rating Scale after 8 weeks of treatment, where higher scores indicate more severe depressive symptoms.
The dataset is available online, so you could read it directly from the web:
Preview the data structure:
glimpse(mdd_data)Rows: 208
Columns: 4
$ PatientID <dbl> 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 1…
$ Treatment_num <dbl> 1, 0, 0, 2, 1, 1, 1, 2, 0, 1, 2, 1, 1, 2, 1, 0, 1, 2, 2,…
$ HDRS8 <dbl> 19, 16, 21, 14, 24, 10, 6, 12, 5, 10, 12, 25, 29, 10, 12…
$ Treatment <chr> "ADM", "placebo", "placebo", "CT", "ADM", "ADM", "ADM", …
Tell R that the variable Treatment is a factor (i.e. a categorical variable):
mdd_data <- mdd_data |>
mutate(Treatment = factor(Treatment))
glimpse(mdd_data)Rows: 208
Columns: 4
$ PatientID <dbl> 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 1…
$ Treatment_num <dbl> 1, 0, 0, 2, 1, 1, 1, 2, 0, 1, 2, 1, 1, 2, 1, 0, 1, 2, 2,…
$ HDRS8 <dbl> 19, 16, 21, 14, 24, 10, 6, 12, 5, 10, 12, 25, 29, 10, 12…
$ Treatment <fct> ADM, placebo, placebo, CT, ADM, ADM, ADM, CT, placebo, A…
Formatting tables with the kableExtra package
So far we’ve printed our summary tables directly to the screen as plain tibbles. This is fine for checking your work, but it is not how tables should look in a dissertation or journal paper. Raw tibble output shows column types (e.g. <dbl>, <fct>), too many decimal places, and non-user-friendly column names, none of which you want a reader to see.
The kable() function from the package kableExtraconverts a tibble into a clean, readable table.
First, we must load the kableExtra package:
library(kableExtra)A few things you will want to control every time you want to make a publication-ready table:
- Rounding: use the
digitsargument ofkable()to round numeric columns (e.g.digits = 2for 2 decimal places), rather than rounding the values in your data. This keeps your underlying numbers precise while only rounding what is displayed. - Column names: use the
col.namesargument to replace raw variable names (e.g.HDRS8) with readable labels (e.g."HDRS score at 8 weeks"). - Caption: use the
captionargument to give every table a short description of what it shows.
Round only at the point of display using kable(digits = ...). Avoid rounding the variables themselves with round() earlier in your workflow, since this can introduce small errors if those rounded values are then used in further calculations.
Frequency tables
Frequency tables summarise how many (and what percentage of) observations fall into each category of a categorical variable. Let’s build one for Treatment:
freq_table <- mdd_data |>
count(Treatment) |>
mutate(f = n / sum(n),
Percent = f * 100)
freq_table# A tibble: 3 × 4
Treatment n f Percent
<fct> <int> <dbl> <dbl>
1 ADM 107 0.514 51.4
2 CT 50 0.240 24.0
3 placebo 51 0.245 24.5
Printed as a tibble, the f column has far too many decimal places, the Percent column is not rounded to 2 decimal places, and the column names are not very user friendly. Passing this to kable() with digits = 2 rounds all numeric columns to 2 decimal places, and col.names lets us relabel the columns:
freq_table |>
kable(
digits = 2,
col.names = c("Treatment Group", "n", "Rel. frequency","Percent (%)"),
caption = "Frequency, relative frequency, and percentage of participants in each treatment group."
)| Treatment Group | n | Rel. frequency | Percent (%) |
|---|---|---|---|
| ADM | 107 | 0.51 | 51.44 |
| CT | 50 | 0.24 | 24.04 |
| placebo | 51 | 0.25 | 24.52 |
We can add kableExtra styling by piping the result of kable() into kable_styling():
freq_table |>
kable(
digits = 2,
col.names = c("Treatment Group", "n", "Rel. frequency", "Percent (%)"),
caption = "Frequency, relative frequency, and percentage of participants in each treatment group."
) |>
kable_styling(
full_width = FALSE,
position = "center"
)| Treatment Group | n | Rel. frequency | Percent (%) |
|---|---|---|---|
| ADM | 107 | 0.51 | 51.44 |
| CT | 50 | 0.24 | 24.04 |
| placebo | 51 | 0.25 | 24.52 |
Some useful kable_styling() options:
full_width = FALSE: the table only takes up as much width as it needs, rather than stretching across the whole page.position = "center": centers the table on the page.
Descriptives tables: n, M, and SD
For a numerical variable such as HDRS8, the standard descriptive statistics to report are the sample size (\(n\)), mean (\(M\)), and standard deviation (\(SD\)).
So far, when using summarise() we have calculated these statistics across all participants at once:
mdd_data |>
summarise(
n = n(),
M = mean(HDRS8),
SD = sd(HDRS8)
)# A tibble: 1 × 3
n M SD
<int> <dbl> <dbl>
1 208 13.4 6.38
But what we are interested in, is comparing the effectiveness of the differen treatments for depression. To do so, we want these statistics calculated separately for each group (e.g. for each Treatment), so that they can be compared. To do this, we add a group_by() step before summarise(). This tells R to compute the number of values, mean HDRS8, and standard deviation of HDRS8 separately for each treatment group.
descriptives_table <- mdd_data |>
group_by(Treatment) |>
summarise(
n = n(),
M = mean(HDRS8),
SD = sd(HDRS8)
)
descriptives_table# A tibble: 3 × 4
Treatment n M SD
<fct> <int> <dbl> <dbl>
1 ADM 107 12.3 6.72
2 CT 50 13.9 5.08
3 placebo 51 15.1 6.45
group_by(Treatment) tells R to split the data into groups according to the values of Treatment, and then summarise() calculates n, M, and SD separately within each group, rather than across the whole sample. This combination of group_by() and summarise() is how we will create descriptives broken down by group across your DAPR courses.
group_by() + summarise()
group_by() on its own doesn’t change how the data looks, it just tells R to do grouped computations. It’s only when you follow it with summarise() (or another function like mutate()) that you see the effect as the calculation is done separately for each group.
Again, before putting this in a report we round to 2 decimal places, relabel the columns, and add a caption:
descriptives_table |>
kable(
digits = 2,
col.names = c("Treatment", "n", "M", "SD"),
caption = "Sample size, mean, and standard deviation of HDRS scores at 8 weeks by treatment group."
) |>
kable_styling(
full_width = FALSE,
position = "center"
)| Treatment | n | M | SD |
|---|---|---|---|
| ADM | 107 | 12.30 | 6.72 |
| CT | 50 | 13.94 | 5.08 |
| placebo | 51 | 15.10 | 6.45 |
We’ve only covered a handful of kableExtra options here. The package can do much more including grouped headers, cell highlighting, footnotes, and more.
Have a look at the kableExtra vignette, and try playing around with some of the extra settings shown there on your own tables (e.g. descriptives_table or freq_table). Being comfortable experimenting with formatting options like this will make your life a lot easier when polishing tables for your dissertation.
Most dissertations and papers in Psychology and related disciplines are expected to follow APA style (currently 7th edition), the formatting conventions of the American Psychological Association. APA style gives precise rules for how numbers and tables should be presented, and markers will check your work against these conventions. Some of the most relevant rules for descriptives tables are:
- Report means and standard deviations to 2 decimal places (e.g.
M = 15.34,SD = 4.02), and use no more decimal places than the precision of your measurement justifies. - Report sample sizes (\(n\)) as whole numbers, with no decimal places.
- Use statistical symbols (e.g. M, SD, n) as column headings, rather than plain English or raw variable names.
- Tables should be numbered (e.g. “Table 1”) with a clear, descriptive caption/title, and should be understandable without reading the main text.
This is precisely why we control rounding, column names, and captions through kable(): it lets you match APA formatting requirements directly from your R code, rather than manually editing numbers in Word afterwards (which is error-prone and hard to reproduce if your data changes).
Get into the habit of piping your summary tables through kable() (and kableExtra where useful) every time, so that producing publication-ready tables becomes automatic by the time you write up your dissertation.
Footnotes
DeRubeis RJ, Hollon SD, Amsterdam JD, Shelton RC, Young PR, Salomon RM, O’Reardon JP, Lovett ML, Gladis MM, Brown LL, Gallop R. Cognitive therapy vs medications in the treatment of moderate to severe depression. Arch Gen Psychiatry. 2005 Apr;62(4):409-16. doi: 10.1001/archpsyc.62.4.409. PMID: 15809408.↩︎