The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Introduction to tooth

The tooth package provides tools for dental public health research: caries index calculation, odontogram heatmap visualisations, and tooth numbering conversion.

Sample Data

The package includes sim_exam, a simulated dataset with 20 patients, 7 teeth per quadrant, and 7 surfaces per tooth (5 coronal + 2 root).

library(tooth)
data(sim_exam)
head(sim_exam)
#>   record_id tooth_num tooth_surface code lesion_code act filling_code
#> 1       P01       ll1           buc    2           0   0            0
#> 2       P01       ll1           dis    2           0   0            4
#> 3       P01       ll1           lin    2           0   0            0
#> 4       P01       ll1           mes    2           4   2            0
#> 5       P01       ll1           occ    2           0   0            0
#> 6       P01       ll1         rootb    2           5   1            0

Calculating DMFT

calc_dmft() takes long-format surface data and returns one row per person with Decayed (DT), Filled (FT), Missing (MT), and combined index counts.

dmft <- calc_dmft(sim_exam)
head(dmft)
#> # A tibble: 6 x 9
#>   record_id    DT    FT    MT   DFT  DMFT num_teeth DT_yn DFT_yn
#>   <chr>     <int> <int> <int> <int> <int>     <int> <int>  <int>
#> 1 P01          18     2     2    20    22        26     1      1
#> 2 P02          17     3     3    20    23        25     1      1
#> 3 P03          16     6     1    22    23        27     1      1
#> 4 P04          18     5     1    23    24        27     1      1
#> 5 P05          10     8     1    18    19        27     1      1
#> 6 P06          18     5     0    23    23        28     1      1
summary(dmft[, c("DT", "FT", "MT", "DMFT", "num_teeth")])
#>        DT              FT             MT            DMFT         num_teeth    
#>  Min.   :10.00   Min.   :2.00   Min.   :0.00   Min.   :19.00   Min.   :25.00  
#>  1st Qu.:17.00   1st Qu.:3.00   1st Qu.:1.00   1st Qu.:23.00   1st Qu.:26.00  
#>  Median :18.00   Median :4.00   Median :1.00   Median :24.00   Median :27.00  
#>  Mean   :18.05   Mean   :4.45   Mean   :1.25   Mean   :23.75   Mean   :26.75  
#>  3rd Qu.:20.00   3rd Qu.:6.00   3rd Qu.:2.00   3rd Qu.:25.25   3rd Qu.:27.00  
#>  Max.   :22.00   Max.   :8.00   Max.   :3.00   Max.   :26.00   Max.   :28.00

Stratification

Add a grouping variable and pass it via strata:

sim_exam$treatment <- ifelse(
  as.numeric(gsub("P", "", sim_exam$record_id)) <= 10, "SDF", "ART"
)

dmft_by_arm <- calc_dmft(sim_exam, strata = "treatment")
head(dmft_by_arm)
#> # A tibble: 6 x 10
#>   record_id treatment    DT    FT    MT   DFT  DMFT num_teeth DT_yn DFT_yn
#>   <chr>     <chr>     <int> <int> <int> <int> <int>     <int> <int>  <int>
#> 1 P01       SDF          18     2     2    20    22        26     1      1
#> 2 P02       SDF          17     3     3    20    23        25     1      1
#> 3 P03       SDF          16     6     1    22    23        27     1      1
#> 4 P04       SDF          18     5     1    23    24        27     1      1
#> 5 P05       SDF          10     8     1    18    19        27     1      1
#> 6 P06       SDF          18     5     0    23    23        28     1      1

Separate Root Caries

dmft_root <- calc_dmft(sim_exam, root_lesion_col = "lesion_code")
head(dmft_root[, c("record_id", "DT", "MT", "DMFT", "RDT")])
#> # A tibble: 6 x 5
#>   record_id    DT    MT  DMFT   RDT
#>   <chr>     <int> <int> <int> <int>
#> 1 P01          18     2    22     9
#> 2 P02          17     3    23     6
#> 3 P03          16     1    23    11
#> 4 P04          18     1    24     8
#> 5 P05          10     1    19     8
#> 6 P06          18     0    23     8

RDT (root-decayed teeth) is counted independently from coronal DT.

Calculating DMFS

dmfs <- calc_dmfs(sim_exam)
head(dmfs)
#> # A tibble: 6 x 8
#>   record_id    DS    FS    MS   DFS  DMFS DS_yn DFS_yn
#>   <chr>     <dbl> <int> <dbl> <int> <int> <int>  <int>
#> 1 P01          21     8    10    29    39     1      1
#> 2 P02          27    11    15    38    53     1      1
#> 3 P03          25    13     5    38    43     1      1
#> 4 P04          31    10     5    41    46     1      1
#> 5 P05          12    14     5    26    31     1      1
#> 6 P06          23    16     0    39    39     1      1

Drawing a Single Tooth

draw_tooth() returns polygon geometry for one tooth. Use it to understand the surface layout or build custom visualisations.

library(ggplot2)

sv <- data.frame(
  tooth_surface = c("buc", "lin", "mes", "dis", "occ", "rootb", "rootl"),
  prop = c(0.12, 0.05, 0.30, 0.25, 0.45, 0.08, 0.03)
)

parts <- draw_tooth("ur1", "ur", 1, TRUE, sv,
                    surfaces = c("buc","lin","mes","dis","occ","rootb","rootl"),
                    display_label = "FDI 11")

ggplot() +
  geom_polygon(data = parts$crown,
               aes(x = x, y = y, group = group_id, fill = value),
               color = "grey50") +
  geom_polygon(data = parts$roots,
               aes(x = x, y = y, group = group_id, fill = value),
               color = "grey50") +
  geom_segment(data = parts$diags,
               aes(x = x, y = y, xend = xend, yend = yend),
               color = "grey50") +
  geom_path(data = parts$outline, aes(x = x, y = y),
            color = "grey40") +
  geom_text(data = parts$labels,
            aes(x = x, y = y, label = label),
            size = 5, fontface = "bold", color = "grey40") +
  geom_text(data = parts$num_label,
            aes(x = x, y = y, label = label),
            size = 7, fontface = "bold") +
  scale_fill_gradient(low = "white", high = "#C62828",
                      limits = c(0, 0.5), name = "Proportion") +
  coord_fixed() +
  theme_void() +
  theme(plot.background = element_rect(fill = "white", color = NA))

Surface abbreviations: B = Buccal, L = Lingual, M = Mesial, D = Distal, O = Occlusal, RB = Root-Buccal, RL = Root-Lingual.

Odontogram Heatmaps

Preparing data

Aggregate surface data to proportions per tooth-surface:

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

decay_prop <- sim_exam %>%
  filter(tooth_surface %in% c("buc", "lin", "mes", "dis", "occ")) %>%
  group_by(tooth_num, tooth_surface) %>%
  summarise(
    prop = mean(lesion_code %in% 3:6 & act == 2, na.rm = TRUE),
    .groups = "drop"
  )

Basic odontogram

build_odontogram(
  data = decay_prop,
  teeth_per_quadrant = 7,
  title = "Active Caries Proportion by Surface",
  legend_title = "Caries\nProportion",
  surfaces = c("buc", "lin", "mes", "dis", "occ")
)

With root surfaces (RB and RL)

decay_all <- sim_exam %>%
  group_by(tooth_num, tooth_surface) %>%
  summarise(
    prop = mean(lesion_code %in% 3:6 & act == 2, na.rm = TRUE),
    .groups = "drop"
  )

build_odontogram(
  data = decay_all,
  teeth_per_quadrant = 7,
  title = "Active Caries \u2014 Coronal and Root Surfaces",
  legend_title = "Caries\nProportion",
  surfaces = c("buc", "lin", "mes", "dis", "occ", "rootb", "rootl")
)

No surface labels

Set show_labels = FALSE for a cleaner publication-ready look:

build_odontogram(
  data = decay_prop,
  teeth_per_quadrant = 7,
  title = "Active Caries \u2014 Clean Export",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  show_labels = FALSE,
  tooth_label_size = 3.5
)

FDI tooth numbering

Display tooth numbers in FDI (ISO 3950) notation instead of quadrant format:

build_odontogram(
  data = decay_prop,
  teeth_per_quadrant = 7,
  title = "Active Caries \u2014 FDI Numbering",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  numbering = "fdi"
)

Fixed colour scale

Use min_val and max_val to ensure consistent colour scales across multiple plots:

build_odontogram(
  data = decay_prop,
  teeth_per_quadrant = 7,
  title = "Fixed Scale (0 to 0.5)",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  min_val = 0, max_val = 0.5
)

Stratified by treatment arm

decay_by_arm <- sim_exam %>%
  filter(tooth_surface %in% c("buc", "lin", "mes", "dis", "occ")) %>%
  group_by(treatment, tooth_num, tooth_surface) %>%
  summarise(
    prop = mean(lesion_code %in% 3:6 & act == 2, na.rm = TRUE),
    .groups = "drop"
  )

build_odontogram(
  data = decay_by_arm,
  teeth_per_quadrant = 7,
  title = "Active Caries",
  legend_title = "Caries\nProportion",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  strata = "treatment",
  ncol = 1
)

Custom strata labels

Rename strata panels with strata_labels:

build_odontogram(
  data = decay_by_arm,
  teeth_per_quadrant = 7,
  title = "Active Caries",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  strata = "treatment",
  strata_labels = c("SDF" = "Silver Diamine Fluoride",
                    "ART" = "Atraumatic Restorative Treatment"),
  ncol = 1
)

Summary statistics per stratum

Pass a stats data frame to display sample size and clinical measures below each panel title:

# Compute stats from DMFT results
dmft_summary <- dmft_by_arm %>%
  group_by(treatment) %>%
  summarise(
    n = dplyr::n(),
    mean_DT = round(mean(DT, na.rm = TRUE), 1),
    mean_DMFT = round(mean(DMFT, na.rm = TRUE), 1),
    .groups = "drop"
  )
dmft_summary
#> # A tibble: 2 x 4
#>   treatment     n mean_DT mean_DMFT
#>   <chr>     <int>   <dbl>     <dbl>
#> 1 ART          10    18.5      24  
#> 2 SDF          10    17.6      23.5

build_odontogram(
  data = decay_by_arm,
  teeth_per_quadrant = 7,
  title = "Active Caries",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  strata = "treatment",
  stats = dmft_summary,
  ncol = 1
)

Significance testing with p-value stars

Add stats_test, stats_var, and stats_raw to compute and display significance stars comparing strata:

build_odontogram(
  data = decay_by_arm,
  teeth_per_quadrant = 7,
  title = "Active Caries",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  strata = "treatment",
  stats = dmft_summary,
  stats_test = "wilcox",
  stats_var = "DMFT",
  stats_raw = dmft_by_arm,
  ncol = 1
)
#> Warning in wilcox.test.default(g1, g2): cannot compute exact p-value with ties

Stars follow: *** p < 0.001, ** p < 0.01, * p < 0.05, ns otherwise. Use stats_test = "t" for a t-test or stats_test = "wilcox" for a Wilcoxon rank-sum (non-parametric).

Footnote

Add an abbreviation key and p-value definitions below the plot:

build_odontogram(
  data = decay_by_arm,
  teeth_per_quadrant = 7,
  title = "Active Caries",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  strata = "treatment",
  stats = dmft_summary,
  stats_test = "wilcox",
  stats_var = "DMFT",
  stats_raw = dmft_by_arm,
  footnote = paste0(
    "B = Buccal, L = Lingual, M = Mesial, D = Distal, O = Occlusal.\n",
    "DT = Decayed Teeth, DMFT = Decayed/Missing/Filled Teeth.\n",
    "* p < 0.05, ** p < 0.01, *** p < 0.001 (Wilcoxon rank-sum test)."
  ),
  ncol = 1
)
#> Warning in wilcox.test.default(g1, g2): cannot compute exact p-value with ties

Primary Dentition

For primary teeth, use dentition = "primary" (5 teeth per quadrant):

primary_teeth <- paste0(
  rep(c("ur", "ul", "lr", "ll"), each = 5), rep(1:5, 4)
)
d_primary <- expand.grid(
  tooth_num = primary_teeth,
  tooth_surface = c("buc", "lin", "mes", "dis", "occ"),
  stringsAsFactors = FALSE
)
d_primary$prop <- runif(nrow(d_primary), 0, 0.5)

build_odontogram(
  data = d_primary,
  dentition = "primary",
  title = "Primary Dentition \u2014 Simulated Caries",
  color_high = "#E65100",
  surfaces = c("buc", "lin", "mes", "dis", "occ"),
  show_roots = FALSE
)

Tooth Numbering Conversion

Convert between FDI (ISO 3950), Universal (ADA), and quadrant notation:

tooth_convert("11", from = "fdi", to = "quadrant")
#> [1] "ur1"
tooth_convert("ur1", from = "quadrant", to = "universal")
#> [1] "8"
tooth_convert(c("11", "21", "36", "46"), from = "fdi", to = "quadrant")
#> [1] "ur1" "ul1" "ll6" "lr6"

Tooth Configuration

Generate a layout table for any arch configuration:

tooth_config("permanent", teeth_per_quadrant = 7)
#> # A tibble: 28 x 4
#>    quadrant   num tooth_id is_upper
#>    <chr>    <int> <chr>    <lgl>   
#>  1 ur           1 ur1      TRUE    
#>  2 ur           2 ur2      TRUE    
#>  3 ur           3 ur3      TRUE    
#>  4 ur           4 ur4      TRUE    
#>  5 ur           5 ur5      TRUE    
#>  6 ur           6 ur6      TRUE    
#>  7 ur           7 ur7      TRUE    
#>  8 ul           1 ul1      TRUE    
#>  9 ul           2 ul2      TRUE    
#> 10 ul           3 ul3      TRUE    
#> # i 18 more rows
tooth_config("primary")
#> # A tibble: 20 x 4
#>    quadrant   num tooth_id is_upper
#>    <chr>    <int> <chr>    <lgl>   
#>  1 ur           1 ur1      TRUE    
#>  2 ur           2 ur2      TRUE    
#>  3 ur           3 ur3      TRUE    
#>  4 ur           4 ur4      TRUE    
#>  5 ur           5 ur5      TRUE    
#>  6 ul           1 ul1      TRUE    
#>  7 ul           2 ul2      TRUE    
#>  8 ul           3 ul3      TRUE    
#>  9 ul           4 ul4      TRUE    
#> 10 ul           5 ul5      TRUE    
#> 11 lr           1 lr1      FALSE   
#> 12 lr           2 lr2      FALSE   
#> 13 lr           3 lr3      FALSE   
#> 14 lr           4 lr4      FALSE   
#> 15 lr           5 lr5      FALSE   
#> 16 ll           1 ll1      FALSE   
#> 17 ll           2 ll2      FALSE   
#> 18 ll           3 ll3      FALSE   
#> 19 ll           4 ll4      FALSE   
#> 20 ll           5 ll5      FALSE

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.