Plotting Dental Inventory with ggplot2

This is a tutorial on an intuitive (or at very least, pretty) way to visualise a dental inventory in a sample. It requires only a basic knowledge of R, and, of course, that you have installed it along with RStudio.

Required packages:

  • tidyverse
  • patchwork

Upload data

dental_inv <- readr::read_csv("https://raw.githubusercontent.com/bbartholdy/dental-inv-plot/main/data/dental-inv.csv")
## Rows: 22 Columns: 34
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (32): t11, t12, t13, t14, t15, t16, t17, t18, t21, t22, t23, t24, t25, t...
## dbl  (2): feat, find
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The variables are:

variable name description
feat skeletal identifier
find skeletal identifier
t11:18; t21:28; t31:38; t41:48 status of tooth (t + FDI notation).
p = present; m = missing; dna = removed for dna sampling; aml = antemortem loss.

Convert data to long format

First step is to convert the data to long format with the pivot_longer function. This will take all of the tooth columns and combine them to two columns; one with the tooth name, and one with the tooth status.

dental_inv_long <- dental_inv %>%
  pivot_longer(t11:t48, names_to = "tooth", values_to = "status")
dental_inv_long %>%
  slice_head(n = 10) # show first 10 entries
## # A tibble: 10 × 4
##     feat  find tooth status
##    <dbl> <dbl> <chr> <chr> 
##  1    29    24 t11   p     
##  2    29    24 t12   p     
##  3    29    24 t13   m     
##  4    29    24 t14   p     
##  5    29    24 t15   p     
##  6    29    24 t16   p     
##  7    29    24 t17   p     
##  8    29    24 t18   m     
##  9    29    24 t21   p     
## 10    29    24 t22   m


It would also be useful to have a column that indicates to which region of the mouth the tooth belongs.

maxilla <- c(paste0("t", 11:18), paste0("t", 21:28))
left <- c(paste0("t", 21:28), paste0("t", 31:38))
dental_inv_long <- dental_inv %>%
  pivot_longer(t11:t48, names_to = "tooth", values_to = "status") %>%
   mutate(region = if_else(tooth %in% maxilla, "maxilla", "mandible"), 
         side = if_else(tooth %in% left, "left", "right"), .before = status)


Prepare separate plots

Indicate order of the teeth in each region as in the FDI diagram shown above. starting from the upper right third molar. We will use this to reorder the teeth shown in the plot.

upper_order <- c(paste0("t", 18:11), paste0("t", 21:28))
lower_order <- c(paste0("t", 48:41), paste0("t", 31:38))
upper_order
##  [1] "t18" "t17" "t16" "t15" "t14" "t13" "t12" "t11" "t21" "t22" "t23" "t24"
## [13] "t25" "t26" "t27" "t28"
lower_order
##  [1] "t48" "t47" "t46" "t45" "t44" "t43" "t42" "t41" "t31" "t32" "t33" "t34"
## [13] "t35" "t36" "t37" "t38"


The plots for the maxilla and mandible are prepared separately, then combined with the patchwork package.

First, the plot of the maxillary dentition:

# Plot for the maxillary dentition
maxilla_pl <- dental_inv_long %>%
  filter(region == "maxilla") %>% # we only want maxillary teeth
  mutate(tooth = factor(tooth, levels = upper_order)) %>% # make sure the order on the plot is according to the order the teeth are positioned in the mouth
  ggplot(aes(x = tooth, fill = status)) +
  geom_bar(position = "stack") + # stacked bar plot
  theme_minimal() +
  theme(axis.title.x = element_blank(), # remove x-axis title
        panel.grid = element_blank()) + # remove ugly gridlines
  scale_fill_viridis_d() # colour-blind-friendly pallette
maxilla_pl


Then, the plot of the mandibular dentition:

# Plot for the mandibular dentition
mandible_pl <- dental_inv_long %>%
  filter(region == "mandible") %>%
  mutate(tooth = factor(tooth, levels = lower_order)) %>%
  ggplot(aes(x = tooth, fill = status)) +
  geom_bar(position = "stack") +
  theme_minimal() +
  theme(axis.title.x = element_blank(),
        panel.grid = element_blank()) +
  scale_fill_viridis_d()
mandible_pl


Combine plots

UPDATE: The two dental plots are easily combined using the patchwork package. Thanks @jfy133 for bringing it to my attention! (and of course @thomasp85, the package developer)

We will combine them using / instead of +, because we want one on top of the other to mimic the maxilla’s and mandible’s position in the mouth:

maxilla_pl / mandible_pl


The plot_layout function allows us to easily combine the identical legends.

maxilla_pl / mandible_pl + plot_layout(guides = "collect")


And done! This gives (in my opinion) a nice, intuitive overview of the dental inventory in a sample, and could also be used to look at dental diseases (caries, calculus, periodontitis, etc).

Bjørn Peare Bartholdy
Bjørn Peare Bartholdy
PhD Student in Archaeology

My research interests include dental anthropology, skeletal biology, and statistics. This doesn’t necessarily mean I’m good at it…