diff --git a/.nojekyll b/.nojekyll index f1812ca..a737b10 100644 --- a/.nojekyll +++ b/.nojekyll @@ -1 +1 @@ -2da7e1a8 \ No newline at end of file +a49949c2 \ No newline at end of file diff --git a/R-for-Clinical-Study-Reports-and-Submission.pdf b/R-for-Clinical-Study-Reports-and-Submission.pdf index 05ac080..00e3cd8 100644 Binary files a/R-for-Clinical-Study-Reports-and-Submission.pdf and b/R-for-Clinical-Study-Reports-and-Submission.pdf differ diff --git a/search.json b/search.json index e992298..7d9f1d9 100644 --- a/search.json +++ b/search.json @@ -117,7 +117,7 @@ "href": "tlf-overview.html#tools", "title": "1  Overview", "section": "1.3 Tools", - "text": "1.3 Tools\nTo exemplify the generation of TLFs in RTF format, we rely on the functionality provided by two R packages:\n\ntidyverse: preparation of datasets in a format suitable for reporting purposes. The tidyverse package offers a comprehensive suite of tools and functions for data manipulation and transformation, ensuring that the data is structured appropriately.\nr2rtf: creation RTF files. The r2rtf package offers functions specifically designed for generating RTF files, allowing us to produce TLFs in the desired format.\n\n\n\n\n\n\n\nNote\n\n\n\nThere are indeed several other R packages available that can assist in creating TLFs in ASCII, RTF, and Word formats such as rtables, huxtable, pharmaRTF, gt, officer, and flextable. However, in this particular context, we will concentrate on demonstrating the concept using the r2rtf package. It is highly recommended for readers to explore and experiment with various R packages to identify the most suitable tools that align with their specific needs and objectives.\n\n\n\n1.3.1 tidyverse\nThe tidyverse is a comprehensive collection of R packages that aim to simplify the workflow of manipulating, visualizing, and analyzing data in R. These packages adhere to the principles outlined in the the tidy tools manifesto and offer user-friendly interfaces for interactive data analysis.\nThe creators of the tidyverse, Posit, have provided exceptional cheatsheets and tutorials that serve as valuable resources for learning and mastering the functionalities of these packages.\nFurthermore, there are several books available that serve as introductions to the tidyverse. For example:\n\nThe tidyverse cookbook\nR for Data Science\n\n\n\n\n\n\n\nNote\n\n\n\nIn this book, we assume that the reader already has experience in using the tidyverse. This prior knowledge and familiarity with the tidyverse tools enable a more efficient and focused exploration of the concepts presented throughout the book.\n\n\n\n\n1.3.2 r2rtf\nr2rtf is an R package specifically designed to create production-ready tables and figures in RTF format.\n\nProvide simple “verb” functions that correspond to each component of a table, to help you translate a data frame to a table in an RTF file.\nEnable pipes (|>).\nFocus on the table format only. Data manipulation and analysis tasks can be handled by other R packages like the tidyverse.\n\nBefore generating an RTF table using r2rtf, there are a few steps to follow:\n\nDetermine the desired layout of the table.\nBreak down the layout into smaller tasks, which can be programmed.\nExecute the program to generate the table.\n\nWe provide a brief introduction of r2rtf and show how to transfer data frames into table, listing, and figures (TLFs).\nWe provide a concise introduction to r2rtf and demonstrate how to convert data frames into TLFs. For more comprehensive examples and additional features, we encourage readers to explore the r2rtf package website.\nTo illustrate the basic usage of the r2rtf package, we will work with the “r2rtf_adae” dataset, available within the r2rtf package. This dataset contains information on adverse events (AEs) from a clinical trial, which will serve as a practical example for generating RTF tables using r2rtf.\nTo begin, let’s load the required packages:\n\nlibrary(tidyverse) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this example, we will focus on three variables from the r2rtf_adae dataset:\n\nUSUBJID: unique subject identifier.\nTRTA: actual treatment group.\nAEDECOD: dictionary-derived derm.\n\n\n\n\n\n\n\nNote\n\n\n\nAdditional information about these variables can be found on the help page of the dataset, which can be accessed by using the command ?r2rtf_adae in R.\n\n\n\nr2rtf_adae |>\n select(USUBJID, TRTA, AEDECOD) |>\n head(4)\n#> USUBJID TRTA AEDECOD\n#> 1 01-701-1015 Placebo APPLICATION SITE ERYTHEMA\n#> 2 01-701-1015 Placebo APPLICATION SITE PRURITUS\n#> 3 01-701-1015 Placebo DIARRHOEA\n#> 4 01-701-1023 Placebo ERYTHEMA\n\nTo manipulate the data and create a data frame containing the necessary information for the RTF table, we can use the dplyr and tidyr packages within the tidyverse.\n\ntbl <- r2rtf_adae %>%\n count(TRTA, AEDECOD) %>%\n pivot_wider(names_from = TRTA, values_from = n, values_fill = 0)\n\ntbl %>% head(4)\n#> # A tibble: 4 × 4\n#> AEDECOD Placebo `Xanomeline High Dose` `Xanomeline Low Dose`\n#> <chr> <int> <int> <int>\n#> 1 ABDOMINAL PAIN 1 2 3\n#> 2 AGITATION 2 1 2\n#> 3 ALOPECIA 1 0 0\n#> 4 ANXIETY 2 0 4\n\nHaving prepared the dataset tbl, we can now proceed with constructing the final RTF table using the r2rtf package. The r2rtf package has various functions, each designed for a specific type of table layout. Some commonly used verbs include:\n\nrtf_page(): RTF page information\nrtf_title(): RTF title information\nrtf_colheader(): RTF column header information\nrtf_body(): RTF table body information\nrtf_footnote(): RTF footnote information\nrtf_source(): RTF data source information\n\nFunctions provided by the r2rtf package are designed to work seamlessly with the pipe operator (|>). This allows for a more concise and readable code structure, enhancing the efficiency of table creation in RTF format. A full list of functions in the r2rtf package can be found in the package reference page.\nHere is a minimal example that demonstrates how to combine functions using pipes to create an RTF table.\n\nrtf_body() is used to define table body layout.\nrtf_encode() transfers table layout information into RTF syntax.\nwrite_rtf() save RTF encoding into a file with file extension .rtf.\n\n\nhead(tbl) |>\n rtf_body() |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae1.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn the previous example, we may want to add more column space to the first column. We can achieve the goal by updating the col_rel_width argument in the rtf_body() function.\nIn the example below, the col_rel_width argument expects a vector with the same length as the number of columns in the table tbl. This vector defines the relative width of each column within a predetermined total column width. Here, the relative width is defined as 3:2:2:2 that allow us to allocate more space to specific columns.\n\n\n\n\n\n\nNote\n\n\n\nOnly the ratio of the col_rel_width values is considered. Therefore, using col_rel_width = c(6, 4, 4, 4) or col_rel_width = c(1.5, 1, 1, 1) would yield equivalent results, as they maintain the same ratio.\n\n\n\nhead(tbl) |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae2.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn the previous example, we encountered a misalignment issue with the column header. To address this, we can use the rtf_colheader() function to adjust column header width and provide more informative column headers.\nWithin the rtf_colheader() function, the colheader argument is used to specify the content of the column header. The columns are separated using the | symbol. In the following example, we define the column header as \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\", representing the four columns in the table:\n\nhead(tbl) |>\n rtf_colheader(\n colheader = \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\",\n col_rel_width = c(3, 2, 2, 2)\n ) |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) |>\n rtf_encode() %>%\n write_rtf(\"tlf/intro-ae3.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn rtf_body() and rtf_colheader(), the text_justification argument is used to align text within the generated RTF table. The default value is \"c\", representing center justification. However, you can customize the text justification by column using a character vector with a length equal to the number of displayed columns. Here is a table displaying the possible inputs for the text_justification argument:\n\n\n\n\n\ntype\nname\n\n\n\n\nl\nleft\n\n\nc\ncenter\n\n\nr\nright\n\n\nd\ndecimal\n\n\nj\njustified\n\n\n\n\n\nBelow is an example to make the first column left-aligned and the rest columns center-aligned.\n\nhead(tbl) |>\n rtf_body(text_justification = c(\"l\", \"c\", \"c\", \"c\")) |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae5.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe border_left, border_right, border_top, and border_bottom arguments in the rtf_body() and rtf_colheader() functions are used to control the cell borders in the RTF table. If we want to remove the top border of \"Adverse Events\" in the header, we can change the default value \"single\" to \"\" in the border_top argument. Below is an example to demonstrate the possibility of adding multiple column headers with proper border lines.\n\n\n\n\n\n\nNote\n\n\n\nthe r2rtf package supports 26 different border types, each offering unique border styles. For more details and examples regarding these border types, you can refer to the r2rtf package website.\n\n\n\nhead(tbl) %>%\n rtf_colheader(\n colheader = \" | Treatment\",\n col_rel_width = c(3, 6)\n ) |>\n rtf_colheader(\n colheader = \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\",\n border_top = c(\"\", \"single\", \"single\", \"single\"),\n col_rel_width = c(3, 2, 2, 2)\n ) |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) %>%\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae7.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe r2rtf R package website provides additional examples that demonstrate how to customize various aspects of the generated RTF tables. These examples cover topics such as customizing the title, subtitle, footnote, data source, and handling special characters within the table content.\nIn the upcoming chapters of this book, we will introduce and explore these features as they become relevant to the specific use cases and scenarios discussed. By following along with the chapters, readers will gradually learn how to leverage these features to customize and enhance their RTF tables in real examples.", + "text": "1.3 Tools\nTo exemplify the generation of TLFs in RTF format, we rely on the functionality provided by two R packages:\n\ntidyverse: preparation of datasets in a format suitable for reporting purposes. The tidyverse package offers a comprehensive suite of tools and functions for data manipulation and transformation, ensuring that the data is structured appropriately.\nr2rtf: creation RTF files. The r2rtf package offers functions specifically designed for generating RTF files, allowing us to produce TLFs in the desired format.\n\n\n\n\n\n\n\nNote\n\n\n\nThere are indeed several other R packages available that can assist in creating TLFs in ASCII, RTF, and Word formats such as rtables, huxtable, pharmaRTF, gt, officer, and flextable. However, in this particular context, we will concentrate on demonstrating the concept using the r2rtf package. It is highly recommended for readers to explore and experiment with various R packages to identify the most suitable tools that align with their specific needs and objectives.\n\n\n\n1.3.1 tidyverse\nThe tidyverse is a comprehensive collection of R packages that aim to simplify the workflow of manipulating, visualizing, and analyzing data in R. These packages adhere to the principles outlined in the the tidy tools manifesto and offer user-friendly interfaces for interactive data analysis.\nThe creators of the tidyverse, Posit, have provided exceptional cheatsheets and tutorials that serve as valuable resources for learning and mastering the functionalities of these packages.\nFurthermore, there are several books available that serve as introductions to the tidyverse. For example:\n\nThe tidyverse cookbook\nR for Data Science\n\n\n\n\n\n\n\nNote\n\n\n\nIn this book, we assume that the reader already has experience in using the tidyverse. This prior knowledge and familiarity with the tidyverse tools enable a more efficient and focused exploration of the concepts presented throughout the book.\n\n\n\n\n1.3.2 r2rtf\nr2rtf is an R package specifically designed to create production-ready tables and figures in RTF format.\n\nProvide simple “verb” functions that correspond to each component of a table, to help you translate a data frame to a table in an RTF file.\nEnable pipes (|>).\nFocus on the table format only. Data manipulation and analysis tasks can be handled by other R packages like the tidyverse.\n\nBefore generating an RTF table using r2rtf, there are a few steps to follow:\n\nDetermine the desired layout of the table.\nBreak down the layout into smaller tasks, which can be programmed.\nExecute the program to generate the table.\n\nWe provide a brief introduction of r2rtf and show how to transfer data frames into table, listing, and figures (TLFs).\nWe provide a concise introduction to r2rtf and demonstrate how to convert data frames into TLFs. For more comprehensive examples and additional features, we encourage readers to explore the r2rtf package website.\nTo illustrate the basic usage of the r2rtf package, we will work with the “r2rtf_adae” dataset, available within the r2rtf package. This dataset contains information on adverse events (AEs) from a clinical trial, which will serve as a practical example for generating RTF tables using r2rtf.\nTo begin, let’s load the required packages:\n\nlibrary(tidyverse) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this example, we will focus on three variables from the r2rtf_adae dataset:\n\nUSUBJID: unique subject identifier.\nTRTA: actual treatment group.\nAEDECOD: dictionary-derived derm.\n\n\n\n\n\n\n\nNote\n\n\n\nAdditional information about these variables can be found on the help page of the dataset, which can be accessed by using the command ?r2rtf_adae in R.\n\n\n\nr2rtf_adae |> select(USUBJID, TRTA, AEDECOD)\n#> USUBJID TRTA AEDECOD\n#> 1 01-701-1015 Placebo APPLICATION SITE ERYTHEMA\n#> 2 01-701-1015 Placebo APPLICATION SITE PRURITUS\n#> 3 01-701-1015 Placebo DIARRHOEA\n#> 4 01-701-1023 Placebo ERYTHEMA\n#> # ℹ 1187 more rows\n\nTo manipulate the data and create a data frame containing the necessary information for the RTF table, we can use the dplyr and tidyr packages within the tidyverse.\n\ntbl <- r2rtf_adae %>%\n count(TRTA, AEDECOD) %>%\n pivot_wider(names_from = TRTA, values_from = n, values_fill = 0)\n\ntbl\n#> # A tibble: 242 × 4\n#> AEDECOD Placebo `Xanomeline High Dose` `Xanomeline Low Dose`\n#> <chr> <int> <int> <int>\n#> 1 ABDOMINAL PAIN 1 2 3\n#> 2 AGITATION 2 1 2\n#> 3 ALOPECIA 1 0 0\n#> 4 ANXIETY 2 0 4\n#> # ℹ 238 more rows\n\nHaving prepared the dataset tbl, we can now proceed with constructing the final RTF table using the r2rtf package. The r2rtf package has various functions, each designed for a specific type of table layout. Some commonly used verbs include:\n\nrtf_page(): RTF page information\nrtf_title(): RTF title information\nrtf_colheader(): RTF column header information\nrtf_body(): RTF table body information\nrtf_footnote(): RTF footnote information\nrtf_source(): RTF data source information\n\nFunctions provided by the r2rtf package are designed to work seamlessly with the pipe operator (|>). This allows for a more concise and readable code structure, enhancing the efficiency of table creation in RTF format. A full list of functions in the r2rtf package can be found in the package reference page.\nHere is a minimal example that demonstrates how to combine functions using pipes to create an RTF table.\n\nrtf_body() is used to define table body layout.\nrtf_encode() transfers table layout information into RTF syntax.\nwrite_rtf() save RTF encoding into a file with file extension .rtf.\n\n\ntbl |>\n head() |>\n rtf_body() |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae1.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn the previous example, we may want to add more column space to the first column. We can achieve the goal by updating the col_rel_width argument in the rtf_body() function.\nIn the example below, the col_rel_width argument expects a vector with the same length as the number of columns in the table tbl. This vector defines the relative width of each column within a predetermined total column width. Here, the relative width is defined as 3:2:2:2 that allow us to allocate more space to specific columns.\n\n\n\n\n\n\nNote\n\n\n\nOnly the ratio of the col_rel_width values is considered. Therefore, using col_rel_width = c(6, 4, 4, 4) or col_rel_width = c(1.5, 1, 1, 1) would yield equivalent results, as they maintain the same ratio.\n\n\n\ntbl |>\n head() |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae2.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn the previous example, we encountered a misalignment issue with the column header. To address this, we can use the rtf_colheader() function to adjust column header width and provide more informative column headers.\nWithin the rtf_colheader() function, the colheader argument is used to specify the content of the column header. The columns are separated using the | symbol. In the following example, we define the column header as \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\", representing the four columns in the table:\n\ntbl |>\n head() |>\n rtf_colheader(\n colheader = \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\",\n col_rel_width = c(3, 2, 2, 2)\n ) |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) |>\n rtf_encode() %>%\n write_rtf(\"tlf/intro-ae3.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn rtf_body() and rtf_colheader(), the text_justification argument is used to align text within the generated RTF table. The default value is \"c\", representing center justification. However, you can customize the text justification by column using a character vector with a length equal to the number of displayed columns. Here is a table displaying the possible inputs for the text_justification argument:\n\n\n\n\n\ntype\nname\n\n\n\n\nl\nleft\n\n\nc\ncenter\n\n\nr\nright\n\n\nd\ndecimal\n\n\nj\njustified\n\n\n\n\n\nBelow is an example to make the first column left-aligned and the rest columns center-aligned.\n\ntbl |>\n head() |>\n rtf_body(text_justification = c(\"l\", \"c\", \"c\", \"c\")) |>\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae5.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe border_left, border_right, border_top, and border_bottom arguments in the rtf_body() and rtf_colheader() functions are used to control the cell borders in the RTF table. If we want to remove the top border of \"Adverse Events\" in the header, we can change the default value \"single\" to \"\" in the border_top argument. Below is an example to demonstrate the possibility of adding multiple column headers with proper border lines.\n\n\n\n\n\n\nNote\n\n\n\nthe r2rtf package supports 26 different border types, each offering unique border styles. For more details and examples regarding these border types, you can refer to the r2rtf package website.\n\n\n\ntbl |>\n head() |>\n rtf_colheader(\n colheader = \" | Treatment\",\n col_rel_width = c(3, 6)\n ) |>\n rtf_colheader(\n colheader = \"Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose\",\n border_top = c(\"\", \"single\", \"single\", \"single\"),\n col_rel_width = c(3, 2, 2, 2)\n ) |>\n rtf_body(col_rel_width = c(3, 2, 2, 2)) %>%\n rtf_encode() |>\n write_rtf(\"tlf/intro-ae7.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe r2rtf R package website provides additional examples that demonstrate how to customize various aspects of the generated RTF tables. These examples cover topics such as customizing the title, subtitle, footnote, data source, and handling special characters within the table content.\nIn the upcoming chapters of this book, we will introduce and explore these features as they become relevant to the specific use cases and scenarios discussed. By following along with the chapters, readers will gradually learn how to leverage these features to customize and enhance their RTF tables in real examples.", "crumbs": [ "Delivering TLFs in CSR", "1  Overview" @@ -128,7 +128,7 @@ "href": "tlf-disposition.html", "title": "2  Disposition", "section": "", - "text": "Following ICH E3 guidance, a summary table needs to be provided to include all participants who entered the study in Section 10.1, Disposition of Participants.\nThe disposition of participants table reports the numbers of participants who were randomized, and who entered and completed each phase of the study. In addition, the reasons for all post-randomization discontinuations, grouped by treatment and by major reason (lost to follow-up, adverse event, poor compliance, etc.) are reported.\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we show how to create a typical disposition table.\n\n\n\n\n\n\n\n\n\nThe first step is to read in the relevant datasets into R. For a disposition table, all the required information is saved in a Subject-level Analysis Dataset (ADSL). This dataset is provided in sas7bdat format, which is a SAS data format currently used in many clinical trial analysis and reporting. The haven package is able to read the dataset, while maintaining its attributes (e.g., variable labels).\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\n\nThe following variables are used in the preparation of a simplified disposition of participants table:\n\nUSUBJID: unique subject identifier\nTRT01P: planned treatment\nTRT01PN: planned treatment numeric encoding\nDISCONFL: discontinued from study flag\nDCREASCD: discontinued from study reason coded\n\n\nadsl %>%\n select(USUBJID, TRT01P, TRT01PN, DISCONFL, DCREASCD) %>%\n head(4)\n#> # A tibble: 4 × 5\n#> USUBJID TRT01P TRT01PN DISCONFL DCREASCD \n#> <chr> <chr> <dbl> <chr> <chr> \n#> 1 01-701-1015 Placebo 0 \"\" Completed \n#> 2 01-701-1023 Placebo 0 \"Y\" Adverse Event \n#> 3 01-701-1028 Xanomeline High Dose 81 \"\" Completed \n#> 4 01-701-1033 Xanomeline Low Dose 54 \"Y\" Sponsor Decision\n\nIn the code below, we calculate the number of participants in the analysis population by treatment arms.\n\nn_rand <- adsl %>%\n group_by(TRT01PN) %>%\n summarize(n = n()) %>%\n pivot_wider(\n names_from = TRT01PN,\n names_prefix = \"n_\",\n values_from = n\n ) %>%\n mutate(row = \"Participants in population\")\n\nn_rand\n#> # A tibble: 1 × 4\n#> n_0 n_54 n_81 row \n#> <int> <int> <int> <chr> \n#> 1 86 84 84 Participants in population\n\n\nn_disc <- adsl %>%\n group_by(TRT01PN) %>%\n summarize(\n n = sum(DISCONFL == \"Y\"),\n pct = formatC(n / n() * 100,\n digits = 1, format = \"f\", width = 5\n )\n ) %>%\n pivot_wider(\n names_from = TRT01PN,\n values_from = c(n, pct)\n ) %>%\n mutate(row = \"Discontinued\")\n\nn_disc\n#> # A tibble: 1 × 7\n#> n_0 n_54 n_81 pct_0 pct_54 pct_81 row \n#> <int> <int> <int> <chr> <chr> <chr> <chr> \n#> 1 28 59 57 \" 32.6\" \" 70.2\" \" 67.9\" Discontinued\n\nIn the code below, we calculate the number and percentage of participants who completed/discontinued the study for different reasons by treatment arms.\n\nn_reason <- adsl %>%\n group_by(TRT01PN) %>%\n mutate(n_total = n()) %>%\n group_by(TRT01PN, DCREASCD) %>%\n summarize(\n n = n(),\n pct = formatC(n / unique(n_total) * 100,\n digits = 1, format = \"f\", width = 5\n )\n ) %>%\n pivot_wider(\n id_cols = DCREASCD,\n names_from = TRT01PN,\n values_from = c(n, pct),\n values_fill = list(n = 0, pct = \" 0.0\")\n ) %>%\n rename(row = DCREASCD)\n\nn_reason\n#> # A tibble: 10 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 Adverse Event 8 44 40 \" 9.3\" \" 52.4\" \" 47.6\"\n#> 2 Completed 58 25 27 \" 67.4\" \" 29.8\" \" 32.1\"\n#> 3 Death 2 1 0 \" 2.3\" \" 1.2\" \" 0.0\"\n#> 4 I/E Not Met 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> 5 Lack of Efficacy 3 0 1 \" 3.5\" \" 0.0\" \" 1.2\"\n#> 6 Lost to Follow-up 1 1 0 \" 1.2\" \" 1.2\" \" 0.0\"\n#> 7 Physician Decision 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> 8 Protocol Violation 1 1 1 \" 1.2\" \" 1.2\" \" 1.2\"\n#> 9 Sponsor Decision 2 2 3 \" 2.3\" \" 2.4\" \" 3.6\"\n#> 10 Withdrew Consent 9 10 8 \" 10.5\" \" 11.9\" \" 9.5\"\n\nIn the code below, we calculate the number and percentage of participants who complete the study by treatment arms. We split n_reason because we want to customize the row order of the table.\n\nn_complete <- n_reason %>%\n filter(row == \"Completed\")\n\nn_complete\n#> # A tibble: 1 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 Completed 58 25 27 \" 67.4\" \" 29.8\" \" 32.1\"\n\nIn the code below, we calculate the numbers and percentages of participants who discontinued the study for different reasons by treatment arms. For display purpose, paste0(\" \", row) is used to add leading spaces to produce indentation in the final report.\n\nn_reason <- n_reason %>%\n filter(row != \"Completed\") %>%\n mutate(row = paste0(\" \", row))\n\nn_reason\n#> # A tibble: 9 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 \" Adverse Event\" 8 44 40 \" 9.3\" \" 52.4\" \" 47.6\"\n#> 2 \" Death\" 2 1 0 \" 2.3\" \" 1.2\" \" 0.0\"\n#> 3 \" I/E Not Met\" 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> 4 \" Lack of Efficacy\" 3 0 1 \" 3.5\" \" 0.0\" \" 1.2\"\n#> 5 \" Lost to Follow-up\" 1 1 0 \" 1.2\" \" 1.2\" \" 0.0\"\n#> 6 \" Physician Decision\" 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> 7 \" Protocol Violation\" 1 1 1 \" 1.2\" \" 1.2\" \" 1.2\"\n#> 8 \" Sponsor Decision\" 2 2 3 \" 2.3\" \" 2.4\" \" 3.6\"\n#> 9 \" Withdrew Consent\" 9 10 8 \" 10.5\" \" 11.9\" \" 9.5\"\n\nNow we combine individual rows into one table for reporting purpose. tbl_disp is used as input for r2rtf to create final report.\n\ntbl_disp <- bind_rows(n_rand, n_complete, n_disc, n_reason) %>%\n select(row, ends_with(c(\"_0\", \"_54\", \"_81\")))\n\ntbl_disp\n#> # A tibble: 12 × 7\n#> row n_0 pct_0 n_54 pct_54 n_81 pct_81 \n#> <chr> <int> <chr> <int> <chr> <int> <chr> \n#> 1 \"Participants in population\" 86 <NA> 84 <NA> 84 <NA> \n#> 2 \"Completed\" 58 \" 67.4\" 25 \" 29.8\" 27 \" 32.1\"\n#> 3 \"Discontinued\" 28 \" 32.6\" 59 \" 70.2\" 57 \" 67.9\"\n#> 4 \" Adverse Event\" 8 \" 9.3\" 44 \" 52.4\" 40 \" 47.6\"\n#> 5 \" Death\" 2 \" 2.3\" 1 \" 1.2\" 0 \" 0.0\"\n#> 6 \" I/E Not Met\" 1 \" 1.2\" 0 \" 0.0\" 2 \" 2.4\"\n#> 7 \" Lack of Efficacy\" 3 \" 3.5\" 0 \" 0.0\" 1 \" 1.2\"\n#> 8 \" Lost to Follow-up\" 1 \" 1.2\" 1 \" 1.2\" 0 \" 0.0\"\n#> 9 \" Physician Decision\" 1 \" 1.2\" 0 \" 0.0\" 2 \" 2.4\"\n#> 10 \" Protocol Violation\" 1 \" 1.2\" 1 \" 1.2\" 1 \" 1.2\"\n#> 11 \" Sponsor Decision\" 2 \" 2.3\" 2 \" 2.4\" 3 \" 3.6\"\n#> 12 \" Withdrew Consent\" 9 \" 10.5\" 10 \" 11.9\" 8 \" 9.5\"\n\nIn the below code, formatting of the final table is defined. Items that were not discussed in the previous sections, are highlighted below.\nThe rtf_title defines table title. We can provide a vector for the title argument. Each value is a separate line. The format can also be controlled by providing a vector input in text format.\n\ntbl_disp %>%\n # Table title\n rtf_title(\"Disposition of Participants\") %>%\n # First row of column header\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3, rep(2, 3))\n ) %>%\n # Second row of column header\n rtf_colheader(\" | n | (%) | n | (%) | n | (%)\",\n col_rel_width = c(3, rep(c(0.7, 1.3), 3)),\n border_top = c(\"\", rep(\"single\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n # Table body\n rtf_body(\n col_rel_width = c(3, rep(c(0.7, 1.3), 3)),\n text_justification = c(\"l\", rep(\"c\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n # Encoding RTF syntax\n rtf_encode() %>%\n # Save to a file\n write_rtf(\"tlf/tbl_disp.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe procedure to generate a disposition table can be summarized as follows:\n\nStep 1: Read subject level data (i.e., adsl) into R.\nStep 2: Count participants in the analysis population and name the dataset n_rand.\nStep 3: Calculate the number and percentage of participants who discontinued the study by treatment arm, and name the dataset n_disc.\nStep 4: Calculate the numbers and percentages of participants who discontinued the study for different reasons by treatment arm, and name the dataset n_reason.\nStep 5: Calculate the number and percentage of participants who completed the study by treatment arm, and name the dataset n_complete.\nStep 6: Bind n_rand, n_disc, n_reason, and n_complete by row.\nStep 7: Write the final table to RTF", + "text": "Following ICH E3 guidance, a summary table needs to be provided to include all participants who entered the study in Section 10.1, Disposition of Participants.\nThe disposition of participants table reports the numbers of participants who were randomized, and who entered and completed each phase of the study. In addition, the reasons for all post-randomization discontinuations, grouped by treatment and by major reason (lost to follow-up, adverse event, poor compliance, etc.) are reported.\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we show how to create a typical disposition table.\n\n\n\n\n\n\n\n\n\nThe first step is to read in the relevant datasets into R. For a disposition table, all the required information is saved in a Subject-level Analysis Dataset (ADSL). This dataset is provided in sas7bdat format, which is a SAS data format currently used in many clinical trial analysis and reporting. The haven package is able to read the dataset, while maintaining its attributes (e.g., variable labels).\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\n\nThe following variables are used in the preparation of a simplified disposition of participants table:\n\nUSUBJID: unique subject identifier\nTRT01P: planned treatment\nTRT01PN: planned treatment numeric encoding\nDISCONFL: discontinued from study flag\nDCREASCD: discontinued from study reason coded\n\n\nadsl %>% select(USUBJID, TRT01P, TRT01PN, DISCONFL, DCREASCD)\n#> # A tibble: 254 × 5\n#> USUBJID TRT01P TRT01PN DISCONFL DCREASCD \n#> <chr> <chr> <dbl> <chr> <chr> \n#> 1 01-701-1015 Placebo 0 \"\" Completed \n#> 2 01-701-1023 Placebo 0 \"Y\" Adverse Event \n#> 3 01-701-1028 Xanomeline High Dose 81 \"\" Completed \n#> 4 01-701-1033 Xanomeline Low Dose 54 \"Y\" Sponsor Decision\n#> # ℹ 250 more rows\n\nIn the code below, we calculate the number of participants in the analysis population by treatment arms.\n\nn_rand <- adsl %>%\n group_by(TRT01PN) %>%\n summarize(n = n()) %>%\n pivot_wider(\n names_from = TRT01PN,\n names_prefix = \"n_\",\n values_from = n\n ) %>%\n mutate(row = \"Participants in population\")\n\nn_rand\n#> # A tibble: 1 × 4\n#> n_0 n_54 n_81 row \n#> <int> <int> <int> <chr> \n#> 1 86 84 84 Participants in population\n\n\nn_disc <- adsl %>%\n group_by(TRT01PN) %>%\n summarize(\n n = sum(DISCONFL == \"Y\"),\n pct = formatC(n / n() * 100,\n digits = 1, format = \"f\", width = 5\n )\n ) %>%\n pivot_wider(\n names_from = TRT01PN,\n values_from = c(n, pct)\n ) %>%\n mutate(row = \"Discontinued\")\n\nn_disc\n#> # A tibble: 1 × 7\n#> n_0 n_54 n_81 pct_0 pct_54 pct_81 row \n#> <int> <int> <int> <chr> <chr> <chr> <chr> \n#> 1 28 59 57 \" 32.6\" \" 70.2\" \" 67.9\" Discontinued\n\nIn the code below, we calculate the number and percentage of participants who completed/discontinued the study for different reasons by treatment arms.\n\nn_reason <- adsl %>%\n group_by(TRT01PN) %>%\n mutate(n_total = n()) %>%\n group_by(TRT01PN, DCREASCD) %>%\n summarize(\n n = n(),\n pct = formatC(n / unique(n_total) * 100,\n digits = 1, format = \"f\", width = 5\n )\n ) %>%\n pivot_wider(\n id_cols = DCREASCD,\n names_from = TRT01PN,\n values_from = c(n, pct),\n values_fill = list(n = 0, pct = \" 0.0\")\n ) %>%\n rename(row = DCREASCD)\n\nn_reason\n#> # A tibble: 10 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 Adverse Event 8 44 40 \" 9.3\" \" 52.4\" \" 47.6\"\n#> 2 Completed 58 25 27 \" 67.4\" \" 29.8\" \" 32.1\"\n#> 3 Death 2 1 0 \" 2.3\" \" 1.2\" \" 0.0\"\n#> 4 I/E Not Met 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> # ℹ 6 more rows\n\nIn the code below, we calculate the number and percentage of participants who complete the study by treatment arms. We split n_reason because we want to customize the row order of the table.\n\nn_complete <- n_reason %>%\n filter(row == \"Completed\")\n\nn_complete\n#> # A tibble: 1 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 Completed 58 25 27 \" 67.4\" \" 29.8\" \" 32.1\"\n\nIn the code below, we calculate the numbers and percentages of participants who discontinued the study for different reasons by treatment arms. For display purpose, paste0(\" \", row) is used to add leading spaces to produce indentation in the final report.\n\nn_reason <- n_reason %>%\n filter(row != \"Completed\") %>%\n mutate(row = paste0(\" \", row))\n\nn_reason\n#> # A tibble: 9 × 7\n#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 \n#> <chr> <int> <int> <int> <chr> <chr> <chr> \n#> 1 \" Adverse Event\" 8 44 40 \" 9.3\" \" 52.4\" \" 47.6\"\n#> 2 \" Death\" 2 1 0 \" 2.3\" \" 1.2\" \" 0.0\"\n#> 3 \" I/E Not Met\" 1 0 2 \" 1.2\" \" 0.0\" \" 2.4\"\n#> 4 \" Lack of Efficacy\" 3 0 1 \" 3.5\" \" 0.0\" \" 1.2\"\n#> # ℹ 5 more rows\n\nNow we combine individual rows into one table for reporting purpose. tbl_disp is used as input for r2rtf to create final report.\n\ntbl_disp <- bind_rows(n_rand, n_complete, n_disc, n_reason) %>%\n select(row, ends_with(c(\"_0\", \"_54\", \"_81\")))\n\ntbl_disp\n#> # A tibble: 12 × 7\n#> row n_0 pct_0 n_54 pct_54 n_81 pct_81 \n#> <chr> <int> <chr> <int> <chr> <int> <chr> \n#> 1 \"Participants in population\" 86 <NA> 84 <NA> 84 <NA> \n#> 2 \"Completed\" 58 \" 67.4\" 25 \" 29.8\" 27 \" 32.1\"\n#> 3 \"Discontinued\" 28 \" 32.6\" 59 \" 70.2\" 57 \" 67.9\"\n#> 4 \" Adverse Event\" 8 \" 9.3\" 44 \" 52.4\" 40 \" 47.6\"\n#> # ℹ 8 more rows\n\nIn the below code, formatting of the final table is defined. Items that were not discussed in the previous sections, are highlighted below.\nThe rtf_title defines table title. We can provide a vector for the title argument. Each value is a separate line. The format can also be controlled by providing a vector input in text format.\n\ntbl_disp %>%\n # Table title\n rtf_title(\"Disposition of Participants\") %>%\n # First row of column header\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3, rep(2, 3))\n ) %>%\n # Second row of column header\n rtf_colheader(\" | n | (%) | n | (%) | n | (%)\",\n col_rel_width = c(3, rep(c(0.7, 1.3), 3)),\n border_top = c(\"\", rep(\"single\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n # Table body\n rtf_body(\n col_rel_width = c(3, rep(c(0.7, 1.3), 3)),\n text_justification = c(\"l\", rep(\"c\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n # Encoding RTF syntax\n rtf_encode() %>%\n # Save to a file\n write_rtf(\"tlf/tbl_disp.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe procedure to generate a disposition table can be summarized as follows:\n\nStep 1: Read subject level data (i.e., adsl) into R.\nStep 2: Count participants in the analysis population and name the dataset n_rand.\nStep 3: Calculate the number and percentage of participants who discontinued the study by treatment arm, and name the dataset n_disc.\nStep 4: Calculate the numbers and percentages of participants who discontinued the study for different reasons by treatment arm, and name the dataset n_reason.\nStep 5: Calculate the number and percentage of participants who completed the study by treatment arm, and name the dataset n_complete.\nStep 6: Bind n_rand, n_disc, n_reason, and n_complete by row.\nStep 7: Write the final table to RTF", "crumbs": [ "Delivering TLFs in CSR", "2  Disposition" @@ -172,7 +172,7 @@ "href": "tlf-baseline.html", "title": "4  Baseline characteristics", "section": "", - "text": "Following ICH E3 guidance, we need to summarize critical demographic and baseline characteristics of the participants in Section 11.2, Demographic and Other Baseline Characteristics.\nIn this chapter, we illustrate how to create a simplified baseline characteristics table for a study.\n\n\n\n\n\n\n\n\n\nThere are many R packages that can efficiently summarize baseline information. The table1 R package is one of them.\n\nlibrary(table1)\nlibrary(r2rtf)\nlibrary(haven)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(stringr)\nlibrary(tools)\n\nAs in previous chapters, we first read the adsl dataset that contains all the required information for the baseline characteristics table.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\n\nFor simplicity, we only analyze SEX, AGE and, RACE in this example using the table1 R package. More details of the table1 R package can be found in the package vignettes.\nThe table1 R package directly creates an HTML report.\n\nana <- adsl %>%\n mutate(\n SEX = factor(SEX, c(\"F\", \"M\"), c(\"Female\", \"Male\")),\n RACE = toTitleCase(tolower(RACE))\n )\n\ntbl <- table1(~ SEX + AGE + RACE | TRT01P, data = ana)\ntbl\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPlacebo\n(N=86)\nXanomeline High Dose\n(N=84)\nXanomeline Low Dose\n(N=84)\nOverall\n(N=254)\n\n\n\n\nSEX\n\n\n\n\n\n\nFemale\n53 (61.6%)\n40 (47.6%)\n50 (59.5%)\n143 (56.3%)\n\n\nMale\n33 (38.4%)\n44 (52.4%)\n34 (40.5%)\n111 (43.7%)\n\n\nAge\n\n\n\n\n\n\nMean (SD)\n75.2 (8.59)\n74.4 (7.89)\n75.7 (8.29)\n75.1 (8.25)\n\n\nMedian [Min, Max]\n76.0 [52.0, 89.0]\n76.0 [56.0, 88.0]\n77.5 [51.0, 88.0]\n77.0 [51.0, 89.0]\n\n\nRACE\n\n\n\n\n\n\nBlack or African American\n8 (9.3%)\n9 (10.7%)\n6 (7.1%)\n23 (9.1%)\n\n\nWhite\n78 (90.7%)\n74 (88.1%)\n78 (92.9%)\n230 (90.6%)\n\n\nAmerican Indian or Alaska Native\n0 (0%)\n1 (1.2%)\n0 (0%)\n1 (0.4%)\n\n\n\n\n\n\n\n\nThe code below transfer the output into a dataframe that only contains ASCII characters recommended by regulatory agencies. tbl_base is used as input for r2rtf to create the final report.\n\ntbl_base <- tbl %>%\n as.data.frame() %>%\n as_tibble() %>%\n mutate(across(\n everything(),\n ~ str_replace_all(.x, intToUtf8(160), \" \")\n ))\n\n\nnames(tbl_base) <- str_replace_all(names(tbl_base), intToUtf8(160), \" \")\ntbl_base\n#> # A tibble: 11 × 5\n#> ` ` Placebo `Xanomeline High Dose` `Xanomeline Low Dose` Overall\n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 \"\" \"(N=86… \"(N=84)\" \"(N=84)\" \"(N=25…\n#> 2 \"SEX\" \"\" \"\" \"\" \"\" \n#> 3 \" Female\" \"53 (6… \"40 (47.6%)\" \"50 (59.5%)\" \"143 (…\n#> 4 \" Male\" \"33 (3… \"44 (52.4%)\" \"34 (40.5%)\" \"111 (…\n#> 5 \"Age\" \"\" \"\" \"\" \"\" \n#> 6 \" Mean (SD)\" \"75.2 … \"74.4 (7.89)\" \"75.7 (8.29)\" \"75.1 …\n#> 7 \" Median [Min,… \"76.0 … \"76.0 [56.0, 88.0]\" \"77.5 [51.0, 88.0]\" \"77.0 …\n#> 8 \"RACE\" \"\" \"\" \"\" \"\" \n#> 9 \" Black or Afr… \"8 (9.… \"9 (10.7%)\" \"6 (7.1%)\" \"23 (9…\n#> 10 \" White\" \"78 (9… \"74 (88.1%)\" \"78 (92.9%)\" \"230 (…\n#> 11 \" American Ind… \"0 (0%… \"1 (1.2%)\" \"0 (0%)\" \"1 (0.…\n\nWe define the format of the output. We highlight items that are not discussed in previous discussion.\ntext_indent_first and text_indent_left are used to control the indent space of text. They are helpful when you need to control the white space of a long phrase, “AMERICAN INDIAN OR ALASKA NATIVE” in the table provides an example.\n\ncolheader1 <- paste(names(tbl_base), collapse = \"|\")\ncolheader2 <- paste(tbl_base[1, ], collapse = \"|\")\nrel_width <- c(2.5, rep(1, 4))\n\ntbl_base[-1, ] %>%\n rtf_title(\n \"Baseline Characteristics of Participants\",\n \"(All Participants Randomized)\"\n ) %>%\n rtf_colheader(colheader1,\n col_rel_width = rel_width\n ) %>%\n rtf_colheader(colheader2,\n border_top = \"\",\n col_rel_width = rel_width\n ) %>%\n rtf_body(\n col_rel_width = rel_width,\n text_justification = c(\"l\", rep(\"c\", 4)),\n text_indent_first = -240,\n text_indent_left = 180\n ) %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_base.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn conclusion, the procedure to generate demographic and baseline characteristics table is summarized as follows:\n\nStep 1: Read the data set.\nStep 2: Use table1::table1() to get the baseline characteristics table.\nStep 3: Transfer the output from Step 2 into a data frame that only contains ASCII characters.\nStep 4: Define the format of the RTF table by using the R package r2rtf.", + "text": "Following ICH E3 guidance, we need to summarize critical demographic and baseline characteristics of the participants in Section 11.2, Demographic and Other Baseline Characteristics.\nIn this chapter, we illustrate how to create a simplified baseline characteristics table for a study.\n\n\n\n\n\n\n\n\n\nThere are many R packages that can efficiently summarize baseline information. The table1 R package is one of them.\n\nlibrary(table1)\nlibrary(r2rtf)\nlibrary(haven)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(stringr)\nlibrary(tools)\n\nAs in previous chapters, we first read the adsl dataset that contains all the required information for the baseline characteristics table.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\n\nFor simplicity, we only analyze SEX, AGE and, RACE in this example using the table1 R package. More details of the table1 R package can be found in the package vignettes.\nThe table1 R package directly creates an HTML report.\n\nana <- adsl %>%\n mutate(\n SEX = factor(SEX, c(\"F\", \"M\"), c(\"Female\", \"Male\")),\n RACE = toTitleCase(tolower(RACE))\n )\n\ntbl <- table1(~ SEX + AGE + RACE | TRT01P, data = ana)\ntbl\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPlacebo\n(N=86)\nXanomeline High Dose\n(N=84)\nXanomeline Low Dose\n(N=84)\nOverall\n(N=254)\n\n\n\n\nSEX\n\n\n\n\n\n\nFemale\n53 (61.6%)\n40 (47.6%)\n50 (59.5%)\n143 (56.3%)\n\n\nMale\n33 (38.4%)\n44 (52.4%)\n34 (40.5%)\n111 (43.7%)\n\n\nAge\n\n\n\n\n\n\nMean (SD)\n75.2 (8.59)\n74.4 (7.89)\n75.7 (8.29)\n75.1 (8.25)\n\n\nMedian [Min, Max]\n76.0 [52.0, 89.0]\n76.0 [56.0, 88.0]\n77.5 [51.0, 88.0]\n77.0 [51.0, 89.0]\n\n\nRACE\n\n\n\n\n\n\nBlack or African American\n8 (9.3%)\n9 (10.7%)\n6 (7.1%)\n23 (9.1%)\n\n\nWhite\n78 (90.7%)\n74 (88.1%)\n78 (92.9%)\n230 (90.6%)\n\n\nAmerican Indian or Alaska Native\n0 (0%)\n1 (1.2%)\n0 (0%)\n1 (0.4%)\n\n\n\n\n\n\n\n\nThe code below transfer the output into a dataframe that only contains ASCII characters recommended by regulatory agencies. tbl_base is used as input for r2rtf to create the final report.\n\ntbl_base <- tbl %>%\n as.data.frame() %>%\n as_tibble() %>%\n mutate(across(\n everything(),\n ~ str_replace_all(.x, intToUtf8(160), \" \")\n ))\n\n\nnames(tbl_base) <- str_replace_all(names(tbl_base), intToUtf8(160), \" \")\ntbl_base\n#> # A tibble: 11 × 5\n#> ` ` Placebo `Xanomeline High Dose` `Xanomeline Low Dose` Overall \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 \"\" \"(N=86)\" \"(N=84)\" \"(N=84)\" \"(N=254)\"\n#> 2 \"SEX\" \"\" \"\" \"\" \"\" \n#> 3 \" Female\" \"53 (61.6%)\" \"40 (47.6%)\" \"50 (59.5%)\" \"143 (56…\n#> 4 \" Male\" \"33 (38.4%)\" \"44 (52.4%)\" \"34 (40.5%)\" \"111 (43…\n#> # ℹ 7 more rows\n\nWe define the format of the output. We highlight items that are not discussed in previous discussion.\ntext_indent_first and text_indent_left are used to control the indent space of text. They are helpful when you need to control the white space of a long phrase, “AMERICAN INDIAN OR ALASKA NATIVE” in the table provides an example.\n\ncolheader1 <- paste(names(tbl_base), collapse = \"|\")\ncolheader2 <- paste(tbl_base[1, ], collapse = \"|\")\nrel_width <- c(2.5, rep(1, 4))\n\ntbl_base[-1, ] %>%\n rtf_title(\n \"Baseline Characteristics of Participants\",\n \"(All Participants Randomized)\"\n ) %>%\n rtf_colheader(colheader1,\n col_rel_width = rel_width\n ) %>%\n rtf_colheader(colheader2,\n border_top = \"\",\n col_rel_width = rel_width\n ) %>%\n rtf_body(\n col_rel_width = rel_width,\n text_justification = c(\"l\", rep(\"c\", 4)),\n text_indent_first = -240,\n text_indent_left = 180\n ) %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_base.rtf\")\n\n\n\n\n\n\n\n\n\n\nIn conclusion, the procedure to generate demographic and baseline characteristics table is summarized as follows:\n\nStep 1: Read the data set.\nStep 2: Use table1::table1() to get the baseline characteristics table.\nStep 3: Transfer the output from Step 2 into a data frame that only contains ASCII characters.\nStep 4: Define the format of the RTF table by using the R package r2rtf.", "crumbs": [ "Delivering TLFs in CSR", "4  Baseline characteristics" @@ -183,7 +183,7 @@ "href": "tlf-efficacy-ancova.html", "title": "5  Efficacy table", "section": "", - "text": "5.1 Analysis dataset\nTo prepare the analysis, both adsl and adlbc datasets are required.\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadlb <- read_sas(\"data-adam/adlbc.sas7bdat\")\nFirst, both the population and the data in scope are selected. The analysis is done on the efficacy population, identified by EFFFL == \"Y\", and all records post baseline (AVISITN >= 1) and on or before Week 24 (AVISITN <= 24). Here the variable AVISITN is the numerical analysis visit. For example, if the analysis visit is recorded as “Baseline” (i.e., AVISIT = Baseline), AVISITN = 0; if the analysis visit is recorded as “Week 24” (i.e., AVISIT = Week 24), AVISITN = 24; if the analysis visit is blank, AVISITN is also blank. We will discuss these missing values in Section 6.4.\ngluc <- adlb %>%\n left_join(adsl %>% select(USUBJID, EFFFL), by = \"USUBJID\") %>%\n # PARAMCD is parameter code and here we focus on Glucose (mg/dL)\n filter(EFFFL == \"Y\" & PARAMCD == \"GLUC\") %>%\n arrange(TRTPN) %>%\n mutate(TRTP = factor(TRTP, levels = unique(TRTP)))\n\nana <- gluc %>%\n filter(AVISITN > 0 & AVISITN <= 24) %>%\n arrange(AVISITN) %>%\n mutate(AVISIT = factor(AVISIT, levels = unique(AVISIT)))\nBelow is the first few records of the analysis dataset.\nana %>%\n select(USUBJID, TRTPN, AVISIT, AVAL, BASE, CHG) %>%\n head(4)\n#> # A tibble: 4 × 6\n#> USUBJID TRTPN AVISIT AVAL BASE CHG\n#> <chr> <dbl> <fct> <dbl> <dbl> <dbl>\n#> 1 01-701-1015 0 \" Week 2\" 4.66 4.72 -0.0555\n#> 2 01-701-1023 0 \" Week 2\" 5.77 5.33 0.444 \n#> 3 01-701-1047 0 \" Week 2\" 5.55 5.55 0 \n#> 4 01-701-1118 0 \" Week 2\" 4.88 4.05 0.833", + "text": "5.1 Analysis dataset\nTo prepare the analysis, both adsl and adlbc datasets are required.\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadlb <- read_sas(\"data-adam/adlbc.sas7bdat\")\nFirst, both the population and the data in scope are selected. The analysis is done on the efficacy population, identified by EFFFL == \"Y\", and all records post baseline (AVISITN >= 1) and on or before Week 24 (AVISITN <= 24). Here the variable AVISITN is the numerical analysis visit. For example, if the analysis visit is recorded as “Baseline” (i.e., AVISIT = Baseline), AVISITN = 0; if the analysis visit is recorded as “Week 24” (i.e., AVISIT = Week 24), AVISITN = 24; if the analysis visit is blank, AVISITN is also blank. We will discuss these missing values in Section 6.4.\ngluc <- adlb %>%\n left_join(adsl %>% select(USUBJID, EFFFL), by = \"USUBJID\") %>%\n # PARAMCD is parameter code and here we focus on Glucose (mg/dL)\n filter(EFFFL == \"Y\" & PARAMCD == \"GLUC\") %>%\n arrange(TRTPN) %>%\n mutate(TRTP = factor(TRTP, levels = unique(TRTP)))\n\nana <- gluc %>%\n filter(AVISITN > 0 & AVISITN <= 24) %>%\n arrange(AVISITN) %>%\n mutate(AVISIT = factor(AVISIT, levels = unique(AVISIT)))\nBelow is the first few records of the analysis dataset.\nana %>% select(USUBJID, TRTPN, AVISIT, AVAL, BASE, CHG)\n#> # A tibble: 1,377 × 6\n#> USUBJID TRTPN AVISIT AVAL BASE CHG\n#> <chr> <dbl> <fct> <dbl> <dbl> <dbl>\n#> 1 01-701-1015 0 \" Week 2\" 4.66 4.72 -0.0555\n#> 2 01-701-1023 0 \" Week 2\" 5.77 5.33 0.444 \n#> 3 01-701-1047 0 \" Week 2\" 5.55 5.55 0 \n#> 4 01-701-1118 0 \" Week 2\" 4.88 4.05 0.833 \n#> # ℹ 1,373 more rows", "crumbs": [ "Delivering TLFs in CSR", "5  Efficacy table" @@ -227,7 +227,7 @@ "href": "tlf-efficacy-ancova.html#missing-data-imputation", "title": "5  Efficacy table", "section": "5.4 Missing data imputation", - "text": "5.4 Missing data imputation\nIn clinical trials, missing data is inevitable. In this study, there are missing values in glucose data.\n\ncount(ana, AVISIT)\n#> # A tibble: 8 × 2\n#> AVISIT n\n#> <fct> <int>\n#> 1 \" Week 2\" 229\n#> 2 \" Week 4\" 211\n#> 3 \" Week 6\" 197\n#> 4 \" Week 8\" 187\n#> 5 \" Week 12\" 167\n#> 6 \" Week 16\" 147\n#> 7 \" Week 20\" 126\n#> 8 \" Week 24\" 113\n\nFor simplicity and illustration purpose, we use the last observation carried forward (LOCF) approach to handle missing data. LOCF approach is a single imputation approach that is not recommended in real application. Interested readers can find more discussion on missing data approaches in the book: The Prevention and Treatment of Missing Data in Clinical Trials.\n\nana_locf <- ana %>%\n group_by(USUBJID) %>%\n mutate(locf = AVISITN == max(AVISITN)) %>%\n filter(locf)", + "text": "5.4 Missing data imputation\nIn clinical trials, missing data is inevitable. In this study, there are missing values in glucose data.\n\ncount(ana, AVISIT)\n#> # A tibble: 8 × 2\n#> AVISIT n\n#> <fct> <int>\n#> 1 \" Week 2\" 229\n#> 2 \" Week 4\" 211\n#> 3 \" Week 6\" 197\n#> 4 \" Week 8\" 187\n#> # ℹ 4 more rows\n\nFor simplicity and illustration purpose, we use the last observation carried forward (LOCF) approach to handle missing data. LOCF approach is a single imputation approach that is not recommended in real application. Interested readers can find more discussion on missing data approaches in the book: The Prevention and Treatment of Missing Data in Clinical Trials.\n\nana_locf <- ana %>%\n group_by(USUBJID) %>%\n mutate(locf = AVISITN == max(AVISITN)) %>%\n filter(locf)", "crumbs": [ "Delivering TLFs in CSR", "5  Efficacy table" @@ -293,7 +293,7 @@ "href": "tlf-ae-summary.html", "title": "7  AE summary", "section": "", - "text": "Following ICH E3 guidance, we summarize number of participants that were included in each safety analysis in Section 12.2, Adverse Events (AEs).\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we illustrate how to summarize AEs information for a study.\n\n\n\n\n\n\n\n\n\nThe data used to summarize AE information is in adsl and adae datasets.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadae <- read_sas(\"data-adam/adae.sas7bdat\")\n\nWe first summarize participants in population by treatment arm.\n\npop <- adsl %>%\n filter(SAFFL == \"Y\") %>%\n rename(TRTAN = TRT01AN) %>%\n count(TRTAN, name = \"tot\")\n\npop\n#> # A tibble: 3 × 2\n#> TRTAN tot\n#> <dbl> <int>\n#> 1 0 86\n#> 2 54 84\n#> 3 81 84\n\nWe transform the data to simplify the analysis of each required AE criteria of interest.\n\nWith one or more adverse events\nWith drug-related adverse events\nWith serious adverse events\nWith serious drug-related adverse events\nWho died\n\n\ntidy_ae <- adae %>%\n mutate(\n all = SAFFL == \"Y\",\n drug = AEREL %in% c(\"POSSIBLE\", \"PROBABLE\"),\n ser = AESER == \"Y\",\n drug_ser = drug & ser,\n die = AEOUT == \"FATAL\"\n ) %>%\n select(USUBJID, TRTAN, all, drug, ser, drug_ser, die) %>%\n pivot_longer(cols = c(all, drug, ser, drug_ser, die))\n\ntidy_ae %>% head(4)\n#> # A tibble: 4 × 4\n#> USUBJID TRTAN name value\n#> <chr> <dbl> <chr> <lgl>\n#> 1 01-701-1015 0 all TRUE \n#> 2 01-701-1015 0 drug TRUE \n#> 3 01-701-1015 0 ser FALSE\n#> 4 01-701-1015 0 drug_ser FALSE\n\nWe summarize the number and percentage of participants who meet each AE criteria.\n\nfmt_num <- function(x, digits, width = digits + 4) {\n formatC(\n x,\n digits = digits,\n format = \"f\",\n width = width\n )\n}\n\n\nana <- tidy_ae %>%\n filter(value == TRUE) %>%\n group_by(TRTAN, name) %>%\n summarise(n = n_distinct(USUBJID)) %>%\n left_join(pop, by = \"TRTAN\") %>%\n mutate(\n pct = fmt_num(n / tot * 100, digits = 1),\n n = fmt_num(n, digits = 0),\n pct = paste0(\"(\", pct, \")\")\n )\n\nana %>% head(4)\n#> # A tibble: 4 × 5\n#> # Groups: TRTAN [2]\n#> TRTAN name n tot pct \n#> <dbl> <chr> <chr> <int> <chr> \n#> 1 0 all \" 69\" 86 ( 80.2)\n#> 2 0 die \" 2\" 86 ( 2.3)\n#> 3 0 drug \" 44\" 86 ( 51.2)\n#> 4 54 all \" 77\" 84 ( 91.7)\n\nWe prepare reporting-ready dataset for each AE group.\n\nt_ae <- ana %>%\n pivot_wider(\n id_cols = \"name\",\n names_from = TRTAN,\n values_from = c(n, pct),\n values_fill = list(\n n = \" 0\",\n pct = \"( 0.0)\"\n )\n )\n\nt_ae <- t_ae %>%\n mutate(name = factor(\n name,\n c(\"all\", \"drug\", \"ser\", \"drug_ser\", \"die\"),\n c(\n \"With one or more adverse events\",\n \"With drug-related adverse events\",\n \"With serious adverse events\",\n \"With serious drug-related adverse events\",\n \"Who died\"\n )\n )) %>%\n arrange(name)\n\nWe prepare reporting-ready dataset for the analysis population.\n\nt_pop <- pop %>%\n mutate(\n name = \"Participants in population\",\n tot = fmt_num(tot, digits = 0)\n ) %>%\n pivot_wider(\n id_cols = name,\n names_from = TRTAN,\n names_prefix = \"n_\",\n values_from = tot\n )\n\nt_pop\n#> # A tibble: 1 × 4\n#> name n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> \n#> 1 Participants in population \" 86\" \" 84\" \" 84\"\n\nThe final report data is saved in tbl_ae_summary.\n\ntbl_ae_summary <- bind_rows(t_pop, t_ae) %>%\n select(name, ends_with(\"_0\"), ends_with(\"_54\"), ends_with(\"_81\"))\n\ntbl_ae_summary\n#> # A tibble: 6 × 7\n#> name n_0 pct_0 n_54 pct_54 n_81 pct_81\n#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> \n#> 1 Participants in population \" 8… <NA> \" 8… <NA> \" 8… <NA> \n#> 2 With one or more adverse events \" 6… ( 80… \" 7… ( 91.… \" 7… ( 94.…\n#> 3 With drug-related adverse events \" 4… ( 51… \" 7… ( 86.… \" 7… ( 83.…\n#> 4 With serious adverse events \" … ( 0… \" … ( 1.… \" … ( 2.…\n#> 5 With serious drug-related adverse events \" … ( 0… \" … ( 1.… \" … ( 1.…\n#> 6 Who died \" … ( 2… \" … ( 1.… \" … ( 0.…\n\nWe define the format of the output using code below:\n\ntbl_ae_summary %>%\n rtf_title(\n \"Analysis of Adverse Event Summary\",\n \"(Safety Analysis Population)\"\n ) %>%\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3.5, rep(2, 3))\n ) %>%\n rtf_colheader(\" | n | (%) | n | (%) | n | (%)\",\n col_rel_width = c(3.5, rep(c(0.7, 1.3), 3)),\n border_top = c(\"\", rep(\"single\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n rtf_body(\n col_rel_width = c(3.5, rep(c(0.7, 1.3), 3)),\n text_justification = c(\"l\", rep(\"c\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n rtf_footnote(\"Every subject is counted a single time for each applicable row and column.\") %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_ae_summary.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe procedure to generate an AE summary table can be summarized as follows:\n\nStep 1: Read data (i.e., adae and adsl) into R.\nStep 2: Summarize participants in population by treatment arm, and name the dataset as t_pop.\nStep 3: Summarize participants in population by required AE criteria of interest, and name the dataset as t_ae.\nStep 4: Row-wise combine t_pop and t_ae and format it by using r2rtf.", + "text": "Following ICH E3 guidance, we summarize number of participants that were included in each safety analysis in Section 12.2, Adverse Events (AEs).\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we illustrate how to summarize AEs information for a study.\n\n\n\n\n\n\n\n\n\nThe data used to summarize AE information is in adsl and adae datasets.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadae <- read_sas(\"data-adam/adae.sas7bdat\")\n\nWe first summarize participants in population by treatment arm.\n\npop <- adsl %>%\n filter(SAFFL == \"Y\") %>%\n rename(TRTAN = TRT01AN) %>%\n count(TRTAN, name = \"tot\")\n\npop\n#> # A tibble: 3 × 2\n#> TRTAN tot\n#> <dbl> <int>\n#> 1 0 86\n#> 2 54 84\n#> 3 81 84\n\nWe transform the data to simplify the analysis of each required AE criteria of interest.\n\nWith one or more adverse events\nWith drug-related adverse events\nWith serious adverse events\nWith serious drug-related adverse events\nWho died\n\n\ntidy_ae <- adae %>%\n mutate(\n all = SAFFL == \"Y\",\n drug = AEREL %in% c(\"POSSIBLE\", \"PROBABLE\"),\n ser = AESER == \"Y\",\n drug_ser = drug & ser,\n die = AEOUT == \"FATAL\"\n ) %>%\n select(USUBJID, TRTAN, all, drug, ser, drug_ser, die) %>%\n pivot_longer(cols = c(all, drug, ser, drug_ser, die))\n\ntidy_ae\n#> # A tibble: 5,955 × 4\n#> USUBJID TRTAN name value\n#> <chr> <dbl> <chr> <lgl>\n#> 1 01-701-1015 0 all TRUE \n#> 2 01-701-1015 0 drug TRUE \n#> 3 01-701-1015 0 ser FALSE\n#> 4 01-701-1015 0 drug_ser FALSE\n#> # ℹ 5,951 more rows\n\nWe summarize the number and percentage of participants who meet each AE criteria.\n\nfmt_num <- function(x, digits, width = digits + 4) {\n formatC(\n x,\n digits = digits,\n format = \"f\",\n width = width\n )\n}\n\n\nana <- tidy_ae %>%\n filter(value == TRUE) %>%\n group_by(TRTAN, name) %>%\n summarise(n = n_distinct(USUBJID)) %>%\n left_join(pop, by = \"TRTAN\") %>%\n mutate(\n pct = fmt_num(n / tot * 100, digits = 1),\n n = fmt_num(n, digits = 0),\n pct = paste0(\"(\", pct, \")\")\n )\n\nana\n#> # A tibble: 12 × 5\n#> # Groups: TRTAN [3]\n#> TRTAN name n tot pct \n#> <dbl> <chr> <chr> <int> <chr> \n#> 1 0 all \" 69\" 86 ( 80.2)\n#> 2 0 die \" 2\" 86 ( 2.3)\n#> 3 0 drug \" 44\" 86 ( 51.2)\n#> 4 54 all \" 77\" 84 ( 91.7)\n#> 5 54 die \" 1\" 84 ( 1.2)\n#> 6 54 drug \" 73\" 84 ( 86.9)\n#> 7 54 drug_ser \" 1\" 84 ( 1.2)\n#> 8 54 ser \" 1\" 84 ( 1.2)\n#> 9 81 all \" 79\" 84 ( 94.0)\n#> 10 81 drug \" 70\" 84 ( 83.3)\n#> 11 81 drug_ser \" 1\" 84 ( 1.2)\n#> 12 81 ser \" 2\" 84 ( 2.4)\n\nWe prepare reporting-ready dataset for each AE group.\n\nt_ae <- ana %>%\n pivot_wider(\n id_cols = \"name\",\n names_from = TRTAN,\n values_from = c(n, pct),\n values_fill = list(\n n = \" 0\",\n pct = \"( 0.0)\"\n )\n )\n\nt_ae <- t_ae %>%\n mutate(name = factor(\n name,\n c(\"all\", \"drug\", \"ser\", \"drug_ser\", \"die\"),\n c(\n \"With one or more adverse events\",\n \"With drug-related adverse events\",\n \"With serious adverse events\",\n \"With serious drug-related adverse events\",\n \"Who died\"\n )\n )) %>%\n arrange(name)\n\nWe prepare reporting-ready dataset for the analysis population.\n\nt_pop <- pop %>%\n mutate(\n name = \"Participants in population\",\n tot = fmt_num(tot, digits = 0)\n ) %>%\n pivot_wider(\n id_cols = name,\n names_from = TRTAN,\n names_prefix = \"n_\",\n values_from = tot\n )\n\nt_pop\n#> # A tibble: 1 × 4\n#> name n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> \n#> 1 Participants in population \" 86\" \" 84\" \" 84\"\n\nThe final report data is saved in tbl_ae_summary.\n\ntbl_ae_summary <- bind_rows(t_pop, t_ae) %>%\n select(name, ends_with(\"_0\"), ends_with(\"_54\"), ends_with(\"_81\"))\n\ntbl_ae_summary\n#> # A tibble: 6 × 7\n#> name n_0 pct_0 n_54 pct_54 n_81 pct_81 \n#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> \n#> 1 Participants in population \" 86\" <NA> \" 84\" <NA> \" 84\" <NA> \n#> 2 With one or more adverse events \" 69\" ( 80.2) \" 77\" ( 91.7) \" 79\" ( 94.0)\n#> 3 With drug-related adverse events \" 44\" ( 51.2) \" 73\" ( 86.9) \" 70\" ( 83.3)\n#> 4 With serious adverse events \" 0\" ( 0.0) \" 1\" ( 1.2) \" 2\" ( 2.4)\n#> # ℹ 2 more rows\n\nWe define the format of the output using code below:\n\ntbl_ae_summary %>%\n rtf_title(\n \"Analysis of Adverse Event Summary\",\n \"(Safety Analysis Population)\"\n ) %>%\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3.5, rep(2, 3))\n ) %>%\n rtf_colheader(\" | n | (%) | n | (%) | n | (%)\",\n col_rel_width = c(3.5, rep(c(0.7, 1.3), 3)),\n border_top = c(\"\", rep(\"single\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n rtf_body(\n col_rel_width = c(3.5, rep(c(0.7, 1.3), 3)),\n text_justification = c(\"l\", rep(\"c\", 6)),\n border_left = c(\"single\", rep(c(\"single\", \"\"), 3))\n ) %>%\n rtf_footnote(\"Every subject is counted a single time for each applicable row and column.\") %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_ae_summary.rtf\")\n\n\n\n\n\n\n\n\n\n\nThe procedure to generate an AE summary table can be summarized as follows:\n\nStep 1: Read data (i.e., adae and adsl) into R.\nStep 2: Summarize participants in population by treatment arm, and name the dataset as t_pop.\nStep 3: Summarize participants in population by required AE criteria of interest, and name the dataset as t_ae.\nStep 4: Row-wise combine t_pop and t_ae and format it by using r2rtf.", "crumbs": [ "Delivering TLFs in CSR", "7  AE summary" @@ -304,7 +304,7 @@ "href": "tlf-ae-specific.html", "title": "8  Specific AE", "section": "", - "text": "Following ICH E3 guidance, we need to summarize number of participants for each specific AE in Section 12.2, Adverse Events (AEs).\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we illustrate how to summarize simplified specific AE information for a study.\n\n\n\n\n\n\n\n\n\nThe data used to summarize AE information is in adsl and adae datasets.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadae <- read_sas(\"data-adam/adae.sas7bdat\")\n\nFor illustration purpose, we only provide counts in the simplified table. The percentage of participants for each AE can be calculated as shown in Chapter 7.\nHere, we focus on the analysis script for two advanced features for a table layout.\n\ngroup content: AE can be summarized in multiple nested layers. (e.g., by system organ class (SOC, AESOC) and specific AE term (AEDECOD))\npagenization: there are many AE terms that can not be covered in one page. Column headers and SOC information need to be repeated on every page.\n\nIn the code below, we count the number of participants in each AE term by SOC and treatment arm, and we create a new variable order and set it as 0. The variable order can help with the data manipulation later.\n\nfmt_num <- function(x, digits, width = digits + 4) {\n formatC(\n x,\n digits = digits,\n format = \"f\",\n width = width\n )\n}\n\n\nana <- adae %>%\n mutate(\n AESOC = tools::toTitleCase(tolower(AESOC)),\n AEDECOD = tools::toTitleCase(tolower(AEDECOD))\n )\n\nt1 <- ana %>%\n group_by(TRTAN, AESOC) %>%\n summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%\n mutate(AEDECOD = AESOC, order = 0)\n\nt1 %>% head(4)\n#> # A tibble: 4 × 5\n#> # Groups: TRTAN [1]\n#> TRTAN AESOC n AEDECOD order\n#> <dbl> <chr> <chr> <chr> <dbl>\n#> 1 0 Cardiac Disorders \" 13\" Cardiac Disorders 0\n#> 2 0 Ear and Labyrinth Disorders \" 1\" Ear and Labyrinth Disorders 0\n#> 3 0 Eye Disorders \" 4\" Eye Disorders 0\n#> 4 0 Gastrointestinal Disorders \" 17\" Gastrointestinal Disorders 0\n\nIn the code below, we count the number of subjects in each AE term by SOC, AE term, and treatment arm. Here we also create a new variable order and set it as 1.\n\nt2 <- ana %>%\n group_by(TRTAN, AESOC, AEDECOD) %>%\n summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%\n mutate(order = 1)\n\nt2 %>% head(4)\n#> # A tibble: 4 × 5\n#> # Groups: TRTAN, AESOC [1]\n#> TRTAN AESOC AEDECOD n order\n#> <dbl> <chr> <chr> <chr> <dbl>\n#> 1 0 Cardiac Disorders Atrial Fibrillation \" 1\" 1\n#> 2 0 Cardiac Disorders Atrial Hypertrophy \" 1\" 1\n#> 3 0 Cardiac Disorders Atrioventricular Block First Degree \" 1\" 1\n#> 4 0 Cardiac Disorders Atrioventricular Block Second Degree \" 2\" 1\n\nWe prepare reporting data for AE information using code below:\n\nt_ae <- bind_rows(t1, t2) %>%\n pivot_wider(\n id_cols = c(AESOC, order, AEDECOD),\n names_from = TRTAN,\n names_prefix = \"n_\",\n values_from = n,\n values_fill = fmt_num(0, digits = 0)\n ) %>%\n arrange(AESOC, order, AEDECOD) %>%\n select(AESOC, AEDECOD, starts_with(\"n\"))\n\nt_ae %>% head(4)\n#> # A tibble: 4 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 Cardiac Disorders Cardiac Disorders \" 13\" \" 13\" \" 18\"\n#> 2 Cardiac Disorders Atrial Fibrillation \" 1\" \" 1\" \" 3\"\n#> 3 Cardiac Disorders Atrial Flutter \" 0\" \" 1\" \" 1\"\n#> 4 Cardiac Disorders Atrial Hypertrophy \" 1\" \" 0\" \" 0\"\n\nWe prepare reporting data for analysis population using code below:\n\ncount_by <- function(data, # Input data set\n grp, # Group variable\n var, # Analysis variable\n var_label = var, # Analysis variable label\n id = \"USUBJID\") { # Subject ID variable\n data <- data %>% rename(grp = !!grp, var = !!var, id = !!id)\n\n left_join(\n count(data, grp, var),\n count(data, grp, name = \"tot\"),\n by = \"grp\",\n ) %>%\n mutate(\n pct = fmt_num(100 * n / tot, digits = 1),\n n = fmt_num(n, digits = 0),\n npct = paste0(n, \" (\", pct, \")\")\n ) %>%\n pivot_wider(\n id_cols = var,\n names_from = grp,\n values_from = c(n, pct, npct),\n values_fill = list(n = \"0\", pct = fmt_num(0, digits = 0))\n ) %>%\n mutate(var_label = var_label)\n}\n\n\nt_pop <- adsl %>%\n filter(SAFFL == \"Y\") %>%\n count_by(\"TRT01AN\", \"SAFFL\",\n var_label = \"Participants in population\"\n ) %>%\n mutate(\n AESOC = \"pop\",\n AEDECOD = var_label\n ) %>%\n select(AESOC, AEDECOD, starts_with(\"n_\"))\n\nt_pop\n#> # A tibble: 1 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 pop Participants in population \" 86\" \" 84\" \" 84\"\n\nThe final report data is saved in tbl_ae_spec. We also add a blank row between population and AE information in the reporting table.\n\ntbl_ae_spec <- bind_rows(\n t_pop,\n data.frame(AESOC = \"pop\"),\n t_ae\n) %>%\n mutate(AEDECOD = ifelse(AEDECOD == AESOC,\n AEDECOD, paste0(\" \", AEDECOD)\n ))\n\ntbl_ae_spec %>% head(4)\n#> # A tibble: 4 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 pop \" Participants in population\" \" 86\" \" 84\" \" 84\"\n#> 2 pop <NA> <NA> <NA> <NA> \n#> 3 Cardiac Disorders \"Cardiac Disorders\" \" 13\" \" 13\" \" 18\"\n#> 4 Cardiac Disorders \" Atrial Fibrillation\" \" 1\" \" 1\" \" 3\"\n\nWe define the format of the output as below:\nTo obtain the nested layout, we use the page_by argument in the rtf_body function. By defining page_by=\"AESOC\", r2rtf recognizes the variable as a group indicator.\nAfter setting pageby_row = \"first_row\", the first row is displayed as group header. If a group of information is broken into multiple pages, the group header row is repeated on each page by default.\nWe can also customize the text format by providing a matrix that has the same dimension as the input dataset (i.e., tbl_ae_spec). In the code below, we illustrate how to display bold text for group headers to highlight the nested structure of the table layout.\n\nn_row <- nrow(tbl_ae_spec)\nn_col <- ncol(tbl_ae_spec)\nid <- tbl_ae_spec$AESOC == tbl_ae_spec$AEDECOD\nid <- ifelse(is.na(id), FALSE, id)\n\ntext_format <- ifelse(id, \"b\", \"\")\n\n\ntbl_ae_spec %>%\n rtf_title(\n \"Analysis of Participants With Specific Adverse Events\",\n \"(Safety Analysis Population)\"\n ) %>%\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3, rep(1, 3))\n ) %>%\n rtf_colheader(\" | n | n | n \",\n border_top = \"\",\n border_bottom = \"single\",\n col_rel_width = c(3, rep(1, 3))\n ) %>%\n rtf_body(\n col_rel_width = c(1, 3, rep(1, 3)),\n text_justification = c(\"l\", \"l\", rep(\"c\", 3)),\n text_format = matrix(text_format, nrow = n_row, ncol = n_col),\n page_by = \"AESOC\",\n pageby_row = \"first_row\"\n ) %>%\n rtf_footnote(\"Every subject is counted a single time for each applicable row and column.\") %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_spec_ae.rtf\")\n\n\n\n\n\n\n\n\n\n\nMore discussion on page_by, group_by and subline_by features can be found on the r2rtf package website.\nThe procedure to generate a baseline characteristics table can be summarized as follows:\n\nStep 1: Read data (i.e., adae and adsl) into R.\nStep 2: Count the number of participants by SOC and treatment arm (rows with bold text) and save into t1.\nStep 3: Count the number of participants in each AE term by SOC, AE term, and treatment arm (rows without bold text) and save into t2.\nStep 4: Bind t1 and t2 by row into t_ae.\nStep 5: Count the number of participants in each arm as t_pop.\nStep 6: Row-wise combine t_pop and t_ae into tbl_ae_spec.\nStep 7: Format the output by using r2rtf.", + "text": "Following ICH E3 guidance, we need to summarize number of participants for each specific AE in Section 12.2, Adverse Events (AEs).\n\nlibrary(haven) # Read SAS data\nlibrary(dplyr) # Manipulate data\nlibrary(tidyr) # Manipulate data\nlibrary(r2rtf) # Reporting in RTF format\n\nIn this chapter, we illustrate how to summarize simplified specific AE information for a study.\n\n\n\n\n\n\n\n\n\nThe data used to summarize AE information is in adsl and adae datasets.\n\nadsl <- read_sas(\"data-adam/adsl.sas7bdat\")\nadae <- read_sas(\"data-adam/adae.sas7bdat\")\n\nFor illustration purpose, we only provide counts in the simplified table. The percentage of participants for each AE can be calculated as shown in Chapter 7.\nHere, we focus on the analysis script for two advanced features for a table layout.\n\ngroup content: AE can be summarized in multiple nested layers. (e.g., by system organ class (SOC, AESOC) and specific AE term (AEDECOD))\npagenization: there are many AE terms that can not be covered in one page. Column headers and SOC information need to be repeated on every page.\n\nIn the code below, we count the number of participants in each AE term by SOC and treatment arm, and we create a new variable order and set it as 0. The variable order can help with the data manipulation later.\n\nfmt_num <- function(x, digits, width = digits + 4) {\n formatC(\n x,\n digits = digits,\n format = \"f\",\n width = width\n )\n}\n\n\nana <- adae %>%\n mutate(\n AESOC = tools::toTitleCase(tolower(AESOC)),\n AEDECOD = tools::toTitleCase(tolower(AEDECOD))\n )\n\nt1 <- ana %>%\n group_by(TRTAN, AESOC) %>%\n summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%\n mutate(AEDECOD = AESOC, order = 0)\n\nt1\n#> # A tibble: 61 × 5\n#> # Groups: TRTAN [3]\n#> TRTAN AESOC n AEDECOD order\n#> <dbl> <chr> <chr> <chr> <dbl>\n#> 1 0 Cardiac Disorders \" 1… Cardia… 0\n#> 2 0 Ear and Labyrinth Disorders \" … Ear an… 0\n#> 3 0 Eye Disorders \" … Eye Di… 0\n#> 4 0 Gastrointestinal Disorders \" 1… Gastro… 0\n#> 5 0 General Disorders and Administration Site Conditio… \" 2… Genera… 0\n#> 6 0 Hepatobiliary Disorders \" … Hepato… 0\n#> 7 0 Infections and Infestations \" 1… Infect… 0\n#> 8 0 Injury, Poisoning and Procedural Complications \" … Injury… 0\n#> 9 0 Investigations \" 1… Invest… 0\n#> 10 0 Metabolism and Nutrition Disorders \" … Metabo… 0\n#> # ℹ 51 more rows\n\nIn the code below, we count the number of subjects in each AE term by SOC, AE term, and treatment arm. Here we also create a new variable order and set it as 1.\n\nt2 <- ana %>%\n group_by(TRTAN, AESOC, AEDECOD) %>%\n summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>%\n mutate(order = 1)\n\nt2\n#> # A tibble: 373 × 5\n#> # Groups: TRTAN, AESOC [61]\n#> TRTAN AESOC AEDECOD n order\n#> <dbl> <chr> <chr> <chr> <dbl>\n#> 1 0 Cardiac Disorders Atrial Fibrillation \" 1\" 1\n#> 2 0 Cardiac Disorders Atrial Hypertrophy \" 1\" 1\n#> 3 0 Cardiac Disorders Atrioventricular Block First Degree \" 1\" 1\n#> 4 0 Cardiac Disorders Atrioventricular Block Second Degree \" 2\" 1\n#> 5 0 Cardiac Disorders Bradycardia \" 1\" 1\n#> 6 0 Cardiac Disorders Bundle Branch Block Left \" 1\" 1\n#> 7 0 Cardiac Disorders Bundle Branch Block Right \" 1\" 1\n#> 8 0 Cardiac Disorders Cardiac Failure Congestive \" 1\" 1\n#> 9 0 Cardiac Disorders Myocardial Infarction \" 4\" 1\n#> 10 0 Cardiac Disorders Sinus Arrhythmia \" 1\" 1\n#> # ℹ 363 more rows\n\nWe prepare reporting data for AE information using code below:\n\nt_ae <- bind_rows(t1, t2) %>%\n pivot_wider(\n id_cols = c(AESOC, order, AEDECOD),\n names_from = TRTAN,\n names_prefix = \"n_\",\n values_from = n,\n values_fill = fmt_num(0, digits = 0)\n ) %>%\n arrange(AESOC, order, AEDECOD) %>%\n select(AESOC, AEDECOD, starts_with(\"n\"))\n\nt_ae\n#> # A tibble: 265 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 Cardiac Disorders Cardiac Disorders \" 13\" \" 13\" \" 18\"\n#> 2 Cardiac Disorders Atrial Fibrillation \" 1\" \" 1\" \" 3\"\n#> 3 Cardiac Disorders Atrial Flutter \" 0\" \" 1\" \" 1\"\n#> 4 Cardiac Disorders Atrial Hypertrophy \" 1\" \" 0\" \" 0\"\n#> # ℹ 261 more rows\n\nWe prepare reporting data for analysis population using code below:\n\ncount_by <- function(data, # Input data set\n grp, # Group variable\n var, # Analysis variable\n var_label = var, # Analysis variable label\n id = \"USUBJID\") { # Subject ID variable\n data <- data %>% rename(grp = !!grp, var = !!var, id = !!id)\n\n left_join(\n count(data, grp, var),\n count(data, grp, name = \"tot\"),\n by = \"grp\",\n ) %>%\n mutate(\n pct = fmt_num(100 * n / tot, digits = 1),\n n = fmt_num(n, digits = 0),\n npct = paste0(n, \" (\", pct, \")\")\n ) %>%\n pivot_wider(\n id_cols = var,\n names_from = grp,\n values_from = c(n, pct, npct),\n values_fill = list(n = \"0\", pct = fmt_num(0, digits = 0))\n ) %>%\n mutate(var_label = var_label)\n}\n\n\nt_pop <- adsl %>%\n filter(SAFFL == \"Y\") %>%\n count_by(\"TRT01AN\", \"SAFFL\",\n var_label = \"Participants in population\"\n ) %>%\n mutate(\n AESOC = \"pop\",\n AEDECOD = var_label\n ) %>%\n select(AESOC, AEDECOD, starts_with(\"n_\"))\n\nt_pop\n#> # A tibble: 1 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 pop Participants in population \" 86\" \" 84\" \" 84\"\n\nThe final report data is saved in tbl_ae_spec. We also add a blank row between population and AE information in the reporting table.\n\ntbl_ae_spec <- bind_rows(\n t_pop,\n data.frame(AESOC = \"pop\"),\n t_ae\n) %>%\n mutate(AEDECOD = ifelse(AEDECOD == AESOC,\n AEDECOD, paste0(\" \", AEDECOD)\n ))\n\ntbl_ae_spec\n#> # A tibble: 267 × 5\n#> AESOC AEDECOD n_0 n_54 n_81 \n#> <chr> <chr> <chr> <chr> <chr> \n#> 1 pop \" Participants in population\" \" 86\" \" 84\" \" 84\"\n#> 2 pop <NA> <NA> <NA> <NA> \n#> 3 Cardiac Disorders \"Cardiac Disorders\" \" 13\" \" 13\" \" 18\"\n#> 4 Cardiac Disorders \" Atrial Fibrillation\" \" 1\" \" 1\" \" 3\"\n#> # ℹ 263 more rows\n\nWe define the format of the output as below:\nTo obtain the nested layout, we use the page_by argument in the rtf_body function. By defining page_by=\"AESOC\", r2rtf recognizes the variable as a group indicator.\nAfter setting pageby_row = \"first_row\", the first row is displayed as group header. If a group of information is broken into multiple pages, the group header row is repeated on each page by default.\nWe can also customize the text format by providing a matrix that has the same dimension as the input dataset (i.e., tbl_ae_spec). In the code below, we illustrate how to display bold text for group headers to highlight the nested structure of the table layout.\n\nn_row <- nrow(tbl_ae_spec)\nn_col <- ncol(tbl_ae_spec)\nid <- tbl_ae_spec$AESOC == tbl_ae_spec$AEDECOD\nid <- ifelse(is.na(id), FALSE, id)\n\ntext_format <- ifelse(id, \"b\", \"\")\n\n\ntbl_ae_spec %>%\n rtf_title(\n \"Analysis of Participants With Specific Adverse Events\",\n \"(Safety Analysis Population)\"\n ) %>%\n rtf_colheader(\" | Placebo | Xanomeline Low Dose| Xanomeline High Dose\",\n col_rel_width = c(3, rep(1, 3))\n ) %>%\n rtf_colheader(\" | n | n | n \",\n border_top = \"\",\n border_bottom = \"single\",\n col_rel_width = c(3, rep(1, 3))\n ) %>%\n rtf_body(\n col_rel_width = c(1, 3, rep(1, 3)),\n text_justification = c(\"l\", \"l\", rep(\"c\", 3)),\n text_format = matrix(text_format, nrow = n_row, ncol = n_col),\n page_by = \"AESOC\",\n pageby_row = \"first_row\"\n ) %>%\n rtf_footnote(\"Every subject is counted a single time for each applicable row and column.\") %>%\n rtf_encode() %>%\n write_rtf(\"tlf/tlf_spec_ae.rtf\")\n\n\n\n\n\n\n\n\n\n\nMore discussion on page_by, group_by and subline_by features can be found on the r2rtf package website.\nThe procedure to generate a baseline characteristics table can be summarized as follows:\n\nStep 1: Read data (i.e., adae and adsl) into R.\nStep 2: Count the number of participants by SOC and treatment arm (rows with bold text) and save into t1.\nStep 3: Count the number of participants in each AE term by SOC, AE term, and treatment arm (rows without bold text) and save into t2.\nStep 4: Bind t1 and t2 by row into t_ae.\nStep 5: Count the number of participants in each arm as t_pop.\nStep 6: Row-wise combine t_pop and t_ae into tbl_ae_spec.\nStep 7: Format the output by using r2rtf.", "crumbs": [ "Delivering TLFs in CSR", "8  Specific AE" diff --git a/sitemap.xml b/sitemap.xml index d8a37da..b728c5e 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,78 +2,78 @@ https://r4csr.org/index.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/preface.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/tlf-overview.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-disposition.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-population.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-baseline.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-efficacy-ancova.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-efficacy-km.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/tlf-ae-summary.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.252Z https://r4csr.org/tlf-ae-specific.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.252Z https://r4csr.org/tlf-assemble.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.256Z https://r4csr.org/project-overview.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/project-folder.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/project-management.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/submission-overview.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.252Z https://r4csr.org/submission-package.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.252Z https://r4csr.org/submission-environment.html - 2024-02-21T17:00:13.523Z + 2024-02-23T22:17:34.252Z https://r4csr.org/references.html - 2024-02-21T17:00:13.495Z + 2024-02-23T22:17:34.224Z https://r4csr.org/R-for-Clinical-Study-Reports-and-Submission.pdf - 2024-02-21T17:18:50.972Z + 2024-02-23T22:20:51.711Z diff --git a/tlf-ae-specific.html b/tlf-ae-specific.html index fa6fc74..9492b3c 100644 --- a/tlf-ae-specific.html +++ b/tlf-ae-specific.html @@ -406,15 +406,22 @@

8  summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>% mutate(AEDECOD = AESOC, order = 0) -t1 %>% head(4) -#> # A tibble: 4 × 5 -#> # Groups: TRTAN [1] -#> TRTAN AESOC n AEDECOD order -#> <dbl> <chr> <chr> <chr> <dbl> -#> 1 0 Cardiac Disorders " 13" Cardiac Disorders 0 -#> 2 0 Ear and Labyrinth Disorders " 1" Ear and Labyrinth Disorders 0 -#> 3 0 Eye Disorders " 4" Eye Disorders 0 -#> 4 0 Gastrointestinal Disorders " 17" Gastrointestinal Disorders 0 +t1 +#> # A tibble: 61 × 5 +#> # Groups: TRTAN [3] +#> TRTAN AESOC n AEDECOD order +#> <dbl> <chr> <chr> <chr> <dbl> +#> 1 0 Cardiac Disorders " 1… Cardia… 0 +#> 2 0 Ear and Labyrinth Disorders " … Ear an… 0 +#> 3 0 Eye Disorders " … Eye Di… 0 +#> 4 0 Gastrointestinal Disorders " 1… Gastro… 0 +#> 5 0 General Disorders and Administration Site Conditio… " 2… Genera… 0 +#> 6 0 Hepatobiliary Disorders " … Hepato… 0 +#> 7 0 Infections and Infestations " 1… Infect… 0 +#> 8 0 Injury, Poisoning and Procedural Complications " … Injury… 0 +#> 9 0 Investigations " 1… Invest… 0 +#> 10 0 Metabolism and Nutrition Disorders " … Metabo… 0 +#> # ℹ 51 more rows

In the code below, we count the number of subjects in each AE term by SOC, AE term, and treatment arm. Here we also create a new variable order and set it as 1.

@@ -423,15 +430,22 @@

8  summarise(n = fmt_num(n_distinct(USUBJID), digits = 0)) %>% mutate(order = 1) -t2 %>% head(4) -#> # A tibble: 4 × 5 -#> # Groups: TRTAN, AESOC [1] -#> TRTAN AESOC AEDECOD n order -#> <dbl> <chr> <chr> <chr> <dbl> -#> 1 0 Cardiac Disorders Atrial Fibrillation " 1" 1 -#> 2 0 Cardiac Disorders Atrial Hypertrophy " 1" 1 -#> 3 0 Cardiac Disorders Atrioventricular Block First Degree " 1" 1 -#> 4 0 Cardiac Disorders Atrioventricular Block Second Degree " 2" 1

+t2 +#> # A tibble: 373 × 5 +#> # Groups: TRTAN, AESOC [61] +#> TRTAN AESOC AEDECOD n order +#> <dbl> <chr> <chr> <chr> <dbl> +#> 1 0 Cardiac Disorders Atrial Fibrillation " 1" 1 +#> 2 0 Cardiac Disorders Atrial Hypertrophy " 1" 1 +#> 3 0 Cardiac Disorders Atrioventricular Block First Degree " 1" 1 +#> 4 0 Cardiac Disorders Atrioventricular Block Second Degree " 2" 1 +#> 5 0 Cardiac Disorders Bradycardia " 1" 1 +#> 6 0 Cardiac Disorders Bundle Branch Block Left " 1" 1 +#> 7 0 Cardiac Disorders Bundle Branch Block Right " 1" 1 +#> 8 0 Cardiac Disorders Cardiac Failure Congestive " 1" 1 +#> 9 0 Cardiac Disorders Myocardial Infarction " 4" 1 +#> 10 0 Cardiac Disorders Sinus Arrhythmia " 1" 1 +#> # ℹ 363 more rows

We prepare reporting data for AE information using code below:

@@ -446,14 +460,15 @@

8  arrange(AESOC, order, AEDECOD) %>% select(AESOC, AEDECOD, starts_with("n")) -t_ae %>% head(4) -#> # A tibble: 4 × 5 +t_ae +#> # A tibble: 265 × 5 #> AESOC AEDECOD n_0 n_54 n_81 #> <chr> <chr> <chr> <chr> <chr> #> 1 Cardiac Disorders Cardiac Disorders " 13" " 13" " 18" #> 2 Cardiac Disorders Atrial Fibrillation " 1" " 1" " 3" #> 3 Cardiac Disorders Atrial Flutter " 0" " 1" " 1" -#> 4 Cardiac Disorders Atrial Hypertrophy " 1" " 0" " 0"

+#> 4 Cardiac Disorders Atrial Hypertrophy " 1" " 0" " 0" +#> # ℹ 261 more rows

We prepare reporting data for analysis population using code below:

@@ -512,14 +527,15 @@

8  AEDECOD, paste0(" ", AEDECOD) )) -tbl_ae_spec %>% head(4) -#> # A tibble: 4 × 5 +tbl_ae_spec +#> # A tibble: 267 × 5 #> AESOC AEDECOD n_0 n_54 n_81 #> <chr> <chr> <chr> <chr> <chr> #> 1 pop " Participants in population" " 86" " 84" " 84" #> 2 pop <NA> <NA> <NA> <NA> #> 3 Cardiac Disorders "Cardiac Disorders" " 13" " 13" " 18" -#> 4 Cardiac Disorders " Atrial Fibrillation" " 1" " 1" " 3"

+#> 4 Cardiac Disorders " Atrial Fibrillation" " 1" " 1" " 3" +#> # ℹ 263 more rows

We define the format of the output as below:

To obtain the nested layout, we use the page_by argument in the rtf_body function. By defining page_by="AESOC", r2rtf recognizes the variable as a group indicator.

diff --git a/tlf-ae-summary.html b/tlf-ae-summary.html index 06ea550..70314d6 100644 --- a/tlf-ae-summary.html +++ b/tlf-ae-summary.html @@ -412,14 +412,15 @@

select(USUBJID, TRTAN, all, drug, ser, drug_ser, die) %>% pivot_longer(cols = c(all, drug, ser, drug_ser, die)) -tidy_ae %>% head(4) -#> # A tibble: 4 × 4 +tidy_ae +#> # A tibble: 5,955 × 4 #> USUBJID TRTAN name value #> <chr> <dbl> <chr> <lgl> #> 1 01-701-1015 0 all TRUE #> 2 01-701-1015 0 drug TRUE #> 3 01-701-1015 0 ser FALSE -#> 4 01-701-1015 0 drug_ser FALSE +#> 4 01-701-1015 0 drug_ser FALSE +#> # ℹ 5,951 more rows

We summarize the number and percentage of participants who meet each AE criteria.

@@ -444,15 +445,23 @@

pct = paste0("(", pct, ")") ) -ana %>% head(4) -#> # A tibble: 4 × 5 -#> # Groups: TRTAN [2] -#> TRTAN name n tot pct -#> <dbl> <chr> <chr> <int> <chr> -#> 1 0 all " 69" 86 ( 80.2) -#> 2 0 die " 2" 86 ( 2.3) -#> 3 0 drug " 44" 86 ( 51.2) -#> 4 54 all " 77" 84 ( 91.7)

+ana +#> # A tibble: 12 × 5 +#> # Groups: TRTAN [3] +#> TRTAN name n tot pct +#> <dbl> <chr> <chr> <int> <chr> +#> 1 0 all " 69" 86 ( 80.2) +#> 2 0 die " 2" 86 ( 2.3) +#> 3 0 drug " 44" 86 ( 51.2) +#> 4 54 all " 77" 84 ( 91.7) +#> 5 54 die " 1" 84 ( 1.2) +#> 6 54 drug " 73" 84 ( 86.9) +#> 7 54 drug_ser " 1" 84 ( 1.2) +#> 8 54 ser " 1" 84 ( 1.2) +#> 9 81 all " 79" 84 ( 94.0) +#> 10 81 drug " 70" 84 ( 83.3) +#> 11 81 drug_ser " 1" 84 ( 1.2) +#> 12 81 ser " 2" 84 ( 2.4)

We prepare reporting-ready dataset for each AE group.

@@ -508,14 +517,13 @@

tbl_ae_summary #> # A tibble: 6 × 7 -#> name n_0 pct_0 n_54 pct_54 n_81 pct_81 -#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> -#> 1 Participants in population " 8… <NA> " 8… <NA> " 8… <NA> -#> 2 With one or more adverse events " 6… ( 80… " 7… ( 91.… " 7… ( 94.… -#> 3 With drug-related adverse events " 4… ( 51… " 7… ( 86.… " 7… ( 83.… -#> 4 With serious adverse events " … ( 0… " … ( 1.… " … ( 2.… -#> 5 With serious drug-related adverse events " … ( 0… " … ( 1.… " … ( 1.… -#> 6 Who died " … ( 2… " … ( 1.… " … ( 0.…

+#> name n_0 pct_0 n_54 pct_54 n_81 pct_81 +#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> +#> 1 Participants in population " 86" <NA> " 84" <NA> " 84" <NA> +#> 2 With one or more adverse events " 69" ( 80.2) " 77" ( 91.7) " 79" ( 94.0) +#> 3 With drug-related adverse events " 44" ( 51.2) " 73" ( 86.9) " 70" ( 83.3) +#> 4 With serious adverse events " 0" ( 0.0) " 1" ( 1.2) " 2" ( 2.4) +#> # ℹ 2 more rows

We define the format of the output using code below:

diff --git a/tlf-baseline.html b/tlf-baseline.html index f86341b..a894091 100644 --- a/tlf-baseline.html +++ b/tlf-baseline.html @@ -508,19 +508,13 @@

4  names(tbl_base) <- str_replace_all(names(tbl_base), intToUtf8(160), " ") tbl_base #> # A tibble: 11 × 5 -#> ` ` Placebo `Xanomeline High Dose` `Xanomeline Low Dose` Overall -#> <chr> <chr> <chr> <chr> <chr> -#> 1 "" "(N=86… "(N=84)" "(N=84)" "(N=25… -#> 2 "SEX" "" "" "" "" -#> 3 " Female" "53 (6… "40 (47.6%)" "50 (59.5%)" "143 (… -#> 4 " Male" "33 (3… "44 (52.4%)" "34 (40.5%)" "111 (… -#> 5 "Age" "" "" "" "" -#> 6 " Mean (SD)" "75.2 … "74.4 (7.89)" "75.7 (8.29)" "75.1 … -#> 7 " Median [Min,… "76.0 … "76.0 [56.0, 88.0]" "77.5 [51.0, 88.0]" "77.0 … -#> 8 "RACE" "" "" "" "" -#> 9 " Black or Afr… "8 (9.… "9 (10.7%)" "6 (7.1%)" "23 (9… -#> 10 " White" "78 (9… "74 (88.1%)" "78 (92.9%)" "230 (… -#> 11 " American Ind… "0 (0%… "1 (1.2%)" "0 (0%)" "1 (0.…

+#> ` ` Placebo `Xanomeline High Dose` `Xanomeline Low Dose` Overall +#> <chr> <chr> <chr> <chr> <chr> +#> 1 "" "(N=86)" "(N=84)" "(N=84)" "(N=254)" +#> 2 "SEX" "" "" "" "" +#> 3 " Female" "53 (61.6%)" "40 (47.6%)" "50 (59.5%)" "143 (56… +#> 4 " Male" "33 (38.4%)" "44 (52.4%)" "34 (40.5%)" "111 (43… +#> # ℹ 7 more rows

We define the format of the output. We highlight items that are not discussed in previous discussion.

text_indent_first and text_indent_left are used to control the indent space of text. They are helpful when you need to control the white space of a long phrase, “AMERICAN INDIAN OR ALASKA NATIVE” in the table provides an example.

diff --git a/tlf-disposition.html b/tlf-disposition.html index 84f87fd..d59da6e 100644 --- a/tlf-disposition.html +++ b/tlf-disposition.html @@ -386,16 +386,15 @@

<
  • DCREASCD: discontinued from study reason coded
  • -
    adsl %>%
    -  select(USUBJID, TRT01P, TRT01PN, DISCONFL, DCREASCD) %>%
    -  head(4)
    -#> # A tibble: 4 × 5
    -#>   USUBJID     TRT01P               TRT01PN DISCONFL DCREASCD        
    -#>   <chr>       <chr>                  <dbl> <chr>    <chr>           
    -#> 1 01-701-1015 Placebo                    0 ""       Completed       
    -#> 2 01-701-1023 Placebo                    0 "Y"      Adverse Event   
    -#> 3 01-701-1028 Xanomeline High Dose      81 ""       Completed       
    -#> 4 01-701-1033 Xanomeline Low Dose       54 "Y"      Sponsor Decision
    +
    adsl %>% select(USUBJID, TRT01P, TRT01PN, DISCONFL, DCREASCD)
    +#> # A tibble: 254 × 5
    +#>   USUBJID     TRT01P               TRT01PN DISCONFL DCREASCD        
    +#>   <chr>       <chr>                  <dbl> <chr>    <chr>           
    +#> 1 01-701-1015 Placebo                    0 ""       Completed       
    +#> 2 01-701-1023 Placebo                    0 "Y"      Adverse Event   
    +#> 3 01-701-1028 Xanomeline High Dose      81 ""       Completed       
    +#> 4 01-701-1033 Xanomeline Low Dose       54 "Y"      Sponsor Decision
    +#> # ℹ 250 more rows

    In the code below, we calculate the number of participants in the analysis population by treatment arms.

    @@ -458,18 +457,13 @@

    < n_reason #> # A tibble: 10 × 7 -#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 -#> <chr> <int> <int> <int> <chr> <chr> <chr> -#> 1 Adverse Event 8 44 40 " 9.3" " 52.4" " 47.6" -#> 2 Completed 58 25 27 " 67.4" " 29.8" " 32.1" -#> 3 Death 2 1 0 " 2.3" " 1.2" " 0.0" -#> 4 I/E Not Met 1 0 2 " 1.2" " 0.0" " 2.4" -#> 5 Lack of Efficacy 3 0 1 " 3.5" " 0.0" " 1.2" -#> 6 Lost to Follow-up 1 1 0 " 1.2" " 1.2" " 0.0" -#> 7 Physician Decision 1 0 2 " 1.2" " 0.0" " 2.4" -#> 8 Protocol Violation 1 1 1 " 1.2" " 1.2" " 1.2" -#> 9 Sponsor Decision 2 2 3 " 2.3" " 2.4" " 3.6" -#> 10 Withdrew Consent 9 10 8 " 10.5" " 11.9" " 9.5"

    +#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 +#> <chr> <int> <int> <int> <chr> <chr> <chr> +#> 1 Adverse Event 8 44 40 " 9.3" " 52.4" " 47.6" +#> 2 Completed 58 25 27 " 67.4" " 29.8" " 32.1" +#> 3 Death 2 1 0 " 2.3" " 1.2" " 0.0" +#> 4 I/E Not Met 1 0 2 " 1.2" " 0.0" " 2.4" +#> # ℹ 6 more rows

    In the code below, we calculate the number and percentage of participants who complete the study by treatment arms. We split n_reason because we want to customize the row order of the table.

    @@ -490,17 +484,13 @@

    < n_reason #> # A tibble: 9 × 7 -#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 -#> <chr> <int> <int> <int> <chr> <chr> <chr> -#> 1 " Adverse Event" 8 44 40 " 9.3" " 52.4" " 47.6" -#> 2 " Death" 2 1 0 " 2.3" " 1.2" " 0.0" -#> 3 " I/E Not Met" 1 0 2 " 1.2" " 0.0" " 2.4" -#> 4 " Lack of Efficacy" 3 0 1 " 3.5" " 0.0" " 1.2" -#> 5 " Lost to Follow-up" 1 1 0 " 1.2" " 1.2" " 0.0" -#> 6 " Physician Decision" 1 0 2 " 1.2" " 0.0" " 2.4" -#> 7 " Protocol Violation" 1 1 1 " 1.2" " 1.2" " 1.2" -#> 8 " Sponsor Decision" 2 2 3 " 2.3" " 2.4" " 3.6" -#> 9 " Withdrew Consent" 9 10 8 " 10.5" " 11.9" " 9.5"

    +#> row n_0 n_54 n_81 pct_0 pct_54 pct_81 +#> <chr> <int> <int> <int> <chr> <chr> <chr> +#> 1 " Adverse Event" 8 44 40 " 9.3" " 52.4" " 47.6" +#> 2 " Death" 2 1 0 " 2.3" " 1.2" " 0.0" +#> 3 " I/E Not Met" 1 0 2 " 1.2" " 0.0" " 2.4" +#> 4 " Lack of Efficacy" 3 0 1 " 3.5" " 0.0" " 1.2" +#> # ℹ 5 more rows

    Now we combine individual rows into one table for reporting purpose. tbl_disp is used as input for r2rtf to create final report.

    @@ -509,20 +499,13 @@

    < tbl_disp #> # A tibble: 12 × 7 -#> row n_0 pct_0 n_54 pct_54 n_81 pct_81 -#> <chr> <int> <chr> <int> <chr> <int> <chr> -#> 1 "Participants in population" 86 <NA> 84 <NA> 84 <NA> -#> 2 "Completed" 58 " 67.4" 25 " 29.8" 27 " 32.1" -#> 3 "Discontinued" 28 " 32.6" 59 " 70.2" 57 " 67.9" -#> 4 " Adverse Event" 8 " 9.3" 44 " 52.4" 40 " 47.6" -#> 5 " Death" 2 " 2.3" 1 " 1.2" 0 " 0.0" -#> 6 " I/E Not Met" 1 " 1.2" 0 " 0.0" 2 " 2.4" -#> 7 " Lack of Efficacy" 3 " 3.5" 0 " 0.0" 1 " 1.2" -#> 8 " Lost to Follow-up" 1 " 1.2" 1 " 1.2" 0 " 0.0" -#> 9 " Physician Decision" 1 " 1.2" 0 " 0.0" 2 " 2.4" -#> 10 " Protocol Violation" 1 " 1.2" 1 " 1.2" 1 " 1.2" -#> 11 " Sponsor Decision" 2 " 2.3" 2 " 2.4" 3 " 3.6" -#> 12 " Withdrew Consent" 9 " 10.5" 10 " 11.9" 8 " 9.5"

    +#> row n_0 pct_0 n_54 pct_54 n_81 pct_81 +#> <chr> <int> <chr> <int> <chr> <int> <chr> +#> 1 "Participants in population" 86 <NA> 84 <NA> 84 <NA> +#> 2 "Completed" 58 " 67.4" 25 " 29.8" 27 " 32.1" +#> 3 "Discontinued" 28 " 32.6" 59 " 70.2" 57 " 67.9" +#> 4 " Adverse Event" 8 " 9.3" 44 " 52.4" 40 " 47.6" +#> # ℹ 8 more rows

    In the below code, formatting of the final table is defined. Items that were not discussed in the previous sections, are highlighted below.

    The rtf_title defines table title. We can provide a vector for the title argument. Each value is a separate line. The format can also be controlled by providing a vector input in text format.

    diff --git a/tlf-efficacy-ancova.html b/tlf-efficacy-ancova.html index 206fe04..1070f8b 100644 --- a/tlf-efficacy-ancova.html +++ b/tlf-efficacy-ancova.html @@ -412,16 +412,15 @@

    CHG: change from baseline
    -
    ana %>%
    -  select(USUBJID, TRTPN, AVISIT, AVAL, BASE, CHG) %>%
    -  head(4)
    -#> # A tibble: 4 × 6
    -#>   USUBJID     TRTPN AVISIT              AVAL  BASE     CHG
    -#>   <chr>       <dbl> <fct>              <dbl> <dbl>   <dbl>
    -#> 1 01-701-1015     0 "          Week 2"  4.66  4.72 -0.0555
    -#> 2 01-701-1023     0 "          Week 2"  5.77  5.33  0.444 
    -#> 3 01-701-1047     0 "          Week 2"  5.55  5.55  0     
    -#> 4 01-701-1118     0 "          Week 2"  4.88  4.05  0.833
    +
    ana %>% select(USUBJID, TRTPN, AVISIT, AVAL, BASE, CHG)
    +#> # A tibble: 1,377 × 6
    +#>   USUBJID     TRTPN AVISIT              AVAL  BASE     CHG
    +#>   <chr>       <dbl> <fct>              <dbl> <dbl>   <dbl>
    +#> 1 01-701-1015     0 "          Week 2"  4.66  4.72 -0.0555
    +#> 2 01-701-1023     0 "          Week 2"  5.77  5.33  0.444 
    +#> 3 01-701-1047     0 "          Week 2"  5.55  5.55  0     
    +#> 4 01-701-1118     0 "          Week 2"  4.88  4.05  0.833 
    +#> # ℹ 1,373 more rows
    @@ -536,10 +535,7 @@

    #> 2 " Week 4" 211 #> 3 " Week 6" 197 #> 4 " Week 8" 187 -#> 5 " Week 12" 167 -#> 6 " Week 16" 147 -#> 7 " Week 20" 126 -#> 8 " Week 24" 113 +#> # ℹ 4 more rows

    For simplicity and illustration purpose, we use the last observation carried forward (LOCF) approach to handle missing data. LOCF approach is a single imputation approach that is not recommended in real application. Interested readers can find more discussion on missing data approaches in the book: The Prevention and Treatment of Missing Data in Clinical Trials.

    diff --git a/tlf-overview.html b/tlf-overview.html index dbfc173..f307766 100644 --- a/tlf-overview.html +++ b/tlf-overview.html @@ -487,14 +487,13 @@

    -
    r2rtf_adae |>
    -  select(USUBJID, TRTA, AEDECOD) |>
    -  head(4)
    -#>       USUBJID    TRTA                   AEDECOD
    -#> 1 01-701-1015 Placebo APPLICATION SITE ERYTHEMA
    -#> 2 01-701-1015 Placebo APPLICATION SITE PRURITUS
    -#> 3 01-701-1015 Placebo                 DIARRHOEA
    -#> 4 01-701-1023 Placebo                  ERYTHEMA
    +
    r2rtf_adae |> select(USUBJID, TRTA, AEDECOD)
    +#>       USUBJID    TRTA                   AEDECOD
    +#> 1 01-701-1015 Placebo APPLICATION SITE ERYTHEMA
    +#> 2 01-701-1015 Placebo APPLICATION SITE PRURITUS
    +#> 3 01-701-1015 Placebo                 DIARRHOEA
    +#> 4 01-701-1023 Placebo                  ERYTHEMA
    +#> # ℹ 1187 more rows

    To manipulate the data and create a data frame containing the necessary information for the RTF table, we can use the dplyr and tidyr packages within the tidyverse.

    @@ -502,14 +501,15 @@

    count(TRTA, AEDECOD) %>% pivot_wider(names_from = TRTA, values_from = n, values_fill = 0) -tbl %>% head(4) -#> # A tibble: 4 × 4 +tbl +#> # A tibble: 242 × 4 #> AEDECOD Placebo `Xanomeline High Dose` `Xanomeline Low Dose` #> <chr> <int> <int> <int> #> 1 ABDOMINAL PAIN 1 2 3 #> 2 AGITATION 2 1 2 #> 3 ALOPECIA 1 0 0 -#> 4 ANXIETY 2 0 4

    +#> 4 ANXIETY 2 0 4 +#> # ℹ 238 more rows

    Having prepared the dataset tbl, we can now proceed with constructing the final RTF table using the r2rtf package. The r2rtf package has various functions, each designed for a specific type of table layout. Some commonly used verbs include:

      @@ -528,10 +528,11 @@

      -
      head(tbl) |>
      -  rtf_body() |>
      -  rtf_encode() |>
      -  write_rtf("tlf/intro-ae1.rtf")
      +
      tbl |>
      +  head() |>
      +  rtf_body() |>
      +  rtf_encode() |>
      +  write_rtf("tlf/intro-ae1.rtf")
      @@ -558,10 +559,11 @@

      -
      head(tbl) |>
      -  rtf_body(col_rel_width = c(3, 2, 2, 2)) |>
      -  rtf_encode() |>
      -  write_rtf("tlf/intro-ae2.rtf")
      +
      tbl |>
      +  head() |>
      +  rtf_body(col_rel_width = c(3, 2, 2, 2)) |>
      +  rtf_encode() |>
      +  write_rtf("tlf/intro-ae2.rtf")

      @@ -575,14 +577,15 @@

      , representing the four columns in the table:

      -
      head(tbl) |>
      -  rtf_colheader(
      -    colheader = "Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose",
      -    col_rel_width = c(3, 2, 2, 2)
      -  ) |>
      -  rtf_body(col_rel_width = c(3, 2, 2, 2)) |>
      -  rtf_encode() %>%
      -  write_rtf("tlf/intro-ae3.rtf")
      +
      tbl |>
      +  head() |>
      +  rtf_colheader(
      +    colheader = "Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose",
      +    col_rel_width = c(3, 2, 2, 2)
      +  ) |>
      +  rtf_body(col_rel_width = c(3, 2, 2, 2)) |>
      +  rtf_encode() %>%
      +  write_rtf("tlf/intro-ae3.rtf")
      @@ -630,10 +633,11 @@

      -
      head(tbl) |>
      -  rtf_body(text_justification = c("l", "c", "c", "c")) |>
      -  rtf_encode() |>
      -  write_rtf("tlf/intro-ae5.rtf")
      +
      tbl |>
      +  head() |>
      +  rtf_body(text_justification = c("l", "c", "c", "c")) |>
      +  rtf_encode() |>
      +  write_rtf("tlf/intro-ae5.rtf")

      @@ -659,19 +663,20 @@

      -
      head(tbl) %>%
      -  rtf_colheader(
      -    colheader = " | Treatment",
      -    col_rel_width = c(3, 6)
      -  ) |>
      -  rtf_colheader(
      -    colheader = "Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose",
      -    border_top = c("", "single", "single", "single"),
      -    col_rel_width = c(3, 2, 2, 2)
      -  ) |>
      -  rtf_body(col_rel_width = c(3, 2, 2, 2)) %>%
      -  rtf_encode() |>
      -  write_rtf("tlf/intro-ae7.rtf")
      +
      tbl |>
      +  head() |>
      +  rtf_colheader(
      +    colheader = " | Treatment",
      +    col_rel_width = c(3, 6)
      +  ) |>
      +  rtf_colheader(
      +    colheader = "Adverse Events | Placebo | Xanomeline High Dose | Xanomeline Low Dose",
      +    border_top = c("", "single", "single", "single"),
      +    col_rel_width = c(3, 2, 2, 2)
      +  ) |>
      +  rtf_body(col_rel_width = c(3, 2, 2, 2)) %>%
      +  rtf_encode() |>
      +  write_rtf("tlf/intro-ae7.rtf")

      diff --git a/tlf-population.html b/tlf-population.html index dbf0f8d..85d91d3 100644 --- a/tlf-population.html +++ b/tlf-population.html @@ -391,16 +391,15 @@

      SAFFL: safety population flag

    -
    adsl %>%
    -  select(USUBJID, ITTFL, EFFFL, SAFFL) %>%
    -  head(4)
    -#> # A tibble: 4 × 4
    -#>   USUBJID     ITTFL EFFFL SAFFL
    -#>   <chr>       <chr> <chr> <chr>
    -#> 1 01-701-1015 Y     Y     Y    
    -#> 2 01-701-1023 Y     Y     Y    
    -#> 3 01-701-1028 Y     Y     Y    
    -#> 4 01-701-1033 Y     Y     Y
    +
    adsl %>% select(USUBJID, ITTFL, EFFFL, SAFFL)
    +#> # A tibble: 254 × 4
    +#>   USUBJID     ITTFL EFFFL SAFFL
    +#>   <chr>       <chr> <chr> <chr>
    +#> 1 01-701-1015 Y     Y     Y    
    +#> 2 01-701-1023 Y     Y     Y    
    +#> 3 01-701-1028 Y     Y     Y    
    +#> 4 01-701-1033 Y     Y     Y    
    +#> # ℹ 250 more rows

    3.1 Helper functions

    diff --git a/tlf/intro-ae1.pdf b/tlf/intro-ae1.pdf index c037211..ef622c2 100644 Binary files a/tlf/intro-ae1.pdf and b/tlf/intro-ae1.pdf differ diff --git a/tlf/intro-ae2.pdf b/tlf/intro-ae2.pdf index 4c7ecd6..656a5f0 100644 Binary files a/tlf/intro-ae2.pdf and b/tlf/intro-ae2.pdf differ diff --git a/tlf/intro-ae3.pdf b/tlf/intro-ae3.pdf index 214b45a..b10cac9 100644 Binary files a/tlf/intro-ae3.pdf and b/tlf/intro-ae3.pdf differ diff --git a/tlf/intro-ae5.pdf b/tlf/intro-ae5.pdf index 0150e19..3a34422 100644 Binary files a/tlf/intro-ae5.pdf and b/tlf/intro-ae5.pdf differ diff --git a/tlf/intro-ae7.pdf b/tlf/intro-ae7.pdf index 9877eaf..241b554 100644 Binary files a/tlf/intro-ae7.pdf and b/tlf/intro-ae7.pdf differ diff --git a/tlf/rtf-combine-toggle.docx b/tlf/rtf-combine-toggle.docx index c6ad1a2..38bae53 100644 Binary files a/tlf/rtf-combine-toggle.docx and b/tlf/rtf-combine-toggle.docx differ diff --git a/tlf/tbl_disp.pdf b/tlf/tbl_disp.pdf index ceead93..eac3974 100644 Binary files a/tlf/tbl_disp.pdf and b/tlf/tbl_disp.pdf differ diff --git a/tlf/tbl_pop.pdf b/tlf/tbl_pop.pdf index 6be0f75..e62fb7e 100644 Binary files a/tlf/tbl_pop.pdf and b/tlf/tbl_pop.pdf differ diff --git a/tlf/tlf_ae_summary.pdf b/tlf/tlf_ae_summary.pdf index 47ab3db..e9dbb80 100644 Binary files a/tlf/tlf_ae_summary.pdf and b/tlf/tlf_ae_summary.pdf differ diff --git a/tlf/tlf_base.pdf b/tlf/tlf_base.pdf index df22b5d..aa7e1f9 100644 Binary files a/tlf/tlf_base.pdf and b/tlf/tlf_base.pdf differ diff --git a/tlf/tlf_eff.pdf b/tlf/tlf_eff.pdf index 6d09650..6d33fac 100644 Binary files a/tlf/tlf_eff.pdf and b/tlf/tlf_eff.pdf differ diff --git a/tlf/tlf_eff1.pdf b/tlf/tlf_eff1.pdf index 7e02189..a9aca15 100644 Binary files a/tlf/tlf_eff1.pdf and b/tlf/tlf_eff1.pdf differ diff --git a/tlf/tlf_eff2.pdf b/tlf/tlf_eff2.pdf index ec36b93..8f6f1e5 100644 Binary files a/tlf/tlf_eff2.pdf and b/tlf/tlf_eff2.pdf differ diff --git a/tlf/tlf_km.pdf b/tlf/tlf_km.pdf index 360f0b8..5902072 100644 Binary files a/tlf/tlf_km.pdf and b/tlf/tlf_km.pdf differ diff --git a/tlf/tlf_spec_ae.pdf b/tlf/tlf_spec_ae.pdf index 8b5484f..69c24c1 100644 Binary files a/tlf/tlf_spec_ae.pdf and b/tlf/tlf_spec_ae.pdf differ