Pekka Tiikkainen, Bayer Pharmaceuticals
CTAS is an R package for identifying outliers and anomalies in clinical trial time series. Its main focus is on flagging sites with one or more study parameters whose profiles differ from those of the other sites. However, also anomalies for individual subjects can be identified with the results the package gives.
This document focuses on the data format requirements of the input and output data. For details on methodology, please refer to the paper and presentation published at the PHUSE EU Connect 22 meeting. The files are attached to this repository.
First the clinical data has to be wrangled in a format compatible with the main function’s parameters (please see below for definitions). Please note that data preparation step is intentionally out-of-scope for the package because each user is likely to have their data in a different format.
Then the main function (process_a_study) is called separately for each study you wish to process. The function returns the results as a list of data frames. For details on the output, please see below.
library(ctas)
#> Loading required package: dplyr
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
data("ctas_data", package = "ctas")
ctas_data
#> $data
#> # A tibble: 6,890 × 7
#> subject_id timepoint_rank timepoint_1_name result parameter_id
#> <chr> <int> <chr[1d]> <dbl> <chr>
#> 1 1 1 AB 38.1 param1
#> 2 1 2 AC 36.9 param1
#> 3 1 3 AD 38.5 param1
#> 4 1 4 AE 39.8 param1
#> 5 1 5 AF 40.6 param1
#> 6 1 6 AG 36.7 param1
#> 7 1 7 AH 38.2 param1
#> 8 1 8 AI 40.0 param1
#> 9 1 9 AJ 37.0 param1
#> 10 1 10 AK 35.1 param1
#> # ℹ 6,880 more rows
#> # ℹ 2 more variables: timepoint_2_name <chr>, baseline <lgl>
#>
#> $parameters
#> # A tibble: 2 × 11
#> parameter_id parameter_name parameter_category_1 parameter_category_2
#> <chr> <chr> <chr> <chr>
#> 1 param1 param1 category 1 category 2
#> 2 param2 param2 category 1 category 2
#> # ℹ 7 more variables: parameter_category_3 <chr>, time_point_count_min <lgl>,
#> # subject_count_min <lgl>, max_share_missing <lgl>,
#> # generate_change_from_baseline <lgl>,
#> # timeseries_features_to_calculate <lgl>, use_only_custom_timeseries <lgl>
#>
#> $subjects
#> # A tibble: 177 × 4
#> subject_id site country region
#> <chr> <chr> <chr> <chr>
#> 1 1 AAA AA A
#> 2 2 AAA AA A
#> 3 3 AAA AA A
#> 4 4 AAB AA A
#> 5 5 AAB AA A
#> 6 6 AAB AA A
#> 7 7 AAC AA A
#> 8 8 AAC AA A
#> 9 9 AAC AA A
#> 10 10 AAC AA A
#> # ℹ 167 more rows
#>
#> $custom_timeseries
#> # A tibble: 0 × 3
#> # ℹ 3 variables: timeseries_id <chr>, parameter_id <chr>, timepoint_combo <chr>
#>
#> $custom_reference_groups
#> # A tibble: 0 × 3
#> # ℹ 3 variables: parameter_id <chr>, feature <chr>, ref_group <chr>
feats <- c(
"autocorr",
"average",
"own_site_simil_score",
"sd",
"unique_value_count_relative",
"lof",
"range"
) %>%
paste(collapse = ";")
ls_ctas <- process_a_study(
data = ctas_data$data,
subjects = ctas_data$subjects,
parameters = ctas_data$parameters,
custom_timeseries = ctas_data$custom_timeseries,
custom_reference_groups = ctas_data$custom_reference_groups,
default_timeseries_features_to_calculate = feats,
default_minimum_timepoints_per_series = 3,
default_minimum_subjects_per_series = 3,
default_max_share_missing_timepoints_per_series = 0.5,
default_generate_change_from_baseline = FALSE,
autogenerate_timeseries = TRUE
)
ls_ctas
#> $timeseries
#> # A tibble: 10 × 6
#> timeseries_id parameter_id baseline timepoint_combo timepoint_combo_read…¹
#> <chr> <chr> <chr> <chr> <chr>
#> 1 ts_1_autogen_or… param1 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 2 ts_2_autogen_or… param1 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 3 ts_3_autogen_or… param1 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 4 ts_4_autogen_or… param1 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 5 ts_5_autogen_or… param1 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 6 ts_6_autogen_or… param2 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 7 ts_7_autogen_or… param2 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 8 ts_8_autogen_or… param2 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 9 ts_9_autogen_or… param2 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> 10 ts_10_autogen_o… param2 original 1;2;3;4;5;6;7;… AB_timepoint name 2;A…
#> # ℹ abbreviated name: ¹timepoint_combo_readable
#> # ℹ 1 more variable: timepoint_count <dbl>
#>
#> $timeseries_features
#> # A tibble: 7,902 × 7
#> timeseries_id subject_id feature feature_value site country region
#> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
#> 1 ts_1_autogen_original 1 range 5.45 AAA AA A
#> 2 ts_1_autogen_original 1 sd 1.40 AAA AA A
#> 3 ts_1_autogen_original 1 unique_v… 1 AAA AA A
#> 4 ts_1_autogen_original 1 autocorr 0.120 AAA AA A
#> 5 ts_1_autogen_original 1 average 38.2 AAA AA A
#> 6 ts_1_autogen_original 1 lof 1.06 AAA AA A
#> 7 ts_1_autogen_original 4 range 14.6 AAB AA A
#> 8 ts_1_autogen_original 4 sd 3.70 AAB AA A
#> 9 ts_1_autogen_original 4 unique_v… 1 AAB AA A
#> 10 ts_1_autogen_original 4 autocorr -0.0317 AAB AA A
#> # ℹ 7,892 more rows
#>
#> $PCA_coordinates
#> # A tibble: 1,137 × 4
#> timeseries_id subject_id pc1 pc2
#> <chr> <chr> <dbl> <dbl>
#> 1 ts_1_autogen_original 1 -37.7 1.07
#> 2 ts_1_autogen_original 4 14.6 2.67
#> 3 ts_1_autogen_original 10 47.6 -2.96
#> 4 ts_1_autogen_original 12 35.0 1.42
#> 5 ts_1_autogen_original 14 0.0945 -34.7
#> 6 ts_1_autogen_original 16 2.97 -9.54
#> 7 ts_1_autogen_original 17 -8.90 3.78
#> 8 ts_1_autogen_original 19 -29.5 2.09
#> 9 ts_1_autogen_original 22 -12.3 1.59
#> 10 ts_1_autogen_original 24 6.02 -5.75
#> # ℹ 1,127 more rows
#>
#> $site_scores
#> # A tibble: 2,221 × 10
#> timeseries_id site country region feature pvalue_kstest_logp
#> <chr> <chr> <chr> <chr> <chr> <dbl>
#> 1 ts_1_autogen_original AAA AA A range 0.490
#> 2 ts_1_autogen_original AAB AA A range 0.0130
#> 3 ts_1_autogen_original AAC AA A range 0.418
#> 4 ts_1_autogen_original AAD AA A range 1.24
#> 5 ts_1_autogen_original AAE AA A range 0.374
#> 6 ts_1_autogen_original AAF AA A range 0.0324
#> 7 ts_1_autogen_original AAG AA A range 0.977
#> 8 ts_1_autogen_original ABA AB A range 1.17
#> 9 ts_1_autogen_original BAA BA B range 0.283
#> 10 ts_1_autogen_original BAB BA B range 1.77
#> # ℹ 2,211 more rows
#> # ℹ 4 more variables: kstest_statistic <dbl>, fdr_corrected_pvalue_logp <dbl>,
#> # ref_group <chr>, subject_count <int>
The function process_a_study has a number of parameters that need to be provided. Some of these are data frames while others are simple variables.
The following gives more details on each parameter. Examples are also provided where needed.
Semicolon-delimited string of time series features to calculate. The list must contain at least one of the following feature codes:
Feature code | Description |
---|---|
autocorr | Auto-correlation |
average | Average value |
own_site_simil_score | Measure of co-clustering of time series from the same study site |
sd | Standard deviation |
unique_value_count_relative | Number of unique values divided by number of values available |
range | Maximum difference between two time points in a series |
lof | Local Outlier Factor. Values around one are inliers while the larger the value, more of an outlier the timeseries is |
Minimum number of time points for auto-generated time series. This value will be used for a parameter if they do not have the time_point_count_min column defined in the parameters df.
Minimum number of eligible subjects for auto-generated time series. This value will be used for a parameter if they do not have the subject_count_min column defined in the parameters df.
Maximum share of missing time points a subject can have in order to be eligible for a time series. This value will be used for a parameter if they do not have the max_share_missing defined in the parameters df.
If TRUE, change-from-baseline adjusted version of each time series is generated (in addition to the time series with actual measurements).
If TRUE, time series are autogenerated in a data-driven matter for all parameters. If set to FALSE, the custom_timeseries data frame must have at least one entry.
Data frame with one row per study subject.
Data frame columns:
Column name | Data type | Data required | Description |
---|---|---|---|
subject_id | chr | Y | Unique identifier for each subject |
country | chr | Y | Country where the subject is enrolled. |
site | chr | Y | Name of the study site. |
region | chr | (Y) | Region of the world for the country. Required if the custom reference table has “region” defined for any of the rows. |
Data frame with one row for each parameter that should be processed. Parameters are, for example, laboratory assays and vital sign measurements done during the course of a study.
Data frame columns:
Column name | Data type | Data required | Description |
---|---|---|---|
parameter_id | chr | Y | Parameter identifier |
parameter_category_1 | chr | Top level category of the parameter (e.g. LB for a laboratory assay). | |
parameter_category_2 | chr | Second level category of the parameter (e.g. General chemistry for a laboratory assay). | |
parameter_category_3 | chr | Third level category of the parameter (e.g. Local lab for a laboratory assay). | |
parameter_name | chr | Y | Parameter name |
time_point_count_min | int | Minimum number of time points required for auto-generated time series. If not provided, global default value is used. | |
subject_count_min | int | Minimum number of eligible subjects required for auto-generated time series. If not provided, global default value is used. | |
max_share_missing | float | Maximum number of missing data points an eligible subject can have for auto-generated time series. If not provided, global default value is used. | |
generate_change_from_baseline | boolean | Whether to also generate baseline-adjusted time series for the parameter. If not provided, global default value is used. | |
timeseries_features_to_calculate | chr | A comma-delimited list of time series features to calculate for the parameter. If not provided, global default value is used. | |
use_only_custom_timeseries | boolean | Whether to only use the custom timeseries for the parameter (i.e. ignore any autogenerated time series). |
Example for two parameters, one vital sign and one local lab. For any time series generated for the vital sign, at least 50 eligible subjects are required. For the local lab, time series with one time point only are acceptable. These parameter-specific properties override the corresponding global parameters (see above).
parameter_id | parameter_category_1 | parameter_category_2 | parameter_category_3 | parameter_name | time_point_count_min | subject_count_min | max_share_missing | generate_change_from_baseline |
---|---|---|---|---|---|---|---|---|
dummy_param_1 | VS | NA | NA | DIABP | 50 | |||
dummy_param_2 | LB | General chemistry | Local lab | Glucose | 1 |
Data frame for the data collected during the study with one row per data point.
Data frame columns:
Column name | Data type | Data required | Description |
---|---|---|---|
subject_id | chr | Y | Pointer to the subject identifier in the subjects table. |
parameter_id | chr | Y | Pointer to the parameter identifier in the parameters table. |
timepoint_1_name | chr | Y | Name of the top level time point (e.g. Visit 1). |
timepoint_2_name | chr | Name of the second level time point (e.g. “30 mins after dosing”). | |
timepoint_rank | int | Y | Integer for ranking time points into chronological order. Ranking is separate for each parameter. |
result | float | Y | Numerical result of the parameter. |
baseline | float | Possible baseline value for the parameter and the subject. This is used to calculate change-from-baseline time series. |
Example of data contents:
subject_id | parameter_id | timepoint_1_name | timepoint_2_name | timepoint_rank | result | baseline |
---|---|---|---|---|---|---|
subj_1 | dummy_param_1 | Visit 1 | pre-dose | 1 | 72 | 65 |
subj_1 | dummy_param_1 | Visit 1 | post-dose | 2 | 81 | 65 |
subj_1 | dummy_param_1 | Visit 2 | NA | 3 | 69 | 65 |
subj_1 | dummy_param_1 | Visit 3 | NA | 4 | 70 | 65 |
subj_1 | dummy_param_2 | Visit 3 | NA | 1 | 85 | 84 |
subj_2 | dummy_param_2 | Visit 3 | NA | 1 | 102 | 96 |
The study team might be interested in specific time series - for instance in an oncology trial one might be interested in data collected in the first two treatment cycles.
When time series are auto-generated in a data-driven manner, it cannot be guaranteed that a particular time series is generated. To make sure that a specific time series is analyzed, this can be defined with the custom_timeseries data frame.
Please note: if you do not have any custom time series to process, you still have to provide the main function a blank data frame!
Data frame columns:
Column name | Data type | Data required | Description |
---|---|---|---|
timeseries_id | chr | Y | Identifier for the the custom time series. |
parameter_id | chr | Y | Pointer to the parameter identifier in the parameters table. |
timepoint_combo | chr | Y | Semicolon-delimited string of timepoint ranks that the time series consists of. These must match values in the timepoint_rank column of the data df. |
In the example below, a custom time series has been defined for dummy_param_1. Timepoints are defined with their respective ranks in the data df.
timeseries_id | parameter_id | timepoint_combo |
---|---|---|
custom_ts_1 | dummy_param_1 | 1;2;3;4 |
For some parameters, it might make more sense to compare a site to other sites in the same country or region than globally. This can be a case for subject weight or height for example where it is better to compare, say, the average height or weight of Japanese sites to other East Asian sites. If compared globally, most Japanese sites would get flagged since in general Japanese are lighter and shorter than people elsewhere.
Data frame columns:
Column name | Data type | Data required | Description |
---|---|---|---|
parameter_id | chr | Y | Pointer to the parameter identifier in the parameters table. |
feature | chr | Y | Time series feature for which to use the custom reference group. |
ref_group | chr | Y | “country” if the parameter features should only be compared to sites from the same country, “region” if the comparison should be made to other sites from the same region of the world. |
Output is a list of four data frames. Their details are explained in the following.
Time series processed. Contains both the auto-generated and/or custom time series.
Data frame columns:
Column name | Data type | Description |
---|---|---|
timeseries_id | chr | Unique identifier for the time series. |
parameter_id | chr | The parameter the time series is for. |
baseline | chr | Whether the time series values are original measurements or baseline-adjusted. |
timepoint_combo | chr | Ranks of the time points that constitute the time series. Semicolon delimited string. |
timepoint_combo_readable | chr | Names of the time points that constitute the time series. Semicolon-delimited string. |
timepoint_count | int | Number of timepoints in the time series. |
Data frame with features calculated for time series.
Data frame columns:
Column name | Data type | Description |
---|---|---|
timeseries_id | chr | Unique identifier of the time series. Points to the timeseries table. |
subject_id | chr | Subject ID pointing to the input df subjects. |
feature | chr | Feature code. |
feature_value | float | Feature value. |
Data frame with the top two principal components for a time series. These are useful for the visualization of the similarity of subjects.
Data frame columns:
Column name | Data type | Description |
---|---|---|
timeseries_id | chr | Unique identifier of the time series. Points to the timeseries table. |
subject_id | chr | Subject ID pointing to the input df subjects. |
pc1 | float | The first principal component. |
pc2 | float | The second principal component. |
Biasness scores for sites. The data frame contains a row for each site/time series/feature combination.
Data frame columns:
Column name | Data type | Description |
---|---|---|
timeseries_id | chr | Unique identifier of the time series. Points to the timeseries table. |
site | chr | Study site for which the score has been calculated. |
country | chr | Site’s country. |
feature | chr | Time series feature the score is for. |
pvalue_kstest_logp | float | Negative logarithm of the raw p-value. |
kstest_statistic | float | Test statistic of the Kolmogorov-Smirnov test. |
fdr_corrected_pvalue_logp | float | Site score: negative logarithm of the multiple testing corrected p-value. FDR (False Discovery Rate) is used for correction. |
reference_group | chr | Reference group for the site. “All” means that the site has been compared to all sites in the study. |
subject_count | int | Number of site subjects who are eligible for the time series. |