REDCapCAST

library(REDCapCAST)

This vignette covers the basics to get you started with the two basic features of REDCapCAST:

  • Casting REDCap metadata to create a new REDCap database or extend an existing with a new instrument

  • Reading REDCap data in a convenient and focused way, by only getting the data you need, while preserving as much metadata as possible.

Casting meta data

The easiest way is to use the shiny_cast(). You can access a hosted version here or launch it locally like this:

shiny_cast()

Reading data from REDCap

To get you started, the easiest way possible, you can use the easy_redcap() function (example below).

You will need an API-key for your REDCap server, the uri/URL/address for the API connection (usually “https://redcap.YOUR-institution.site/api/”).

This function includes a few convenience features to ease your further work.

If your project uses repeating instruments possible as a longitudinal project, you can choose to widen the data. If not, the result will be a list of each instrument you have chosen to extract data from. Make sure to specify only the fields or instruments you need, and avoid to save any of the data locally, but always source from REDCap to avoid possibly insecure local storage of sensitive data.

easy_redcap(
  uri = "YOUR URI",
  project.name = "MY_PROJECT",
  widen.data = TRUE,
  fields = c("record_id", "OTHER FIELDS")
)

Splitting the dataset

The easy_redcap() function does a few things under the hood. Below are a few examples to show how the nicely formatted output is achieved.

A sample dataset and Data Dictionary/metadata is provided for this demonstration:

redcapcast_data |> gt::gt()
record_id redcap_event_name redcap_repeat_instrument redcap_repeat_instance cpr inclusion inclusion_time dob age age_integer sex cohabitation hypertension diabetes region baseline_data_start_complete mrs_assessed mrs_date mrs_score mrs_complete con_mrs con_calc consensus_complete event_datetime event_age event_type new_event_complete
1 inclusion NA NA 1203401OB4 2023-03-13 12:38:49 1940-03-12 83.00239 83 female Yes No Yes East Incomplete Yes 2023-03-13 1 Incomplete NA NA NA NA NA NA NA
2 inclusion NA NA 0102342303 2023-03-01 10:38:57 1934-02-01 89.07780 89 male Yes No No South Incomplete Yes 2023-03-07 1 Incomplete NA NA NA NA NA NA NA
2 follow1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA Yes 2023-03-09 3 Incomplete NA NA Incomplete NA NA NA NA
2 follow1 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:49:42 NA TIA Incomplete
3 inclusion NA NA 2301569823 2022-03-08 12:01:07 1956-01-23 66.12319 66 male No Yes Yes North Incomplete NA NA NA Incomplete NA NA NA NA NA NA NA
3 follow1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA Yes 2022-08-16 2 Incomplete NA NA Incomplete NA NA NA NA
3 follow2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA Yes 2023-03-13 1 Incomplete NA NA Incomplete NA NA NA NA
3 follow1 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:49:58 NA AIS Incomplete
3 follow1 New Event (?) 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:01 NA ICH Incomplete
3 follow2 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:05 NA ICH Incomplete
3 follow2 New Event (?) 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:07 NA TIA Incomplete
3 follow2 New Event (?) 3 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:09 NA AIS Incomplete
4 inclusion NA NA 0204051342 2023-03-14 20:39:19 1905-04-02 117.94903 117 female NA NA NA NA Incomplete NA NA NA Incomplete NA NA NA NA NA NA NA
4 follow1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA Incomplete NA NA Incomplete NA NA NA NA
4 follow2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA Incomplete NA NA Incomplete NA NA NA NA
4 follow1 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2001-04-11 08:39:05 96 TIA Complete
4 follow1 New Event (?) 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2010-04-11 08:39:25 105 TIA Complete
4 follow2 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:19 118 AIS Complete
4 follow2 New Event (?) 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:22 118 ICH Incomplete
4 follow2 New Event (?) 3 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-01-18 12:50:24 118 Unknown Complete
5 inclusion NA NA 0201976043 2023-03-23 08:50:31 1897-01-02 126.21751 126 male No Yes Yes East Complete NA NA NA Incomplete NA NA NA NA NA NA NA
5 follow1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA Incomplete NA NA Incomplete NA NA NA NA
5 follow1 New Event (?) 1 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-04-11 09:00:33 127 AIS Complete
5 follow1 New Event (?) 2 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2024-04-11 09:00:41 127 ICH Complete
6 inclusion NA NA 1202320122 2024-01-25 08:49:28 1932-02-12 91.95261 91 female No Yes No East Complete NA NA NA Incomplete NA NA NA NA NA NA NA
redcapcast_meta |> gt::gt()
field_name form_name section_header field_type field_label select_choices_or_calculations field_note text_validation_type_or_show_slider_number text_validation_min text_validation_max identifier branching_logic required_field custom_alignment question_number matrix_group_name matrix_ranking field_annotation
record_id baseline_data_start NA text ID NA NA NA NA NA NA NA NA NA NA NA NA NA
cpr baseline_data_start NA text CPR (Danish civil registration number) NA ddmmyyxxxx NA NA NA y NA y NA NA NA NA NA
inclusion baseline_data_start NA text Inclusion date NA NA date_ymd NA NA NA NA NA NA NA NA NA NA
inclusion_time baseline_data_start NA text Inclusion time NA NA time_hh_mm_ss NA NA NA NA NA NA NA NA NA NA
dob baseline_data_start NA text Date of birth (From CPR) NA NA date_ymd NA NA NA NA NA NA NA NA NA @CALCTEXT(if([cpr]!="", concat(if(mid([cpr], 7, 1)>=0 and mid([cpr], 7, 1)<=3,19, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=36 and mid([cpr], 7, 1)>=4 and mid([cpr], 7, 1)<=4,20, if(mid([cpr], 5, 2)>=37 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=4 and mid([cpr], 7, 1)<=4,19, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=57 and mid([cpr], 7, 1)>=5 and mid([cpr], 7, 1)<=5,20, if(mid([cpr], 5, 2)>=58 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=5 and mid([cpr], 7, 1)<=5,18, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=57 and mid([cpr], 7, 1)>=6 and mid([cpr], 7, 1)<=6,20, if(mid([cpr], 5, 2)>=58 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=6 and mid([cpr], 7, 1)<=6,18, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=57 and mid([cpr], 7, 1)>=7 and mid([cpr], 7, 1)<=7,20, if(mid([cpr], 5, 2)>=58 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=7 and mid([cpr], 7, 1)<=7,18, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=57 and mid([cpr], 7, 1)>=8 and mid([cpr], 7, 1)<=8,20, if(mid([cpr], 5, 2)>=58 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=8 and mid([cpr], 7, 1)<=8,18, if(mid([cpr], 5, 2)>=0 and mid([cpr], 5, 2)<=36 and mid([cpr], 7, 1)>=9 and mid([cpr], 7, 1)<=9,20, if(mid([cpr], 5, 2)>=37 and mid([cpr], 5, 2)<=99 and mid([cpr], 7, 1)>=9 and mid([cpr], 7, 1)<=9,19,17))))))))))))), mid([cpr], 5, 2), "-",mid([cpr],3, 2), "-", left([cpr], 2) ), ""))
age baseline_data_start NA calc Age Note: Apparently, the build in datediff() function does not handle counting whole years. This results in wrongly counting age higher around the date of birth. if([cpr]!="" and [inclusion]!="", datediff([dob], [inclusion], 'y'), "") NA NA NA NA NA NA NA NA NA NA NA NA
age_integer baseline_data_start NA calc Age integer Note: as opposed to the build in datediff() this handles counting years as integers very well. Calculate decimal years in statistical programming software. In R you can use with(ds, stRoke::age_calc(dob, inclusion)). if([cpr]!="", left([inclusion], 4)-left([dob], 4) - if(mid([dob], 6, 2) < mid([inclusion], 6, 2) or (mid([dob], 6, 2) = mid([inclusion], 6, 2) and mid([dob], 9, 2) <= mid([inclusion], 9, 2)), 0, 1),"") NA NA NA NA NA NA NA NA NA NA NA NA
sex baseline_data_start NA text Legal sex NA NA NA NA NA NA NA NA NA NA NA NA @CALCTEXT(if([cpr]!="",if((right([cpr],1)=1 or right([cpr],1)=3 or right([cpr],1)=5 or right([cpr],1)=7 or right([cpr],1)=9),"male","female"),""))
cohabitation baseline_data_start History and social radio Cohabitation 1, Yes | 2, No NA NA NA NA NA NA NA NA NA NA NA NA
hypertension baseline_data_start NA radio Hypertension 1, Yes | 2, No NA NA NA NA NA NA NA NA NA NA NA NA
diabetes baseline_data_start NA radio Diabetes 1, Yes | 2, No NA NA NA NA NA NA NA NA NA NA NA NA
region baseline_data_start Area dropdown Region 1, North | 2, East | 3, South | 4, West NA autocomplete NA NA NA NA NA NA NA NA NA NA
mrs_assessed mrs NA radio Assesed 1, Yes | 2, No NA NA NA NA NA NA NA NA NA NA NA NA
mrs_date mrs NA text Assessment date NA NA date_dmy NA NA NA NA NA NA NA NA NA NA
mrs_score mrs NA radio mRS score 0, 0 | 1, 1 | 2, 2 | 3, 3 | 4, 4 | 5, 5 NA NA NA NA NA NA NA NA NA NA NA NA
con_event_note consensus NA descriptive [follow1_arm_1][event_type][1] : [follow1_arm_1][event_type][2] NA NA NA NA NA NA NA NA NA NA NA NA NA
con_mrs consensus NA text Same event type NA NA NA NA NA NA NA NA NA NA NA NA @IF('[follow1_arm_1][event_type][1]'='[follow1_arm_1][event_type][2]',@DEFAULT='pass',@DEFAULT="fail")
con_calc consensus NA text calc NA NA NA NA NA NA NA NA NA NA NA NA @CALCTEXT(if([follow1_arm_1][event_type][1]=[follow1_arm_1][event_type][2],[follow1_arm_1][event_type][1],"fail"))
event_datetime new_event NA text Time of event NA NA datetime_seconds_ymd NA NA NA NA NA NA NA NA NA NA
event_age new_event NA calc Age at event if([event_datetime]!="", left([event_datetime], 4)-left([inclusion_arm_1][dob], 4) - if(mid([inclusion_arm_1][dob], 6, 2) < mid([event_datetime], 6, 2) or (mid([inclusion_arm_1][dob], 6, 2) = mid([event_datetime], 6, 2) and mid([inclusion_arm_1][dob], 9, 2) <= mid([event_datetime], 9, 2)), 0, 1),"") NA NA NA NA NA NA NA NA NA NA NA NA
event_type new_event NA radio Neurovascular event 1, TIA | 2, AIS | 3, ICH | 4, SAH | 99, Unknown NA NA NA NA NA NA NA NA NA NA NA NA

To save the metadata as labels in the dataset, we can save field labels and the choices from radio buttons and dropdown features:

labelled_data <-
  apply_field_label(
    data = redcapcast_data,
    meta = redcapcast_meta
  ) |>
  apply_factor_labels(meta = redcapcast_meta)

The REDCap_split function splits the data set into a list of data.frames.

list <-
  REDCap_split(
    records = labelled_data,
    metadata = redcapcast_meta,
    forms = "all"
  ) |>
  # Next steps cleans up and removes generic columns
  sanitize_split()
str(list)
#> List of 3
#>  $ baseline_data_start: tibble [6 × 14] (S3: tbl_df/tbl/data.frame)
#>   ..$ record_id                   : num [1:6] 1 2 3 4 5 6
#>   .. ..- attr(*, "label")= chr "ID"
#>   ..$ redcap_event_name           : chr [1:6] "inclusion" "inclusion" "inclusion" "inclusion" ...
#>   ..$ cpr                         : chr [1:6] "1203401OB4" "0102342303" "2301569823" "0204051342" ...
#>   .. ..- attr(*, "label")= chr "CPR (Danish civil registration number)"
#>   ..$ inclusion                   : Date[1:6], format: "2023-03-13" "2023-03-01" ...
#>   ..$ inclusion_time              : 'hms' num [1:6] 12:38:49 10:38:57 12:01:07 20:39:19 ...
#>   .. ..- attr(*, "units")= chr "secs"
#>   ..$ dob                         : Date[1:6], format: "1940-03-12" "1934-02-01" ...
#>   ..$ age                         : num [1:6] 83 89.1 66.1 117.9 126.2 ...
#>   .. ..- attr(*, "label")= chr "Age\r\nNote: Apparently, the build in datediff() function does not handle counting whole years. This results in"| __truncated__
#>   ..$ age_integer                 : num [1:6] 83 89 66 117 126 91
#>   .. ..- attr(*, "label")= chr "Age integer\r\nNote: as opposed to the build in datediff() this handles counting years as integers very well. C"| __truncated__
#>   ..$ sex                         : chr [1:6] "female" "male" "male" "female" ...
#>   .. ..- attr(*, "label")= chr "Legal sex"
#>   ..$ cohabitation                : 'labelled' chr [1:6] "Yes" "Yes" "No" NA ...
#>   .. ..- attr(*, "label")= chr "Cohabitation"
#>   .. ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>   ..$ hypertension                : 'labelled' chr [1:6] "No" "No" "Yes" NA ...
#>   .. ..- attr(*, "label")= chr "Hypertension"
#>   .. ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>   ..$ diabetes                    : 'labelled' chr [1:6] "Yes" "No" "Yes" NA ...
#>   .. ..- attr(*, "label")= chr "Diabetes"
#>   .. ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>   ..$ region                      : 'labelled' chr [1:6] "East" "South" "North" NA ...
#>   .. ..- attr(*, "label")= chr "Region"
#>   .. ..- attr(*, "labels")= Named chr [1:4] "1" "2" "3" "4"
#>   .. .. ..- attr(*, "names")= chr [1:4] "North" "East" "South" "West"
#>   ..$ baseline_data_start_complete: chr [1:6] "Incomplete" "Incomplete" "Incomplete" "Incomplete" ...
#>  $ mrs                : tibble [5 × 6] (S3: tbl_df/tbl/data.frame)
#>   ..$ record_id        : num [1:5] 1 2 2 3 3
#>   .. ..- attr(*, "label")= chr "ID"
#>   ..$ redcap_event_name: chr [1:5] "inclusion" "inclusion" "follow1" "follow1" ...
#>   ..$ mrs_assessed     : 'labelled' chr [1:5] "Yes" "Yes" "Yes" "Yes" ...
#>   .. ..- attr(*, "label")= chr "Assesed"
#>   .. ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>   ..$ mrs_date         : Date[1:5], format: "2023-03-13" "2023-03-07" ...
#>   ..$ mrs_score        : 'labelled' num [1:5] 1 1 3 2 1
#>   .. ..- attr(*, "label")= chr "mRS score"
#>   .. ..- attr(*, "labels")= Named chr [1:6] "0" "1" "2" "3" ...
#>   .. .. ..- attr(*, "names")= chr [1:6] "0" "1" "2" "3" ...
#>   ..$ mrs_complete     : chr [1:5] "Incomplete" "Incomplete" "Incomplete" "Incomplete" ...
#>  $ new_event          : tibble [13 × 8] (S3: tbl_df/tbl/data.frame)
#>   ..$ record_id               : num [1:13] 2 3 3 3 3 3 4 4 4 4 ...
#>   .. ..- attr(*, "label")= chr "ID"
#>   ..$ redcap_event_name       : chr [1:13] "follow1" "follow1" "follow1" "follow2" ...
#>   ..$ redcap_repeat_instrument: chr [1:13] "new_event" "new_event" "new_event" "new_event" ...
#>   ..$ redcap_repeat_instance  : num [1:13] 1 1 2 1 2 3 1 2 1 2 ...
#>   ..$ event_datetime          : POSIXct[1:13], format: "2024-01-18 12:49:42" "2024-01-18 12:49:58" ...
#>   ..$ event_age               : num [1:13] NA NA NA NA NA NA 96 105 118 118 ...
#>   .. ..- attr(*, "label")= chr "Age at event"
#>   ..$ event_type              : 'labelled' chr [1:13] "TIA" "AIS" "ICH" "ICH" ...
#>   .. ..- attr(*, "label")= chr "Neurovascular event"
#>   .. ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>   ..$ new_event_complete      : chr [1:13] "Incomplete" "Incomplete" "Incomplete" "Incomplete" ...

The easy_redcap() will then (optionally) continue to widen the data, by transforming the list of data.frames to a single data.frame with one row for each subject/record_id (wide data format):

wide_data <- redcap_wider(list,
  event.glue = "{.value}____{redcap_event_name}",
  inst.glue = "{.value}____{redcap_repeat_instance}"
)
#> Joining with `by = join_by(record_id)`
#> Joining with `by = join_by(record_id)`
wide_data |> str()
#> tibble [6 × 49] (S3: tbl_df/tbl/data.frame)
#>  $ record_id                         : num [1:6] 1 2 3 4 5 6
#>   ..- attr(*, "label")= chr "ID"
#>  $ cpr                               : chr [1:6] "1203401OB4" "0102342303" "2301569823" "0204051342" ...
#>   ..- attr(*, "label")= chr "CPR (Danish civil registration number)"
#>  $ inclusion                         : Date[1:6], format: "2023-03-13" "2023-03-01" ...
#>  $ inclusion_time                    : 'hms' num [1:6] 12:38:49 10:38:57 12:01:07 20:39:19 ...
#>   ..- attr(*, "units")= chr "secs"
#>  $ dob                               : Date[1:6], format: "1940-03-12" "1934-02-01" ...
#>  $ age                               : num [1:6] 83 89.1 66.1 117.9 126.2 ...
#>   ..- attr(*, "label")= chr "Age\r\nNote: Apparently, the build in datediff() function does not handle counting whole years. This results in"| __truncated__
#>  $ age_integer                       : num [1:6] 83 89 66 117 126 91
#>   ..- attr(*, "label")= chr "Age integer\r\nNote: as opposed to the build in datediff() this handles counting years as integers very well. C"| __truncated__
#>  $ sex                               : chr [1:6] "female" "male" "male" "female" ...
#>   ..- attr(*, "label")= chr "Legal sex"
#>  $ cohabitation                      : 'labelled' chr [1:6] "Yes" "Yes" "No" NA ...
#>   ..- attr(*, "label")= chr "Cohabitation"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ hypertension                      : 'labelled' chr [1:6] "No" "No" "Yes" NA ...
#>   ..- attr(*, "label")= chr "Hypertension"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ diabetes                          : 'labelled' chr [1:6] "Yes" "No" "Yes" NA ...
#>   ..- attr(*, "label")= chr "Diabetes"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ region                            : 'labelled' chr [1:6] "East" "South" "North" NA ...
#>   ..- attr(*, "label")= chr "Region"
#>   ..- attr(*, "labels")= Named chr [1:4] "1" "2" "3" "4"
#>   .. ..- attr(*, "names")= chr [1:4] "North" "East" "South" "West"
#>  $ baseline_data_start_complete      : chr [1:6] "Incomplete" "Incomplete" "Incomplete" "Incomplete" ...
#>  $ mrs_assessed____inclusion         : 'labelled' chr [1:6] "Yes" "Yes" NA NA ...
#>   ..- attr(*, "label")= chr "Assesed"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ mrs_assessed____follow1           : 'labelled' chr [1:6] NA "Yes" "Yes" NA ...
#>   ..- attr(*, "label")= chr "Assesed"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ mrs_assessed____follow2           : 'labelled' chr [1:6] NA NA "Yes" NA ...
#>   ..- attr(*, "label")= chr "Assesed"
#>   ..- attr(*, "labels")= Named chr [1:2] "1" "2"
#>   .. ..- attr(*, "names")= chr [1:2] "Yes" "No"
#>  $ mrs_date____inclusion             : Date[1:6], format: "2023-03-13" "2023-03-07" ...
#>  $ mrs_date____follow1               : Date[1:6], format: NA "2023-03-09" ...
#>  $ mrs_date____follow2               : Date[1:6], format: NA NA ...
#>  $ mrs_score____inclusion            : 'labelled' num [1:6] 1 1 NA NA NA NA
#>   ..- attr(*, "label")= chr "mRS score"
#>   ..- attr(*, "labels")= Named chr [1:6] "0" "1" "2" "3" ...
#>   .. ..- attr(*, "names")= chr [1:6] "0" "1" "2" "3" ...
#>  $ mrs_score____follow1              : 'labelled' num [1:6] NA 3 2 NA NA NA
#>   ..- attr(*, "label")= chr "mRS score"
#>   ..- attr(*, "labels")= Named chr [1:6] "0" "1" "2" "3" ...
#>   .. ..- attr(*, "names")= chr [1:6] "0" "1" "2" "3" ...
#>  $ mrs_score____follow2              : 'labelled' num [1:6] NA NA 1 NA NA NA
#>   ..- attr(*, "label")= chr "mRS score"
#>   ..- attr(*, "labels")= Named chr [1:6] "0" "1" "2" "3" ...
#>   .. ..- attr(*, "names")= chr [1:6] "0" "1" "2" "3" ...
#>  $ mrs_complete____inclusion         : chr [1:6] "Incomplete" "Incomplete" NA NA ...
#>  $ mrs_complete____follow1           : chr [1:6] NA "Incomplete" "Incomplete" NA ...
#>  $ mrs_complete____follow2           : chr [1:6] NA NA "Incomplete" NA ...
#>  $ event_datetime____1____follow1    : POSIXct[1:6], format: NA "2024-01-18 12:49:42" ...
#>  $ event_datetime____1____follow2    : POSIXct[1:6], format: NA NA ...
#>  $ event_age____1____follow1         : num [1:6] NA NA NA 96 127 NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_age____1____follow2         : num [1:6] NA NA NA 118 NA NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_type____1____follow1        : 'labelled' chr [1:6] NA "TIA" "AIS" "TIA" ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ event_type____1____follow2        : 'labelled' chr [1:6] NA NA "ICH" "AIS" ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ new_event_complete____1____follow1: chr [1:6] NA "Incomplete" "Incomplete" "Complete" ...
#>  $ new_event_complete____1____follow2: chr [1:6] NA NA "Incomplete" "Complete" ...
#>  $ event_datetime____2____follow1    : POSIXct[1:6], format: NA NA ...
#>  $ event_datetime____2____follow2    : POSIXct[1:6], format: NA NA ...
#>  $ event_datetime____3____follow1    : POSIXct[1:6], format: NA NA ...
#>  $ event_datetime____3____follow2    : POSIXct[1:6], format: NA NA ...
#>  $ event_age____2____follow1         : num [1:6] NA NA NA 105 127 NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_age____2____follow2         : num [1:6] NA NA NA 118 NA NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_age____3____follow1         : num [1:6] NA NA NA NA NA NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_age____3____follow2         : num [1:6] NA NA NA 118 NA NA
#>   ..- attr(*, "label")= chr "Age at event"
#>  $ event_type____2____follow1        : 'labelled' chr [1:6] NA NA "ICH" "TIA" ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ event_type____2____follow2        : 'labelled' chr [1:6] NA NA "TIA" "ICH" ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ event_type____3____follow1        : 'labelled' chr [1:6] NA NA NA NA ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ event_type____3____follow2        : 'labelled' chr [1:6] NA NA "AIS" "Unknown" ...
#>   ..- attr(*, "label")= chr "Neurovascular event"
#>   ..- attr(*, "labels")= Named chr [1:5] "1" "2" "3" "4" ...
#>   .. ..- attr(*, "names")= chr [1:5] "TIA" "AIS" "ICH" "SAH" ...
#>  $ new_event_complete____2____follow1: chr [1:6] NA NA "Incomplete" "Complete" ...
#>  $ new_event_complete____2____follow2: chr [1:6] NA NA "Incomplete" "Incomplete" ...
#>  $ new_event_complete____3____follow1: chr [1:6] NA NA NA NA ...
#>  $ new_event_complete____3____follow2: chr [1:6] NA NA "Incomplete" "Complete" ...

Transfer suffixes to labels:

wide_data_suffixes <- wide_data |> suffix2label()

Creating a nice table

wide_data_suffixes |>
  dplyr::select(sex, hypertension, diabetes,mrs_score____follow2) |>
  gtsummary::tbl_summary()
Characteristic N = 61
Legal sex
    female 3 (50%)
    male 3 (50%)
Hypertension
    No 2 (40%)
    Yes 3 (60%)
    Unknown 1
Diabetes
    No 2 (40%)
    Yes 3 (60%)
    Unknown 1
mRS score (follow2)
    1 1 (100%)
    Unknown 5
1 n (%)