| Title: | Days Alive and Out of Hospital (DAOH) Calculation |
|---|---|
| Description: | Calculates Days Alive and Out of Hospital (DAOH) from administrative admission/discharge/mortality data using three algorithms (nights, days, exact) and three death-handling approaches (midday, midnight, zero). Includes tools for comparing methods (Bland-Altman, ICC, reclassification), and plotting. |
| Authors: | David Cumin [aut, cre] |
| Maintainer: | David Cumin <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-06-19 16:40:54 UTC |
| Source: | https://github.com/cran/daoh |
Computes the mean difference, standard deviation of differences, and 95% limits of agreement between two DAOH vectors (matched by patientID x indexDate).
bland_altman_daoh(res_a, res_b, use_pc = TRUE)bland_altman_daoh(res_a, res_b, use_pc = TRUE)
res_a, res_b
|
data.frames (output of |
use_pc |
Logical. If |
A list with elements mean_diff, sd_diff, loa_lower,
loa_upper, and data (data.frame of paired values for plotting).
The main calculation function. For each patient-index-date pair, computes DAOH using the specified hospital-time algorithm and death-handling approach.
calc_daoh( events, index_dates, period = 90, method = c("nights", "days", "exact"), death_method = c("midday", "midnight", "zero"), gap_hours = 12, origin = as.Date("1970-01-01") )calc_daoh( events, index_dates, period = 90, method = c("nights", "days", "exact"), death_method = c("midday", "midnight", "zero"), gap_hours = 12, origin = as.Date("1970-01-01") )
events |
|
index_dates |
|
period |
Numeric. Follow-up period in days. Default 90. |
method |
Character. Hospital-time algorithm: |
death_method |
Character. How to handle death: |
gap_hours |
Numeric. Gap tolerance in hours for merging adjacent admissions. Default 12. |
origin |
Date. Numeric reference date. Default |
A data.frame with one row per patient-index-date pair and columns:
patientIDPatient identifier.
indexDateIndex date.
n_episodesNumber of merged hospital episodes in period.
dihDays in hospital (numeric, using chosen algorithm).
ddDays dead within the period (0 if survived or death=0).
daohDAOH in days.
daohPCDAOH as a percentage of the period (0-100).
where:
Period length in days (e.g., 90).
Total hospital time within the period (days), computed
from merged, boundary-truncated intervals. See hospital_time().
Total dead time within the period (days), computed from
date of death. See dead_time().
Overlapping hospital events are merged using a 12-hour gap tolerance before summing: any two admissions separated by <= 12 hours are treated as a single continuous episode. This removes double-counting and models the clinical reality that rapid re-admissions represent continuous care.
For merged hospital episodes in the period, the systematic
difference between algorithms is:
because each episode contributes one additional day under the days algorithm (admission = 00:00, discharge = 24:00 vs. both at noon). Therefore:
# --- Example 1: Simple day-stay (highlights nights vs days difference) --- events <- data.frame( patientID = "P1", admission = as.Date("2020-03-10"), discharge = as.Date("2020-03-10"), # same-day admission dod = NA ) idx <- data.frame(patientID = "P1", indexDate = as.Date("2020-03-10")) calc_daoh(events, idx, period = 30, method = "nights")$daoh # 30 days calc_daoh(events, idx, period = 30, method = "days")$daoh # 29 days # --- Example 2: Death handling --- events2 <- data.frame( patientID = "P2", admission = as.Date("2020-03-10"), discharge = as.Date("2020-03-12"), dod = as.Date("2020-03-20") # died 8 days after discharge ) idx2 <- data.frame(patientID = "P2", indexDate = as.Date("2020-03-10")) calc_daoh(events2, idx2, period = 30, method = "nights", death_method = "midday")$daoh # credits pre-death days calc_daoh(events2, idx2, period = 30, method = "nights", death_method = "zero")$daoh # returns 0# --- Example 1: Simple day-stay (highlights nights vs days difference) --- events <- data.frame( patientID = "P1", admission = as.Date("2020-03-10"), discharge = as.Date("2020-03-10"), # same-day admission dod = NA ) idx <- data.frame(patientID = "P1", indexDate = as.Date("2020-03-10")) calc_daoh(events, idx, period = 30, method = "nights")$daoh # 30 days calc_daoh(events, idx, period = 30, method = "days")$daoh # 29 days # --- Example 2: Death handling --- events2 <- data.frame( patientID = "P2", admission = as.Date("2020-03-10"), discharge = as.Date("2020-03-12"), dod = as.Date("2020-03-20") # died 8 days after discharge ) idx2 <- data.frame(patientID = "P2", indexDate = as.Date("2020-03-10")) calc_daoh(events2, idx2, period = 30, method = "nights", death_method = "midday")$daoh # credits pre-death days calc_daoh(events2, idx2, period = 30, method = "nights", death_method = "zero")$daoh # returns 0
Computes two-way mixed ICC (consistency) treating each DAOH calculation method as a rater, following Shrout & Fleiss (1979). Requires the irr package.
daoh_icc(results_list, use_pc = TRUE)daoh_icc(results_list, use_pc = TRUE)
results_list |
Named list of data.frames (output of |
use_pc |
Logical. Use |
The output of irr::icc() for the combined method matrix.
Assesses the practical impact of switching from one DAOH method to another by computing the proportion of patients who move between quartiles. Analogous to the Net Reclassification Index (NRI).
daoh_reclassify(res_a, res_b, n_groups = 4, use_pc = TRUE)daoh_reclassify(res_a, res_b, n_groups = 4, use_pc = TRUE)
res_a, res_b
|
data.frames (output of |
n_groups |
Integer. Number of groups (default 4 = quartiles). |
use_pc |
Logical. Use |
A list with:
confusion_matrixTable of group assignments under a vs b.
pct_reclassifiedPercentage of patients changing group.
mean_group_shiftMean absolute group shift.
# See vignette("getting_started", package = "daoh")# See vignette("getting_started", package = "daoh")
For each specified centile boundary, classifies patients as inside or outside that boundary under each algorithm — using each algorithm's own empirical threshold — then reports the proportion classified differently. This quantifies the clinical impact of algorithm choice at cut-points such as "bottom 10% poor outcome", which are common in adaptive trial enrichment and secondary outcome definitions.
daoh_reclassify_centile( res_a, res_b, boundaries = c(0.05, 0.1, 0.9, 0.95), use_pc = TRUE )daoh_reclassify_centile( res_a, res_b, boundaries = c(0.05, 0.1, 0.9, 0.95), use_pc = TRUE )
res_a, res_b
|
data.frames (output of |
boundaries |
Numeric vector of centile probabilities at which to
evaluate reclassification. Values below 0.5 define lower ("poor outcome")
boundaries; values at or above 0.5 define upper ("excellent outcome")
boundaries. Default |
use_pc |
Logical. Use |
Unlike daoh_reclassify(), which uses a pooled distribution to set group
boundaries, this function applies each algorithm's threshold independently.
That reflects the realistic scenario in which a researcher picks a single
algorithm, defines a centile-based threshold from their own study population,
and applies it — so the threshold itself changes with the algorithm.
A data.frame with one row per boundary and columns:
boundaryHuman-readable label, e.g. "Bottom 5%" or
"Top 10%".
centileThe probability supplied in boundaries.
threshold_aEmpirical centile value for algorithm A.
threshold_bEmpirical centile value for algorithm B.
n_patientsNumber of matched patient-index pairs evaluated.
n_reclassifiedNumber of patients classified differently across the boundary.
pct_reclassifiedPercentage classified differently.
daoh_reclassify() for group-based (quartile) reclassification.
# See vignette("getting_started", package = "daoh")# See vignette("getting_started", package = "daoh")
Compute DAOH summary statistics across all methods and periods
daoh_summary(results_list, quantiles = c(0.1, 0.25, 0.5))daoh_summary(results_list, quantiles = c(0.1, 0.25, 0.5))
results_list |
Named list of data.frames, each the output of
|
quantiles |
Numeric vector of quantiles to report. Default
|
A data.frame with one row per method/period and columns for mean, median, and the requested quantiles.
A minimal example designed to illustrate the difference between the 'nights' and 'days' algorithms for a same-day (day-stay) admission. Under 'nights' the patient contributes 0 nights (DAOH = 30). Under 'days' the patient contributes 1 day (DAOH = 29).
example_daystayexample_daystay
A list with two data.frames:
eventsOne row: day-stay admission on the index date, no death.
index_datesOne row: the index date for this patient.
data(example_daystay) calc_daoh(example_daystay$events, example_daystay$index_dates, period = 30, method = "nights") calc_daoh(example_daystay$events, example_daystay$index_dates, period = 30, method = "days")data(example_daystay) calc_daoh(example_daystay$events, example_daystay$index_dates, period = 30, method = "nights") calc_daoh(example_daystay$events, example_daystay$index_dates, period = 30, method = "days")
A patient with four hospital admissions who dies 8 days after the last discharge, within a 30-day follow-up period. Illustrates all seven DAOH variants.
example_deathexample_death
A list with two data.frames:
eventsFour rows of admissions plus date of death.
index_datesOne index date (day of first admission).
data(example_death) # All seven variants for (meth in c("nights", "days", "exact")) { for (dm in c("midday", "midnight", "zero")) { res <- calc_daoh(example_death$events, example_death$index_dates, period = 30, method = meth, death_method = dm) cat(meth, dm, "DAOH =", round(res$daoh, 2), "\n") } }data(example_death) # All seven variants for (meth in c("nights", "days", "exact")) { for (dm in c("midday", "midnight", "zero")) { res <- calc_daoh(example_death$events, example_death$index_dates, period = 30, method = meth, death_method = dm) cat(meth, dm, "DAOH =", round(res$daoh, 2), "\n") } }
A larger synthetic dataset suitable for demonstrating summary statistics, Bland-Altman analysis, and reclassification. Patients have between 1 and 5 admissions over a 365-day period, with a 5% mortality rate.
example_populationexample_population
A list with two data.frames:
eventsHospital events with columns patientID, admission, discharge, dod.
index_datesOne index date per patient.
data(example_population) res_n <- calc_daoh(example_population$events, example_population$index_dates, period = 90, method = "nights") res_d <- calc_daoh(example_population$events, example_population$index_dates, period = 90, method = "days") bland_altman_daoh(res_n, res_d)data(example_population) res_n <- calc_daoh(example_population$events, example_population$index_dates, period = 90, method = "nights") res_d <- calc_daoh(example_population$events, example_population$index_dates, period = 90, method = "days") bland_altman_daoh(res_n, res_d)
Applies one of the three DAOH hospital-time algorithms to convert admission and discharge dates (or datetimes) into numeric time intervals expressed in fractional days from a reference origin.
hospital_time( admission_dates, discharge_dates, method = c("nights", "days", "exact"), origin = as.Date("1970-01-01") )hospital_time( admission_dates, discharge_dates, method = c("nights", "days", "exact"), origin = as.Date("1970-01-01") )
admission_dates |
Date or POSIXct vector of admission dates/times. |
discharge_dates |
Date or POSIXct vector of discharge dates/times. |
method |
Character string: |
origin |
Date or POSIXct used as numeric zero. Defaults to
|
A data.frame with columns start (numeric, days since origin) and
end (numeric, days since origin) representing the hospital time
interval under the chosen algorithm.
Let be the admission date and the discharge date for
event , expressed as calendar dates (integers at midnight).
Nights algorithm
Equivalent to assuming both admission and discharge occur at 12:00 (noon). A same-day admission contributes 0 nights. This matches the conventional hospital "length of stay" metric.
Days algorithm
Equivalent to assuming admission at 00:00 and discharge at 24:00 of the
respective dates. A same-day admission contributes 1 day. For any
admission, , so the
total difference across merged episodes equals days:
Exact algorithm
Uses recorded timestamps directly (in fractional days). Partial days are included.
# Same-day admission: nights=0, days=1 hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-01"), method = "nights" ) hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-01"), method = "days" ) # Two-night stay hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-03"), method = "nights" # 2 nights )# Same-day admission: nights=0, days=1 hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-01"), method = "nights" ) hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-01"), method = "days" ) # Two-night stay hospital_time( admission_dates = as.Date("2020-03-01"), discharge_dates = as.Date("2020-03-03"), method = "nights" # 2 nights )
Reads one of the three synthetic example datasets bundled with the
package as CSV files in inst/extdata/. This is the preferred way
to access examples without needing to run the data-generation script.
load_example(name = c("daystay", "death", "population"))load_example(name = c("daystay", "death", "population"))
name |
Character. One of:
|
A named list with elements events and index_dates, both
data.frames ready to pass directly to calc_daoh().
ex <- load_example("daystay") calc_daoh(ex$events, ex$index_dates, period = 30, method = "nights") calc_daoh(ex$events, ex$index_dates, period = 30, method = "days")ex <- load_example("daystay") calc_daoh(ex$events, ex$index_dates, period = 30, method = "nights") calc_daoh(ex$events, ex$index_dates, period = 30, method = "days")
Given a set of intervals [start, end], merges those that overlap or are
separated by less than gap time units. This is used to consolidate
hospital admissions that are separated by short gaps (e.g., < 12 hours),
treating them as a single continuous episode.
merge_intervals(starts, ends, gap = 0.5)merge_intervals(starts, ends, gap = 0.5)
starts |
Numeric or POSIXct vector of interval start times. |
ends |
Numeric or POSIXct vector of interval end times (must be >= the corresponding start). |
gap |
Numeric. Intervals with a gap smaller than this value are
merged. Units must match |
A data.frame with columns start and end representing the
merged intervals, sorted by start time.
# Two admissions 10 hours apart -> merged into one merge_intervals( starts = c(0, 1.5), ends = c(1.0, 2.5), gap = 0.5 ) # Two admissions 2 days apart -> kept separate merge_intervals( starts = c(0, 3), ends = c(1, 4), gap = 0.5 )# Two admissions 10 hours apart -> merged into one merge_intervals( starts = c(0, 1.5), ends = c(1.0, 2.5), gap = 0.5 ) # Two admissions 2 days apart -> kept separate merge_intervals( starts = c(0, 3), ends = c(1, 4), gap = 0.5 )
Plot a Bland-Altman comparison of two DAOH methods
plot_daoh_ba( ba_result, method_a = "Method A", method_b = "Method B", use_hex = TRUE )plot_daoh_ba( ba_result, method_a = "Method A", method_b = "Method B", use_hex = TRUE )
ba_result |
Output of |
method_a, method_b
|
Character labels for the two methods. |
use_hex |
Logical. Use |
A ggplot2 object.
Produces a histogram of DAOH scores (as percentage) with deaths overlaid in a contrasting colour. The y-axis is log-scaled to aid visualisation of the bimodal distribution.
plot_daoh_dist(result, log_y = TRUE, title = "DAOH distribution", bins = 50)plot_daoh_dist(result, log_y = TRUE, title = "DAOH distribution", bins = 50)
result |
data.frame (output of |
log_y |
Logical. Use log10 y-axis (default |
title |
Character. Plot title. |
bins |
Integer. Number of histogram bins (default 50). |
A ggplot2 object.
Plots a heatmap of the reclassification table (method A group vs method B group) with cell counts and an annotation of the overall reclassification rate.
plot_daoh_reclassify( reclass_result, method_a = "Method A", method_b = "Method B" )plot_daoh_reclassify( reclass_result, method_a = "Method A", method_b = "Method B" )
reclass_result |
Output of |
method_a, method_b
|
Character labels for the two methods. |
A ggplot2 object.