library(adm)

# need n1 and n0 for the header of the table
# these will just be kept in a vector
n <- summary(lbw$low)
n0str <- paste0("n=", n["0"])
n1str <- paste0("n=", n["1"])

# make some derived variables

lbw$age.lte20 <- factor(ifelse(lbw$age <= 20,1,0))
lbw$lwt.lt110 <- factor(ifelse(lbw$lwt < 110,1,0))
lbw$ptl.ge1 <- factor(ifelse(lbw$ptl != '0',1,0))
lbw$ftv.ge1 <- factor(ifelse(lbw$ftv != '0',1,0))
lbw$black <- factor(ifelse(lbw$race == 'black',1,0))
lbw$white <- factor(ifelse(lbw$race == 'white',1,0))
lbw$otherrace <- factor(ifelse(lbw$race == 'other',1,0))

# create the header
# note that &___; are called HTML entities and allow you produce special characters
header <- list()
HEADERATTR <- list(NULL, list(colspan = 2), NULL, list(colspan = 2), NULL, NULL)
header <- c(header, 
            html(
              html(
                # &nbsp; are non-breaking spaces for some padding
                # in the empty column
                c("", "Low birth weight", "&nbsp;&nbsp;&nbsp;", 
                  "Non-low birth weight", "", ""), 
                "th", 
                HEADERATTR
              ), 
              "tr")
)

HEADERATTR2 <- mapply(c, 
                      HEADERATTR, 
                      list(NULL, list(style = "border-bottom: black solid"), NULL, 
                           list(style = "border-bottom: black solid"), NULL, NULL
                      )
)

header <- c(header, 
            html(
              html(
                c("", n1str, "", n0str, "", ""), 
                "th", 
                HEADERATTR2), 
              "tr")
)
header <- c(header, 
            html(
              html(
                c("Variable", "n", "%", "", "n", "%", 
                  "&chi;<sup>2</sup>", "p &dagger;"), 
                "th", 
                list(list(style = "border-bottom: black solid"))), 
              "tr")
)
header <- paste(header, collapse = "")

# now lets work on the body
# watch it - rownames (without a period)
# is a function you probably don't want to mask so use
# row.names instead
row.names <- c(
  "Age of mother, &le;20 years", 
  "Race", 
  "&nbsp;-&nbsp; Black", 
  "&nbsp;-&nbsp; White", 
  "&nbsp;-&nbsp; Other", 
  "Weight at last menstrual period &lt;110 lbs", 
  "History of premature labor", 
  "Smoked during pregnancy", 
  "History of hypertension", 
  "History of uterine irritability", 
  "At least one physician visit during first trimester")

# build a function to output each row
row.builder <- function(varname) {
  if(varname == "NULL") return(rep("", 7))
  tab <- table(lbw[, varname], lbw$low)
  props <- sprintf("%.1f", prop.table(tab, 2)[2, ]*100)
  stats <- summary(tab)
  # automatically determine if Fisher test is appropriate
  if(stats$approx.ok) {
    chisq <- sprintf("%.2f", stats$statistic)
    pval <- stats$p.value
    twotail <- ""
  } else {
    chisq <- "&Dagger;"
    pval <- fisher.test(tab)$p.value
    twotail <- "&sect;"
  }
  star <- ifelse(pval<0.05, "*", "")
  pval <- ifelse(pval<0.001, "&lt;0.001", 
                 ifelse(pval<0.01, sprintf("%.3f", stats$p.value), 
                        sprintf("%.2f", stats$p.value)))
  pval <- paste(pval, star, twotail, sep = "")
  c(tab[2, 2], props[2], "", tab[2, 1], props[1], chisq, pval)
}

# now that's more like magic
vars <- c("age.lte20", "NULL", "black", "white", "otherrace", 
          "lwt.lt110", "ptl.ge1", "smoke", "ht", "ui", "ftv.ge1")
body <- do.call(rbind, lapply(vars, row.builder))

# all but last row
row.names1 <- sapply(row.names[-length(row.names)], 
                     function(x) html(x, "td", list(list(style="font: bold"))))
body1 <- apply(body[-nrow(body), ], 1, 
               function(x) html(x, "td", list(list(style = "text-align: center"))))

# last row special formatting
row.names2 <- html(row.names[length(row.names)], "td", 
                   list(list(style = "font: bold; border-bottom: black solid")))
body2 <- html(body[nrow(body), ], "td", 
              list(list(style = "text-align: center; border-bottom: black solid")))

# recombine
row.names <- c(row.names1, row.names2)
body <- c(body1, body2)

# join row.names to body
body <- html(paste(row.names, body), "tr")

# join header to body
all <- paste(header, body, sep = "")

# title (the \ at the end of the line let's me break this long string without
# an error)
cat(html("<b>Table 1.</b> Frequency of exposures in cases of low birth \
         weight (&lt;2500 g) vs. non-low birth weight.", "p"), 
    file = "table1.html")

# table output
table <- html(all, "table", list(list(style = "border-collapse:collapse")))
cat(table, file = "table1.html", append = TRUE)

# footnotes
footnotes <- c(
  "&dagger; &alpha;=0.05. Significant p-values are marked with an asterisk (*).<br/>", 
  "&Dagger; Fisher's Exact Test<br/>", 
  "&sect; Two-tailed p-value<br/>")
cat(html(footnotes, "small"), file = "table1.html", append = TRUE)

# file was written above but we'll read it back in and display here
cat(readLines("table1.html"))

Table 1. Frequency of exposures in cases of low birth weight (<2500 g) vs. non-low birth weight.

Low birth weight     Non-low birth weight
n=59 n=130
Variable n % n % χ2 p †
Age of mother, ≤20 years 23 39.0 46 35.4 0.23 0.63
Race
 -  Black 11 18.6 15 11.5 1.73 0.19
 -  White 23 39.0 73 56.2 4.79 0.03*
 -  Other 25 42.4 42 32.3 1.80 0.18
Weight at last menstrual period <110 lbs 21 35.6 21 16.2 8.87 0.003*
History of premature labor 18 30.5 12 9.2 13.76 <0.001*
Smoked during pregnancy 30 50.8 44 33.8 4.92 0.03*
History of hypertension 7 11.9 5 3.8 0.04§
History of uterine irritability 14 23.7 14 10.8 5.40 0.02*
At least one physician visit during first trimester 23 39.0 66 50.8 2.26 0.13

† α=0.05. Significant p-values are marked with an asterisk (*).
‡ Fisher’s Exact Test
§ Two-tailed p-value