Investigated vaccine

A Phase III Study to Assess the Immunogenicity and Safety of SARS-CoV-2 GBP5 in Adults Aged 18 Years and Older.

Dose 1 at Visit 2, dose 2 at Visit 4. Test for immunogenocity at Visit 6, 2 weeks after the second dose.

Data for this analysis is from Vietnamese subjects.

Loading data

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
library(janitor)
## 
## Attaching package: 'janitor'
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
df = read_csv("/Users/nnthieu/Downloads/GBP510/GBP510_final.csv")
## Rows: 9122 Columns: 14
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (7): treatment, screeningno, visit, methodoftest, results, sex, sitename
## dbl  (3): age, barcodeid, result2
## date (3): dob, barcodedate, dateofbloodsampling
## time (1): samplingtime
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(df)
## # A tibble: 6 × 14
##   treatment screeningno visit   methodoftest results sex    dob          age
##   <chr>     <chr>       <chr>   <chr>        <chr>   <chr>  <date>     <dbl>
## 1 GBP510    401-0002    Visit 2 ELISA        34      Female 1983-10-29    37
## 2 GBP510    401-0002    Visit 4 ELISA        335     Female 1983-10-29    37
## 3 GBP510    401-0002    Visit 6 ELISA        7599    Female 1983-10-29    37
## 4 GBP510    401-0002    Visit 7 ELISA        4792    Female 1983-10-29    37
## 5 GBP510    401-0002    Visit 8 ELISA        NULL    Female 1983-10-29    37
## 6 GBP510    401-0002    Visit 9 ELISA        NULL    Female 1983-10-29    37
## # ℹ 6 more variables: barcodedate <date>, barcodeid <dbl>, sitename <chr>,
## #   dateofbloodsampling <date>, samplingtime <time>, result2 <dbl>
glimpse(df)
## Rows: 9,122
## Columns: 14
## $ treatment           <chr> "GBP510", "GBP510", "GBP510", "GBP510", "GBP510", …
## $ screeningno         <chr> "401-0002", "401-0002", "401-0002", "401-0002", "4…
## $ visit               <chr> "Visit 2", "Visit 4", "Visit 6", "Visit 7", "Visit…
## $ methodoftest        <chr> "ELISA", "ELISA", "ELISA", "ELISA", "ELISA", "ELIS…
## $ results             <chr> "34", "335", "7599", "4792", "NULL", "NULL", "8.08…
## $ sex                 <chr> "Female", "Female", "Female", "Female", "Female", …
## $ dob                 <date> 1983-10-29, 1983-10-29, 1983-10-29, 1983-10-29, 1…
## $ age                 <dbl> 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37…
## $ barcodedate         <date> 2021-12-28, 2021-12-28, 2021-12-28, 2021-12-28, 2…
## $ barcodeid           <dbl> 103009, 103009, 103009, 103009, 103009, 103009, 10…
## $ sitename            <chr> "GBP510_003(401)", "GBP510_003(401)", "GBP510_003(…
## $ dateofbloodsampling <date> 2021-12-09, 2021-12-09, 2021-12-09, 2021-12-09, 2…
## $ samplingtime        <time> 09:05:00, 09:05:00, 09:05:00, 09:05:00, 09:05:00,…
## $ result2             <dbl> 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.…
summary(df)
##   treatment         screeningno           visit           methodoftest      
##  Length:9122        Length:9122        Length:9122        Length:9122       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##    results              sex                 dob                  age       
##  Length:9122        Length:9122        Min.   :1970-01-01   Min.   :18.00  
##  Class :character   Class :character   1st Qu.:1980-01-01   1st Qu.:31.00  
##  Mode  :character   Mode  :character   Median :1990-08-16   Median :41.00  
##                                        Mean   :2003-11-01   Mean   :41.93  
##                                        3rd Qu.:2003-10-27   3rd Qu.:51.00  
##                                        Max.   :2069-12-11   Max.   :79.00  
##   barcodedate           barcodeid        sitename         dateofbloodsampling 
##  Min.   :2021-12-28   Min.   :103001   Length:9122        Min.   :2021-12-09  
##  1st Qu.:2021-12-28   1st Qu.:103143   Class :character   1st Qu.:2021-12-10  
##  Median :2021-12-28   Median :103290   Mode  :character   Median :2021-12-16  
##  Mean   :2021-12-28   Mean   :103284                      Mean   :2021-12-14  
##  3rd Qu.:2021-12-28   3rd Qu.:103422                      3rd Qu.:2021-12-17  
##  Max.   :2021-12-28   Max.   :103558                      Max.   :2021-12-18  
##  samplingtime         result2       
##  Length:9122       Min.   :0.06000  
##  Class1:hms        1st Qu.:0.06000  
##  Class2:difftime   Median :0.07000  
##  Mode  :numeric    Mean   :0.08224  
##                    3rd Qu.:0.07000  
##                    Max.   :0.87000

Immunogenicity at second week after the second dose of the investigated vaccine using FRNT testing

## # A tibble: 6 × 4
##   treatment visit   methodoftest results
##   <chr>     <chr>   <chr>        <chr>  
## 1 GBP510    Visit 6 FRNT         966.35 
## 2 GBP510    Visit 6 FRNT         170.62 
## 3 GBP510    Visit 6 FRNT         218.82 
## 4 GBP510    Visit 6 FRNT         292.5  
## 5 GBP510    Visit 6 FRNT         366.98 
## 6 GBP510    Visit 6 FRNT         367.68
frnt_visit6_df <- frnt_visit6_df %>%
  mutate(results = as.numeric(results))
summary_frnt_v6 <- frnt_visit6_df %>%
  group_by(treatment) %>%
  summarize(
    mean = mean(results, na.rm = TRUE),
    SE = sd(results, na.rm = TRUE) / sqrt(n()),
    lower_CI = mean(results, na.rm = TRUE) - qt(0.975, df = n() - 1) * (sd(results, na.rm = TRUE) / sqrt(n())),
    upper_CI = mean(results, na.rm = TRUE) + qt(0.975, df = n() - 1) * (sd(results, na.rm = TRUE) / sqrt(n()))
  )
print(summary_frnt_v6)
## # A tibble: 2 × 5
##   treatment  mean    SE lower_CI upper_CI
##   <chr>     <dbl> <dbl>    <dbl>    <dbl>
## 1 ChAdOx1-S  170.  10.0     150.     190.
## 2 GBP510     447.  20.7     407.     488.
t.test(results ~ treatment, data = frnt_visit6_df)
## 
##  Welch Two Sample t-test
## 
## data:  results by treatment
## t = -12.081, df = 496.38, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group ChAdOx1-S and group GBP510 is not equal to 0
## 95 percent confidence interval:
##  -322.5894 -232.3424
## sample estimates:
## mean in group ChAdOx1-S    mean in group GBP510 
##                169.8788                447.3447

The neutrolized antibodies of GBP510 to virus is significantly higher than that of ChAdOx1-S at visit 6, 2 weeks after the second dose.

Ratio of immunogenicity between 2 vaccines and 95% CI of the ratio

# Calculate means and standard deviations for each treatment group
summary_stats <- frnt_visit6_df  %>%
  group_by(treatment) %>%
  summarize(
    mean = mean(results, na.rm = TRUE),
    sd = sd(results, na.rm = TRUE),
    n = n()
  )
# Calculate the ratio of means
ratio_means <- summary_stats$mean[summary_stats$treatment == 'GBP510'] / summary_stats$mean[summary_stats$treatment == 'ChAdOx1-S']

# Calculate the standard error of the ratio using the delta method
se_ratio <- ratio_means * sqrt(
  (summary_stats$sd[summary_stats$treatment == 'ChAdOx1-S']^2 / (summary_stats$mean[summary_stats$treatment == 'ChAdOx1-S']^2 * summary_stats$n[summary_stats$treatment == 'ChAdOx1-S'])) +
    (summary_stats$sd[summary_stats$treatment == 'GBP510']^2 / (summary_stats$mean[summary_stats$treatment == 'GBP510']^2 * summary_stats$n[summary_stats$treatment == 'GBP510']))
)


# Calculate the 95% confidence interval for the ratio
z_value <- qnorm(0.975)  # 1.96 for 95% CI
lower_CI <- ratio_means - z_value * se_ratio
upper_CI <- ratio_means + z_value * se_ratio

# Print the results
cat("Ratio of means:", ratio_means, "\n")
## Ratio of means: 2.633317
cat("Standard Error of the ratio:", se_ratio, "\n")
## Standard Error of the ratio: 0.1974595
cat("95% Confidence Interval of the ratio: [", lower_CI, ", ", upper_CI, "]\n")
## 95% Confidence Interval of the ratio: [ 2.246304 ,  3.020331 ]

Percents of subjects with >= 4 fold-rise of GMT at Visit 6, 2 weeks after the second vaccine dose compared to the baseline

# Percentage of participants with ≥ 4-fold rise in wild-type virus neutralizing antibody titer from baseline to Visit 6

frnt_visit26_df = df %>% select(treatment, screeningno, visit, methodoftest, results) %>%
  filter((visit == "Visit 2" | visit == "Visit 6") & methodoftest == "FRNT") 

head(frnt_visit26_df)
## # A tibble: 6 × 5
##   treatment screeningno visit   methodoftest results
##   <chr>     <chr>       <chr>   <chr>        <chr>  
## 1 GBP510    401-0002    Visit 2 FRNT         8.08   
## 2 GBP510    401-0002    Visit 6 FRNT         966.35 
## 3 GBP510    401-0016    Visit 2 FRNT         8.08   
## 4 GBP510    401-0016    Visit 6 FRNT         170.62 
## 5 GBP510    401-0019    Visit 2 FRNT         8.08   
## 6 GBP510    401-0019    Visit 6 FRNT         218.82
frnt_visit26_df$visit <- ifelse(frnt_visit26_df$visit == "Visit 2", "Visit2", "Visit6")

frnt_visit26_df <- frnt_visit26_df %>%
  pivot_wider( names_from = visit, values_from = results)
head(frnt_visit26_df)
## # A tibble: 6 × 5
##   treatment screeningno methodoftest Visit2 Visit6
##   <chr>     <chr>       <chr>        <chr>  <chr> 
## 1 GBP510    401-0002    FRNT         8.08   966.35
## 2 GBP510    401-0016    FRNT         8.08   170.62
## 3 GBP510    401-0019    FRNT         8.08   218.82
## 4 GBP510    401-0030    FRNT         8.08   292.5 
## 5 GBP510    401-0033    FRNT         8.08   366.98
## 6 GBP510    401-0035    FRNT         8.08   367.68
frnt_visit26_df <- frnt_visit26_df %>% mutate(Visit2 = as.numeric(Visit2))
frnt_visit26_df <- frnt_visit26_df %>% mutate(Visit6 = as.numeric(Visit6))

frnt_visit26_df = frnt_visit26_df %>% mutate(rise4 = case_when(
  (Visit6 / Visit2) >= 4 ~ "1",
  TRUE ~ "0"
))
# head(frnt_visit26_df)

# Create a contingency table of counts
count_tb_frnt26 <- frnt_visit26_df %>%
  tabyl(treatment, rise4)
print(count_tb_frnt26)
##  treatment  0   1
##  ChAdOx1-S 11 171
##     GBP510  7 355
# Convert counts to percentages
percentage_table_frnt26 <- count_tb_frnt26 %>%
  adorn_percentages("row") %>%
  adorn_pct_formatting(digits = 1)

# View the percentage table
print(percentage_table_frnt26)
##  treatment    0     1
##  ChAdOx1-S 6.0% 94.0%
##     GBP510 1.9% 98.1%

Boxplot of FRNT at Visit 2, 4 and 6

# Filter the dataset for the specified conditions
filtered_df <-  df %>% select(treatment, screeningno, visit, methodoftest, results) %>%
  filter(visit %in% c("Visit 2", "Visit 4", "Visit 6") & methodoftest == "FRNT")
filtered_df = filtered_df %>% mutate(results = as.numeric(results))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `results = as.numeric(results)`.
## Caused by warning:
## ! NAs introduced by coercion
head(filtered_df )
## # A tibble: 6 × 5
##   treatment screeningno visit   methodoftest results
##   <chr>     <chr>       <chr>   <chr>          <dbl>
## 1 GBP510    401-0002    Visit 2 FRNT            8.08
## 2 GBP510    401-0002    Visit 4 FRNT           NA   
## 3 GBP510    401-0002    Visit 6 FRNT          966.  
## 4 GBP510    401-0016    Visit 2 FRNT            8.08
## 5 GBP510    401-0016    Visit 4 FRNT            8.08
## 6 GBP510    401-0016    Visit 6 FRNT          171.
# Filter the dataset for the specified conditions

filtered_df = filtered_df %>% mutate(results = as.numeric(results))

# Create the boxplot
ggplot(filtered_df, aes(x = visit, y = results, fill = treatment)) +
  geom_boxplot() +
  scale_y_log10() +
  labs(
    y = "GMT by FRNT  (log10 scale)",
    title = "GMT of FRNT by Visit and Treatment"
  ) +
  theme_minimal()
## Warning: Removed 422 rows containing non-finite values (`stat_boxplot()`).

Vaccine GBO510 is superior to ChAdOx1-S at visit 6, 2 weeks after the second dose.