| Title: | Cardiovascular Applications in Research Data |
|---|---|
| Description: | Datasets and tools for cardiovascular research data. Queries the FDA MAUDE database of medical device adverse event reports through 'openFDA', resolves its free-text manufacturer and brand strings onto companies and ablation platforms, and groups its coded problem terms into clinical complications. Extracts measurements from echocardiogram report text, looks up procedural billing codes, reads variant annotations, and fits cosinor models of circadian rhythm. |
| Authors: | Anish S. Shah [aut, cre, cph] (ORCID: <https://orcid.org/0000-0002-9729-1558>), Reese Fuller [ctb] |
| Maintainer: | Anish S. Shah <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-12 06:20:43 UTC |
| Source: | https://github.com/shah-in-boots/card |
Tests each component of a cosinor model for a non-zero amplitude, or compares nested models fitted to the same data.
## S3 method for class 'cosinor' anova(object, ..., test = "F")## S3 method for class 'cosinor' anova(object, ..., test = "F")
object, ...
|
models of class |
test |
currently only |
With a single model, one row is returned per component testing
- whether that component carries a rhythm
at all, on 2 degrees of freedom. This is the question a multiple-component
model raises and the package previously had no way to answer: a user fitting
tau = c(24, 12) could read off a 12-hour amplitude but not ask whether it
was distinguishable from noise.
The test is a Wald statistic on the relevant block of vcov.cosinor(),
which for least squares is algebraically identical to refitting without
those two columns and comparing residual sums of squares. No refit happens.
With several models it compares them in the order given, requiring that they were fitted to the same observations.
A data.frame of class anova with one row per component, or per
model comparison.
cosinor_zero_amplitude() for the joint test over all components
data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12, 8)) anova(model)data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12, 8)) anova(model)
cosinor objectAugment accepts a cosinor model object and adds information about each observation in the dataset. This includes the predicted values in the .fitted column and the residuals in the .resid column. New columns always begin with a . prefix to avoid overwriting columns in original dataset.
## S3 method for class 'cosinor' augment(x, ...)## S3 method for class 'cosinor' augment(x, ...)
x |
A |
... |
For extensibility |
a tibble object
Other cosinor:
cosinor(),
ggcosinor()
A flat list of complication categories for adjudicating cardiac electrophysiology procedure adverse event narratives. Each entry represents a single complication type with a clinical definition and subcategory schema. The built-in definitions are written for catheter-based cardiac electrophysiology procedures and are most directly informed by AF ablation literature. Users can subset or replace categories to fit other cardiac procedures and can also supply their own complication lists in the same format.
complication_definitionscomplication_definitions
A named list of complication categories. Each element is a list with three components:
title: Character. The full clinical name of the complication.
definition: Character. A clinical definition written for use by an LLM or human adjudicator to identify the complication in adverse event narrative text.
classification: A named list or named character vector. Names are
short subcategory codes; values are scalar character definitions of each
subcategory. Codes may reflect subtype, acuity, intervention, or outcome.
Within a selected category, subcategories are not necessarily mutually
exclusive. Categories may also be co-assigned when more than one
mechanism or outcome is plausible from the narrative. Every category
includes an "insufficient_info" level for narratives that lack
sufficient detail to classify more specifically.
Derived from:
2024 EHRA/HRS/APHRS/LAHRS Expert Consensus Statement on Catheter and Surgical Ablation of AF (Tzeis et al., Europace 2024;26:euae043)
Procedure-Related Complications of Catheter Ablation for AF (Tzeis et al., JACC 2023;82:1524-1536)
2023 ACC/AHA/ACCP/HRS Guideline for Diagnosis and Management of AF (Joglar et al., JACC 2024;83:109-279)
MANIFEST-17K: Multinational Survey on Safety of Postapproval Clinical Use of Pulsed Field Ablation (Ekanem et al., Circulation 2024)
Considerations Regarding Safety with PFA for AF (Heart Rhythm O2, 2024;5:e01169)
Tzeis S, Gerstenfeld EP, Kalman J, et al. 2024 European Heart Rhythm Association/Heart Rhythm Society/Asia Pacific Heart Rhythm Society/Latin American Heart Rhythm Society expert consensus statement on catheter and surgical ablation of atrial fibrillation. Europace. 2024;26(4):euae043.
# Access a single complication complication_definitions$pericardial$title complication_definitions$pericardial$definition names(complication_definitions$pericardial$classification) # List all complication category names names(complication_definitions) # Get all titles vapply(complication_definitions, \(x) x$title, character(1)) # Select a subset relevant to AF ablation af_ablation_complications <- complication_definitions[c( "pericardial", "stroke", "vascular", "pv_stenosis", "esophageal", "phrenic", "arrhythmia", "coronary", "hemolysis", "respiratory", "infection", "death", "device_malfunction", "no_harm", "other" )] # Define a custom complication list in the same format my_complications <- list( lead_dislodgement = list( title = "Lead Dislodgement", definition = "Displacement of a pacemaker or ICD lead from its implant site, resulting in loss of capture, sensing failure, or change in pacing threshold. The narrative may describe lead repositioning, revision surgery, or new pacing parameters.", classification = list( reprogrammed = "Lead dislodgement managed by device reprogramming without surgical intervention.", repositioned = "Lead surgically repositioned or replaced.", insufficient_info = "Lead dislodgement suspected but insufficient detail to determine management." ) ) )# Access a single complication complication_definitions$pericardial$title complication_definitions$pericardial$definition names(complication_definitions$pericardial$classification) # List all complication category names names(complication_definitions) # Get all titles vapply(complication_definitions, \(x) x$title, character(1)) # Select a subset relevant to AF ablation af_ablation_complications <- complication_definitions[c( "pericardial", "stroke", "vascular", "pv_stenosis", "esophageal", "phrenic", "arrhythmia", "coronary", "hemolysis", "respiratory", "infection", "death", "device_malfunction", "no_harm", "other" )] # Define a custom complication list in the same format my_complications <- list( lead_dislodgement = list( title = "Lead Dislodgement", definition = "Displacement of a pacemaker or ICD lead from its implant site, resulting in loss of capture, sensing failure, or change in pacing threshold. The narrative may describe lead repositioning, revision surgery, or new pacing parameters.", classification = list( reprogrammed = "Lead dislodgement managed by device reprogramming without surgical intervention.", repositioned = "Lead surgically repositioned or replaced.", insufficient_info = "Lead dislodgement suspected but insufficient detail to determine management." ) ) )
Confidence intervals for the mesor, amplitudes and acrophases of a fitted cosinor model, by the delta method or from the joint confidence ellipse.
## S3 method for class 'cosinor' confint( object, parm, level = 0.95, type = c("cosinor", "linear"), method = c("delta", "ellipse"), ... )## S3 method for class 'cosinor' confint( object, parm, level = 0.95, type = c("cosinor", "linear"), method = c("delta", "ellipse"), ... )
object |
model of class |
parm |
parameters to return intervals for, given as names or as
positions within |
level |
the confidence level required |
type |
parameterisation to return intervals in, |
method |
|
... |
not currently used, but required for extensibility |
A matrix with one row per requested parameter and two columns giving the lower and upper bounds.
The amplitude and acrophase are a nonlinear function of the regression
coefficients, so method = "delta" propagates vcov.cosinor() through the
Jacobian and forms a symmetric Wald interval. That is the conventional
approach and it is accurate when the rhythm is well determined.
It degrades where the amplitude is small relative to its own standard
error, which is exactly the situation of a weak higher harmonic. The
amplitude is not differentiable at the origin and the acrophase gradient
diverges as , so a symmetric interval can extend below zero for a
quantity that cannot be negative, or span more than a full circle. Such
bounds are reported as given rather than silently truncated - a lower bound
below zero is a signal that the delta method has stopped applying, and
clamping it to zero would hide that.
Under Gaussian errors is exactly bivariate
normal, so the exact joint region is an ellipse
and method = "ellipse" returns the conservative limits obtained by
sweeping its boundary - the smallest and largest distance from the pole for
the amplitude, and the tangent radii for the acrophase (Bingham et al. 1982;
Cornelissen 2014). These respect the parameter space by construction.
When the ellipse covers the pole the no-rhythm null is not rejected and the
acrophase is not identifiable at all. The amplitude then has a lower bound
of zero and the acrophase bounds are returned as NA with a warning,
because the honest answer is the whole circle rather than any interval.
Bingham C, Arbogast B, Cornelissen G, Lee J, Halberg F. Inferential statistical methods for estimating and comparing cosinor parameters. Chronobiologia 1982;9(4):397-439.
Cornelissen G. Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling 2014;11:16. doi:10.1186/1742-4682-11-16
cosinor_methods, cosinor_area(), anova.cosinor()
data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) confint(model) confint(model, parm = "amp1", method = "ellipse")data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) confint(model) confint(model, parm = "amp1", method = "ellipse")
cosinor
cosinor() fits a regression model of a time variable to a
continuous outcome use trigonometric features. This approaches uses the
linearization of the parameters to assess their statistics and
distribution.
cosinor() and its companion functions will leave before version 1.0, for
a separate package for recurring and periodic events that does not yet
exist. Until it does they stay here unchanged; there is no replacement yet.
cosinor(t, ...) ## Default S3 method: cosinor(t, ...) ## S3 method for class 'data.frame' cosinor(t, y, tau, population = NULL, ...) ## S3 method for class 'matrix' cosinor(t, y, tau, population = NULL, ...) ## S3 method for class 'formula' cosinor(formula, data, tau, population = NULL, ...) ## S3 method for class 'recipe' cosinor(t, data, tau, population = NULL, ...)cosinor(t, ...) ## Default S3 method: cosinor(t, ...) ## S3 method for class 'data.frame' cosinor(t, y, tau, population = NULL, ...) ## S3 method for class 'matrix' cosinor(t, y, tau, population = NULL, ...) ## S3 method for class 'formula' cosinor(formula, data, tau, population = NULL, ...) ## S3 method for class 'recipe' cosinor(t, data, tau, population = NULL, ...)
t |
Represents the ordered time indices that provide the positions for the cosine wave. Depending on the context:
|
... |
Not currently used, but required for extensibility. |
y |
When t is a
|
tau |
A vector that determines the periodicity of the time index. The number of elements in the vector determine the number of components (e.g. single versus multiple cosinor).
|
population |
Represents the population to be analyzed with a
population-mean cosinor. Defaults to NULL, assuming individual cosinors are
being generated. When a
When a
|
formula |
A formula specifying the outcome terms on the left-hand side, and the predictor terms on the right-hand side. |
data |
When a
|
A cosinor object.
Other cosinor:
augment.cosinor(),
ggcosinor()
# Data setup data("twins") # Formula interface model <- cosinor(rDYX ~ hour, twins, tau = 24)# Data setup data("twins") # Formula interface model <- cosinor(rDYX ~ hour, twins, tau = 24)
The joint confidence region for a component's (beta, gamma)
pair, together with the conservative amplitude and acrophase limits derived
from it.
cosinor_area(object, level = 0.95, component = 1, ...)cosinor_area(object, level = 0.95, component = 1, ...)
object |
Model of class |
level |
Confidence level requested |
component |
Which component to describe, for a multiple-component model. Defaults to the first. |
... |
Not currently used, but required for extensibility. |
Under Gaussian errors is exactly
bivariate normal, so the joint region is an ellipse rather than a rectangle
of marginal intervals. Conservative limits follow from sweeping its boundary:
the smallest and largest distance from the pole give the amplitude, the
tangent radii give the acrophase (Bingham et al. 1982; Cornelissen 2014).
These respect the parameter space where a symmetric Wald interval does not.
When the region covers the pole the rhythm is not distinguishable from zero
amplitude, the amplitude lower limit is zero and the acrophase is not
identifiable at all - reported as NA rather than as an interval, because
the honest answer is the whole circle.
Earlier versions computed these limits and then returned only the plotting coordinates, so none of it reached the caller.
A list with area, the boundary coordinates used for plotting;
limits, a matrix of amplitude and acrophase confidence limits;
covers_pole, whether the region includes the origin; and component.
Bingham C, Arbogast B, Cornelissen G, Lee J, Halberg F. Inferential statistical methods for estimating and comparing cosinor parameters. Chronobiologia 1982;9(4):397-439.
Cornelissen G. Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling 2014;11:16. doi:10.1186/1742-4682-11-16
confint.cosinor() with method = "ellipse", ggellipse()
data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) cosinor_area(model, component = 2)$limitsdata(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) cosinor_area(model, component = 2)$limits
Extract the special/global features of a multiple component
cosinor. In a multiple component model, there are specific parameters that
are not within the model itself, but must be extracted from the model fit.
When extracted, can be used to improve the plot of a multiple component
cosinor. However, this is only possible if the cosinor is harmonic (see
details). For single-component models, the orthophase is the same as the
acrophase and the global amplitude
Global Amplitude (Ag) = the overall amplitude is defined as half the difference between the peak and trough values
Orthophase (Po) = the lag until the peak time
Bathyphase (Pb) = the lag until the trough time
cosinor_features(object, population = TRUE, ...)cosinor_features(object, population = TRUE, ...)
object |
Model of class |
population |
If the object is a population cosinor, should the features be calculated for the individual cosinors or for the population-cosinors. Default is TRUE. This has no effect on "Individual" cosinor objects.
|
... |
For extensibility |
These calculations can only occur if the periods of the cosinor are harmonic - as in, the longest period is a integer multiple of the smallest period (known as the fundamental frequency). Otherwise, these statistics are not accurate or interpretable.
When returning the cosinor features for a single model, will return
an object of class list. When returning the cosinor features for every
individual in a population cosinor, will return an object of class
tibble.
data(twins) model <- cosinor(rDYX ~ hour, twins, c(24, 8), "patid") results <- cosinor_features(model, population = FALSE) head(results)data(twins) model <- cosinor(rDYX ~ hour, twins, c(24, 8), "patid") results <- cosinor_features(model, population = FALSE) head(results)
Goodness of fit of a cosinor from data that has multiple collections at different timepoints or from multiple cycles. The RSS is partitioned into pure error (SSPE) and lack of fit (SSLOF). An F-test compares the SSPE and SSLOF to detect appropriateness of model.
The fitted values for each time point are:
cosinor_goodness_of_fit(object, level = 0.95, ...)cosinor_goodness_of_fit(object, level = 0.95, ...)
object |
requires cosinor model generated with cosinor to calculate statistics. |
level |
confidence level desired |
... |
additional parameters may be needed for extensibility |
f-statistic as result of goodness of fit
How cosinor() decides whether the periods it was given can be
told apart by the data, and why it does not use the criterion the
chronobiology literature would suggest.
A multiple-component model can only separate two periods if the
design carries enough information to distinguish them. The textbook
criterion is spectral resolution: two frequencies are separable when
for an observation span
(Cornelissen 2014).
That criterion cannot be applied here. Time indices are routinely folded -
the bundled twins data records the clock hour, so its span is 23 hours
however many days were pooled into it - and a span-based check rejects
tau = c(24, 12) on that data, which is both the documented example and a
perfectly well-conditioned fit.
The design matrix answers the question the span cannot. cosinor()
therefore reports the condition number and warns when it
exceeds 30, a threshold that separates every well-posed case from the
degenerate ones by more than an order of magnitude:
tau |
on twins |
24 |
1.4 |
c(24, 12) |
1.5 |
c(24, 8) |
1.5 |
c(24, 12, 8) |
1.5 |
c(24, 23.5) |
129 |
The warning is not an error, because closely spaced periods are sometimes
fitted deliberately - a free-running rhythm against a 24-hour zeitgeber, for
instance. It is worth heeding: tau = c(24, 23.5) on twins returns
amplitudes of 7.4 and 7.1 against a single-component amplitude of 0.30,
because two near-collinear components can grow without bound as long as they
cancel. The condition number is stored on the object and printed by
summary() for that reason, rather than only being warned about once.
The threshold can be moved with options(card.cosinor.kappa = ...).
Cornelissen G. Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling 2014;11:16. doi:10.1186/1742-4682-11-16
Standard extractor methods for a fitted cosinor model. Every
statistic the package reports is built from vcov.cosinor(), so these are
the functions to reach for when writing a new one.
## S3 method for class 'cosinor' coef(object, type = c("cosinor", "linear", "all"), ...) ## S3 method for class 'cosinor' vcov(object, type = c("linear", "cosinor"), ...) ## S3 method for class 'cosinor' sigma(object, ...) ## S3 method for class 'cosinor' nobs(object, ...) ## S3 method for class 'cosinor' df.residual(object, ...) ## S3 method for class 'cosinor' logLik(object, ...)## S3 method for class 'cosinor' coef(object, type = c("cosinor", "linear", "all"), ...) ## S3 method for class 'cosinor' vcov(object, type = c("linear", "cosinor"), ...) ## S3 method for class 'cosinor' sigma(object, ...) ## S3 method for class 'cosinor' nobs(object, ...) ## S3 method for class 'cosinor' df.residual(object, ...) ## S3 method for class 'cosinor' logLik(object, ...)
object |
model of class |
type |
parameterisation to return. |
... |
not currently used, but required for extensibility |
The individual cosinor is ordinary least squares on the design
matrix [1, x_1, z_1, ..., x_p, z_p], so coef(), vcov(), sigma(),
df.residual() and logLik() agree exactly with the equivalent stats::lm
fit. That equivalence is asserted in the package's tests and is the
cheapest available check on the fitting code.
coef() a named numeric vector; vcov() a named
(2p+1) x (2p+1) matrix; sigma() a single numeric; nobs() and
df.residual() a single integer; logLik() an object of class logLik.
type = "linear" returns the regression coefficients
that least squares solves for; type = "cosinor" returns the mesor, amplitudes and acrophases
that are interpreted. The second is a nonlinear
function of the first, and its covariance is obtained by the delta method.
For a population-mean cosinor the random unit is the subject, so vcov()
returns the between-subject covariance of the per-subject coefficients
divided by the number of subjects, on degrees of freedom.
sigma() and logLik() are not defined - a population fit never maximised
a single likelihood, so returning an AIC for it would be a plausible
wrong number rather than an answer.
Cornelissen G. Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling 2014;11:16. doi:10.1186/1742-4682-11-16
cosinor(), confint.cosinor(), anova.cosinor()
Fits a nested family of harmonic cosinor models and reports the evidence for each additional component, so the harmonic order can be chosen rather than assumed.
cosinor_order( object, max_order = 4L, criterion = c("bic", "aic", "ftest"), level = 0.95, ... )cosinor_order( object, max_order = 4L, criterion = c("bic", "aic", "ftest"), level = 0.95, ... )
object |
Model of class |
max_order |
Largest number of harmonics to consider |
criterion |
Which criterion selects the returned order. |
level |
Confidence level for |
... |
Not currently used, but required for extensibility |
Candidate models take periods where is the longest period of object, so each
model is nested in the next and the sequential F test is valid. The tau
of object beyond its first element is ignored; only the fundamental is
used.
Higher harmonics describe the shape of a single rhythm rather than
separate rhythms. A model chosen this way is a truncated Fourier series, and
the quantities to interpret from it are the composite ones from
cosinor_features() - the global amplitude, orthophase and bathyphase -
rather than the individual harmonic amplitudes.
A tibble with one row per candidate order and columns order,
tau, npar, sigma, r.squared, logLik, AIC, BIC, statistic,
df1, df2, p.value, kappa and selected. The test columns compare
each order against the one below it and are NA in the first row.
Confidence intervals computed from the selected model, and reported as though the order had been fixed in advance, are anticonservative: they do not account for the selection having looked at the same data. This is a known gap in the cosinor literature rather than something this function solves. Where the order matters to a conclusion, fix it from prior knowledge instead.
anova.cosinor(), cosinor_features(), cosinor_identifiability
data(twins) model <- cosinor(rDYX ~ hour, twins, tau = 24) cosinor_order(model, max_order = 3)data(twins) model <- cosinor(rDYX ~ hour, twins, tau = 24) cosinor_order(model, max_order = 3)
cosinor_reg() is a parsnip friendly method for specification of cosinor regression model before fitting.
cosinor_reg(mode = "regression", period = NULL) ## S3 method for class 'cosinor_reg' update(object, period = NULL, fresh = FALSE, ...) ## S3 method for class 'cosinor_reg' print(x, ...)cosinor_reg(mode = "regression", period = NULL) ## S3 method for class 'cosinor_reg' update(object, period = NULL, fresh = FALSE, ...) ## S3 method for class 'cosinor_reg' print(x, ...)
mode |
A character string that describes the type of model. In this case, it only supports type of "regression". |
period |
A non-negative number or vector of numbers that represent the expected periodicity of the data to be analyzed. |
object |
Cosinor model specification |
fresh |
A logical for whether the arguments should be modified in place or replaced altogether |
... |
Extensible |
x |
Cosinor model specification |
data(twins) # The example fits deliberately rather than stopping at the specification. # A specification builds and prints correctly even when the engine is # registered incompletely, so only a fit exercises the registration. cosinor_reg(period = c(24, 8)) |> parsnip::set_engine("card") |> parsnip::set_mode("regression") |> parsnip::fit(rDYX ~ hour, data = twins)data(twins) # The example fits deliberately rather than stopping at the specification. # A specification builds and prints correctly even when the engine is # registered incompletely, so only a fit exercises the registration. cosinor_reg(period = c(24, 8)) |> parsnip::set_engine("card") |> parsnip::set_mode("regression") |> parsnip::fit(rDYX ~ hour, data = twins)
Tests the null hypothesis that every component of a cosinor model has zero amplitude, which is the test for whether the data carry a rhythm at all.
cosinor_zero_amplitude(object, level = 0.95)cosinor_zero_amplitude(object, level = 0.95)
object |
model of class |
level |
confidence level used for the reported critical value |
The null is for all j, so a
p-component model contributes 2p parameters to the numerator:
compared against .
Earlier versions of this function fixed the numerator at 2 degrees of
freedom and the denominator at N - 3 whatever tau was given, so a
two-component model on the twins data reported F = 707 on (2, 16383)
where the test is F = 354 on (4, 16381). The statistic was inflated in
proportion to the number of components.
For a population-mean cosinor the same expression is evaluated against the
between-subject covariance on degrees of freedom, giving the
Hotelling test of Bingham et al. (1982).
A list with the fstat observed, the fdist critical value at
level, the numerator and denominator degrees of freedom df1 and df2,
and the p.value.
Cornelissen G. Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling 2014;11:16. doi:10.1186/1742-4682-11-16
anova.cosinor() for the same test applied one component at a time
data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) cosinor_zero_amplitude(model)data(twins) model <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) cosinor_zero_amplitude(model)
A set of functions to extract common echocardiogram measurements from free text reports. These functions use regular expressions to identify and extract both qualitative descriptions and quantitative measurements.
The following measurements can be extracted:
Left atrial (LA) size (qualitative description)
Left atrial diameter (quantitative measurement in cm)
Left ventricular ejection fraction (LVEF, percentage)
Left ventricular internal diameter in diastole (LVIDd, in cm)
A compact set of clinically important findings from a full report
extract_la_size(text, range = c("upper", "lower")) extract_lvef(text, min_val = 5, max_val = 90) extract_lvidd(text, min_val = 1, max_val = 10) extract_la_diameter(text, min_val = 1, max_val = 10) extract_echo_findings(text)extract_la_size(text, range = c("upper", "lower")) extract_lvef(text, min_val = 5, max_val = 90) extract_lvidd(text, min_val = 1, max_val = 10) extract_la_diameter(text, min_val = 1, max_val = 10) extract_echo_findings(text)
text |
Character vector of echo report text |
range |
Which end of a graded range such as |
min_val, max_val
|
Plausible range for the measurement, outside of which a
match is discarded. The defaults are |
These functions use regular expressions to parse unstructured text from echo reports. They handle common variations in terminology and units. Measurements outside plausible ranges are returned as NA.
A measurement is only read from the same clause as the term that names it, so
a value belonging to a neighboring structure is not attributed to the wrong
one. Where several candidate values appear, the first one inside min_val to
max_val is taken, which skips stray digits such as the "2d" in "LVEF by 2D
Simpson is 55%". Linear dimensions are returned in centimeters. A report that
writes the units has them taken at face value; where the units are omitted,
as structured measurement fields often do, they are inferred from min_val
and max_val by reading the value as centimeters when that is plausible and
as millimeters when only the millimeter reading is. The two readings cannot
both be plausible for a chamber dimension, so "LVIDd: 52" resolves to 5.2
cm. This inference only applies where the surrounding text names the
measurement; the fallback search used by extract_la_diameter() matches on a
nearby keyword alone, and there the units are what mark a number as a
measurement at all, so they remain required.
All functions are vectorized over text and return one element (or row) per
report. Qualitative grades are normalized to "none", "trace", "trivial",
"mild", "moderate", and "severe" so they can be used directly as an
ordered factor.
extract_la_size(): Character vector of LA size ("normal", "mild", etc.)
extract_la_diameter(): Numeric LA diameter in cm
extract_lvef(): Numeric LVEF percentage
extract_lvidd(): Numeric LVIDd in cm
extract_echo_findings(): Tibble of key structure/function findings, one row per report
report <- "The left atrium is mildly to moderately dilated. LVEF is 55%." extract_la_size(report) # Returns "moderate" extract_la_size(report, range = "lower") # Returns "mild" extract_lvef(report) # Returns 55 # Units are inferred where a report leaves them out extract_lvidd("LVIDd: 52") # Returns 5.2, as no ventricle is 52 cm across extract_lvidd("LVIDd: 5.2") # Returns 5.2report <- "The left atrium is mildly to moderately dilated. LVEF is 55%." extract_la_size(report) # Returns "moderate" extract_la_size(report, range = "lower") # Returns "mild" extract_lvef(report) # Returns 55 # Units are inferred where a report leaves them out extract_lvidd("LVIDd: 52") # Returns 5.2, as no ventricle is 52 cm across extract_lvidd("LVIDd: 5.2") # Returns 5.2
Used in the model-building examples for repeat testing.
gehgeh
A tibble
This is a generative function used to call CMS procedure codes. It is used to create a dataset that can be generally used to map procedure codes to their descriptions, allowing for understanding of interventions performed. The currently supported codes are explained in the details.
The following procedure codes are currently supported:
ICD9 procedure codes, most recently updated on 2014-10-01
ICD10 procedure codes, most recently updated on 2023-01-11
HCPCS prcoedure codes, most recently updated on 2023-11-29
CPT procedure codes, most recently updated on 2023-11-29
get_procedure_codes(format, version) procedure_codes(format, version)get_procedure_codes(format, version) procedure_codes(format, version)
format |
The format of the procedure codes, written as a |
version |
The version of the procedure codes, which are generally
written as a year. Currently supported: |
CMS will usually release updated version of these codes on an annual basis. Each dataset that is supported below can be identified by the year it was published (not the go-live date, but the publically-available date). The previous versions that are included in the package are as below.
ICD9: 2014
ICD10: 2023
HCPCS: 2023
CPT: 2023
A tbl_df with the columns code and description. The code
refers to the procedure code, while the description refers to the
description of the procedure. The cpt tables carry a third column,
category, naming the procedure family the code belongs to (such as AAA
for infrarenal aortic aneurysm repair, or CARD for cardiac procedures),
which the other formats do not supply.
maude_codes() for the FDA MDR adverse event code tables,
which are bundled data rather than an accessor.
# Procedure codes from the 2014 version of ICD-9 icd9 <- get_procedure_codes(format = "icd9", version = 2014)# Procedure codes from the 2014 version of ICD-9 icd9 <- get_procedure_codes(format = "icd9", version = 2014)
ggplot of cosinor model that can visualize a variety of cosinor model subtypes, including single-component, multiple-component, individual, and population cosinor models, built using cosinor. For single component cosinor, the following values are plotted:
M = midline estimating statistic of rhythm
A = amplitude
P = phi or acrophase (shift from 0 to peak)
If using a multiple-component cosinor, the terms are different. If the periods or frequencies resonate or are harmonic, then the following are calculated. If the periods are not harmonic, the values are just descriptors of the curve.
M = midline estimating statistic of rhythm
Ag = global amplitude, which is the distance between peak and trough (this is the same value as the amplitude from single component)
Po = orthophase (the equivalent of the acrophase in a single component), the lag time to peak value
Pb = bathyphase, the lag time to trough value
ggcosinor(object, labels = TRUE, ...)ggcosinor(object, labels = TRUE, ...)
object |
Model of class |
labels |
Logical value if annotations should be placed on plot, default
= TRUE. The labels depend on the type of plot. The labels are attempted to
be placed "smartly" using the |
... |
For extensibility. This function will use different implementations based on the type of model (single or multiple component). Attributes of the object will be passed down, or calculated on the fly. |
Object of class ggplot that can be layered
Other cosinor:
augment.cosinor(),
cosinor()
data(triplets) m1 <- cosinor(rDYX ~ hour, twins, tau = 24) m2 <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) ggcosinor(m1, labels = FALSE) ggcosinor(m2) ggcosinor(list(single = m1, multiple = m2))data(triplets) m1 <- cosinor(rDYX ~ hour, twins, tau = 24) m2 <- cosinor(rDYX ~ hour, twins, tau = c(24, 12)) ggcosinor(m1, labels = FALSE) ggcosinor(m2) ggcosinor(list(single = m1, multiple = m2))
This is a ggplot-styled graphical representation of the ellipse region generated by the cosinor analysis. It requires the same data used by cosinor model to be fit with the model cosinor. This includes the amplitude, acrophase,
ggellipse(object, level = 0.95, ...)ggellipse(object, level = 0.95, ...)
object |
Requires a cosinor model to extract the correct statistics to generate the plot. |
level |
Confidence level for ellipse |
... |
Additional parameters may be needed for extensibility |
Object of class ggplot to help identify confidence intervals
data("twins") m <- cosinor(rDYX ~ hour, twins, tau = 24) ggellipse(m)data("twins") m <- cosinor(rDYX ~ hour, twins, tau = 24) ggellipse(m)
One row summarising a fitted cosinor model.
## S3 method for class 'cosinor' glance(x, ...)## S3 method for class 'cosinor' glance(x, ...)
x |
a |
... |
For extensibility |
logLik, AIC and BIC are NA for a population-mean cosinor,
which pools per-subject fits rather than maximising a single likelihood.
kappa is the design condition number, reported here because a model whose
periods the data cannot separate is otherwise indistinguishable from a good
one in this table. See cosinor_identifiability.
a tibble with one row
data(twins) glance(cosinor(rDYX ~ hour, twins, tau = c(24, 12)))data(twins) glance(cosinor(rDYX ~ hour, twins, tau = c(24, 12)))
Data is a single patient data output from HRV Toolbox. It contains granular data of calculated HRV in 5-second sliding windows.
hrvhrv
An tibble data frame
maude_adjudicate() reads one report's narrative and returns, for each
complication family its coded problem terms point at, a set of 0/1
flags decided by an ellmer chat model. Only the families the terms
suggest are sent, one at a time, each with its clinical definition.
maude_adjudicate( terms, event_narrative, chat_object, definitions = card::complication_definitions, index = card::maude_complication_index, delimiter = ";" )maude_adjudicate( terms, event_narrative, chat_object, definitions = card::complication_definitions, index = card::maude_complication_index, delimiter = ";" )
terms |
MAUDE problem terms, as a character vector or as one
|
event_narrative |
One adverse event narrative, usually the
|
chat_object |
An ellmer chat object, cloned and reset before each
family so prior turns are not reused. See |
definitions |
Named list of complication definitions, each carrying a
|
index |
Named list mapping complication identifiers to normalised MAUDE
problem terms, named within |
delimiter |
What separates the terms when |
chat_object is an ellmer chat built by ellmer::chat() or one of the
provider constructors such as ellmer::chat_openai(), and it has to support
$chat_structured(), $clone(), $set_turns() and $set_system_prompt().
Credentials stay in that object, as maude_adjudicate() never sees an API
key.
Each selected family is sent on its own, as a fresh clone of the chat with
the turns and tools cleared, so narrative text from one family does not carry
into the next. The schema is an ellmer::type_object() carrying one optional
boolean per classification branch, described by that branch's clinical
definition, and a branch the model omits keeps its default of 0.
A fixed internal system prompt governs the adjudication. It treats the terms
and the narrative as untrusted text and ignores instructions found inside
them, decides only from the supplied definitions, and marks nothing TRUE
that the narrative does not directly support. It will change over time, which
is what the returned "prompt_hash" is for.
A named list of complication families, each holding named 0/1
flags for its classifications.
Three attributes record what produced the adjudication –
"adjudication_model" from chat_object, "prompt_hash" of the internal
system prompt, and "definitions_hash" of the definitions used. The
prompt and the definitions both change over time, and a dataset
adjudicated under one pair is not comparable to a dataset adjudicated
under another, so store these beside the flags when adjudicating in bulk.
## Not run: # Assumes OPENAI_API_KEY is set in ~/.Renviron or the current environment. chat_object <- ellmer::chat_openai( model = "gpt-4.1-mini" ) maude_adjudicate( terms = "Pericardial Effusion; Low blood pressure / hypotension", event_narrative = paste( "Small pericardial effusion noted at case end without hemodynamic", "compromise. Observed overnight without drainage." ), chat_object = chat_object ) ## End(Not run)## Not run: # Assumes OPENAI_API_KEY is set in ~/.Renviron or the current environment. chat_object <- ellmer::chat_openai( model = "gpt-4.1-mini" ) maude_adjudicate( terms = "Pericardial Effusion; Low blood pressure / hypotension", event_narrative = paste( "Small pericardial effusion noted at case end without hemodynamic", "compromise. Observed overnight without drainage." ), chat_object = chat_object ) ## End(Not run)
Load Medical Device Report (MDR) adverse event coding tables published by the FDA, as bundled with the package.
maude_codes(annex)maude_codes(annex)
annex |
Which annex to return: |
The annexes are:
A: Device problem codes
E: Clinical signs, symptoms, or conditions
F: Health impact codes
A tbl_df of codes and related metadata for the requested annex.
All returned annexes share the same core columns:
annex, imdrf_code, fda_code, ncit_code, term,
level_1, level_2, level_3, and definition. level_1 is never
missing; level_2 and level_3 are missing for terms that do not go that
deep.
Each row carries one coded term in term, and level_1 through level_3
give that term's place in the annex hierarchy, read from the IMDRF code
rather than inferred. A three-character code such as A05 is a family, and
on those rows term and level_1 are the same value while level_2 and
level_3 are NA. Deeper codes carry their ancestors, so A0501 has
level_1 of "Mechanical Problem" and level_2 of its own term.
This matters because FDA codes a substantial share of reports at the family
level: "Material Integrity Problem" and "Use of Device Problem" are
values that appear in the data, not merely headings above it. Joining
returned MAUDE terms to this table on term therefore picks up leaf and
family codings alike, and level_1 rolls either up to its family.
Annex E places some terms under more than one family – "Brain Injury"
belongs to both "Nervous System" and "Injury" – so those codes appear on
one row per parent. A join on term or imdrf_code will multiply such rows,
which is correct but will double-count if the result is then tallied. Reduce
to imdrf_code first, or pick a parent, before counting.
FDA MDR Adverse Event Codes: Coding Resources for Medical Device Reports https://www.fda.gov/medical-devices/mdr-adverse-event-codes/coding-resources-medical-device-reports
# Load Annex E health effects codes annex_e <- maude_codes("E") # Roll device problem terms up to their family, leaf or family coded alike terms <- c("Material Deformation", "Material Integrity Problem") annex_a <- maude_codes("A") annex_a$level_1[match(terms, annex_a$term)]# Load Annex E health effects codes annex_e <- maude_codes("E") # Roll device problem terms up to their family, leaf or family coded alike terms <- c("Material Deformation", "Material Integrity Problem") annex_a <- maude_codes("A") annex_a$level_1[match(terms, annex_a$term)]
A named list of MAUDE problem terms, lower-cased, grouped under the
complication categories of complication_definitions, plus a residual
not_indexed group. It is what maude_term_to_complication() and
maude_adjudicate() read to decide which families a report's coded terms
point at. A term may sit under more than one category.
maude_complication_indexmaude_complication_index
A named list of character vectors. Names are the complication
identifiers of complication_definitions, plus not_indexed.
To use your own grouping, build a list of the same shape, one character
vector per category and named as in your definitions, and pass it as
index. Terms are compared after case, punctuation and spacing are
stripped, so they can be written as MAUDE displays them.
maude_complication_index$pericardial maude_complication_index$stroke maude_complication_index$device_malfunction maude_complication_index$not_indexedmaude_complication_index$pericardial maude_complication_index$stroke maude_complication_index$device_malfunction maude_complication_index$not_indexed
Curated table of the manufacturing entities that appear in
MAUDE, the strings each is known by, and the dated corporate ownership
above it. Read by maude_normalize_manufacturer(), maude_resolve_owner()
and maude_search_terms().
maude_entities()maude_entities()
aliases are literal search terms separated by |, matched
case-insensitively on word boundaries. A leading ^ or trailing $ anchors
the term, which a few plant codes need. A trailing * lets a term match into
a longer word, as "MEDTRONICS" and "POLARXFIT" require, and a space matches
any run of punctuation or none, since MAUDE writes a compound name every way.
One row per entity per ownership interval. parent is the company that owned
the entity between from and to, either of which may be NA for an
unbounded interval, and an entity with no row covering a date owned itself on
that date. note records why a row reads as it does, and source cites the
closing date. A row without a source has a date that could not be traced to
a primary announcement, and its note says how approximate the date is.
A tbl_df with columns entity, aliases, parent, from, to,
note and source. from and to are Date.
maude_platforms() for the ablation brands,
maude_normalize_manufacturer() and maude_search_terms() for the lookups
over this table.
# Every entity that Medtronic has acquired entities <- maude_entities() entities[!is.na(entities$parent) & entities$parent == "Medtronic", ]# Every entity that Medtronic has acquired entities <- maude_entities() entities[!is.na(entities$parent) & entities$parent == "Medtronic", ]
Counts how many MAUDE mentions the registry can name, by asking
the openFDA count endpoint for the most common manufacturer or brand
strings and matching them against maude_entities() or maude_platforms().
maude_entity_coverage( stream = c("manufacturer", "ablation", "brand"), api_key = NULL )maude_entity_coverage( stream = c("manufacturer", "ablation", "brand"), api_key = NULL )
stream |
Which stream to measure: |
api_key |
An openFDA API key, passed through to |
Measured rather than stored, because a coverage figure written into documentation goes stale the next time a row is added and nothing says so.
The count endpoint returns at most 999 terms, which is the head of a much longer tail, so the fraction is over the mentions it can see. The ablation specialists sit far below that cut-off in a cardiovascular-wide count, which is why the ablation stream is measured on its own.
A tbl_df of one row per unmatched string, ordered by how many
mentions it carries, with the matched fraction attached as a "matched"
attribute and the total as "mentions".
maude_entities(), maude_platforms()
## Not run: missed <- maude_entity_coverage("manufacturer") attr(missed, "matched") head(missed) ## End(Not run)## Not run: missed <- maude_entity_coverage("manufacturer") attr(missed, "matched") head(missed) ## End(Not run)
maude_normalize_manufacturer() maps the manufacturer_name returned by
maude_query() onto the entity that made the device, collapsing plants,
spelling variants, and contract manufacturers onto one name.
maude_normalize_ablation() maps a cardiac ablation device_brand_name onto
its platform, the energy modality it delivers, and the entity that makes it.
maude_resolve_owner() follows an entity up its chain of acquisitions to the
company that owned it on a given date.
maude_search_terms() runs the other way, from a company to every name its
reports have been filed under, which is what a query has to search on.
maude_normalize_manufacturer(manufacturer_name, entities = maude_entities()) maude_normalize_ablation(device_brand_name, platforms = maude_platforms()) maude_resolve_owner(entity, as_of, entities = maude_entities()) maude_search_terms(company, as_of = NULL, entities = maude_entities())maude_normalize_manufacturer(manufacturer_name, entities = maude_entities()) maude_normalize_ablation(device_brand_name, platforms = maude_platforms()) maude_resolve_owner(entity, as_of, entities = maude_entities()) maude_search_terms(company, as_of = NULL, entities = maude_entities())
manufacturer_name |
Character vector of raw MAUDE manufacturer strings,
as returned in the |
entities |
The entity registry to read, defaulting to |
device_brand_name |
Character vector of raw MAUDE brand strings, as
returned in the |
platforms |
The platform registry to read, defaulting to
|
entity |
Character vector of entity names, as returned by
|
as_of |
For |
company |
The company to search from, named as the registry names it. |
All four read maude_entities() or maude_platforms(), and all return NA
rather than a guess for a value the registry does not cover. On the result of
maude_query() the usual pipeline is:
events |>
dplyr::mutate(
entity = maude_normalize_manufacturer(manufacturer_name),
company = maude_resolve_owner(entity, as_of = Sys.Date()),
at_time = maude_resolve_owner(entity, as_of = date_received)
)
maude_normalize_manufacturer() returns a character vector the same
length as manufacturer_name, holding the entity name or NA where the
registry does not cover the string.
maude_resolve_owner() returns a character vector the same length as
entity, holding the company that owned it on as_of. An entity nobody
owned on that date resolves to itself, which is how a divestiture is
expressed: Physio-Control resolves to Medtronic in 2005, to itself in 2014,
and to Stryker in 2020.
maude_search_terms() returns a tbl_df with one row per searchable term:
entity — the entity the term names.
term — the string to search openFDA on, anchors removed.
from, to — the window during which company owned that entity, NA
where the window is unbounded. The company's own terms carry NA for
both.
maude_normalize_ablation() returns a tbl_df with one row per input:
device_brand_name — the input string, unchanged.
platform — the device platform, such as "Arctic Front".
modality — one of "cryoablation", "radiofrequency",
"pulsed field", "pulsed field or radiofrequency", or "laser". NA
both for an unmatched brand and for a matched device that delivers no
energy, so check platform to tell the two apart.
entity — the entity that makes the platform. Pass it to
maude_resolve_owner() for the corporate parent.
Which company a string names does not change with time. Who owns that company
does. Keeping them apart is what lets one acquisition be one row rather than a
rewrite of every alias beneath it, and it is why maude_resolve_owner()
requires as_of while maude_normalize_manufacturer() takes no date at all.
In the pipeline above, company counts every St. Jude report as Abbott, which is what a question
about today's market wants. at_time counts a 2010 report as St. Jude, which
is what a question about who was shipping the device then wants. Neither is a
default, because picking one silently would answer a question the caller did
not ask.
Normalising happens after the reports are in hand, so it cannot help with the
reports a query never asked for. A filter on "Abbott" does not reach the
St. Jude reports of 2016, and nothing about the result says so.
maude_search_terms() is the direction that fixes it:
abbott <- maude_search_terms("Abbott")
maude_query(manufacturer_name = abbott$term, limit = 1000)
With as_of, the set is restricted to what the company owned on that date;
without it, every name the company's entities have ever filed under is
returned, which is what a retrospective query wants.
Product code does not determine ablation modality. OAE covers cryoablation
and radiofrequency alike, so a rule that resolves it from the product code and
falls back to radiofrequency will put cryoballoon, pulsed-field, and laser
devices into a radiofrequency arm. Deriving the modality from the brand is what
separates them, and a matched device that delivers no energy, such as a
mapping catheter or a sheath, carries NA rather than being swept into an arm.
maude_entities() and maude_platforms() for the registries these
read, and maude_query() for the columns they are applied to.
# Plants, spelling variants, and a contract manufacturer collapse onto one # entity, without anyone having to say when maude_normalize_manufacturer(c( "BOSTON SCIENTIFIC CORPORATION", "FARAPULSE, INC.", "BIOSENSE WEBSTER, INC. (JUAREZ)", "VENUSA DE MEXICO S.A. DE C.V.", "SOME COMPANY NOT IN THE REGISTRY" )) # Ownership is the part that needs a date maude_resolve_owner( "St. Jude Medical", as_of = as.Date(c("2010-01-01", "2024-01-01")) ) # A divestiture, which a single-parent table cannot express maude_resolve_owner( "Physio-Control", as_of = as.Date(c("2005-01-01", "2014-01-01", "2020-01-01")) ) # The other direction: what a query for Abbott has to search on maude_search_terms("Abbott") # Modality comes from the brand, not the product code maude_normalize_ablation(c( "ARCTIC FRONT ADVANCE PRO CARDIAC CRYOABLATION CATHETER", "FARAWAVE PULSED FIELD ABLATION CATHETER", "PENTARAY NAV" ))# Plants, spelling variants, and a contract manufacturer collapse onto one # entity, without anyone having to say when maude_normalize_manufacturer(c( "BOSTON SCIENTIFIC CORPORATION", "FARAPULSE, INC.", "BIOSENSE WEBSTER, INC. (JUAREZ)", "VENUSA DE MEXICO S.A. DE C.V.", "SOME COMPANY NOT IN THE REGISTRY" )) # Ownership is the part that needs a date maude_resolve_owner( "St. Jude Medical", as_of = as.Date(c("2010-01-01", "2024-01-01")) ) # A divestiture, which a single-parent table cannot express maude_resolve_owner( "Physio-Control", as_of = as.Date(c("2005-01-01", "2014-01-01", "2020-01-01")) ) # The other direction: what a query for Abbott has to search on maude_search_terms("Abbott") # Modality comes from the brand, not the product code maude_normalize_ablation(c( "ARCTIC FRONT ADVANCE PRO CARDIAC CRYOABLATION CATHETER", "FARAWAVE PULSED FIELD ABLATION CATHETER", "PENTARAY NAV" ))
Curated table mapping the brand strings that appear in MAUDE
onto the ablation platform they belong to, the energy that platform
delivers, and the entity that makes it. Read by
maude_normalize_ablation().
maude_platforms()maude_platforms()
aliases work as they do in maude_entities(): literal terms separated by
|, matched case-insensitively on word boundaries.
modality is NA for a device that is matched but delivers no energy, which
is deliberate. A mapping catheter, a sheath and an irrigation pump all carry
the ablation product codes and all appear in an ablation query, and sweeping
them into an energy arm would count an access device as an ablation.
entity names the company that makes the platform, never its corporate
parent, so the table does not have to be edited when a company is acquired.
Pass it to maude_resolve_owner() for the parent.
A tbl_df with columns platform, aliases, modality, entity,
note and source.
maude_normalize_ablation(), maude_entities()
# Which platforms deliver pulsed field energy, and who makes them platforms <- maude_platforms() platforms[ !is.na(platforms$modality) & platforms$modality == "pulsed field", c("platform", "entity") ]# Which platforms deliver pulsed field energy, and who makes them platforms <- maude_platforms() platforms[ !is.na(platforms$modality) & platforms$modality == "pulsed field", c("platform", "entity") ]
maude_query() searches the Manufacturer and User Facility Device Experience
(MAUDE) database through the openFDA API and returns one row per report. It
builds the search string, paginates, and parses the records.
maude_fda_api_call() sends one request as written. Reach for it for the
count endpoint, for custom pagination, or for a query the arguments of
maude_query() cannot express.
maude_query( search = NULL, device_generic_name = NULL, device_brand_name = NULL, manufacturer_name = NULL, event_type = NULL, report_number = NULL, device_problem = NULL, patient_problem = NULL, ..., limit = 100, date_start = NULL, date_end = NULL, api_key = NULL, descriptions_from_web = FALSE, verbose = interactive() ) maude_fda_api_call( search_query, limit = 100, skip = 0, api_key = NULL, sort = NULL, count = NULL, max_retries = 3, verbose = FALSE )maude_query( search = NULL, device_generic_name = NULL, device_brand_name = NULL, manufacturer_name = NULL, event_type = NULL, report_number = NULL, device_problem = NULL, patient_problem = NULL, ..., limit = 100, date_start = NULL, date_end = NULL, api_key = NULL, descriptions_from_web = FALSE, verbose = interactive() ) maude_fda_api_call( search_query, limit = 100, skip = 0, api_key = NULL, sort = NULL, count = NULL, max_retries = 3, verbose = FALSE )
search |
A search string, or |
device_generic_name |
Character vector filtering |
device_brand_name |
Character vector filtering |
manufacturer_name |
Character vector filtering
|
event_type |
Character vector filtering |
report_number |
Character vector filtering |
device_problem |
Character vector filtering
|
patient_problem |
Character vector filtering
|
... |
Further named openFDA fields and the values to filter them on. |
limit |
How many records to return. For |
date_start, date_end
|
Bounds on |
api_key |
An openFDA API key, which raises the daily request ceiling. Register at https://open.fda.gov/apis/authentication/. |
descriptions_from_web |
Whether to backfill missing
|
verbose |
Whether to report pagination, retries, and how many records
came back. Defaults to |
search_query |
The whole query string, sent to openFDA as written,
including any date or field filters. Used by |
skip |
How many records to skip, which is how |
sort |
An openFDA sort specification, such as |
count |
An openFDA field to tabulate instead of returning records,
such as |
max_retries |
How many times to retry a transient API failure, meaning
HTTP 429 or 5xx. Defaults to |
A query is built from the named field arguments, ..., and search, joined
by AND. search takes Elasticsearch syntax over the fields openFDA lists
at https://open.fda.gov/apis/device/event/searchable-fields/, so anything
the arguments cannot say can be written there by hand. vignette("maude")
has the syntax and worked queries. The field list is openFDA's and changes
more often than this page can follow.
limit caps the whole call. maude_query() requests 999 records at a time,
sorted on date_received:desc so that pages do not shift, and pauses between
requests to stay under openFDA's rate limit of about 240 a minute. The daily
ceiling is 1,000 requests without an api_key and 120,000 with one, so a key
is worth registering for bulk work. openFDA refuses a skip past 25,000
records, which caps limit at 26,000; a larger pull has to be split by date
range.
A query matching nothing is answered with HTTP 404 rather than an empty array. Both functions return an empty tibble for it, since an empty stratum is a result and not a failure to retry.
MAUDE holds publicly releasable reports from mandatory reporters (manufacturers, importers, and device user facilities) and voluntary ones (clinicians, patients, and consumers), from about 1992 onward, updated weekly.
A tbl_df of device adverse event reports, one row per report:
mdr_report_key — the record's key in MAUDE, and what the narrative
archives and FDA detail pages are keyed on.
report_number — MDR report number.
event_type — "Malfunction", "Injury", "Death", or "Other".
date_received — date FDA received the report.
date_of_event — date the event itself happened, where the reporter
gave one. Its distance from date_received is the reporting lag.
report_source_code — who filed, as "Manufacturer report"
(mandatory), "Voluntary report" (a clinician or patient), or
"User Facility report".
reporter_country_code — two-letter country of the reporter. MAUDE
carries substantial ex-US reporting, so this is what separates reports
that could have responded to a US regulatory action from those that
could not.
mfr_report_type — "Initial", "Thirty-Day", or "Follow-Up". It
does not name the report it follows.
device_generic_name, device_brand_name, manufacturer_name — the
device and its maker as the reporter typed them, which is what
maude_normalize_manufacturer() and maude_normalize_ablation() are
for.
event_description — the narrative blocks, " | "-delimited.
patient_problem — coded patient problems from
patient[].patient_problems, de-duplicated and "; "-delimited.
device_problem — coded device problems from the top-level
product_problems, de-duplicated and "; "-delimited.
An empty tibble is returned when nothing matched.
The result carries a "total" attribute giving how many reports the query
matched, which is larger than nrow() whenever limit truncated it.
With count, maude_fda_api_call() returns a two-column tibble of
term and count instead, at most 1000 rows.
maude_fda_api_call(count = ) reaches the openFDA count endpoint, which
aggregates on the server and returns one term/count pair per distinct
value, so a frequency table that would otherwise take hundreds of paginated
requests takes one.
Two limits are worth knowing. The endpoint returns at most 1000 terms and
offers no pagination cursor, so a longer tail is truncated silently – check
whether nrow() has hit the cap before treating the table as complete. And
counts are of mentions rather than of reports, since a report coded with
three problems contributes to three terms, so the column does not sum to the
number of matching reports.
The coded device problem terms arrive at the top level of the record, in
product_problems, and are never returned inside device[], even though
device.device_problem_codes is a valid field to search on. That asymmetry
is why device_problem filters on one field and is read from another.
openFDA Device Adverse Event API: https://open.fda.gov/apis/device/event/
MAUDE Database Overview: https://open.fda.gov/data/maude/
openFDA API Query Parameters: https://open.fda.gov/apis/query-parameters/
## Not run: # Search for pacemaker-related adverse events pacemaker_events <- maude_query("pacemaker", limit = 10) # Filter by received-date range results <- maude_query( search = "pacemaker", date_start = as.Date("2026-01-01"), date_end = as.Date("2026-01-31"), limit = 50 ) # Add an extra searchable field via ... results <- maude_query( device_generic_name = "infusion pump", device.product_code = "LVP", limit = 50 ) # Frequency table of device problem terms in one request, rather than # paginating through every matching record problems <- maude_fda_api_call( search_query = "device.device_report_product_code:QZI", count = "product_problems.exact", limit = 999 ) ## End(Not run)## Not run: # Search for pacemaker-related adverse events pacemaker_events <- maude_query("pacemaker", limit = 10) # Filter by received-date range results <- maude_query( search = "pacemaker", date_start = as.Date("2026-01-01"), date_end = as.Date("2026-01-31"), limit = 50 ) # Add an extra searchable field via ... results <- maude_query( device_generic_name = "infusion pump", device.product_code = "LVP", limit = 50 ) # Frequency table of device problem terms in one request, rather than # paginating through every matching record problems <- maude_fda_api_call( search_query = "device.device_report_product_code:QZI", count = "product_problems.exact", limit = 999 ) ## End(Not run)
maude_term_to_complication() groups MAUDE problem terms into the
complication categories defined by a user-supplied matching index. Input
terms are normalised before matching, so differences in capitalisation,
punctuation, and spacing do not affect the result.
A term may appear in more than one complication category if the underlying
MAUDE index overlaps across categories. Returned values preserve the original
user-supplied terms rather than the normalised versions used for matching.
Terms that do not match any index entry are returned under "not_indexed".
maude_term_to_complication( term, definitions = card::complication_definitions, index = card::maude_complication_index )maude_term_to_complication( term, definitions = card::complication_definitions, index = card::maude_complication_index )
term |
Character vector of MAUDE problem terms to group into complications. |
definitions |
Named list of complication definitions. Defaults to complication_definitions. |
index |
Named list mapping complication identifiers to normalised MAUDE problem terms. Defaults to maude_complication_index. |
A named list. Each list name is a complication identifier, or
"not_indexed" for unmatched terms, and each element is a character vector
of the original input terms that matched that complication. Empty
complication groups are omitted.
maude_term_to_complication(c( "Cardiac Tamponade", "Low blood pressure / hypotension", "No Health Consequences or Impact" ))maude_term_to_complication(c( "Cardiac Tamponade", "Low blood pressure / hypotension", "No Health Consequences or Impact" ))
cosinor
Predict from a cosinor
## S3 method for class 'cosinor' predict(object, new_data, type = "numeric", ...)## S3 method for class 'cosinor' predict(object, new_data, type = "numeric", ...)
object |
A |
new_data |
A data frame or matrix of new predictors. |
type |
A single character. The type of predictions to generate. Valid options are:
|
... |
Additional arguments passed to the prediction function |
A tibble of predictions. The number of rows in the tibble is
guaranteed to be the same as the number of rows in new_data.
Takes variant-level results from query_genetic_variants() and aggregates
them to unique genes with summary statistics about associated variants.
query_genes_by_phenotype( phenotype, database = "clinvar", api_key = NULL, max_results = 500, genes = NULL, clean_gene_symbols = TRUE )query_genes_by_phenotype( phenotype, database = "clinvar", api_key = NULL, max_results = 500, genes = NULL, clean_gene_symbols = TRUE )
phenotype |
Character string specifying the phenotype or disease
condition to search for (same as |
database |
Character string specifying which database to query. |
api_key |
Optional character string containing your NCBI API key. |
max_results |
Integer specifying maximum number of variants to query before aggregation. Default is 500. Large queries will automatically paginate in 500-record batches and may take several minutes due to API rate limits. There is no hard maximum. |
genes |
Optional character vector of gene symbols to filter results. |
clean_gene_symbols |
Logical indicating whether to clean gene symbols. Default is TRUE. |
A tibble with one row per unique gene, containing:
Gene symbol
Total number of variants for this gene
Number of pathogenic/likely pathogenic variants
Number of benign/likely benign variants
Number of variants of uncertain significance
Unique phenotypes associated with this gene (collapsed)
Chromosome(s) where gene is located
Source database
Query online genetic variant databases (ClinVar and others) to retrieve gene-disease associations based on a phenotype or clinical condition.
query_genetic_variants( phenotype, database = "clinvar", api_key = NULL, max_results = 100, genes = NULL, clean_gene_symbols = TRUE )query_genetic_variants( phenotype, database = "clinvar", api_key = NULL, max_results = 100, genes = NULL, clean_gene_symbols = TRUE )
phenotype |
Character string specifying the phenotype or disease condition to search for (e.g., "atrial fibrillation", "hypertrophic cardiomyopathy"). |
database |
Character string specifying which database to query. Currently supports "clinvar" (default). Additional databases may be added in future versions. |
api_key |
Optional character string containing your NCBI API key. Providing an API key increases the rate limit from 3 to 10 requests per second. Get a key at: https://www.ncbi.nlm.nih.gov/account/ |
max_results |
Integer specifying maximum number of results to return. Default is 100. Large queries will automatically paginate in 500-record batches and may take several minutes due to API rate limits. There is no hard maximum, but consider the total available (shown in the message) when requesting large numbers. |
genes |
Optional character vector of gene symbols to filter results. If provided, only variants in these genes will be returned. Default is NULL (return all genes). |
clean_gene_symbols |
Logical indicating whether to clean/normalize gene symbol values (remove/replace pseudogenes such as LOC*, LINC*, MIR*). Default is TRUE. |
The function queries the specified genetic variant database and returns a standardized table of results. For ClinVar, it uses the NCBI E-utilities API with automatic rate limiting to comply with NCBI's usage policies (3 requests/second without API key, 10 requests/second with API key).
The search is performed using disease/phenotype field matching and may return variants for the exact condition as well as related conditions (e.g., searching for "autism" may return "autism spectrum disorder").
A tibble with the following columns:
Gene symbol (e.g., "TTN")
Database-specific variant identifier
Human-readable variant name (HGVS notation when available)
Chromosome location
Genomic position
Clinical interpretation (e.g., "Pathogenic", "Benign")
Level of expert review
Associated phenotypes/conditions
Effect on protein/transcript
Source database
## Not run: af_variants <- query_genetic_variants("atrial fibrillation") ## End(Not run)## Not run: af_variants <- query_genetic_variants("atrial fibrillation") ## End(Not run)
Read a VEP output file into a tidy tibble.
read_vep_data( file, format = c("tab", "vcf"), columns = NULL, parse_extra = TRUE )read_vep_data( file, format = c("tab", "vcf"), columns = NULL, parse_extra = TRUE )
file |
Path to the VEP output file. Can be uncompressed or gzipped. |
format |
Character string specifying the output format. One of:
|
columns |
Character vector of column names to include in the output. Default is NULL (returns all columns). Can reference both fixed VEP columns (e.g., "Gene", "Consequence") and Extra/CSQ fields (e.g., "SYMBOL", "IMPACT", "SIFT", "LoF"). |
parse_extra |
Logical. Whether to parse the Extra column (tab) or CSQ field (VCF) into individual columns. Default is TRUE. |
read_vep_data() parses tab-delimited and VCF-style VEP outputs and returns one
row per annotated consequence record. By default it expands Extra (tab) or
CSQ (VCF) into separate columns for easier downstream filtering.
This function uses read_vep_header to infer structure and then
reads the data body in a single pass.
Tab-delimited format:
14 columns: 13 fixed fields plus "Extra" column
Extra contains semicolon-delimited key=value pairs
Standalone flags are converted to YES (for example, CANONICAL)
VCF format:
Standard VCF columns with CSQ in INFO field
CSQ contains pipe-delimited values ordered by the CSQ header definition
The function automatically:
Detects and handles gzipped files
Skips metadata + header lines
Replaces VEP dash placeholders ("-") with NA
Preserves metadata for provenance tracking
A tibble with one row per variant-transcript annotation. If parse_extra is TRUE, the Extra/CSQ column is replaced by its parsed key-value pairs as individual columns. VEP dashes ("-") are replaced with NA. The returned tibble has the following attributes:
The complete header structure from read_vep_header()
Normalized path to the source VEP file
read_vep_header for reading just the header information
## Not run: # Read all columns from tab-delimited VEP output df <- read_vep_data("sample.filtered") # Read only specific columns of interest df <- read_vep_data( "sample.filtered", columns = c("Uploaded_variation", "Gene", "Consequence", "SYMBOL", "IMPACT", "SIFT", "PolyPhen", "LoF") ) # Read without parsing Extra (faster, returns raw Extra string) df <- read_vep_data("sample.filtered", parse_extra = FALSE) # Access metadata attr(df, "vep_header")$meta$vep_version attr(df, "vep_header")$meta$assembly # Read gzipped VCF format df <- read_vep_data("sample.vep.vcf.gz", format = "vcf") ## End(Not run)## Not run: # Read all columns from tab-delimited VEP output df <- read_vep_data("sample.filtered") # Read only specific columns of interest df <- read_vep_data( "sample.filtered", columns = c("Uploaded_variation", "Gene", "Consequence", "SYMBOL", "IMPACT", "SIFT", "PolyPhen", "LoF") ) # Read without parsing Extra (faster, returns raw Extra string) df <- read_vep_data("sample.filtered", parse_extra = FALSE) # Access metadata attr(df, "vep_header")$meta$vep_version attr(df, "vep_header")$meta$assembly # Read gzipped VCF format df <- read_vep_data("sample.vep.vcf.gz", format = "vcf") ## End(Not run)
Read only the metadata section of a VEP output file.
read_vep_header(file, format = c("tab", "vcf"), n_max = 1000)read_vep_header(file, format = c("tab", "vcf"), n_max = 1000)
file |
Path to the VEP output file. Can be uncompressed or gzipped. |
format |
Character string specifying the output format. One of: - _tab_ = Default VEP tab-delimited output format - _vcf_ = VCF format output (produced with `--vcf` flag) |
n_max |
Maximum number of lines to scan for header content. Default is 1000, which should be sufficient for most VEP files. |
This is useful when you want to inspect VEP version, assembly, parsed field definitions, or column layout before loading the full annotation table.
VEP can output results in different formats depending on command-line flags. This function supports the two most common modes:
Tab-delimited format (default or --tab):
Metadata lines begin with ##
Column header line begins with single #
Annotation fields are described under "Extra column keys"
VCF format (--vcf):
Standard VCF headers with ## metadata
Consequence annotations are stored in INFO/CSQ
CSQ field order is declared in ##INFO=<ID=CSQ,...> header line
The returned structure is consistent across formats. If no annotation fields
are found, annotations is returned as NULL.
A named list containing:
- *meta* = List of file metadata including format, VEP version, assembly, command line, and raw metadata lines - *columns* = Named character vector where names are column names and values are descriptions (if available) - *annotations* = Named list where names are annotation field names and values are their definitions from the header. For tab format these are Extra column fields; for VCF format these are CSQ fields (with NA values since VCF doesn't include descriptions). NULL if no annotations.}
https://www.ensembl.org/info/docs/tools/vep/vep_formats.html for VEP output format documentation
## Not run: # Read header from tab-delimited VEP output header <- read_vep_header("variants.vep.txt", format = "tab") names(header$columns) names(header$annotations) # Get description for a specific annotation header$annotations[["CLIN_SIG"]] # Read header from VCF format VEP output header <- read_vep_header("variants.vep.vcf", format = "vcf") ## End(Not run)## Not run: # Read header from tab-delimited VEP output header <- read_vep_header("variants.vep.txt", format = "tab") names(header$columns) names(header$annotations) # Get description for a specific annotation header$annotations[["CLIN_SIG"]] # Read header from VCF format VEP output header <- read_vep_header("variants.vep.vcf", format = "vcf") ## End(Not run)
Data is from a outcomes study on cardiovascular outcomes. It contains the first visit date, the last known date, and times of various events that have happened. They document death at right censoring as well. These events are non-ordered.
stressstress
An tibble data frame
Tidy summarizes information about the components of a cosinor
model.
## S3 method for class 'cosinor' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)## S3 method for class 'cosinor' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)
x |
A |
conf.int |
Logical indicating whether or not to include confidence interval in tidied output |
conf.level |
The confidence level to use if between 0 and 1, with default to 0.95 (the 95% confidence interval). |
... |
For extensibility |
The standard error is obtained by the delta method from the
covariance of the regression coefficients, since the amplitude and acrophase
are a nonlinear function of them. See confint.cosinor() for where that
approximation stops applying.
The statistic column reports each component's F test against zero
amplitude, on 2 and degrees of freedom. It is a
per-component test rather than a per-parameter one, so the amplitude and
acrophase of a given component share a statistic and a p-value: the null
being tested is , and an acrophase has no
meaning under it. The mesor is tested separately by a t statistic.
a tibble object
Clinical data is also available for visualization and comparison. Other HRV measures are used here for comparison and testing out functions.
tripletstriplets
A tbl_df
Data is from an algorithm that generates a summary HRV measure using the Poincare phase-space plot, generated from kurtoses of the x and y axis. Clinical data is also available for visualization and comparison. There are repeat rows for each hour that Dyx was taken.
twinstwins
An tibble data frame
This is a dataset from the archived/orphaned zipcode package.
zipcodezipcode
A data frame with character vector zipcodes and latitude/longitude