Author: Kazuki Yoshida
Date: 2020-07-25

# Introduction to tableone

#### Kazuki Yoshida

#### 2020-07-25

## What is tableone?

The tableone package is an R package that eases the construction of “Table 1”, i.e., patient baseline characteristics table commonly found in biomedical research papers. The packages can summarize both continuous and categorical variables mixed within one table. Categorical variables can be summarized as counts and/or percentages. Continuous variables can be summarized in the “normal” way (means and standard deviations) or “nonnormal” way (medians and interquartile ranges).

## Load packages

    ## tableone package itself
    library(tableone)
    ## survival package for Mayo Clinic's PBC data
    library(survival)
    data(pbc)

## Single group summary

### Simplest use case

The simplest use case is summarizing the whole dataset. You can just feed in the data frame to the main workhorse function CreateTableOne(). You can see there are 418 patients in the dataset.

    CreateTableOne(data = pbc)

                           Overall
      n                        418
      id (mean (SD))        209.50 (120.81)
      time (mean (SD))     1917.78 (1104.67)
      status (mean (SD))      0.83 (0.96)
      trt (mean (SD))         1.49 (0.50)
      age (mean (SD))        50.74 (10.45)
      sex = f (%)              374 (89.5)
      ascites (mean (SD))     0.08 (0.27)
      hepato (mean (SD))      0.51 (0.50)
      spiders (mean (SD))     0.29 (0.45)
      edema (mean (SD))       0.10 (0.25)
      bili (mean (SD))        3.22 (4.41)
      chol (mean (SD))      369.51 (231.94)
      albumin (mean (SD))     3.50 (0.42)
      copper (mean (SD))     97.65 (85.61)
      alk.phos (mean (SD)) 1982.66 (2140.39)
      ast (mean (SD))       122.56 (56.70)
      trig (mean (SD))      124.70 (65.15)
      platelet (mean (SD))  257.02 (98.33)
      protime (mean (SD))    10.73 (1.02)
      stage (mean (SD))       3.02 (0.88)

### Categorical variable conversion

Most of the categorical variables are coded numerically, so we either have to transform them to factors in the dataset or use factorVars argument to transform them on-the-fly. Also it’s a better practice to specify which variables to summarize by the vars argument, and exclude the ID variable(s). How do we know which ones are numerically-coded categorical variables? Please check your data dictionary (in this case help(pbc)). This time I am saving the result object in a variable.

    ## Get variables names
    dput(names(pbc))

    c("id", "time", "status", "trt", "age", "sex", "ascites", "hepato",
    "spiders", "edema", "bili", "chol", "albumin", "copper", "alk.phos",
    "ast", "trig", "platelet", "protime", "stage")

    ## Vector of variables to summarize
    myVars <- c("time", "status", "trt", "age", "sex", "ascites", "hepato",
              "spiders", "edema", "bili", "chol", "albumin", "copper", "alk.phos",
              "ast", "trig", "platelet", "protime", "stage")
    ## Vector of categorical variables that need transformation
    catVars <- c("status", "trt", "ascites", "hepato",
                 "spiders", "edema", "stage")
    ## Create a TableOne object
    tab2 <- CreateTableOne(vars = myVars, data = pbc, factorVars = catVars)

OK. It’s more interpretable now. Binary categorical variables are summarized as counts and percentages of the second level. For example, if it is coded as 0 and 1, the “1” level is summarized. For 3+ category variable all levels are summarized. Please bear in mind, the percentages are calculated after excluding missing values.

    tab2

                           Overall
      n                        418
      time (mean (SD))     1917.78 (1104.67)
      status (%)
         0                     232 (55.5)
         1                      25 ( 6.0)
         2                     161 (38.5)
      trt = 2 (%)              154 (49.4)
      age (mean (SD))        50.74 (10.45)
      sex = f (%)              374 (89.5)
      ascites = 1 (%)           24 ( 7.7)
      hepato = 1 (%)           160 (51.3)
      spiders = 1 (%)           90 (28.8)
      edema (%)
         0                     354 (84.7)
         0.5                    44 (10.5)
         1                      20 ( 4.8)
      bili (mean (SD))        3.22 (4.41)
      chol (mean (SD))      369.51 (231.94)
      albumin (mean (SD))     3.50 (0.42)
      copper (mean (SD))     97.65 (85.61)
      alk.phos (mean (SD)) 1982.66 (2140.39)
      ast (mean (SD))       122.56 (56.70)
      trig (mean (SD))      124.70 (65.15)
      platelet (mean (SD))  257.02 (98.33)
      protime (mean (SD))    10.73 (1.02)
      stage (%)
         1                      21 ( 5.1)
         2                      92 (22.3)
         3                     155 (37.6)
         4                     144 (35.0)

### Showing all levels for categorical variables

If you want to show all levels, you can use showAllLevels argument to the print() method.

    print(tab2, showAllLevels = TRUE, formatOptions = list(big.mark = ","))

                           level Overall
      n                               418
      time (mean (SD))           1,917.78 (1,104.67)
      status (%)           0          232 (55.5)
                           1           25 ( 6.0)
                           2          161 (38.5)
      trt (%)              1          158 (50.6)
                           2          154 (49.4)
      age (mean (SD))               50.74 (10.45)
      sex (%)              m           44 (10.5)
                           f          374 (89.5)
      ascites (%)          0          288 (92.3)
                           1           24 ( 7.7)
      hepato (%)           0          152 (48.7)
                           1          160 (51.3)
      spiders (%)          0          222 (71.2)
                           1           90 (28.8)
      edema (%)            0          354 (84.7)
                           0.5         44 (10.5)
                           1           20 ( 4.8)
      bili (mean (SD))               3.22 (4.41)
      chol (mean (SD))             369.51 (231.94)
      albumin (mean (SD))            3.50 (0.42)
      copper (mean (SD))            97.65 (85.61)
      alk.phos (mean (SD))       1,982.66 (2,140.39)
      ast (mean (SD))              122.56 (56.70)
      trig (mean (SD))             124.70 (65.15)
      platelet (mean (SD))         257.02 (98.33)
      protime (mean (SD))           10.73 (1.02)
      stage (%)            1           21 ( 5.1)
                           2           92 (22.3)
                           3          155 (37.6)
                           4          144 (35.0)

### Detailed information including missingness

If you need more detailed information including the number/proportion missing. Use the summary() method on the result object. The continuous variables are shown first, and the categorical variables are shown second.

    summary(tab2)

         ### Summary of continuous variables ###

    strata: Overall
               n miss p.miss mean     sd median    p25  p75   min   max  skew kurt
    time     418    0    0.0 1918 1104.7   1730 1092.8 2614  41.0  4795  0.47 -0.5
    age      418    0    0.0   51   10.4     51   42.8   58  26.3    78  0.09 -0.6
    bili     418    0    0.0    3    4.4      1    0.8    3   0.3    28  2.72  8.1
    chol     418  134   32.1  370  231.9    310  249.5  400 120.0  1775  3.41 14.3
    albumin  418    0    0.0    3    0.4      4    3.2    4   2.0     5 -0.47  0.6
    copper   418  108   25.8   98   85.6     73   41.2  123   4.0   588  2.30  7.6
    alk.phos 418  106   25.4 1983 2140.4   1259  871.5 1980 289.0 13862  2.99  9.7
    ast      418  106   25.4  123   56.7    115   80.6  152  26.4   457  1.45  4.3
    trig     418  136   32.5  125   65.1    108   84.2  151  33.0   598  2.52 11.8
    platelet 418   11    2.6  257   98.3    251  188.5  318  62.0   721  0.63  0.9
    protime  418    2    0.5   11    1.0     11   10.0   11   9.0    18  2.22 10.0

    =======================================================================================

         ### Summary of categorical variables ###

    strata: Overall
         var   n miss p.miss level freq percent cum.percent
      status 418    0    0.0     0  232    55.5        55.5
                                 1   25     6.0        61.5
                                 2  161    38.5       100.0

         trt 418  106   25.4     1  158    50.6        50.6
                                 2  154    49.4       100.0

         sex 418    0    0.0     m   44    10.5        10.5
                                 f  374    89.5       100.0

     ascites 418  106   25.4     0  288    92.3        92.3
                                 1   24     7.7       100.0

      hepato 418  106   25.4     0  152    48.7        48.7
                                 1  160    51.3       100.0

     spiders 418  106   25.4     0  222    71.2        71.2
                                 1   90    28.8       100.0

       edema 418    0    0.0     0  354    84.7        84.7
                               0.5   44    10.5        95.2
                                 1   20     4.8       100.0

       stage 418    6    1.4     1   21     5.1         5.1
                                 2   92    22.3        27.4
                                 3  155    37.6        65.0
                                 4  144    35.0       100.0

### Summarizing nonnormal variables

It looks like most of the continuous variables are highly skewed except time, age, albumin, and platelet (biomarkers are usually distributed with strong positive skews). Summarizing them as such may please your future peer reviewer(s). Let’s do it with the nonnormal argument to the print() method. Can you see the difference. If you just say nonnormal = TRUE, all variables are summarized the “nonnormal” way.

    biomarkers <- c("bili","chol","copper","alk.phos","ast","trig","protime")
    print(tab2, nonnormal = biomarkers, formatOptions = list(big.mark = ","))

                              Overall
      n                            418
      time (mean (SD))        1,917.78 (1,104.67)
      status (%)
         0                         232 (55.5)
         1                          25 ( 6.0)
         2                         161 (38.5)
      trt = 2 (%)                  154 (49.4)
      age (mean (SD))            50.74 (10.45)
      sex = f (%)                  374 (89.5)
      ascites = 1 (%)               24 ( 7.7)
      hepato = 1 (%)               160 (51.3)
      spiders = 1 (%)               90 (28.8)
      edema (%)
         0                         354 (84.7)
         0.5                        44 (10.5)
         1                          20 ( 4.8)
      bili (median [IQR])         1.40 [0.80, 3.40]
      chol (median [IQR])       309.50 [249.50, 400.00]
      albumin (mean (SD))         3.50 (0.42)
      copper (median [IQR])      73.00 [41.25, 123.00]
      alk.phos (median [IQR]) 1,259.00 [871.50, 1,980.00]
      ast (median [IQR])        114.70 [80.60, 151.90]
      trig (median [IQR])       108.00 [84.25, 151.00]
      platelet (mean (SD))      257.02 (98.33)
      protime (median [IQR])     10.60 [10.00, 11.10]
      stage (%)
         1                          21 ( 5.1)
         2                          92 (22.3)
         3                         155 (37.6)
         4                         144 (35.0)

### Fine tuning

If you want to fine tune the table further, please check out ?print.TableOne for the full list of options.

## Multiple group summary

Often you want to group patients and summarize group by group. It’s also pretty simple. Grouping by exposure categories is probably the most common way, so let’s do it by the treatment variable. According to ?pbc, it is coded as (1) D-penicillmain (it’s really “D-penicillamine”), (2) placebo, and (NA) not randomized. NA’s do not function as a grouping variable, so it is dropped. If you do want to show the result for the NA group, then you need to recoded it something other than NA.

    tab3 <- CreateTableOne(vars = myVars, strata = "trt" , data = pbc, factorVars = catVars)
    print(tab3, nonnormal = biomarkers, formatOptions = list(big.mark = ","))

                             Stratified by trt
                              1                           2                           p      test
      n                            158                         154
      time (mean (SD))        2,015.62 (1,094.12)         1,996.86 (1,155.93)          0.883
      status (%)                                                                       0.894
         0                          83 (52.5)                   85 ( 55.2)
         1                          10 ( 6.3)                    9 (  5.8)
         2                          65 (41.1)                   60 ( 39.0)
      trt = 2 (%)                    0 ( 0.0)                  154 (100.0)            <0.001
      age (mean (SD))            51.42 (11.01)               48.58 (9.96)              0.018
      sex = f (%)                  137 (86.7)                  139 ( 90.3)             0.421
      ascites = 1 (%)               14 ( 8.9)                   10 (  6.5)             0.567
      hepato = 1 (%)                73 (46.2)                   87 ( 56.5)             0.088
      spiders = 1 (%)               45 (28.5)                   45 ( 29.2)             0.985
      edema (%)                                                                        0.877
         0                         132 (83.5)                  131 ( 85.1)
         0.5                        16 (10.1)                   13 (  8.4)
         1                          10 ( 6.3)                   10 (  6.5)
      bili (median [IQR])         1.40 [0.80, 3.20]           1.30 [0.72, 3.60]        0.842 nonnorm
      chol (median [IQR])       315.50 [247.75, 417.00]     303.50 [254.25, 377.00]    0.544 nonnorm
      albumin (mean (SD))         3.52 (0.44)                 3.52 (0.40)              0.874
      copper (median [IQR])      73.00 [40.00, 121.00]       73.00 [43.00, 139.00]     0.717 nonnorm
      alk.phos (median [IQR]) 1,214.50 [840.75, 2,028.00] 1,283.00 [922.50, 1,949.75]  0.812 nonnorm
      ast (median [IQR])        111.60 [76.73, 151.51]      117.40 [83.78, 151.90]     0.459 nonnorm
      trig (median [IQR])       106.00 [84.50, 146.00]      113.00 [84.50, 155.00]     0.370 nonnorm
      platelet (mean (SD))      258.75 (100.32)             265.20 (90.73)             0.555
      protime (median [IQR])     10.60 [10.03, 11.00]        10.60 [10.00, 11.40]      0.588 nonnorm
      stage (%)                                                                        0.201
         1                          12 ( 7.6)                    4 (  2.6)
         2                          35 (22.2)                   32 ( 20.8)
         3                          56 (35.4)                   64 ( 41.6)
         4                          55 (34.8)                   54 ( 35.1)

### Testing

As you can see in the previous table, when there are two or more groups group comparison p-values are printed along with the table (well, let’s not argue the appropriateness of hypothesis testing for table 1 in an RCT for now.). Very small p-values are shown with the less than sign. The hypothesis test functions used by default are chisq.test() for categorical variables (with continuity correction) and oneway.test() for continous variables (with equal variance assumption, i.e., regular ANOVA). Two-group ANOVA is equivalent of t-test.

You may be worried about the nonnormal variables and small cell counts in the stage variable. In such a situation, you can use the nonnormal argument like before as well as the exact (test) argument in the print() method. Now kruskal.test() is used for the nonnormal continous variables and fisher.test() is used for categorical variables specified in the exact argument. kruskal.test() is equivalent to wilcox.test() in the two-group case. The column named test is to indicate which p-values were calculated using the non-default tests.

To also show standardized mean differences, use the smd option.

    print(tab3, nonnormal = biomarkers, exact = "stage", smd = TRUE)

                             Stratified by trt
                              1                         2                         p      test    SMD
      n                           158                       154
      time (mean (SD))        2015.62 (1094.12)         1996.86 (1155.93)          0.883          0.017
      status (%)                                                                   0.894          0.054
         0                         83 (52.5)                 85 ( 55.2)
         1                         10 ( 6.3)                  9 (  5.8)
         2                         65 (41.1)                 60 ( 39.0)
      trt = 2 (%)                   0 ( 0.0)                154 (100.0)           <0.001            NaN
      age (mean (SD))           51.42 (11.01)             48.58 (9.96)             0.018          0.270
      sex = f (%)                 137 (86.7)                139 ( 90.3)            0.421          0.111
      ascites = 1 (%)              14 ( 8.9)                 10 (  6.5)            0.567          0.089
      hepato = 1 (%)               73 (46.2)                 87 ( 56.5)            0.088          0.207
      spiders = 1 (%)              45 (28.5)                 45 ( 29.2)            0.985          0.016
      edema (%)                                                                    0.877          0.058
         0                        132 (83.5)                131 ( 85.1)
         0.5                       16 (10.1)                 13 (  8.4)
         1                         10 ( 6.3)                 10 (  6.5)
      bili (median [IQR])        1.40 [0.80, 3.20]         1.30 [0.72, 3.60]       0.842 nonnorm  0.171
      chol (median [IQR])      315.50 [247.75, 417.00]   303.50 [254.25, 377.00]   0.544 nonnorm  0.038
      albumin (mean (SD))        3.52 (0.44)               3.52 (0.40)             0.874          0.018
      copper (median [IQR])     73.00 [40.00, 121.00]     73.00 [43.00, 139.00]    0.717 nonnorm <0.001
      alk.phos (median [IQR]) 1214.50 [840.75, 2028.00] 1283.00 [922.50, 1949.75]  0.812 nonnorm  0.037
      ast (median [IQR])       111.60 [76.73, 151.51]    117.40 [83.78, 151.90]    0.459 nonnorm  0.084
      trig (median [IQR])      106.00 [84.50, 146.00]    113.00 [84.50, 155.00]    0.370 nonnorm  0.017
      platelet (mean (SD))     258.75 (100.32)           265.20 (90.73)            0.555          0.067
      protime (median [IQR])    10.60 [10.03, 11.00]      10.60 [10.00, 11.40]     0.588 nonnorm  0.146
      stage (%)                                                                    0.205 exact    0.246
         1                         12 ( 7.6)                  4 (  2.6)
         2                         35 (22.2)                 32 ( 20.8)
         3                         56 (35.4)                 64 ( 41.6)
         4                         55 (34.8)                 54 ( 35.1)

## Exporting

My typical next step is to export the table to Excel for editing, and then to Word (clinical medical journals usually do not offer LaTeX submission).

### Quick and dirty way

The quick and dirty way that I used to do is copy and paste. Use the quote = TRUE argument to show the quotes and noSpaces = TRUE to remove spaces used to align text in the R console (the latter is optional). Now you can just copy and paste the whole thing to an Excel spread sheet. After pasting, click the small pasting icon to choose Use Text Import Wizard…, in the dialogue you can just click finish to fit the values in the appropriate cells. Then you can edit or re-align things as you like. I usualy center-align the group summaries, and right-aligh the p-values.

    print(tab3, nonnormal = biomarkers, exact = "stage", quote = TRUE, noSpaces = TRUE)

                               "Stratified by trt"
     ""                         "1"                         "2"                         "p"      "test"
      "n"                       "158"                       "154"                       ""       ""
      "time (mean (SD))"        "2015.62 (1094.12)"         "1996.86 (1155.93)"         "0.883"  ""
      "status (%)"              ""                          ""                          "0.894"  ""
      "   0"                    "83 (52.5)"                 "85 (55.2)"                 ""       ""
      "   1"                    "10 (6.3)"                  "9 (5.8)"                   ""       ""
      "   2"                    "65 (41.1)"                 "60 (39.0)"                 ""       ""
      "trt = 2 (%)"             "0 (0.0)"                   "154 (100.0)"               "<0.001" ""
      "age (mean (SD))"         "51.42 (11.01)"             "48.58 (9.96)"              "0.018"  ""
      "sex = f (%)"             "137 (86.7)"                "139 (90.3)"                "0.421"  ""
      "ascites = 1 (%)"         "14 (8.9)"                  "10 (6.5)"                  "0.567"  ""
      "hepato = 1 (%)"          "73 (46.2)"                 "87 (56.5)"                 "0.088"  ""
      "spiders = 1 (%)"         "45 (28.5)"                 "45 (29.2)"                 "0.985"  ""
      "edema (%)"               ""                          ""                          "0.877"  ""
      "   0"                    "132 (83.5)"                "131 (85.1)"                ""       ""
      "   0.5"                  "16 (10.1)"                 "13 (8.4)"                  ""       ""
      "   1"                    "10 (6.3)"                  "10 (6.5)"                  ""       ""
      "bili (median [IQR])"     "1.40 [0.80, 3.20]"         "1.30 [0.72, 3.60]"         "0.842"  "nonnorm"
      "chol (median [IQR])"     "315.50 [247.75, 417.00]"   "303.50 [254.25, 377.00]"   "0.544"  "nonnorm"
      "albumin (mean (SD))"     "3.52 (0.44)"               "3.52 (0.40)"               "0.874"  ""
      "copper (median [IQR])"   "73.00 [40.00, 121.00]"     "73.00 [43.00, 139.00]"     "0.717"  "nonnorm"
      "alk.phos (median [IQR])" "1214.50 [840.75, 2028.00]" "1283.00 [922.50, 1949.75]" "0.812"  "nonnorm"
      "ast (median [IQR])"      "111.60 [76.73, 151.51]"    "117.40 [83.78, 151.90]"    "0.459"  "nonnorm"
      "trig (median [IQR])"     "106.00 [84.50, 146.00]"    "113.00 [84.50, 155.00]"    "0.370"  "nonnorm"
      "platelet (mean (SD))"    "258.75 (100.32)"           "265.20 (90.73)"            "0.555"  ""
      "protime (median [IQR])"  "10.60 [10.03, 11.00]"      "10.60 [10.00, 11.40]"      "0.588"  "nonnorm"
      "stage (%)"               ""                          ""                          "0.205"  "exact"
      "   1"                    "12 (7.6)"                  "4 (2.6)"                   ""       ""
      "   2"                    "35 (22.2)"                 "32 (20.8)"                 ""       ""
      "   3"                    "56 (35.4)"                 "64 (41.6)"                 ""       ""
      "   4"                    "55 (34.8)"                 "54 (35.1)"                 ""       ""

### Real export way

If you do not like the manual labor of copy-and-paste, you can potentially automate the task by the following way. The print() method for a TableOne object invisibly return a matrix identical to what you see. You can capture this by assignment to a variable (here tab3Mat). Do not use the quote argument in this case, the noSpaces argument is again optional. The self-contradictory printToggle = FALSE for the print() method avoids unnecessary printing if you wish. Then you can save the object to a CSV file. As it is a regular matrix object, you can save it to an Excel file using packages such as XLConnect.

    tab3Mat <- print(tab3, nonnormal = biomarkers, exact = "stage", quote = FALSE, noSpaces = TRUE, printToggle = FALSE)
    ## Save to a CSV file
    write.csv(tab3Mat, file = "myTable.csv")

## Miscellaneous

### Categorical or continous variables-only

You may want to see the categorical or continous variables only. You can do this by accessing the CatTable part and ContTable part of the TableOne object as follows. summary() methods are defined for both as well as print() method with various arguments. Please see ?print.CatTable and ?print.ContTable for details.

    ## Categorical part only
    tab3$CatTable

                     Stratified by trt
                      1           2            p      test
      n               158         154
      status (%)                                0.894
         0             83 (52.5)   85 ( 55.2)
         1             10 ( 6.3)    9 (  5.8)
         2             65 (41.1)   60 ( 39.0)
      trt = 2 (%)       0 ( 0.0)  154 (100.0)  <0.001
      sex = f (%)     137 (86.7)  139 ( 90.3)   0.421
      ascites = 1 (%)  14 ( 8.9)   10 (  6.5)   0.567
      hepato = 1 (%)   73 (46.2)   87 ( 56.5)   0.088
      spiders = 1 (%)  45 (28.5)   45 ( 29.2)   0.985
      edema (%)                                 0.877
         0            132 (83.5)  131 ( 85.1)
         0.5           16 (10.1)   13 (  8.4)
         1             10 ( 6.3)   10 (  6.5)
      stage (%)                                 0.201
         1             12 ( 7.6)    4 (  2.6)
         2             35 (22.2)   32 ( 20.8)
         3             56 (35.4)   64 ( 41.6)
         4             55 (34.8)   54 ( 35.1)

    ## Continous part only
    print(tab3$ContTable, nonnormal = biomarkers)

                             Stratified by trt
                              1                         2                         p      test
      n                       158                       154
      time (mean (SD))        2015.62 (1094.12)         1996.86 (1155.93)          0.883
      age (mean (SD))           51.42 (11.01)             48.58 (9.96)             0.018
      bili (median [IQR])        1.40 [0.80, 3.20]         1.30 [0.72, 3.60]       0.842 nonnorm
      chol (median [IQR])      315.50 [247.75, 417.00]   303.50 [254.25, 377.00]   0.544 nonnorm
      albumin (mean (SD))        3.52 (0.44)               3.52 (0.40)             0.874
      copper (median [IQR])     73.00 [40.00, 121.00]     73.00 [43.00, 139.00]    0.717 nonnorm
      alk.phos (median [IQR]) 1214.50 [840.75, 2028.00] 1283.00 [922.50, 1949.75]  0.812 nonnorm
      ast (median [IQR])       111.60 [76.73, 151.51]    117.40 [83.78, 151.90]    0.459 nonnorm
      trig (median [IQR])      106.00 [84.50, 146.00]    113.00 [84.50, 155.00]    0.370 nonnorm
      platelet (mean (SD))     258.75 (100.32)           265.20 (90.73)            0.555
      protime (median [IQR])    10.60 [10.03, 11.00]      10.60 [10.00, 11.40]     0.588 nonnorm

- Authored by Kazuki Yoshida
- CRAN page: https://cran.r-project.org/package=tableone
- github page: https://github.com/kaz-yos/tableone