library(tidyverse)
## Warning: 패키지 'tidyverse'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'ggplot2'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'tibble'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'readr'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'purrr'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'dplyr'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'lubridate'는 R 버전 4.2.3에서 작성되었습니다
## ── 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.3 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ 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(readxl)
## Warning: 패키지 'readxl'는 R 버전 4.2.3에서 작성되었습니다
library(tidymodels)
## Warning: 패키지 'tidymodels'는 R 버전 4.2.3에서 작성되었습니다
## ── Attaching packages ────────────────────────────────────── tidymodels 1.1.1 ──
## ✔ broom 1.0.5 ✔ rsample 1.2.0
## ✔ dials 1.2.0 ✔ tune 1.1.2
## ✔ infer 1.0.4 ✔ workflows 1.1.3
## ✔ modeldata 1.2.0 ✔ workflowsets 1.0.1
## ✔ parsnip 1.1.1 ✔ yardstick 1.2.0
## ✔ recipes 1.0.8
## Warning: 패키지 'broom'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'dials'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'modeldata'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'parsnip'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'recipes'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'rsample'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'tune'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'workflows'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'workflowsets'는 R 버전 4.2.3에서 작성되었습니다
## Warning: 패키지 'yardstick'는 R 버전 4.2.3에서 작성되었습니다
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ scales::discard() masks purrr::discard()
## ✖ dplyr::filter() masks stats::filter()
## ✖ recipes::fixed() masks stringr::fixed()
## ✖ dplyr::lag() masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step() masks stats::step()
## • Search for functions across packages at https://www.tidymodels.org/find/
theme_set(theme_light())
rm(list = ls())
folder_path <- "d:/r/data extraction process/"
data <-
tibble(filename = list.files(path = folder_path, full.names = TRUE, pattern = "\\.xlsx$")) %>%
mutate(data = map(filename, ~read_excel(.x, sheet = "DATA EXTRACTION SHEET", skip = 2))) %>%
unnest(data) %>%
select(-c(n_T_revised:'ROB Category')) %>%
filter(!is.na(`Coder name`)) %>%
filter(!`Coder name` == "Record your name") %>%
janitor::clean_names() %>%
mutate(batch = str_extract(filename, "(?<=Group )\\d+")) %>%
select(-filename) %>%
group_by(batch) %>%
arrange(coder_name) %>%
mutate(coders = paste0("coder_", dense_rank(coder_name))) %>%
ungroup() %>%
select(-coder_name)
data_long <- data %>%
pivot_longer(
cols = -c(batch, coders, estimate_id),
names_to = "variable",
values_to = "value"
) %>%
mutate(value = na_if(value, "NA"),
value = na_if(value, "N/A"))
data_pivoted <- data_long %>%
pivot_wider(
names_from = coders,
values_from = value
) %>%
mutate(matched = case_when(
is.na(coder_1) & is.na(coder_2) ~ TRUE,
is.na(coder_1) & !is.na(coder_2) ~ FALSE,
!is.na(coder_1) & is.na(coder_2) ~ FALSE,
coder_1 == coder_2 ~ TRUE,
TRUE ~ FALSE))
data_pivoted %>%
count(batch, estimate_id, variable, matched) %>%
mutate(matched = ifelse(matched, "count_true", "count_false")) %>%
pivot_wider(
names_from = matched,
values_from = n,
values_fill = 0 # Fill in 0 if there are missing values
) %>%
count(variable, count_true) %>%
filter(count_true == 1) %>%
mutate(pct = n / max(n)) %>%
filter(pct != 1) %>%
mutate(variable = fct_reorder(variable, pct)) %>%
ggplot(aes(pct, variable)) +
geom_col() +
scale_x_continuous(label = scales::percent_format()) +
labs(y = "",
x = "between-coder reliability")
each_estimate_id <-
data_pivoted %>%
group_by(estimate_id, batch) %>%
summarize(n = n(),
agreement_rate = sum(matched == 1)/n,
.groups = "drop")
each_estimate_id
## # A tibble: 267 × 4
## estimate_id batch n agreement_rate
## <chr> <chr> <int> <dbl>
## 1 73628889_1 48 90 0.789
## 2 73628889_2 48 90 0.789
## 3 73795904_1 47 90 0.756
## 4 73795904_10 47 90 0.744
## 5 73795904_11 47 90 0.711
## 6 73795904_12 47 90 0.711
## 7 73795904_13 47 90 0.711
## 8 73795904_14 47 90 0.711
## 9 73795904_15 47 90 0.7
## 10 73795904_16 47 90 0.7
## # ℹ 257 more rows
study_level <-
each_estimate_id %>%
mutate(id = str_extract(estimate_id, "\\d+(?=_)")) %>%
group_by(id, batch) %>%
summarize(mean = mean(agreement_rate),
.groups = "drop")
study_level
## # A tibble: 44 × 3
## id batch mean
## <chr> <chr> <dbl>
## 1 73628889 48 0.789
## 2 73795904 47 0.723
## 3 73796232 47 0.8
## 4 73797297 47 0.789
## 5 73797942 51 0.8
## 6 73798224 48 0.0111
## 7 73798778 51 0.8
## 8 73801283 48 0.756
## 9 73801284 48 0.5
## 10 73801962 52 0.833
## # ℹ 34 more rows
each_estimate_id %>%
mutate(id = str_extract(estimate_id, "\\d+(?=_)")) %>%
group_by(id, batch) %>%
summarize(n = n(),
mean = mean(agreement_rate),
.groups = "drop") %>%
ggplot(aes(n, mean)) +
geom_point(size = 2, alpha = .7) +
scale_x_log10() +
geom_smooth(se = F, size = 2) +
labs(title = "he number of outcomes in each study and between-coders reliability")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,
## : pseudoinverse used at 0.30103
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,
## : neighborhood radius 0.30103
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,
## : reciprocal condition number 1.8832e-16
- it does not seem related
# study design and QEX between-coders reliability
data %>%
select(estimate_id, evaluation_method) %>%
filter(!str_detect(evaluation_method, regex("linked", ignore_case = T))) %>%
distinct(estimate_id, .keep_all = T) %>%
inner_join(each_estimate_id, by = "estimate_id") %>%
mutate(evaluation_method = fct_reorder(evaluation_method, agreement_rate, .fun = mean)) %>%
ggplot(aes(agreement_rate, evaluation_method, color = evaluation_method)) +
geom_boxplot(size = 1, show.legend = F) +
scale_x_continuous(label = percent_format()) +
guides(color = guide_legend(reverse = TRUE)) +
labs(y = "",
x = "between-coders reliability",
color = "") +
theme(axis.text.y = element_text(hjust = .5)) +
labs(title = "study design and QEX between-coders reliability")
batch_level <-
study_level %>%
group_by(batch) %>%
summarize(mean = mean(mean),
.groups = "drop")
batch_level
## # A tibble: 4 × 2
## batch mean
## <chr> <dbl>
## 1 47 0.637
## 2 48 0.613
## 3 51 0.779
## 4 52 0.698
read_rob_sheet <- function(filename) {
all_sheets <- excel_sheets(filename)
# Find sheets that contain "QED" but not "group"
qed_sheet <- all_sheets[
str_detect(all_sheets, regex("QED", ignore_case = TRUE)) &
!str_detect(all_sheets, regex("group", ignore_case = TRUE))
]
# If matching sheet(s) found, read the first one
if (length(qed_sheet) >= 1) {
return(read_excel(filename, sheet = qed_sheet[1]))
} else {
return(NULL)
}
}
folder_path_rob <- "d:/r/data extraction_ROB/"
data_rob <-
tibble(filename = list.files(path = folder_path_rob, full.names = TRUE, pattern = "\\.xlsx$")) %>%
mutate(data = purrr::map(filename, read_rob_sheet)) %>%
mutate(sheet = "QED") %>%
unnest(data) %>%
mutate(batch = str_extract(filename, "(?<=Group )\\d+")) %>%
group_by(batch) %>%
arrange(Coder) %>%
filter(!is.na(Coder)) %>%
mutate(coders = paste0("coder_", dense_rank(Coder))) %>%
ungroup() %>%
janitor::clean_names() %>%
select(-coder, -general_3, -general_5, -general_7, -general_6, -code, -x8_external_validity, -x9_limitations) %>%
rename(estimate_id = "general_4",
study_design = "general_8",
eval_design = "general_9"
) %>%
mutate(estimate_id = str_remove_all(estimate_id, "\r\n"))
## New names:
## New names:
## New names:
## New names:
## New names:
## New names:
## New names:
## New names:
## New names:
## New names:
## • `General` -> `General...3`
## • `General` -> `General...4`
## • `General` -> `General...5`
## • `General` -> `General...6`
## • `General` -> `General...7`
## • `General` -> `General...8`
## • `General` -> `General...9`
## • `1: Selection bias - Justification` -> `1: Selection bias -
## Justification...11`
## • `1: Selection bias - Justification` -> `1: Selection bias -
## Justification...12`
## • `1: Selection bias - Justification` -> `1: Selection bias -
## Justification...13`
## • `2: Confounding - Justification` -> `2: Confounding - Justification...15`
## • `2: Confounding - Justification` -> `2: Confounding - Justification...16`
## • `2: Confounding - Justification` -> `2: Confounding - Justification...17`
## • `2: Confounding - Justification` -> `2: Confounding - Justification...18`
## • `2: Confounding - Justification` -> `2: Confounding - Justification...19`
data_rob_long <- data_rob %>%
select(-contains("justification")) %>%
pivot_longer(
cols = -c(batch, coders, estimate_id, sheet),
names_to = "variable",
values_to = "value"
) %>%
mutate(value = na_if(value, "NA"),
value = na_if(value, "N/A")) %>%
mutate(value = case_when(
str_detect(variable, "x") & value %in% c("1", "2") ~ "yes",
str_detect(variable, "x") & value %in% c("3", "4") ~ "no",
str_detect(variable, "x") & value == "8" ~ "unclear",
TRUE ~ value
))
extract_answers <- function(text) {
answers <- list()
for(letter in c('a', 'b', 'c', 'd')) {
pattern <- paste0(letter, "\\)\\s*([\\w\\s]+)")
match <- str_match(text, pattern)
answer <- ifelse(is.na(match[1, 2]), NA, match[1, 2])
if (!is.na(answer)) {
answer <- str_remove_all(answer, "\r\n")
answer <- str_replace_all(answer, regex("N/A", ignore_case = T), "na")
answer <- str_replace_all(answer, regex("probably not",ignore_case = T), "no")
answer <- str_replace_all(answer, regex("probably no",ignore_case = T), "no")
answer <- str_replace_all(answer, regex("probably yes",ignore_case = T), "yes")
answer <- str_trim(answer)
answer <- str_to_lower(answer)
answer <- str_replace_all(answer, "na\\s+[a-d]", "na")
answer <- str_trim(answer)
answer <- str_replace_all(answer,"^n$", "na")
answer <- str_replace_all(answer, "unsure", "unclear")
}
answers[[letter]] <- answer
}
return(answers)
}
selection_sub <-
data_rob %>%
pivot_longer(
cols = -c(batch, coders, estimate_id, sheet),
names_to = "variable",
values_to = "value"
) %>%
mutate(value = na_if(value, "NA"),
value = na_if(value, "N/A")) %>%
mutate(value = case_when(
str_detect(variable, "x") & value %in% c("1", "2") ~ "yes",
str_detect(variable, "x") & value %in% c("3", "4") ~ "no",
str_detect(variable, "x") & value == "8" ~ "unclear",
TRUE ~ value
)) %>%
filter(str_detect(variable, "x1")) %>%
filter(!str_detect(value, regex("linked", ignore_case = T))) %>%
filter(!is.na(value)) %>%
mutate(var_num = str_extract(variable, "\\d+$")) %>%
mutate(variable = str_replace(variable, "justification_\\d+$", "justification")) %>%
pivot_wider(
id_cols = c(estimate_id, sheet, batch, coders),
names_from = variable,
values_from = c(value, var_num),
names_glue = "{.value}_{variable}"
) %>%
select(-var_num_x1_selection_bias_assessment) %>%
add_count(estimate_id, var_num_x1_selection_bias_justification) %>%
rename(justification = "value_x1_selection_bias_justification") %>%
filter(n == 2) %>%
select(-n) %>%
arrange(estimate_id) %>%
filter(var_num_x1_selection_bias_justification == 12) %>%
mutate(answers = purrr::map(justification, extract_answers)) %>%
unnest_wider(answers) %>%
mutate_if(is.character, as.factor)
library(patchwork)
library(viridis)
## 필요한 패키지를 로딩중입니다: viridisLite
## Warning: 패키지 'viridisLite'는 R 버전 4.2.3에서 작성되었습니다
##
## 다음의 패키지를 부착합니다: 'viridis'
## The following object is masked from 'package:scales':
##
## viridis_pal
library(ggrepel)
custom_colors <- viridis_pal()(4)
selection_a <-
selection_sub %>%
add_count(value_x1_selection_bias_assessment, name = "total") %>%
count(value_x1_selection_bias_assessment, a, total) %>%
mutate(pct = n/ total) %>%
mutate(a = fct_relevel(a, "yes", "no", "na", "unclear")) |>
ggplot(aes(pct, value_x1_selection_bias_assessment, fill = a)) +
geom_col(width = .5) +
scale_x_continuous(label = percent_format(),
expand = c(0,0)) +
scale_fill_manual(values = custom_colors) +
labs(y = "Sub-criteria a)",
fill = "",
x = "") +
theme(legend.position = "top",
axis.text.x = element_blank(),
axis.ticks = element_blank(),
axis.text.y = element_text(size = 10, hjust =.5))
selection_b <-
selection_sub %>%
add_count(value_x1_selection_bias_assessment, name = "total") %>%
count(value_x1_selection_bias_assessment, b, total) %>%
mutate(pct = n/ total) %>%
mutate(b = fct_relevel(b, "yes", "no")) |>
ggplot(aes(pct, value_x1_selection_bias_assessment, fill = b)) +
geom_col(width = .5, show.legend = F) +
scale_x_continuous(label = percent_format(),
expand = c(0,0)) +
scale_fill_manual(values = custom_colors) +
labs(y = "Sub-criteria b)",
fill = NULL,
x = "") +
theme(legend.position = "top",
axis.text.x = element_blank(),
axis.ticks = element_blank(),
axis.text.y = element_text(size = 10, hjust =.5))
selection_c <-
selection_sub %>%
add_count(value_x1_selection_bias_assessment, name = "total") %>%
mutate(either_yes = ifelse(a == "yes" | b == "yes", "yes", "no")) %>%
count(value_x1_selection_bias_assessment, either_yes, total) %>%
mutate(pct = n/ total) %>%
mutate(either_yes = fct_relevel(either_yes, "yes", "no")) |>
ggplot(aes(pct, value_x1_selection_bias_assessment, fill = either_yes)) +
geom_col(width = .5, show.legend = F) +
scale_x_continuous(label = percent_format(),
expand = c(0,0)) +
scale_fill_manual(values = custom_colors) +
labs(y = "Either yes in a) or b)",
fill = NULL,
x = "") +
theme(legend.position = "top",
axis.ticks = element_blank(),
axis.text.y = element_text(size = 10, hjust =.5))
selection_a + selection_b + selection_c + plot_layout(nrow = 3)
Focusing on the selection bias assessment in PSM (N = 150 for two coders) - the current decision rules indicate either yes in a) or b) make “yes” for the overall analysis. The decision rules would be great to revisit the relevance between sub criteria and the final assessment outcome. - For instance, There is no sub-criteria for the self-selection bias. the sub criteria needs to revised to make another sub-criteria for the self-selection bias
confounding_sub <-
data_rob %>%
pivot_longer(
cols = -c(batch, coders, estimate_id, sheet),
names_to = "variable",
values_to = "value"
) %>%
mutate(value = na_if(value, "NA"),
value = na_if(value, "N/A")) %>%
mutate(value = case_when(
str_detect(variable, "x") & value %in% c("1", "2") ~ "yes",
str_detect(variable, "x") & value %in% c("3", "4") ~ "no",
str_detect(variable, "x") & value == "8" ~ "unclear",
TRUE ~ value
)) %>%
filter(str_detect(variable, "x2")) %>%
filter(!str_detect(value, regex("linked", ignore_case = T))) %>%
filter(!is.na(value)) %>%
mutate(var_num = str_extract(variable, "\\d+$")) %>%
mutate(variable = str_replace(variable, "justification_\\d+$", "justification"))%>%
pivot_wider(
id_cols = c(estimate_id, sheet, batch, coders),
names_from = variable,
values_from = c(value, var_num),
names_glue = "{.value}_{variable}",
values_fill = list(0),
values_fn = list(value = list, var_num = list)) %>%
unnest() %>%
select(-var_num_x2_confounding_assessment) %>%
add_count(estimate_id, var_num_x2_confounding_justification) %>%
rename(justification = "value_x2_confounding_justification") %>%
filter(n == 2) %>%
select(-n) %>%
arrange(estimate_id) %>%
filter(var_num_x2_confounding_justification == 19) %>%
mutate(answers = purrr::map(justification, extract_answers)) %>%
unnest_wider(answers) %>%
mutate_if(is.character, as.factor)
## Warning: `cols` is now required when using `unnest()`.
## ℹ Please use `cols = c(value_x2_confounding_assessment,
## value_x2_confounding_justification, var_num_x2_confounding_assessment,
## var_num_x2_confounding_justification)`.
data_rob_pivoted <- data_rob_long %>%
add_count(batch,estimate_id, coders, name = "checker") %>%
filter(checker != max(checker)) %>%
select(-checker) %>%
pivot_wider(
names_from = coders,
values_from = value
) %>%
mutate(matched = case_when(
is.na(coder_1) & is.na(coder_2) ~ NA,
is.na(coder_1) & !is.na(coder_2) ~ FALSE,
!is.na(coder_1) & is.na(coder_2) ~ FALSE,
coder_1 == coder_2 ~ TRUE,
TRUE ~ FALSE)) %>%
filter(!is.na(matched))
data_rob_pivoted %>%
count(batch, estimate_id, variable, matched) %>%
mutate(matched = ifelse(matched, "count_true", "count_false")) %>%
pivot_wider(
names_from = matched,
values_from = n,
values_fill = 0 # Fill in 0 if there are missing values
) %>%
count(variable, count_true) %>%
filter(count_true == 1) %>%
mutate(pct = n / max(n)) %>%
filter(pct != 1) %>%
mutate(variable = fct_reorder(variable, pct)) %>%
ggplot(aes(pct, variable)) +
geom_col() +
scale_x_continuous(label = scales::percent_format()) +
labs(y = "",
x = "between-coder reliability",
title = "ROB only QED")
reference_table <- tibble(
eval_design = c(1, 2, 3, 4, 5, 6, 7),
Variable_Name = c(
"Regression discontinuity",
"Instrumental Variable",
"Statistical Matching",
"Difference-in-difference",
"Interrupted time series",
"Natural experiments",
"Endogenous treatment-effect model"
)
) %>%
mutate(eval_design = as.character(eval_design))
data_rob_pivoted %>%
inner_join(data_rob %>%
distinct(estimate_id, .keep_all = T) %>%
select(estimate_id, eval_design) %>%
filter(!str_detect(eval_design, regex("linked", ignore_case = T))), by = "estimate_id") %>%
inner_join(reference_table, count_true, by = "eval_design") %>%
add_count(variable, Variable_Name, name = "total") %>%
count(batch, estimate_id, variable, Variable_Name, matched, total) %>%
filter(variable != "filename") %>%
mutate(matched = ifelse(matched, "count_true", "count_false")) %>%
count(Variable_Name, variable, matched, total) %>%
arrange(Variable_Name) %>%
filter(matched == "count_true") %>%
mutate(pct = n / total) %>%
mutate(variable = fct_reorder(variable, pct)) %>%
ggplot(aes(pct, Variable_Name, fill = Variable_Name)) +
geom_col() +
facet_wrap(~variable) +
scale_x_continuous(label = scales::percent_format(),
expand = c(0.01,0)) +
labs(y = "",
x = "between-coder reliability",
title = "ROB only QED",
fill = "") +
theme(legend.position = "bottom",
axis.text.y = element_text(hjust = .5),
axis.ticks = element_blank(),
plot.title = element_text(size = 15, hjust = .5))
- selection bias (IV, DiD) - confounding (statistical matching, IV) -
reporting bias (IV)
No sub-criteria is likely to make the between-coders reliability greater. For instance, IV seems the lowest, whereas the PSM seems the highest.
each_estimate_id_rob <-
data_rob_pivoted %>%
group_by(estimate_id, batch) %>%
summarize(n = n(),
agreement_rate = sum(matched == 1)/n,
.groups = "drop")
each_estimate_id_rob
## # A tibble: 322 × 4
## estimate_id batch n agreement_rate
## <chr> <chr> <int> <dbl>
## 1 73628889_1 48 10 0.6
## 2 73628889_2 48 10 0.6
## 3 73795470_1 46 10 0
## 4 73795470_2 46 10 0
## 5 73795470_3 46 10 0
## 6 73795470_4 46 10 0
## 7 73795470_5 46 10 0
## 8 73795470_6 46 10 0
## 9 73795904_1 47 10 0.4
## 10 73795904_10 47 10 0.4
## # ℹ 312 more rows
each_estimate_id_rob %>%
inner_join(data_rob %>%
distinct(estimate_id, .keep_all = T) %>%
select(estimate_id, eval_design) %>%
filter(!str_detect(eval_design, regex("linked", ignore_case = T))), by = "estimate_id") %>%
inner_join(reference_table , by = "eval_design") %>%
ggplot(aes(agreement_rate, fill = Variable_Name)) +
geom_density(alpha = .7, linewidth = 0) +
labs(fill = "",
x = "Between-coders reliability",
title = "Density plot depending on the evaluation method")
- IV seems the most difficult evaluation method to assess the risk of
bias the mean is smaller than others. Endogenous treatment-effect model
is the highest between-coders reliability
study_level_rob <-
each_estimate_id_rob %>%
mutate(id = str_extract(estimate_id, "\\d+(?=_)")) %>%
group_by(id, batch) %>%
summarize(mean = mean(agreement_rate),
.groups = "drop")
study_level_rob
## # A tibble: 49 × 3
## id batch mean
## <chr> <chr> <dbl>
## 1 73628889 48 0.6
## 2 73795470 46 0
## 3 73795904 47 0.4
## 4 73796232 47 0.4
## 5 73797668 46 0.425
## 6 73797942 51 0.7
## 7 73798036 46 0.5
## 8 73798224 48 0
## 9 73798237 46 0
## 10 73798251 46 0
## # ℹ 39 more rows
batch_level_rob <-
study_level_rob %>%
group_by(batch) %>%
summarize(mean = mean(mean),
.groups = "drop")
batch_level_rob
## # A tibble: 5 × 2
## batch mean
## <chr> <dbl>
## 1 46 0.222
## 2 47 0.617
## 3 48 0.627
## 4 51 0.617
## 5 52 0.670
batch_level_rob |>
rename(rob = "mean") |>
inner_join(batch_level |>
rename(qex = "mean"),
by = "batch") %>%
mutate(mean = (rob + qex) / 2)
## # A tibble: 4 × 4
## batch rob qex mean
## <chr> <dbl> <dbl> <dbl>
## 1 47 0.617 0.637 0.627
## 2 48 0.627 0.613 0.620
## 3 51 0.617 0.779 0.698
## 4 52 0.670 0.698 0.684