Clinical Data Listings in R: Patient Listings and RTF Output with rlistings

Build the row-per-observation appendix of a Clinical Study Report — a patient adverse-event listing with repeated key columns blanked and long output paginated — with rlistings, then export it to a submission-grade RTF with r2rtf, on public pharmaverse data

Pharma & Clinical

A complete, runnable tutorial for the clinical data listing — the row-per-observation appendix of a Clinical Study Report that shows every record for every subject, unsummarized. Learn what a listing is and how it differs from a summary table, then build a patient adverse-event listing with rlistings: as_listing() with key_cols (the repeated columns blanked on repeat) and disp_cols, paginate_listing() for the long output, and finally a submission-grade RTF exported with r2rtf’s rtf_title / rtf_colheader / rtf_body pipe — on public pharmaverseadam ADAE data, so every line runs.

Published

July 1, 2026

Modified

July 7, 2026

TipKey takeaways
  • A clinical data listing is the row-per-observation appendix of a Clinical Study Report — it shows every record for every subject, unsummarized. Where a table answers “how many?”, a listing answers “show me each one.” It is the traceable, auditable backing for the summary tables.
  • Listings are not deduplicated. A subject with three adverse events gets three rows. This is the opposite of the adverse-event table, which counts each subject once — and it is exactly why regulators ask for both.
  • rlistings builds the listing object; r2rtf renders the deliverable. as_listing() makes a structured listing_df with key columns (repeated values blanked on repeat) and display columns; the direct r2rtf pipe renders it to a submission RTF.
  • Key columns are blanked on repeat. key_cols = c("USUBJID", "TRT01A") prints the subject ID and arm on the first of a subject’s rows and leaves them blank on the rest — the listing hallmark that makes a long, dense appendix readable.
  • Long listings paginate. paginate_listing() splits a listing into fixed-length pages without ever cutting a subject’s block across a page boundary awkwardly — what turns thousands of rows into a paged submission document.

Introduction

A regulator asks to see every adverse event in the trial, subject by subject: not “how many subjects had a headache”, but the actual rows — this subject, this event, this severity, on this study day. That request is answered by a clinical data listing: the row-per-observation appendix that sits behind every summary table in a Clinical Study Report (CSR). A listing shows the data as recorded, unsummarized, so a reviewer can trace any number in a table back to the individual observations that produced it.

Listings live in the appendices of the CSR — section 16 (the Appendices) of the ICH E3 guideline, which defines the structure and content of a study report; §16 collects the study’s supporting material and includes the individual patient data listings (§16.2 and §16.4). They are the least glamorous and most voluminous output in the TLF (tables, listings, and figures) workflow, and for a large study they can run to thousands of pages. That is precisely why they need tooling: a listing must blank repeated key values so it stays readable, paginate cleanly, and export to the RTF (Rich Text Format) that regulatory submissions use.

We build one the industry way — with the pharmaverse rlistings package for the listing object and r2rtf for the RTF — on public pharmaverse data, so every line runs as written. The example is a patient adverse-event (AE) listing, the most common listing in the safety appendix.

Here is the shape of the data we are about to lay out — how many adverse-event records each subject contributes. Most have a handful; a few have many. A summary table hides this spread; a listing shows every one of these rows, which is why it needs pagination:

Histogram of the number of treatment-emergent adverse-event records per subject in the CDISC pilot safety population. The x-axis is the count of adverse-event rows a subject contributes to the listing, from 1 up to about 23; the y-axis is the number of subjects. The distribution is strongly right-skewed in brand azure: most subjects contribute only a few records, a long thin tail of subjects contribute ten or more, and a single subject reaches the low-twenties. This spread is why a listing shows every row and must be paginated, whereas a summary table would collapse each subject to a single count.

By the end you will have turned these records into a proper patient listing — key columns blanked, output paginated, and an RTF file written to disk — the deliverable a clinical programmer hands to the submission. If “ADAE”, “safety population”, or “treatment-emergent” are new, the earlier lessons on building the analysis datasets cover them; here we start from analysis-ready data and focus on the listing.

What a clinical data listing is

A listing and a summary table are built from the same data but answer different questions. The table aggregates — it counts subjects, computes percentages, collapses. The listing enumerates — it prints every record, in order, with nothing thrown away. Both ship in the CSR because they serve different readers: the table for the person reading the result, the listing for the person who needs to verify it.

A well-formed listing has a fixed anatomy:

Part What it is In this lesson
Rows One row per observation — not per subject One row per adverse-event record
Key columns The identifying columns, repeated down the rows but blanked on repeat USUBJID (unique subject identifier), TRT01A (actual treatment arm)
Display columns The data columns shown for each record AE term, severity, serious flag, study day
Ordering A deterministic sort so the same data always lays out the same way By subject, then study day
Pagination Fixed-length pages for a paged document, subjects kept intact paginate_listing() splits the rows

Two conventions make a listing a listing rather than a printed data frame:

  • Key columns are blanked on repeat. The subject ID and arm print once, on a subject’s first row, and are left blank on that subject’s remaining rows. Your eye groups the block without the identifier shouting on every line. rlistings does this for any column you name in key_cols.
  • Nothing is deduplicated or summarized. If a subject reported the same preferred term three times, all three rows appear. That is the point — the listing is the audit trail. (The adverse-event table, by contrast, would count that subject once.)

rlistings vs rtables — which do I use?

Both are pharmaverse layout packages, and they are complementary, not competing. Pick by what the output is:

rlistings rtables / tern
Produces A listing — every record, one row per observation A table — counts, means, percentages, aggregated
Core object listing_df from as_listing() TableTree from basic_table() + layout functions
Answers “Show me each observation” “How many / what is the average”
In the CSR Section 16 appendices (patient data listings) Section 14 summary tables
Deduplicates? No — shows every row Yes — counts each subject once

A rule of thumb: if the reviewer needs to trace a number back to individual records, it is a listing (rlistings); if they need the summary a number rolls up to, it is a table (rtables with tern). Both export to RTF the same way, with r2rtf.

The data

We use the ADAE dataset — the adverse-event ADaM (Analysis Data Model) dataset, one row per adverse-event record — shipped in pharmaverseadam, the synthetic, license-free CDISC (Clinical Data Interchange Standards Consortium) pilot study comparing placebo against two doses of Xanomeline. The pharmaverse datasets are tibbles, so the first move is to coerce to a plain data.framerlistings and r2rtf expect base data frames, and passing a tibble is the single most common source of a confusing error downstream.

We keep the treatment-emergent adverse events for the safety population: SAFFL == "Y" (the safety-population flag — every subject who took at least one dose) and TRTEMFL == "Y" (the treatment-emergent flag — events that started on or after the first dose). A treatment-emergent adverse event (TEAE) is the safety analysis’s unit of interest, so this is the standard AE-listing filter.

library(pharmaverseadam)

# ADAE is a tibble — coerce to a plain data.frame for rlistings / r2rtf.
adae <- as.data.frame(pharmaverseadam::adae)

# Treatment-emergent AEs, safety population.
teae <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y", ]

# The columns the listing will show, ordered by subject then study day.
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "AESER", "ASTDY")
teae <- teae[order(teae$USUBJID, teae$ASTDY), cols]

nrow(teae)      # every treatment-emergent AE record becomes a listing row
[1] 1191
head(teae, 6)
      USUBJID  TRT01A                   AEDECOD    AESEV AESER ASTDY
1 01-701-1015 Placebo APPLICATION SITE ERYTHEMA     MILD     N     2
2 01-701-1015 Placebo APPLICATION SITE PRURITUS     MILD     N     2
3 01-701-1015 Placebo                 DIARRHOEA     MILD     N     8
4 01-701-1023 Placebo                  ERYTHEMA MODERATE     N     3
5 01-701-1023 Placebo                  ERYTHEMA     MILD     N     3
6 01-701-1023 Placebo                  ERYTHEMA     MILD     N     3

The filter keeps well over a thousand records — one row per adverse event, exactly what the listing will enumerate. The six columns are the two key columns (USUBJID, the unique subject identifier; TRT01A, the actual treatment arm) and four display columns: the dictionary-derived preferred term (AEDECOD), the severity (AESEV), the serious flag (AESER), and the study day the event started (ASTDY). We ordered the rows by subject and then study day so the listing is deterministic — the same data always lays out the same way, which a submission requires.

To keep the printed output on this page readable, we build the listing from the first several subjects. In a real submission you would pass the whole teae frame; nothing else changes.

library(pharmaverseadam)

adae <- as.data.frame(pharmaverseadam::adae)
teae <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y", ]
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "AESER", "ASTDY")
teae <- teae[order(teae$USUBJID, teae$ASTDY), cols]

# first 15 records — a legible slice for the page
lst_data <- head(teae, 15)

Build the listing with rlistings

as_listing() turns a data frame into a structured listing_df. You tell it two things: which columns are key columns (key_cols — repeated identifiers that get blanked on repeat) and which are display columns (disp_cols — the data shown for each record). Everything else about how a listing looks — the blanking, the alignment, the spacing — follows from that.

library(rlistings)
library(pharmaverseadam)

adae <- as.data.frame(pharmaverseadam::adae)
teae <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y", ]
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "AESER", "ASTDY")
teae <- teae[order(teae$USUBJID, teae$ASTDY), cols]
lst_data <- head(teae, 15)

lsting <- as_listing(
  lst_data,
  key_cols  = c("USUBJID", "TRT01A"),                 # blanked on repeat
  disp_cols = c("AEDECOD", "AESEV", "AESER", "ASTDY") # shown for every record
)

lsting
  USUBJID            TRT01A                        AEDECOD                   AESEV     AESER   ASTDY
————————————————————————————————————————————————————————————————————————————————————————————————————
01-701-1015         Placebo               APPLICATION SITE ERYTHEMA           MILD       N       2  
                                          APPLICATION SITE PRURITUS           MILD       N       2  
                                                  DIARRHOEA                   MILD       N       8  
01-701-1023         Placebo                        ERYTHEMA                 MODERATE     N       3  
                                                   ERYTHEMA                   MILD       N       3  
                                                   ERYTHEMA                   MILD       N       3  
                                     ATRIOVENTRICULAR BLOCK SECOND DEGREE     MILD       N      22  
01-701-1028   Xanomeline High Dose        APPLICATION SITE ERYTHEMA           MILD       N       3  
                                          APPLICATION SITE PRURITUS           MILD       N      21  
01-701-1034   Xanomeline High Dose        APPLICATION SITE PRURITUS           MILD       N      58  
                                                   FATIGUE                    MILD       N      125 
01-701-1047         Placebo                     HIATUS HERNIA               MODERATE     N       1  
                                                HIATUS HERNIA               MODERATE     N       1  
                                      UPPER RESPIRATORY TRACT INFECTION       MILD       N      23  
                                           BUNDLE BRANCH BLOCK LEFT           MILD       N      27  

Read the printed listing top to bottom and the blanking is obvious: a subject’s ID and arm appear on the first of their rows and are blank on the rest, so each subject reads as one visual block. Notice too that nothing was collapsed — where a subject has the same preferred term on more than one record, both rows are present. That is the listing doing its job: it is the row-by-row audit trail, not a summary.

The object lsting is a listing_df — it still behaves like a data frame (you can subset it), but it carries the layout instructions rlistings and r2rtf need to render it correctly.

Paginate a long listing

A real listing is far too long for one screen or page. paginate_listing() splits it into pages of a fixed number of lines per page (lpp), so it becomes a paged document. It returns a list — one listing_df per page — and keeps the layout (header, key-column blanking) on every page.

library(rlistings)
library(pharmaverseadam)

adae <- as.data.frame(pharmaverseadam::adae)
teae <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y", ]
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "AESER", "ASTDY")
teae <- teae[order(teae$USUBJID, teae$ASTDY), cols]
lst_data <- head(teae, 15)

lsting <- as_listing(
  lst_data,
  key_cols  = c("USUBJID", "TRT01A"),
  disp_cols = c("AEDECOD", "AESEV", "AESER", "ASTDY")
)

pages <- paginate_listing(lsting, lpp = 8)   # ~8 lines per page
--- Page 1/3 ---
  USUBJID            TRT01A                        AEDECOD                   AESEV     AESER   ASTDY
————————————————————————————————————————————————————————————————————————————————————————————————————
01-701-1015         Placebo               APPLICATION SITE ERYTHEMA           MILD       N       2  
                                          APPLICATION SITE PRURITUS           MILD       N       2  
                                                  DIARRHOEA                   MILD       N       8  
01-701-1023         Placebo                        ERYTHEMA                 MODERATE     N       3  
                                                   ERYTHEMA                   MILD       N       3  
                                                   ERYTHEMA                   MILD       N       3  

--- Page 2/3 ---
  USUBJID            TRT01A                        AEDECOD                   AESEV     AESER   ASTDY
————————————————————————————————————————————————————————————————————————————————————————————————————
01-701-1023         Placebo          ATRIOVENTRICULAR BLOCK SECOND DEGREE     MILD       N      22  
01-701-1028   Xanomeline High Dose        APPLICATION SITE ERYTHEMA           MILD       N       3  
                                          APPLICATION SITE PRURITUS           MILD       N      21  
01-701-1034   Xanomeline High Dose        APPLICATION SITE PRURITUS           MILD       N      58  
                                                   FATIGUE                    MILD       N      125 
01-701-1047         Placebo                     HIATUS HERNIA               MODERATE     N       1  

--- Page 3/3 ---
  USUBJID            TRT01A                        AEDECOD                   AESEV     AESER   ASTDY
————————————————————————————————————————————————————————————————————————————————————————————————————
01-701-1047         Placebo                     HIATUS HERNIA               MODERATE     N       1  
                                      UPPER RESPIRATORY TRACT INFECTION       MILD       N      23  
                                           BUNDLE BRANCH BLOCK LEFT           MILD       N      27  
length(pages)   # number of pages this listing produces
[1] 3
cat(toString(pages[[1]]))   # the first page, formatted
  USUBJID            TRT01A                        AEDECOD                   AESEV     AESER   ASTDY
————————————————————————————————————————————————————————————————————————————————————————————————————
01-701-1015         Placebo               APPLICATION SITE ERYTHEMA           MILD       N       2  
                                          APPLICATION SITE PRURITUS           MILD       N       2  
                                                  DIARRHOEA                   MILD       N       8  
01-701-1023         Placebo                        ERYTHEMA                 MODERATE     N       3  
                                                   ERYTHEMA                   MILD       N       3  
                                                   ERYTHEMA                   MILD       N       3  

With lpp = 8 our 15-row slice becomes a small number of pages; the first is shown above, with the same header and blanking as the full listing. In a submission you set lpp to match the page geometry (font, margins, orientation) so each printed page fills correctly — the r2rtf step below owns the final page layout, and paginate_listing() is how you preview and control the row splitting.

Export to a submission RTF with r2rtf

The listing object is the review artifact; the RTF file is the regulatory deliverable. r2rtf renders a data frame to the Rich Text Format that submission documents use, through a small pipe of layer functions: rtf_title() sets the title and subtitle, rtf_colheader() the column headings, rtf_body() the data (with column widths and per-column justification), then rtf_encode() and write_rtf() produce the file.

We render the listing data (a plain data frame of the rows) directly with the r2rtf pipe — the canonical pharmaverse pattern. Column widths are given as relative weights (col_rel_width), and justification is one letter per column ("l" left, "c" centre). We write to a temporary file and report its size so nothing is left in the project.

library(r2rtf)
library(pharmaverseadam)

adae <- as.data.frame(pharmaverseadam::adae)
teae <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y", ]
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "AESER", "ASTDY")
teae <- teae[order(teae$USUBJID, teae$ASTDY), cols]
lst_data <- head(teae, 15)

out <- tempfile(fileext = ".rtf")

lst_data |>
  rtf_title(
    "Listing of Treatment-Emergent Adverse Events",
    "Safety Population"
  ) |>
  rtf_colheader(
    "Subject | Treatment | AE Term | Severity | Serious | Study Day",
    col_rel_width = c(3, 3, 4, 2, 2, 2)
  ) |>
  rtf_body(
    col_rel_width      = c(3, 3, 4, 2, 2, 2),
    text_justification = c("l", "l", "l", "c", "c", "c")
  ) |>
  rtf_encode() |>
  write_rtf(out)

file.info(out)$size   # bytes written — a real RTF the submission can open
[1] 15238

write_rtf() returns the path and writes the file; file.info()$size confirms a non-empty RTF was produced. Open it in Word or any RTF reader and you get the titled, column-headed, paginated listing — the deliverable. The col_rel_width weights (here 3,3,4,2,2,2) share the page width in proportion, so the AE term gets the widest column and the flags the narrowest; they must have one entry per column in both rtf_colheader() and rtf_body().

Note

One deliberate detail: we sent the listing data (lst_data) through the r2rtf pipe, not the listing_df object. The direct data-frame pipe is the robust, canonical pharmaverse pattern for RTF output; it gives you full control over titles, column widths, and justification. rlistings owns the on-screen review object; r2rtf owns the submission file. Two tools, two jobs.

🟢 With an AI agent

Ask Prova “how do I add a page-by-page footnote and a ‘Page x of y’ footer to my r2rtf listing?” — it answers grounded in this pillar’s lessons, with runnable rlistings and r2rtf code you can try on the example pharmaverse data. The runtime is the judge. Ask Prova →

Common issues

You passed a tibble and got a cryptic error. The pharmaverse datasets (pharmaverseadam::adae) are tibbles, and both rlistings and r2rtf expect a plain data.frame. A tibble can trip the layout or the RTF encoder with an error that does not mention tibbles at all. Coerce first: adae <- as.data.frame(pharmaverseadam::adae). This is the single most common listing bug.

formatters::export_as_rtf() errors on your listing. It is tempting to export the listing_df object with formatters::export_as_rtf(), but that bridge into r2rtf is fragile and can fail inside rtf_strwidth. Use the direct r2rtf pipe on the listing data instead (lst_data |> rtf_title() |> rtf_colheader() |> rtf_body() |> rtf_encode() |> write_rtf()), as above — it is the canonical pharmaverse pattern and gives you explicit control of the layout.

The key columns are not blanking. Blanking on repeat only happens for columns named in key_cols, and only when equal values are adjacent. If the subject ID keeps printing on every row, either you did not list it in key_cols or the data is not sorted — as_listing() blanks a repeated value only when the identical value sits directly above. Sort by your key columns first (teae[order(teae$USUBJID), ]).

col_rel_width has the wrong length. rtf_colheader() and rtf_body() each need one width per column, and text_justification one letter per column. A vector that is too short or too long throws an error or silently misaligns the RTF. Count your display columns (plus the key columns you print) and match the vector length exactly — here six columns, so col_rel_width = c(3, 3, 4, 2, 2, 2).

Frequently asked questions

A clinical data listing is the row-per-observation appendix of a Clinical Study Report — it prints every record for every subject, unsummarized, so a reviewer can trace any number in a summary table back to the individual observations behind it. Listings sit in section 16 (the Appendices) of the ICH E3 guideline, as the individual patient data listings (§16.2 and §16.4). Unlike a table, a listing is not deduplicated: a subject with three adverse events appears on three rows.

Use rlistings for a listing (every record, one row per observation — the audit trail) and rtables (usually with tern) for a summary table (counts, means, percentages — aggregated). Rule of thumb: if the reviewer needs to trace a number back to individual records, it is a listing; if they need the summary the records roll up to, it is a table. Both export to RTF with r2rtf.

Use the r2rtf package. Pipe your data frame through the layer functions: df |> rtf_title("Title", "Subtitle") |> rtf_colheader("Col A | Col B", col_rel_width = c(3, 2)) |> rtf_body(col_rel_width = c(3, 2)) |> rtf_encode() |> write_rtf("out.rtf"). rtf_encode() builds the RTF string and write_rtf() writes the file — the Rich Text Format that regulatory submissions use, openable in Word.

That is key-column blanking, the listing hallmark. Columns named in key_cols print their value on the first of a group of identical adjacent rows and are left blank on the rest, so each subject reads as one visual block instead of repeating the identifier on every line. It only works when the data is sorted by those key columns, so the identical values are adjacent.

Use paginate_listing() from rlistings: paginate_listing(lsting, lpp = 40) splits the listing into pages of about lpp lines each and returns a list of one listing_df per page, repeating the header and key-column blanking on every page. Set lpp to match your page geometry so each printed page fills correctly; the final page layout is owned by the r2rtf export step.

Test your understanding

Starting from the treatment-emergent adverse-event data in this lesson, build a listing of the serious adverse events only (AESER == "Y"), keeping the same key columns (USUBJID, TRT01A) and display columns (AEDECOD, AESEV, ASTDY), then export it to an RTF with a title that says “Listing of Serious Treatment-Emergent Adverse Events”. How many rows does the serious-AE listing have compared with the full one, and why is that the listing regulators read first?

Add one more condition to the base-R subset: teae[teae$AESER == "Y", ]. Everything else — the as_listing() call and the r2rtf pipe — is unchanged; you are just filtering the rows before you lay them out. Remember to coerce the tibble with as.data.frame() first, and match the col_rel_width length to your number of columns (five here: two key + three display).

library(rlistings)
library(r2rtf)
library(pharmaverseadam)

adae <- as.data.frame(pharmaverseadam::adae)

# treatment-emergent AND serious, safety population
ser <- adae[adae$SAFFL == "Y" & adae$TRTEMFL == "Y" & adae$AESER == "Y", ]
cols <- c("USUBJID", "TRT01A", "AEDECOD", "AESEV", "ASTDY")
ser <- ser[order(ser$USUBJID, ser$ASTDY), cols]

nrow(ser)   # far fewer rows than the full TEAE listing

# the listing object (key-column blanking, display columns)
lsting <- as_listing(
  ser,
  key_cols  = c("USUBJID", "TRT01A"),
  disp_cols = c("AEDECOD", "AESEV", "ASTDY")
)
lsting

# export to a submission RTF
out <- tempfile(fileext = ".rtf")
ser |>
  rtf_title("Listing of Serious Treatment-Emergent Adverse Events", "Safety Population") |>
  rtf_colheader("Subject | Treatment | AE Term | Severity | Study Day",
                col_rel_width = c(3, 3, 4, 2, 2)) |>
  rtf_body(col_rel_width = c(3, 3, 4, 2, 2),
           text_justification = c("l", "l", "l", "c", "c")) |>
  rtf_encode() |>
  write_rtf(out)
file.info(out)$size

The serious-AE listing has far fewer rows than the full treatment-emergent listing — serious events are, thankfully, the minority. It is the listing safety reviewers read first because a serious adverse event (one that is life-threatening, causes hospitalization, or is otherwise medically important) drives the trial’s benefit-risk assessment; regulators want to see each one, subject by subject, not just a count.

A. A listing counts each subject once; a table shows every record B. A listing shows every record (one row per observation); a table aggregates and counts each subject once C. They are the same output in two file formats

B. A listing enumerates — one row per observation, nothing deduplicated, so a subject with three adverse events has three rows (rlistings). A table aggregates — it counts each subject once and reports n and percent (rtables/tern). A reverses the two; C is wrong because they answer different questions (trace-the-record vs summarize-the-result), which is why the CSR carries both.

Conclusion

A clinical data listing is the row-per-observation backbone of a Clinical Study Report — the appendix that shows every record so a reviewer can verify every table. Building one comes down to two tools with two jobs: rlistings makes the listing object with as_listing() — key columns blanked on repeat, display columns shown, and paginate_listing() for the length — and r2rtf renders the submission file through the rtf_title() |> rtf_colheader() |> rtf_body() |> rtf_encode() |> write_rtf() pipe. Get three things right — coerce the tibble to a data.frame, name the correct key_cols on sorted data so blanking works, and match the col_rel_width length to your columns — and you have produced the auditable, paginated, submission-ready listing that sits behind every summary table in the report.

Note

This lesson is reproducible: every result on this page was produced by the code shown — copy any block and run it to reproduce them. The runtime is the judge.

Reuse

Citation

BibTeX citation:
@online{2026,
  author = {},
  title = {Clinical {Data} {Listings} in {R:} {Patient} {Listings} and
    {RTF} {Output} with Rlistings},
  date = {2026-07-01},
  url = {https://www.datanovia.com/learn/pharma-clinical/04-tlf-generation/patient-listings-rlistings},
  langid = {en}
}
For attribution, please cite this work as:
“Clinical Data Listings in R: Patient Listings and RTF Output with Rlistings.” 2026. July 1. https://www.datanovia.com/learn/pharma-clinical/04-tlf-generation/patient-listings-rlistings.