Skip to contents

Attach a sort_spec to a tabular_spec. The engine applies the sort before pagination, so by may reference any column in spec@data whether or not the column is declared in cols().

Usage

sort_rows(.spec, by = character(), descending = FALSE)

Arguments

.spec

The tabular_spec to attach the sort to. <tabular_spec>: required.

by

Ordered column names to sort by, in precedence order. <character>: default character(). Length 0 is accepted (no-op sort). May reference columns not declared in cols() — sort-only helper columns ride along through the engine.

Restriction: Every entry must be a column in spec@data. Cannot reference arm columns produced by pivot_across(); pivot upstream of the sort instead. Arm cells hold rendered stat strings (e.g. "75.2 (8.3)") that do not order meaningfully.

# Two-key hierarchical sort: SOC clusters by descending count,
# each PT nested under its SOC.
sort_rows(by = c("soc_n", "n_total"), descending = c(TRUE, TRUE))

descending

Per-key sort direction. <logical(1) | logical(length(by))>: default FALSE. TRUE sorts the corresponding key descending; length 1 recycles to every key.

Restriction: No NAs. Length must be 1 or length(by). Tip: For mixed-direction multi-key sorts, pass length(by) values; the engine inverts the xtfrm rank of each descending key and calls order() once on all keys.

Value

The updated tabular_spec. Continue chaining with style(), paginate(), preset(), then render via emit() (or resolve without I/O via as_grid()).

Details

Replace, not stack. A second sort_rows() call REPLACES the prior sort — sort is a single spec, not a stackable list. Call with no arguments to clear.

NA last, regardless of direction. NA values in a sort key are placed at the end whether the key is ascending or descending (matching order(..., na.last = TRUE)).

Factor levels drive the order. Factor columns sort by factor levels, not by the character label. The CDISC BOR ordering (CR < PR < SD < NON-CR/NON-PD < PD < NE < MISSING) survives a tabular pipeline without an explicit mutate() — coerce stat_label to a factor with the levels in clinical order upstream, then sort_rows(by = "stat_label") does the rest.

See also

Sibling build verbs: cols() / col_spec(), headers(), style(), paginate(), preset().

Entry / terminal verbs: tabular(), emit(), as_grid().

Examples

# ---- Example 1: AE table clustered by SOC, PTs nested by count ----
#
# AE-by-SOC/PT table sorted so each SOC is followed immediately by
# its own preferred terms, SOC clusters in descending subject-count
# order. The sort runs on the bundled numeric helpers `soc_n` and
# `n_total`, not the formatted `Total` text, which would sort
# lexically ("14" < "171" < "29").
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)

tabular(
  cdisc_saf_aesocpt,
  titles = c(
    "Table 14.3.1",
    "Adverse Events by System Organ Class and Preferred Term",
    "Safety Population"
  ),
  footnotes = "Subjects are counted once per SOC and once per PT."
) |>
  cols(
    label    = col_spec(label = "SOC / PT", indent = "indent_level"),
    soc      = col_spec(visible = FALSE),
    row_type = col_spec(visible = FALSE),
    soc_n    = col_spec(visible = FALSE),
    n_total  = col_spec(visible = FALSE),
    placebo  = col_spec(label = "Placebo\nN={n['placebo']}"),
    drug_50  = col_spec(label = "Drug 50\nN={n['drug_50']}"),
    drug_100 = col_spec(label = "Drug 100\nN={n['drug_100']}"),
    Total    = col_spec(label = "Total\nN={n['Total']}")
  ) |>
  sort_rows(by = c("soc_n", "n_total"), descending = c(TRUE, TRUE))

 

Table 14.3.1

Adverse Events by System Organ Class and Preferred Term

Safety Population

 

SOC / PTPlacebo
N=86
Drug 50
N=96
Drug 100
N=72
Total
N=254
TOTAL SUBJECTS WITH AN EVENT52 (60.5)81 (84.4)66 (91.7)199 (78.3)
SKIN AND SUBCUTANEOUS TISSUE DISORDERS19 (22.1)36 (37.5)35 (48.6)90 (35.4)
PRURITUS8 (9.3)21 (21.9)25 (34.7)54 (21.3)
ERYTHEMA8 (9.3)14 (14.6)14 (19.4)36 (14.2)
RASH5 (5.8)13 (13.5)8 (11.1)26 (10.2)
HYPERHIDROSIS2 (2.3)4 (4.2)8 (11.1)14 (5.5)
SKIN IRRITATION3 (3.5)6 (6.2)5 (6.9)14 (5.5)
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS15 (17.4)36 (37.5)30 (41.7)81 (31.9)
APPLICATION SITE PRURITUS6 (7.0)23 (24.0)21 (29.2)50 (19.7)
APPLICATION SITE ERYTHEMA3 (3.5)13 (13.5)14 (19.4)30 (11.8)
APPLICATION SITE DERMATITIS5 (5.8)9 (9.4)7 (9.7)21 (8.3)
APPLICATION SITE IRRITATION3 (3.5)9 (9.4)9 (12.5)21 (8.3)
APPLICATION SITE VESICLES1 (1.2)5 (5.2)5 (6.9)11 (4.3)
GASTROINTESTINAL DISORDERS13 (15.1)12 (12.5)17 (23.6)42 (16.5)
DIARRHOEA9 (10.5)5 (5.2)3 (4.2)17 (6.7)
VOMITING3 (3.5)4 (4.2)6 (8.3)13 (5.1)
NAUSEA3 (3.5)3 (3.1)6 (8.3)12 (4.7)
ABDOMINAL PAIN1 (1.2)3 (3.1)1 (1.4)5 (2.0)
SALIVARY HYPERSECRETION0 (0.0)0 (0.0)4 (5.6)4 (1.6)
NERVOUS SYSTEM DISORDERS6 (7.0)18 (18.8)17 (23.6)41 (16.1)
DIZZINESS2 (2.3)9 (9.4)10 (13.9)21 (8.3)
HEADACHE3 (3.5)3 (3.1)5 (6.9)11 (4.3)
SYNCOPE0 (0.0)5 (5.2)2 (2.8)7 (2.8)
SOMNOLENCE2 (2.3)3 (3.1)1 (1.4)6 (2.4)
TRANSIENT ISCHAEMIC ATTACK0 (0.0)2 (2.1)1 (1.4)3 (1.2)
CARDIAC DISORDERS7 (8.1)12 (12.5)14 (19.4)33 (13.0)
SINUS BRADYCARDIA2 (2.3)7 (7.3)8 (11.1)17 (6.7)
MYOCARDIAL INFARCTION4 (4.7)2 (2.1)4 (5.6)10 (3.9)
ATRIAL FIBRILLATION1 (1.2)2 (2.1)2 (2.8)5 (2.0)
SUPRAVENTRICULAR EXTRASYSTOLES1 (1.2)1 (1.0)1 (1.4)3 (1.2)
VENTRICULAR EXTRASYSTOLES0 (0.0)2 (2.1)1 (1.4)3 (1.2)
INFECTIONS AND INFESTATIONS12 (14.0)6 (6.2)11 (15.3)29 (11.4)
NASOPHARYNGITIS2 (2.3)4 (4.2)6 (8.3)12 (4.7)
UPPER RESPIRATORY TRACT INFECTION6 (7.0)1 (1.0)3 (4.2)10 (3.9)
INFLUENZA1 (1.2)1 (1.0)1 (1.4)3 (1.2)
URINARY TRACT INFECTION2 (2.3)0 (0.0)1 (1.4)3 (1.2)
CYSTITIS1 (1.2)0 (0.0)1 (1.4)2 (0.8)
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS5 (5.8)8 (8.3)9 (12.5)22 (8.7)
COUGH1 (1.2)5 (5.2)5 (6.9)11 (4.3)
NASAL CONGESTION3 (3.5)1 (1.0)3 (4.2)7 (2.8)
DYSPNOEA1 (1.2)1 (1.0)1 (1.4)3 (1.2)
EPISTAXIS0 (0.0)1 (1.0)2 (2.8)3 (1.2)
PHARYNGOLARYNGEAL PAIN0 (0.0)1 (1.0)1 (1.4)2 (0.8)
PSYCHIATRIC DISORDERS7 (8.1)9 (9.4)3 (4.2)19 (7.5)
CONFUSIONAL STATE2 (2.3)3 (3.1)1 (1.4)6 (2.4)
AGITATION2 (2.3)3 (3.1)0 (0.0)5 (2.0)
INSOMNIA2 (2.3)0 (0.0)2 (2.8)4 (1.6)
ANXIETY0 (0.0)3 (3.1)0 (0.0)3 (1.2)
DELUSION1 (1.2)0 (0.0)1 (1.4)2 (0.8)
MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS3 (3.5)6 (6.2)5 (6.9)14 (5.5)
BACK PAIN1 (1.2)1 (1.0)3 (4.2)5 (2.0)
ARTHRALGIA1 (1.2)2 (2.1)1 (1.4)4 (1.6)
SHOULDER PAIN1 (1.2)2 (2.1)0 (0.0)3 (1.2)
MUSCLE SPASMS0 (0.0)1 (1.0)1 (1.4)2 (0.8)
ARTHRITIS0 (0.0)0 (0.0)1 (1.4)1 (0.4)
INVESTIGATIONS5 (5.8)4 (4.2)3 (4.2)12 (4.7)
ELECTROCARDIOGRAM ST SEGMENT DEPRESSION4 (4.7)1 (1.0)0 (0.0)5 (2.0)
ELECTROCARDIOGRAM T WAVE INVERSION2 (2.3)1 (1.0)1 (1.4)4 (1.6)
BLOOD GLUCOSE INCREASED0 (0.0)1 (1.0)1 (1.4)2 (0.8)
ELECTROCARDIOGRAM T WAVE AMPLITUDE DECREASED1 (1.2)1 (1.0)0 (0.0)2 (0.8)
BIOPSY0 (0.0)0 (0.0)1 (1.4)1 (0.4)

Subjects are counted once per SOC and once per PT.

# ---- Example 2: BOR table in CDISC factor order ---- # # Efficacy BOR table that must appear in CDISC clinical order # (CR < PR < SD < NON-CR/NON-PD < PD < NE < MISSING), then the # derived ORR / CBR / DCR rate rows ordered by `groupid`, # not alphabetical. `cdisc_eff_resp$stat_label` arrives as character, so # coerce to a factor with the canonical levels upstream and # `sort_rows()` uses those levels directly. bor_levels <- c( "CR", "PR", "SD", "NON-CR/NON-PD", "PD", "NE", "MISSING", "ORR (CR + PR)", "CBR (CR + PR + SD)", "DCR (CR + PR + SD + NON-CR/NON-PD)", "95% CI (Clopper-Pearson)" ) eff <- cdisc_eff_resp eff$stat_label <- factor(eff$stat_label, levels = bor_levels) ne <- stats::setNames(cdisc_eff_n$n, cdisc_eff_n$arm_short) tabular( eff, titles = c( "Table 14.2.1", "Best Overall Response and Response Rates", "Efficacy Evaluable Population" ), footnotes = "Response per RECIST 1.1, investigator assessment." ) |> cols( stat_label = col_spec(label = "Response"), row_type = col_spec(visible = FALSE), groupid = col_spec(visible = FALSE), group_label = col_spec(visible = FALSE), placebo = col_spec(label = "Placebo\nN={ne['placebo']}"), drug_50 = col_spec(label = "Drug 50\nN={ne['drug_50']}"), drug_100 = col_spec(label = "Drug 100\nN={ne['drug_100']}") ) |> sort_rows(by = c("groupid", "stat_label"))

 

Table 14.2.1

Best Overall Response and Response Rates

Efficacy Evaluable Population

 

ResponsePlacebo
N=86
Drug 50
N=84
Drug 100
N=84
CR1 (1.2)1 (1.2)1 (1.2)
PR1 (1.2)00
SD1 (1.2)00
NON-CR/NON-PD001 (1.2)
PD001 (1.2)
NE01 (1.2)0
MISSING83 (96.5)82 (97.6)81 (96.4)
ORR (CR + PR)2 (2.3)1 (1.2)1 (1.2)
95% CI (Clopper-Pearson)(0.3, 8.1)(0.0, 6.5)(0.0, 6.5)
CBR (CR + PR + SD)3 (3.5)1 (1.2)1 (1.2)
95% CI (Clopper-Pearson)(0.7, 9.9)(0.0, 6.5)(0.0, 6.5)
DCR (CR + PR + SD + NON-CR/NON-PD)3 (3.5)1 (1.2)2 (2.4)
95% CI (Clopper-Pearson)(0.7, 9.9)(0.0, 6.5)(0.3, 8.3)

Response per RECIST 1.1, investigator assessment.

# ---- Example 3: Mixed-direction multi-key sort with hidden helper ---- # # Demographics-style table sorted by `variable` ascending and a # hidden numeric key descending. The `descending` argument takes # one value per `by` entry so each key can flip direction # independently. The helper column rides in `spec@data` for the # sort but never renders (visible = FALSE on its col_spec). demo <- cdisc_saf_demo demo$display_order <- match(demo$variable, unique(demo$variable)) tabular(demo, titles = "Demographics, ranked within section") |> cols( variable = col_spec(usage = "group", label = "Characteristic"), stat_label = col_spec(label = "Statistic"), display_order = col_spec(visible = FALSE), placebo = col_spec(label = "Placebo", align = "decimal"), drug_50 = col_spec(label = "Drug 50", align = "decimal"), drug_100 = col_spec(label = "Drug 100", align = "decimal"), Total = col_spec(label = "Total", align = "decimal") ) |> sort_rows( by = c("display_order", "stat_label"), descending = c(FALSE, TRUE) )

 

Demographics, ranked within section

 

StatisticPlaceboDrug 50Drug 100Total
Age (years)
n86         96         72         254         
Q1, Q369.2, 81.8 71.0, 82.0 70.5, 79.0  70.0, 81.0 
Min, Max52  , 89   51  , 88   56  , 88    51  , 89   
Median76.0       78.0       75.5        77.0       
Mean (SD)75.2 (8.59)76.0 (8.11)73.8 (7.94) 75.1 (8.25)
 
Sex, n (%)
M33 (38.4)  41 (42.7)  37 (51.4)  111 (43.7)  
F53 (61.6)  55 (57.3)  35 (48.6)  143 (56.3)  
 
Race, n (%)
WHITE78 (90.7)  90 (93.8)  62 (86.1)  230 (90.6)  
BLACK OR AFRICAN AMERICAN 8 ( 9.3)   6 ( 6.2)   9 (12.5)   23 ( 9.1)  
ASIAN 0          0          0           0         
AMERICAN INDIAN OR ALASKA NATIVE 0          0          1 ( 1.4)    1 ( 0.4)