inst/doc/blood_loss_report.R

## ----setup, results = "hide", echo = FALSE, message = FALSE, warnings = FALSE----
set.seed(290875)

load(system.file("rda", "bloodloss.rda", package = "TH.data"))

### some packages
library("trtf")
library("tram")
library("rms")
library("coin")
library("survival")
library("ATR")
library("multcomp")
library("gridExtra")
library("vcd")
library("colorspace")
library("lattice")
tcols <- diverge_hcl(50, h = c(246, 40), c = 96, l = c(65, 90), alpha = .5)
cols <- qualitative_hcl(3, palette = "Harmonic")
vR <- paste(R.Version()$major, R.Version()$minor, sep = ".")
vtram <- packageDescription("tram")$Version
vtrtf <- packageDescription("trtf")$Version
### plot setup
trellis.par.set(list(plot.symbol = list(col="black", cex = .75),
                     box.rectangle = list(col=1),
                     box.umbrella = list(lty=1, col=1),
                     strip.background = list(col = "white")))
ltheme <- canonical.theme(color = FALSE)     ## in-built B&W theme
ltheme$strip.background$col <- "transparent" ## change strip bg
lattice.options(default.theme = ltheme)

frmt1 <- function(x) formatC(round(x, 1), digits = 1, format = "f") 
frmt3 <- function(x) {
    if (!is.numeric(x)) return(x)
    formatC(round(x, 3), digits = 3, format = "f") 
}

### tree plots
ctrl <- ctree_control(alpha = 0.05, minbucket = 50,
                      teststat = "max", splitstat = "max", maxsurrogate = 3)

en <- function(obj, col = "black", bg = "white", fill = "transparent",
                     ylines = 2, id = TRUE, mainlab = NULL, gp = gpar(), K = 20,
                     type = c("trafo", "distribution", "survivor",  
                              "density", "logdensity", "hazard",
                              "loghazard", "cumhazard", "quantile"),
                     flip = FALSE, axes = TRUE, xaxis = NULL, ...)
{

    ### panel function for ecdf in nodes
    rval <- function(node) {

        nid <- id_node(node)
        dat <- data_party(obj, nid)
        wn <- dat[["(weights)"]]   

        cf <- obj$coef[as.character(nid),c("Hb.prae", "F1.prae", 
                                           "F2.prae", "F13.Akt.prae")]
        cf <- matrix(round(exp(cf), 3), nrow = 1)
        # cf <- rbind(cf, obj$ci[as.character(nid),])
        rownames(cf) <- c("OR")#, "CI")
        colnames(cf) <- c("Hb.prae", "F1.prae", "F2.prae", "F13.Akt.prae")
        colnames(cf) <- c("hemoglobin", "F. I", "F. II", "F. XIII")

        top_vp <- viewport(layout = grid.layout(nrow = 1, ncol = 2,
                           widths = unit(c(1, ylines), c("null", "lines")),
                           heights = unit(1, "null")),
                           width = unit(1, "npc"),
                           height = unit(1, "npc"), # - unit(2, "lines"),
                           name = paste("node_mlt", nid, sep = ""), gp = gp)

        pushViewport(top_vp)
        grid.rect(gp = gpar(fill = bg, col = 0))

        grid.draw(tableGrob(cf))

        upViewport(1)
    }

    return(rval)
}
class(en) <- "grapcon_generator"

### format confidence intervals
ci <- function(m) {
    cf <- coef(m)
    idx <- 1:length(cf)
    i <- grep("(Intercept)", names(cf))
    if (length(i) > 0)
        idx <- idx[-i]
    cbind(exp(cf)[idx], exp(confint(m)[idx,]))
}

vlab <- function(x) {
    lab <- code$desc_EN[code$varname == x]
    lab <- paste0(toupper(substr(lab, 1, 1)), substr(lab, 2, nchar(lab)))
    paste(lab, " (in ", code$unit[code$varname == x], ")", sep = "")
}

pvar <- function(x)
    paste(paste(x[-length(x)], collapse = ", "), ", and ", x[length(x)], sep = "")


## ----vignette, eval = FALSE---------------------------------------------------
#  library("knitr")
#  knit("blood_loss_report.Rnw")
#  library("tools")
#  texi2pdf("blood_loss_report.tex")

## ----preproc, echo = FALSE----------------------------------------------------
x <- c("GA", "AGE", "MULTIPAR", "BMI", "TWIN", "FET.GEW", "IOL", "AIS")
z <- subset(code, type %in% c("confounder", "reason"))$varname
prae <- c("F1.prae", "F2.prae", "Hb.prae", ### "F13.Ag.prae", 
          "F13.Akt.prae") 
          ### "DD.prae")
vars <- c("MBL", prae, z)
blood$mode <- with(blood, SECTIO.prim == "yes" | SECTIO.sek == "yes" |
                     SECTIO.not == "yes") + 1
blood$mode[blood$SECTIO.sek == "yes" | blood$SECTIO.not == "yes"] <- 3
blood$mode <- factor(blood$mode, levels = 1:3,
                     labels = c("Vaginal delivery", "Planned Cesarean", "Unplanned Cesarean"))
blood$VCmode <- blood$mode
levels(blood$VCmode) <- c("Vaginal delivery", "Cesarean Sectio", "Cesarean Sectio")
ct <- table(blood$mode)

## ----table-1, echo = FALSE, results = "asis"----------------------------------
frmtnum <- function(x)
    paste(frmt1(x)[2], " (", paste(frmt1(x)[-2], collapse = "-"), ")", sep = "")
frmtrow <- function(x) {
    if (is.factor(blood[[x]])) return(table(blood[[x]], blood$mode))
    prb <- c(0.25, .5, .75)
    ret <- c(vaginal = frmtnum(quantile(subset(blood, mode == "Vaginal delivery")[[x]], 
                                   prob = prb, na.rm = TRUE)),
             planned = frmtnum(quantile(subset(blood, mode == "Planned Cesarean")[[x]], 
                                   prob = prb, na.rm = TRUE)),
             unplanned = frmtnum(quantile(subset(blood, mode == "Unplanned Cesarean")[[x]], 
                                   prob = prb, na.rm = TRUE)))
    ret <- matrix(ret, nrow = 1)
    rownames(ret) <- "Med (IQR)"
    if (all(!is.na(blood[[x]]))) return(ret)
    ret <- rbind(table(is.na(blood[[x]]), blood$mode)["TRUE",], ret)
    rownames(ret) <- c("missing", "Med (IQR)")
    ret
}
tab <- lapply(vars, frmtrow)
names(tab) <- vars

desc <- code$desc_EN[match(vars, code$varname)]
desc <- rep(desc, sapply(tab, nrow))
desc[dd <- duplicated(desc)] <- ""

unit <- code$unit[match(vars, code$varname)]
unit <- gsub("\\%", "\\\\%", unit)
unit <- gsub("kg/m\\^2", "$\\\\text{kg} / \\\\text{m}^2$", unit)
unit <- rep(unit, sapply(tab, nrow))
unit[dd] <- ""
unit[is.na(unit)] <- ""

xtab <- do.call("rbind", tab)
xtab <- cbind(var = desc, unit = unit, item = rownames(xtab), xtab)

writeLines(paste(xtab[,1], " & ", xtab[,2], "&", xtab[, 3], " & ", xtab[,4],
" & ", xtab[,5], " & ", xtab[,6], " \\\\"))

write.csv(xtab, file = "table_1.csv")


## ----MBL-plot, echo = FALSE, fig.width = 6, fig.height = 5, cache = TRUE, warning = FALSE, dev = c("tiff", "pdf", "png"), dpi = 300----
### unconditional MBL
qy <- 0:max(blood$MBL)
### MBL outcome as interval censored

### interval length: 50 for MBL < 1000; 100 for MBL > 1000
off <- 25
tm1 <- with(blood, ifelse(MBL < 1000, MBL - off, MBL - 2 * off))
tm2 <- with(blood, ifelse(MBL >= 1000, MBL + 2 * off, MBL + off))
### some measurements are more precise, use length 10 here
ex <- !blood$MLB %in% seq(from = 100, to = 6000, by = 50)
stopifnot(sum(ex) == 0)
tm1[ex] <- blood$MBL[ex] - off / 5
tm2[ex] <- blood$MBL[ex] + off / 5
blood$MBLsurv <- Surv(time = tm1, time2 = tm2, type = "interval2")

MBLlim <- c(0, 2700)

nd <- data.frame(mode = sort(unique(blood$mode)))
### takes too long on Windows
if (FALSE) {
plot(m <- as.mlt(Colr(MBLsurv | mode ~ 1, data = blood, order = 15,
                 bounds = c(0, Inf), support = c(250, 2000), 
                 extrapolate = TRUE)), newdata = nd,
     q = qy, type = "distribution", col = cols, lwd = 2, xlim = MBLlim,
     xlab = vlab("MBL"), ylab = "Probability", ylim = c(-.05, 1.05), 
     inset = 10)
rug(blood$MBL[blood$mode == "Vaginal delivery"], lwd = 2, col = cols[1])
rug(blood$MBL[blood$mode == "Planned Cesarean"], side = 3, lwd = 2, col = cols[2])
rug(blood$MBL[blood$mode == "Unplanned Cesarean"], side = 3, lwd = 2, col = cols[3])
legend("bottomright", lwd = 2, col = cols, legend = levels(blood$mode), bty = "n")
}

## ----MBL-plot-check, eval = FALSE, echo = FALSE, fig.width = 6, fig.height = 5, warning = FALSE, dev = c("tiff", "pdf", "png"), dpi = 300----
#  plot(survfit(MBLsurv ~ mode, data = blood), col = cols, xlim = c(0, 2700),
#       lty = 2, xlab = vlab("MBL"), ylab = "1 - Probability", ylim = c(-.05, 1.05))
#  plot(m, newdata= nd, type = "survivor", add = TRUE, col = cols, lty = 1)
#  rug(blood$MBL[blood$mode == "Vaginal delivery"], lwd = 2, col = cols[1])
#  rug(blood$MBL[blood$mode == "Planned Cesarean"], side = 3, lwd = 2, col = cols[2])
#  rug(blood$MBL[blood$mode == "Unplanned Cesarean"], side = 3, lwd = 2, col = cols[3])
#  legend("topright", lwd = 2, col = cols, legend = levels(blood$mode), bty = "n")

## ----MBL-Colr, echo = TRUE, cache = TRUE--------------------------------------
mvars <- c("Hb.prae", "F1.prae", "F2.prae", "F13.Akt.prae")
fm <- paste(mvars, collapse = "+")
### continuous outcome logistic regression
m_MBL <- Colr(as.formula(paste("MBLsurv ~ ", fm)), data = blood, 
              bounds = c(0, Inf), support = c(250, 2000))
### number of observations
sum(complete.cases(model.frame(m_MBL)))
summary(m_MBL)
logLik(m_MBL)

## ----MBL-Colr-ci, echo = TRUE, cache = TRUE-----------------------------------
(ci_all <- ci(m_MBL))

## ----MBL-orm, echo = TRUE-----------------------------------------------------
(m_MBL_orm <- orm(as.formula(paste("MBL ~ ", fm)), data = blood))
### OR and confidence interval for F. XIII 
### (sign of the coefficient is different in rms::orm and tram::Colr)
exp(-coef(m_MBL_orm)["F13.Akt.prae"])
exp(-rev(confint(m_MBL_orm)["F13.Akt.prae",]))

## ----MBL-Colr-Cesar, echo = TRUE, cache = TRUE--------------------------------
m_MBL_C <- Colr(as.formula(paste("MBLsurv | VCmode ~ VCmode:(", fm, ")")), 
                data = blood, bounds = c(0, Inf), support = c(250, 2000))
summary(m_MBL_C)
logLik(m_MBL_C)

## ----MBL-Colr-pre, echo = FALSE, fig.width = 6, fig.height = 5, dev = c("tiff", "pdf", "png"), dpi = 300----
nd <- blood[1,mvars, drop = FALSE]
nd[1,] <- cm <- round(apply(blood[, mvars], 2, median, na.rm = TRUE), 1)
F13 <- seq(from = min(blood$F13.Akt.prae, na.rm = TRUE), 
           to = max(blood$F13.Akt.prae, na.rm = TRUE),
           length = 25)
nd <- nd[rep(1, length(F13)),]
nd[, "F13.Akt.prae"] <- F13
nd$MBLsurv <- 500
X <- model.matrix(as.mlt(m_MBL)$model, data = nd)

cf <- confint(glht(as.mlt(m_MBL), linfct = X))

plot(F13, plogis(cf$confint[, "Estimate"], lower.tail = FALSE), type = "l",
     ylim = c(0, 1), xlab = "Prepartum F. XIII (%)", 
     ylab = expression(paste("Probability PPH (", MBL >= 500, "ml)")))
lwr <- plogis(cf$confint[, "lwr"], lower.tail = FALSE)
upr <-  plogis(cf$confint[, "upr"], lower.tail = FALSE)
polygon(c(F13, rev(F13)), c(lwr, rev(upr)),
        border = NA, col = "lightblue")
lines(F13, plogis(cf$confint[, "Estimate"], lower.tail = FALSE))
rug(blood$F13.Akt.prae, col = rgb(.1, .1, .1, .1))

## ----sample-size, echo = TRUE-------------------------------------------------
blood$PPH <- factor(blood$MBL >= 500, levels = c(FALSE, TRUE), 
                    labels = c("no", "yes"))
summary(m_PPH <- lm(F13.Akt.prae ~ PPH, data = blood))
confint(m_PPH)["PPHyes",]

## ----sample-size-W, echo = TRUE, cache = TRUE---------------------------------
wilcox_test(F13.Akt.prae ~ PPH, data = blood, 
            distribution = approximate(10000), conf.int = TRUE)

## ----MBL-check-1, echo = TRUE, cache = TRUE, warning = FALSE, message = FALSE, results = "hide"----
### Tobit model
tll <- logLik(t_MBL <- Lm(as.formula(paste("MBLsurv ~ ", fm)), 
                          data = blood))
### distribution regression
drll <- logLik(dr_MBL <- Colr(as.formula(paste("MBLsurv | ", fm, "~ 1")), 
                              data = blood, bounds = c(0, Inf), 
                              support = c(250, 2000)))

## ----dr-plot, echo = FALSE, warning = FALSE, dev = c("tiff", "pdf", "png"), dpi = 300----
nd0 <- data.frame("Hb.prae" = 0, "F1.prae" = 0, "F2.prae" = 0, "F13.Akt.prae" = 0)
q <- seq(from = MBLlim[1], to = MBLlim[2], length.out = 200)
p0 <- predict(as.mlt(dr_MBL), newdata = nd0, q = q, type = "trafo")
layout(matrix(1:4, ncol = 2))
for (i in 1:4) {
    nd <- nd0
    nd[[i]] <- 1
    cim <- confint(m_MBL)[i,]
    p <- c(predict(as.mlt(dr_MBL), newdata = nd, q = q, type = "trafo") - p0)
    ylim <- exp(max(abs(c(p[is.finite(p)], cim))) * c(-1, 1))
    cn <- code$desc_EN[code$varname == colnames(nd)[i]]
    plot(q, exp(p), type = "l", main = cn, 
         ylim = ylim, xlab = vlab("MBL"), ylab = expression(exp(beta)))
    polygon(c(-100, 3100, 3100, -100), rep(exp(cim), each = 2),
            border = NA, col = rgb(.1, .1, .1, .1))
    abline(h = exp(coef(m_MBL)[i]), lty = 2)
    abline(h = 1, lty = 1, col = "darkred", lwd = 3)
    rug(blood$MBL, col = rgb(.1, .1, .1, .1))
}

## ----MBL-check-2, echo = FALSE, results = "hide", cache = TRUE, warning = FALSE, message = FALSE----
### Binary logistic regression
lr500 <- glm(as.formula(paste("I(MBL < 500) ~ ", fm)), 
             data = blood, family = binomial())
(ci_500 <- ci(lr500))
lr750 <- glm(as.formula(paste("I(MBL < 750) ~ ", fm)), 
             data = blood, family = binomial())
(ci_750 <- ci(lr750))
lr1000 <- glm(as.formula(paste("I(MBL < 1000) ~ ", fm)), 
              data = blood, family = binomial())
(ci_1000 <- ci(lr1000))

## ----table-2, echo = FALSE, results = "asis"----------------------------------
p_all <- paste("$", format.pval(summary(m_MBL)$test$test$pvalues, eps = 0.001), "$")
p_500 <- paste("$", format.pval(summary(lr500)$coef[-1,4], eps = 0.001), "$")
p_750 <- paste("$", format.pval(summary(lr750)$coef[-1,4], eps = 0.001), "$")
p_1000 <- paste("$", format.pval(summary(lr1000)$coef[-1,4], eps = 0.001), "$")
ci_tab <- rbind(cbind(frmt3(ci_all), p_all), 
                cbind(frmt3(ci_500), p_500),
                cbind(frmt3(ci_750), p_750),
                cbind(frmt3(ci_1000), p_1000))
ci_tab <- cbind(c("All", rep("", 3),
                 "500", rep("", 3),
                 "750", rep("", 3),
                 "1000", rep("", 3)), rownames(ci_tab), ci_tab)
writeLines(paste(ci_tab[,1], " & ", ci_tab[,2], " & ",
                 ci_tab[,3], " & ", ci_tab[,4], " & ", ci_tab[,5], " & ", ci_tab[,6], " \\\\"))
write.csv(ci_tab, file = "table_2.csv")

## ----table-3, echo = FALSE, results = "asis"----------------------------------
ci_tab <- cbind(frmt3(ci(m_MBL_C)), format.pval(summary(m_MBL_C)$test$test$pvalues, eps = 0.001))
ci_tab <- cbind(names(coef(m_MBL_C)), ci_tab)
writeLines(paste(ci_tab[,1], " & ", ci_tab[,2], " & ",
                 ci_tab[,3], " & ", ci_tab[,4], " & ", ci_tab[,5], " \\\\"))
write.csv(ci_tab, file = "table_3.csv")

## ----MBL-xtree, echo = FALSE, cache = TRUE, fig.width = 12.25, fig.height = 4.5, dev = c("tiff", "pdf", "png"), dpi = 300----
### partitioning
xfm <- paste(x, collapse = "+")
zfm <- paste(z, collapse = "+")

xfm_MBL <- as.formula(paste("MBLsurv ~ ", fm, "|", xfm))
zfm_MBL <- as.formula(paste("MBLsurv ~ ", fm, "|", zfm))

blood$DAUER.ap[is.na(blood$DAUER.ap)] <- 0

yfm <- as.formula(paste("MBLsurv ~ ", fm))
xMBL_cc <- complete.cases(blood[, all.vars(yfm)])
zMBL_cc <- complete.cases(blood[, all.vars(yfm)])

xitr_MBL <- trafotree(as.mlt(m_MBL), formula = xfm_MBL, data = blood[xMBL_cc,], 
                      parm = mvars, control = ctrl)

nodeid <- predict(xitr_MBL, newdata = blood, type = "node")
blood$nd <- factor(nodeid, levels = sort(unique(nodeid)),
                   labels = sort(unique(nodeid)))
m <- Colr(as.formula(paste("MBLsurv | nd ~ nd:(", fm, ")")), 
          data = blood[xMBL_cc,], bounds = c(0, Inf), support = c(250, 2000))
cf <- ci(m)
cf <- formatC(round(cf, 3), digits = 3, format = "f") 
xitr_MBL$ci <- matrix(paste(matrix(cf[,2], ncol = 4), 
                            matrix(cf[,3], ncol = 4), sep = "-"), ncol = 4)
rownames(xitr_MBL$ci) <- levels(blood$nd)
plot(rotate(xitr_MBL), terminal_panel = en)

## ----MBL-extreme, echo = FALSE------------------------------------------------
xn <- tapply(1:nrow(blood), blood$nd, function(i) colMeans(blood[i, mvars], na.rm = TRUE))
nd <- as.data.frame(do.call("rbind", xn))
nd$nd <- sort(unique(blood$nd))
cb <- confband(as.mlt(m), newdata = nd, type = "distribution", 
               K = 500, calpha = univariate_calpha())
### 500
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 500)^2),-1]))))[, c(1, 2, 4, 3)]

## ----MBL-extreme-, echo = FALSE-----------------------------------------------
### 750
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 750)^2),-1]))))[, c(1, 2, 4, 3)]

## ----MBL-extreme-1000, echo = FALSE-------------------------------------------
### 1000
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 1000)^2),-1]))))[, c(1, 2, 4, 3)]

## ----MBL-extreme-50, echo = FALSE---------------------------------------------
nd50 <- nd
nd50$F13.Akt.prae <- nd50$F13.Akt.prae + 50
cb <- confband(as.mlt(m), newdata = nd50, type = "distribution", 
               K = 500, calpha = univariate_calpha())
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 500)^2),-1]))))[, c(1, 2, 4, 3)]

## ----MBL-extreme-50-750, echo = FALSE-----------------------------------------
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 750)^2),-1]))))[, c(1, 2, 4, 3)]

## ----MBL-extreme-50-1000, echo = FALSE----------------------------------------
data.frame(Subgroup = nd$nd, do.call("rbind", lapply(cb, function(x) 100 * (1 - x[which.min((x[, "q"] - 1000)^2),-1]))))[, c(1, 2, 4, 3)]

## ----F13-trt, echo = FALSE, results = "hide", dev = c("tiff", "pdf", "png"), dpi = 300----
nd <- nd[rep(1:nrow(nd), 2),]
nd$type <- gl(2, nrow(nd) / 2, labels = c("prepartum F. XIII", "prepartum F. XIII + 50 units"))
nd$F13.Akt.prae <- nd$F13.Akt.prae + c(0, 50)[nd$type]
nd$med <- c(predict(as.mlt(m), newdata = nd, type = "quantile", prob = .5))
nd$MBL1000 <- (1 - c(predict(as.mlt(m), newdata = nd, type = "distribution", q = 1000))) * 100
print(nd)
qy <- 2:2000 
p <- predict(as.mlt(m), newdata = nd, type = "distribution", q = qy)
nd <- nd[rep(1:nrow(nd), each = length(qy)),]
nd$MBL <- rep(qy, nrow(nd) / length(qy))
nd$p <- c(p)
key <- simpleKey(levels(nd$type), points = FALSE, lines = TRUE,
                 space = "top", lty = 1)
key$lines$col <- cols2 <- tcols[c(1, length(tcols))]
plt <- vector(mode = "list", length = 2)
# plt[[1]] <- plot(rotate(xitr_MBL), terminal_panel = en, pop = FALSE)
pfun <- function(x, y, ...) {
    panel.abline(v = c(500, 750, 1000), col = "lightgrey")
    panel.xyplot(x = x, y = y, ...)
}
levels(nd$nd) <- paste("Subgroup", levels(nd$nd))
plt[[2]] <- xyplot(p ~ MBL | nd, data = nd, groups = type, type = "l", 
       panel = pfun,
       key = key, col = cols2, xlab = vlab("MBL"), ylab = "Probability")
#       layout = matrix(1:nlevels(blood$nd), ncol = 1))
plot(plt[[2]])
# grid.arrange(plt[[1]], plt[[2]], ncol = 2)

## ----MBL-ztree, echo = FALSE, cache = TRUE, fig.width = 12.5, fig.height = 4.5, dev = c("tiff", "pdf", "png"), dpi = 300----
zitr_MBL <- trafotree(as.mlt(m_MBL), formula = zfm_MBL, data = blood[zMBL_cc,], 
                     parm = mvars, control = ctrl)
blood$nd <- factor(predict(zitr_MBL, newdata = blood, type = "node"))
m <- Colr(as.formula(paste("MBLsurv | nd ~ nd:(", fm, ")")), 
          data = blood[zMBL_cc,], bounds = c(0, Inf), support = c(250, 2000))
cf <- ci(m)
cf <- formatC(round(cf, 3), digits = 3, format = "f") 
zitr_MBL$ci <- matrix(paste(matrix(cf[,2], ncol = 4), 
                            matrix(cf[,3], ncol = 4), sep = "-"), ncol = 4)
rownames(zitr_MBL$ci) <- levels(blood$nd)
plot(rotate(zitr_MBL), terminal_panel = en)

## ----session, echo = FALSE----------------------------------------------------
sessionInfo()

Try the TH.data package in your browser

Any scripts or data that you put into this service are public.

TH.data documentation built on April 18, 2023, 1:09 a.m.