| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | server <- function(input, output, session) { |
| |
|
| | |
| | |
| | |
| | rv <- reactiveValues( |
| | raw_data = NULL, |
| | data_source = NULL, |
| | data_validated = FALSE, |
| | analysis_complete = FALSE, |
| | results = list(), |
| | unmapped_foods = NULL, |
| | processing = FALSE |
| | ) |
| |
|
| | |
| | |
| | |
| | observeEvent(TRUE, { |
| | showModal(modalDialog( |
| | title = NULL, |
| | size = "l", |
| | easyClose = FALSE, |
| | footer = NULL, |
| | tags$div( |
| | class = "splash-content text-center", |
| |
|
| | |
| | tags$div( |
| | class = "splash-logo-container", |
| | |
| | tags$video( |
| | id = "splash-video", |
| | class = "splash-video", |
| | autoplay = NA, |
| | muted = NA, |
| | playsinline = NA, |
| | tags$source(src = "splash_animation.mp4", type = "video/mp4") |
| | ), |
| | |
| | tags$img( |
| | id = "splash-logo", |
| | class = "splash-logo hidden", |
| | src = "PEPlogo_720x720.jpeg", |
| | alt = "Polyphenol Estimation Pipeline" |
| | ) |
| | ), |
| |
|
| | |
| | tags$div( |
| | class = "splash-info", |
| | tags$p( |
| | class = "splash-attribution", |
| | tags$span(class = "attribution-label", "Pipeline Developed by"), |
| | tags$br(), |
| | tags$span(class = "developer-name", "Stephanie M.G. Wilson"), |
| | tags$br(), |
| | tags$span(class = "developer-affiliation", "University of California, Davis"), |
| | tags$br(), |
| | tags$a( |
| | href = "https://github.com/SWi1/polyphenol_pipeline", |
| | target = "_blank", |
| | class = "repo-link", |
| | "View Pipeline Repository" |
| | ) |
| | ) |
| | ), |
| |
|
| | |
| | tags$div( |
| | class = "splash-buttons", |
| | actionButton("splash_start", "Get Started", class = "btn-primary btn-lg splash-btn") |
| | ), |
| |
|
| | |
| | tags$script(HTML(" |
| | (function() { |
| | // Wait for modal to be fully rendered |
| | var checkVideo = setInterval(function() { |
| | var video = document.getElementById('splash-video'); |
| | var logo = document.getElementById('splash-logo'); |
| | |
| | if (video) { |
| | clearInterval(checkVideo); |
| | |
| | // Stop video 1 second before end to avoid the fade-out frames |
| | video.addEventListener('timeupdate', function() { |
| | if (video.duration && video.currentTime >= video.duration - 1.0) { |
| | video.pause(); |
| | } |
| | }); |
| | |
| | // Fallback: if video fails to load, show static logo |
| | video.addEventListener('error', function() { |
| | video.style.display = 'none'; |
| | if (logo) { |
| | logo.classList.remove('hidden'); |
| | logo.classList.add('fade-in'); |
| | } |
| | }); |
| | |
| | // If video can't play (e.g., autoplay blocked), show static logo |
| | var playPromise = video.play(); |
| | if (playPromise !== undefined) { |
| | playPromise.catch(function(error) { |
| | video.style.display = 'none'; |
| | if (logo) { |
| | logo.classList.remove('hidden'); |
| | logo.classList.add('fade-in'); |
| | } |
| | }); |
| | } |
| | } |
| | }, 100); |
| | })(); |
| | ")) |
| | ) |
| | )) |
| | }, once = TRUE) |
| |
|
| | observeEvent(input$splash_start, { |
| | removeModal() |
| | }) |
| |
|
| | |
| | observeEvent(input$go_to_input, { |
| | updateNavbarPage(session, "main_nav", selected = "input") |
| | }) |
| |
|
| | |
| | |
| | |
| | load_demo_data <- function() { |
| | if (file.exists(DEMO_DATA_FILE)) { |
| | rv$raw_data <- vroom(DEMO_DATA_FILE, show_col_types = FALSE) |
| | rv$data_source <- "ASA24" |
| | rv$data_validated <- TRUE |
| | updateRadioGroupButtons(session, "data_source", selected = "ASA24") |
| | showNotification("Demo data loaded (ASA24 format)", type = "message") |
| | } else { |
| | showNotification("Demo data file not found", type = "error") |
| | } |
| | } |
| |
|
| | observeEvent(input$load_demo, { |
| | load_demo_data() |
| | }) |
| |
|
| | |
| | |
| | |
| | observeEvent(input$diet_file, { |
| | req(input$diet_file) |
| |
|
| | tryCatch({ |
| | file_ext <- tools::file_ext(input$diet_file$name) |
| |
|
| | if (file_ext %in% c("csv", "CSV")) { |
| | rv$raw_data <- vroom(input$diet_file$datapath, show_col_types = FALSE) |
| | } else if (file_ext %in% c("xlsx", "xls")) { |
| | rv$raw_data <- read_xlsx(input$diet_file$datapath) |
| | } else { |
| | showNotification("Unsupported file format. Use CSV or Excel.", type = "error") |
| | return() |
| | } |
| |
|
| | rv$data_source <- input$data_source |
| |
|
| | if (input$data_source == "ASA24") { |
| | validation <- validate_asa24_data(rv$raw_data) |
| | } else { |
| | validation <- validate_nhanes_data(rv$raw_data) |
| | } |
| |
|
| | rv$data_validated <- validation$valid |
| | if (!validation$valid) { |
| | showNotification(validation$message, type = "error", duration = 10) |
| | } else { |
| | showNotification("Data loaded and validated", type = "message") |
| | } |
| |
|
| | }, error = function(e) { |
| | showNotification(paste("Error reading file:", e$message), type = "error") |
| | rv$raw_data <- NULL |
| | rv$data_validated <- FALSE |
| | }) |
| | }) |
| |
|
| | |
| | |
| | |
| | observeEvent(input$reset_btn, { |
| | showModal(modalDialog( |
| | title = "Confirm Reset", |
| | "Are you sure you want to clear all data and results? This cannot be undone.", |
| | footer = tagList( |
| | modalButton("Cancel"), |
| | actionButton("confirm_reset", "Yes, Reset", class = "btn-danger") |
| | ) |
| | )) |
| | }) |
| |
|
| | observeEvent(input$confirm_reset, { |
| | rv$raw_data <- NULL |
| | rv$data_source <- NULL |
| | rv$data_validated <- FALSE |
| | rv$analysis_complete <- FALSE |
| | rv$results <- list() |
| | rv$unmapped_foods <- NULL |
| | removeModal() |
| | showNotification("All data cleared", type = "message") |
| | }) |
| |
|
| | |
| | |
| | |
| | output$data_status <- renderUI({ |
| | if (is.null(rv$raw_data)) { |
| | tags$div( |
| | class = "alert alert-info", |
| | icon("info-circle"), |
| | " Upload a dietary data file or load demo data to begin." |
| | ) |
| | } else if (!rv$data_validated) { |
| | tags$div( |
| | class = "alert alert-warning", |
| | icon("triangle-exclamation"), |
| | " Data validation failed. Check that your file has the required columns." |
| | ) |
| | } else { |
| | n_subjects <- length(unique(rv$raw_data[[if(rv$data_source == "ASA24") "UserName" else "SEQN"]])) |
| | n_rows <- nrow(rv$raw_data) |
| |
|
| | tags$div( |
| | class = "alert alert-success", |
| | icon("check-circle"), |
| | sprintf(" Data loaded: %d subjects, %d food records", n_subjects, n_rows) |
| | ) |
| | } |
| | }) |
| |
|
| | |
| | |
| | |
| | output$data_preview <- renderDT({ |
| | req(rv$raw_data) |
| |
|
| | preview_cols <- if (rv$data_source == "ASA24") { |
| | intersect(c("UserName", "RecallNo", "FoodCode", "Food_Description", "FoodAmt", "KCAL"), |
| | names(rv$raw_data)) |
| | } else { |
| | intersect(c("SEQN", "RecallNo", "DRXIFDCD", "DRXIKCAL"), |
| | names(rv$raw_data)) |
| | } |
| |
|
| | datatable( |
| | head(rv$raw_data[, preview_cols, drop = FALSE], 100), |
| | options = list( |
| | pageLength = 10, |
| | scrollX = TRUE, |
| | dom = 'tip' |
| | ), |
| | class = "display compact", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | |
| | |
| | |
| | observeEvent(input$run_pipeline, { |
| | req(rv$raw_data, rv$data_validated) |
| |
|
| | if (!databases_ready()) { |
| | showNotification("Reference databases not loaded. Cannot run pipeline.", type = "error") |
| | return() |
| | } |
| |
|
| | rv$processing <- TRUE |
| |
|
| | withProgress(message = "Running pipeline...", value = 0, { |
| |
|
| | tryCatch({ |
| | |
| | incProgress(0.1, detail = "Preparing dietary data") |
| |
|
| | if (rv$data_source == "ASA24") { |
| | input_data <- rv$raw_data %>% |
| | rename(subject = UserName) |
| | } else { |
| | input_data <- rv$raw_data %>% |
| | rename(subject = SEQN) |
| | } |
| |
|
| | input_data_clean <- input_data %>% |
| | group_by(subject) %>% |
| | filter(n_distinct(RecallNo) > 1) %>% |
| | ungroup() |
| |
|
| | if ("RecallStatus" %in% names(input_data_clean)) { |
| | input_data_clean <- input_data_clean %>% |
| | filter(RecallStatus != 5) |
| | } |
| |
|
| | |
| | incProgress(0.15, detail = "Calculating nutrient totals") |
| |
|
| | nutrient_cols <- if ("KCAL" %in% names(input_data_clean)) { |
| | kcal_idx <- which(names(input_data_clean) == "KCAL") |
| | b12_idx <- which(names(input_data_clean) == "B12_ADD") |
| | if (length(b12_idx) > 0) { |
| | names(input_data_clean)[kcal_idx:b12_idx] |
| | } else { |
| | "KCAL" |
| | } |
| | } else { |
| | grep("^DRXI", names(input_data_clean), value = TRUE) |
| | } |
| |
|
| | input_total_nutrients <- input_data_clean %>% |
| | group_by(subject, RecallNo) %>% |
| | summarize(across(any_of(nutrient_cols), ~ sum(.x, na.rm = TRUE), .names = "Total_{.col}"), |
| | .groups = "drop") |
| |
|
| | if (rv$data_source == "ASA24") { |
| | input_data_minimal <- input_data_clean %>% |
| | rename(wweia_food_code = FoodCode, food_description = Food_Description) %>% |
| | select(any_of(c("subject", "RecallNo", "wweia_food_code", "food_description", "FoodAmt"))) |
| | } else { |
| | input_data_minimal <- input_data_clean %>% |
| | rename(wweia_food_code = DRXIFDCD) %>% |
| | select(any_of(c("subject", "RecallNo", "wweia_food_code", "DRXIGRMS"))) %>% |
| | rename(FoodAmt = DRXIGRMS) |
| | } |
| |
|
| | |
| | incProgress(0.25, detail = "Disaggregating foods") |
| |
|
| | FDD_adjusted <- apply_brewing_adjustment(FDD_V3) |
| |
|
| | merged_data <- left_join(input_data_minimal, FDD_adjusted, by = "wweia_food_code", |
| | relationship = "many-to-many") %>% |
| | mutate(FoodAmt_Ing_g = FoodAmt * ( |
| | coalesce(brewing_adjustment_percentage, ingredient_percent) / 100)) |
| |
|
| | |
| | incProgress(0.35, detail = "Mapping to FooDB database") |
| |
|
| | input_mapped <- merged_data %>% |
| | left_join(fdd_foodb_mapping, by = "fdd_ingredient") |
| |
|
| | rv$unmapped_foods <- input_mapped %>% |
| | filter(!is.na(fdd_ingredient) & is.na(orig_food_common_name)) %>% |
| | distinct(fdd_ingredient) %>% |
| | pull(fdd_ingredient) |
| |
|
| | |
| | incProgress(0.45, detail = "Calculating polyphenol content") |
| |
|
| | input_mapped_content <- input_mapped %>% |
| | left_join(FooDB_mg_100g, by = "food_id", relationship = "many-to-many") %>% |
| | mutate( |
| | pp_consumed = if_else( |
| | compound_public_id %in% c("FDB000095", "FDB017114") & food_id == 38, |
| | (orig_content_avg_RFadj * 0.01) * FoodAmt_Ing_g * (ingredient_percent / 100), |
| | (orig_content_avg_RFadj * 0.01) * FoodAmt_Ing_g |
| | ) |
| | ) |
| |
|
| | input_kcal <- input_total_nutrients %>% |
| | select(subject, RecallNo, any_of(c("Total_KCAL", "Total_DRXIKCAL"))) |
| |
|
| | if (!"Total_KCAL" %in% names(input_kcal) && "Total_DRXIKCAL" %in% names(input_kcal)) { |
| | input_kcal <- input_kcal %>% rename(Total_KCAL = Total_DRXIKCAL) |
| | } |
| |
|
| | input_polyphenol_kcal <- left_join(input_mapped_content, input_kcal, by = c("subject", "RecallNo")) |
| |
|
| | |
| | incProgress(0.55, detail = "Summarizing total intake") |
| |
|
| | content_by_recall <- input_polyphenol_kcal %>% |
| | group_by(subject, RecallNo) %>% |
| | summarise( |
| | pp_recallsum_mg = sum(pp_consumed, na.rm = TRUE), |
| | Total_KCAL = first(Total_KCAL), |
| | .groups = "drop" |
| | ) %>% |
| | mutate(pp_recallsum_mg1000kcal = pp_recallsum_mg / (Total_KCAL / 1000)) |
| |
|
| | content_by_subject <- content_by_recall %>% |
| | group_by(subject) %>% |
| | summarise( |
| | pp_average_mg = mean(pp_recallsum_mg, na.rm = TRUE), |
| | kcal_average = mean(Total_KCAL, na.rm = TRUE), |
| | pp_average_mg_1000kcal = pp_average_mg / (kcal_average / 1000), |
| | .groups = "drop" |
| | ) |
| |
|
| | rv$results$total_by_subject <- content_by_subject |
| | rv$results$total_by_recall <- content_by_recall |
| |
|
| | |
| | incProgress(0.65, detail = "Calculating class-level intake") |
| |
|
| | input_with_class <- input_polyphenol_kcal %>% |
| | left_join(class_tax, by = "compound_public_id") |
| |
|
| | class_by_recall <- input_with_class %>% |
| | group_by(subject, RecallNo, class) %>% |
| | summarise( |
| | class_intake_mg = sum(pp_consumed, na.rm = TRUE), |
| | Total_KCAL = first(Total_KCAL), |
| | .groups = "drop" |
| | ) %>% |
| | filter(!is.na(class)) %>% |
| | mutate(class_intake_mg1000kcal = class_intake_mg / (Total_KCAL / 1000)) |
| |
|
| | class_by_subject <- class_by_recall %>% |
| | group_by(subject, class) %>% |
| | summarise( |
| | Avg_class_intake_mg = mean(class_intake_mg, na.rm = TRUE), |
| | avg_Total_KCAL = mean(Total_KCAL, na.rm = TRUE), |
| | .groups = "drop" |
| | ) %>% |
| | mutate(class_intake_mg1000kcal = Avg_class_intake_mg / (avg_Total_KCAL / 1000)) |
| |
|
| | rv$results$class_by_subject <- class_by_subject |
| | rv$results$class_by_recall <- class_by_recall |
| |
|
| | |
| | incProgress(0.75, detail = "Identifying food contributors") |
| |
|
| | food_contributors <- input_polyphenol_kcal %>% |
| | group_by(fdd_ingredient) %>% |
| | summarise( |
| | food_pp_average_mg1000kcal = mean(pp_consumed, na.rm = TRUE) / mean(Total_KCAL, na.rm = TRUE) * 1000, |
| | total_times_consumed = n(), |
| | n_subjects = n_distinct(subject), |
| | .groups = "drop" |
| | ) %>% |
| | filter(!is.na(fdd_ingredient)) %>% |
| | arrange(desc(food_pp_average_mg1000kcal)) |
| |
|
| | rv$results$food_contributors <- food_contributors |
| |
|
| | |
| | if (input$calculate_dii) { |
| | incProgress(0.85, detail = "Calculating DII scores") |
| |
|
| | |
| | if (!is.null(FooDB_eugenol)) { |
| | eugenol_intake <- input_mapped %>% |
| | left_join( |
| | FooDB_eugenol %>% select(-c(source_type, food_name, orig_food_common_name, |
| | orig_food_scientific_name, orig_source_id, |
| | orig_source_name, citation, preparation_type)), |
| | by = "food_id" |
| | ) %>% |
| | filter(!is.na(orig_content_avg)) %>% |
| | mutate(eugenol_mg = (orig_content_avg * 0.01) * FoodAmt_Ing_g) %>% |
| | group_by(subject, RecallNo) %>% |
| | summarise(EUGENOL = sum(eugenol_mg, na.rm = TRUE), .groups = "drop") |
| | } else { |
| | eugenol_intake <- input_mapped %>% |
| | distinct(subject, RecallNo) %>% |
| | mutate(EUGENOL = 0) |
| | } |
| |
|
| | |
| | if (!is.null(FooDB_DII_subclasses)) { |
| | subclass_intake <- input_polyphenol_kcal %>% |
| | filter(compound_public_id %in% FooDB_DII_subclasses$compound_public_id) %>% |
| | left_join(FooDB_DII_subclasses %>% select(compound_public_id, component), |
| | by = "compound_public_id") %>% |
| | group_by(subject, RecallNo, component) %>% |
| | summarise(component_sum = sum(pp_consumed, na.rm = TRUE), .groups = "drop") %>% |
| | pivot_wider(names_from = component, values_from = component_sum, values_fill = 0) %>% |
| | rename_with(~ case_when( |
| | . == "Isoflavones" ~ "ISOFLAVONES", |
| | . == "Flavan-3-ols" ~ "FLA3OL", |
| | . == "Flavones" ~ "FLAVONES", |
| | . == "Flavonols" ~ "FLAVONOLS", |
| | . == "Flavanones" ~ "FLAVONONES", |
| | . == "Anthocyanidins" ~ "ANTHOC", |
| | TRUE ~ . |
| | ), .cols = -c(subject, RecallNo)) |
| | } else { |
| | subclass_intake <- input_mapped %>% |
| | distinct(subject, RecallNo) %>% |
| | mutate(ISOFLAVONES = 0, FLA3OL = 0, FLAVONES = 0, |
| | FLAVONOLS = 0, FLAVONONES = 0, ANTHOC = 0) |
| | } |
| |
|
| | |
| | |
| |
|
| | |
| | fdd_unique <- FDD_V3 %>% distinct(fdd_ingredient) |
| |
|
| | |
| | garlic_ingredients <- fdd_unique %>% |
| | filter(grepl("garlic", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "GARLIC") |
| |
|
| | ginger_ingredients <- fdd_unique %>% |
| | filter(grepl("ginger", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "GINGER") |
| |
|
| | onion_ingredients <- fdd_unique %>% |
| | filter(grepl("onion", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "ONION") |
| |
|
| | turmeric_ingredients <- fdd_unique %>% |
| | filter(grepl("turmeric", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "TURMERIC") |
| |
|
| | |
| | |
| | tea_ingredients <- fdd_unique %>% |
| | filter(grepl("tea", fdd_ingredient, ignore.case = TRUE)) %>% |
| | filter(grepl("black|oolong|green", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "TEA") |
| |
|
| | |
| | |
| | pepper_ingredients <- fdd_unique %>% |
| | filter(grepl("pepper", fdd_ingredient, ignore.case = TRUE)) %>% |
| | filter(grepl("spices", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "PEPPER") |
| |
|
| | |
| | thyme_ingredients <- fdd_unique %>% |
| | filter(grepl("thyme|oregano", fdd_ingredient, ignore.case = TRUE)) %>% |
| | mutate(component = "THYME") |
| |
|
| | |
| | dii_foods_df <- bind_rows( |
| | garlic_ingredients, |
| | ginger_ingredients, |
| | onion_ingredients, |
| | turmeric_ingredients, |
| | tea_ingredients, |
| | pepper_ingredients, |
| | thyme_ingredients |
| | ) |
| |
|
| | if (nrow(dii_foods_df) > 0) { |
| | food_component_intake <- input_mapped %>% |
| | filter(fdd_ingredient %in% dii_foods_df$fdd_ingredient) %>% |
| | left_join(dii_foods_df, by = "fdd_ingredient") %>% |
| | group_by(subject, RecallNo, component) %>% |
| | summarise(component_sum = sum(FoodAmt_Ing_g, na.rm = TRUE), .groups = "drop") %>% |
| | pivot_wider(names_from = component, values_from = component_sum, values_fill = 0) |
| | } else { |
| | food_component_intake <- input_mapped %>% |
| | distinct(subject, RecallNo) |
| | } |
| |
|
| | |
| | dii_merge <- input_total_nutrients %>% |
| | rename_with(~ gsub("^Total_", "", .x)) %>% |
| | left_join(eugenol_intake, by = c("subject", "RecallNo")) %>% |
| | left_join(subclass_intake, by = c("subject", "RecallNo")) %>% |
| | left_join(food_component_intake, by = c("subject", "RecallNo")) |
| |
|
| | |
| | nutrient_mapping <- tribble( |
| | ~new_name, ~asa24_name, ~nhanes_name, |
| | "ALCOHOL", "ALC", "DRXIALCO", |
| | "VITB12", "VB12", "DRXIVB12", |
| | "VITB6", "VB6", "DRXIVB6", |
| | "BCAROTENE", "BCAR", "DRXIBCAR", |
| | "CAFF", "CAFF", "DRXICAFF", |
| | "CARB", "CARB", "DRXICARB", |
| | "CHOLES", "CHOLE", "DRXICHOL", |
| | "KCAL", "KCAL", "DRXIKCAL", |
| | "TOTALFAT", "TFAT", "DRXITFAT", |
| | "FIBER", "FIBE", "DRXIFIBE", |
| | "FOLICACID", "FA", "DRXIFA", |
| | "IRON", "IRON", "DRXIIRON", |
| | "MG", "MAGN", "DRXIMAGN", |
| | "MUFA", "MFAT", "DRXIMFAT", |
| | "NIACIN", "NIAC", "DRXINIAC", |
| | "P183", "P183", "DRXIP183", |
| | "P184", "P184", "DRXIP184", |
| | "P205", "P205", "DRXIP205", |
| | "P225", "P225", "DRXIP225", |
| | "P226", "P226", "DRXIP226", |
| | "P182", "P182", "DRXIP182", |
| | "P204", "P204", "DRXIP204", |
| | "PROTEIN", "PROT", "DRXIPROT", |
| | "PUFA", "PFAT", "DRXIPFAT", |
| | "RIBOFLAVIN", "VB2", "DRXIVB2", |
| | "SATFAT", "SFAT", "DRXISFAT", |
| | "SE", "SELE", "DRXISELE", |
| | "THIAMIN", "VB1", "DRXIVB1", |
| | "VITA", "VARA", "DRXIVARA", |
| | "VITC", "VC", "DRXIVC", |
| | "VITD", "VITD", "DRXIVD", |
| | "VITE", "ATOC", "DRXIATOC", |
| | "ZN", "ZINC", "DRXIZINC" |
| | ) |
| |
|
| | is_nhanes <- any(startsWith(names(dii_merge), "DRX")) |
| | old_names <- if (is_nhanes) nutrient_mapping$nhanes_name else nutrient_mapping$asa24_name |
| | new_names <- nutrient_mapping$new_name |
| | existing_idx <- which(old_names %in% names(dii_merge)) |
| |
|
| | if (length(existing_idx) > 0) { |
| | dii_merge <- dii_merge %>% |
| | rename(!!!setNames(old_names[existing_idx], new_names[existing_idx])) |
| | } |
| |
|
| | |
| | dii_cohort <- dii_merge %>% |
| | mutate( |
| | CAFFEINE = if ("CAFF" %in% names(.)) CAFF / 1000 else 0, |
| | N3FAT = rowSums(across(any_of(c("P183", "P184", "P205", "P225", "P226"))), na.rm = TRUE), |
| | N6FAT = rowSums(across(any_of(c("P182", "P204"))), na.rm = TRUE), |
| | TURMERIC = if ("TURMERIC" %in% names(.)) TURMERIC * 1000 else 0, |
| | THYME = if ("THYME" %in% names(.)) THYME * 1000 else 0 |
| | ) |
| |
|
| | |
| | dii_params <- tribble( |
| | ~Variable, ~Overall_inflammatory_score, ~Global_mean, ~SD, |
| | "ALCOHOL", -0.278, 13.98, 3.72, |
| | "VITB12", 0.106, 5.15, 2.7, |
| | "VITB6", -0.365, 1.47, 0.74, |
| | "BCAROTENE", -0.584, 3718, 1720, |
| | "CAFFEINE", -0.11, 8.05, 6.67, |
| | "CARB", 0.097, 272.2, 40, |
| | "CHOLES", 0.11, 279.4, 51.2, |
| | "KCAL", 0.18, 2056, 338, |
| | "EUGENOL", -0.14, 0.01, 0.08, |
| | "TOTALFAT", 0.298, 71.4, 19.4, |
| | "FIBER", -0.663, 18.8, 4.9, |
| | "FOLICACID", -0.19, 273, 70.7, |
| | "GARLIC", -0.412, 4.35, 2.9, |
| | "GINGER", -0.453, 59, 63.2, |
| | "IRON", 0.032, 13.35, 3.71, |
| | "MG", -0.484, 310.1, 139.4, |
| | "MUFA", -0.009, 27, 6.1, |
| | "NIACIN", -0.246, 25.9, 11.77, |
| | "N3FAT", -0.436, 1.06, 1.06, |
| | "N6FAT", -0.159, 10.8, 7.5, |
| | "ONION", -0.301, 35.9, 18.4, |
| | "PROTEIN", 0.021, 79.4, 13.9, |
| | "PUFA", -0.337, 13.88, 3.76, |
| | "RIBOFLAVIN", -0.068, 1.7, 0.79, |
| | "SATFAT", 0.373, 28.6, 8, |
| | "SE", -0.191, 67, 25.1, |
| | "THIAMIN", -0.098, 1.7, 0.66, |
| | "VITA", -0.401, 983.9, 518.6, |
| | "VITC", -0.424, 118.2, 43.46, |
| | "VITD", -0.446, 6.26, 2.21, |
| | "VITE", -0.419, 8.73, 1.49, |
| | "ZN", -0.313, 9.84, 2.19, |
| | "TEA", -0.536, 1.69, 1.53, |
| | "FLA3OL", -0.415, 95.8, 85.9, |
| | "FLAVONES", -0.616, 1.55, 0.07, |
| | "FLAVONOLS", -0.467, 17.7, 6.79, |
| | "FLAVONONES", -0.25, 11.7, 3.82, |
| | "ANTHOC", -0.131, 18.05, 21.14, |
| | "ISOFLAVONES", -0.593, 1.2, 0.2, |
| | "PEPPER", -0.131, 10, 7.07, |
| | "THYME", -0.102, 0.33, 0.99, |
| | "TURMERIC", -0.785, 533.6, 754.3 |
| | ) |
| |
|
| | |
| | dii_long <- dii_cohort %>% |
| | select(subject, RecallNo, any_of(dii_params$Variable)) %>% |
| | pivot_longer(-c(subject, RecallNo), names_to = "Variable", values_to = "Value") %>% |
| | left_join(dii_params, by = "Variable") %>% |
| | filter(!is.na(Overall_inflammatory_score)) %>% |
| | mutate( |
| | Z_SCORE = (Value - Global_mean) / SD, |
| | PERCENTILE = pnorm(Z_SCORE) * 2 - 1, |
| | IND_DII_SCORE = PERCENTILE * Overall_inflammatory_score |
| | ) |
| |
|
| | |
| | dii_scores <- dii_long %>% |
| | group_by(subject, RecallNo) %>% |
| | summarise( |
| | DII_ALL = sum(IND_DII_SCORE, na.rm = TRUE), |
| | DII_NOETOH = sum(IND_DII_SCORE[Variable != "ALCOHOL"], na.rm = TRUE), |
| | n_components = n(), |
| | .groups = "drop" |
| | ) |
| |
|
| | |
| | dii_by_subject <- dii_scores %>% |
| | group_by(subject) %>% |
| | summarise( |
| | DII_ALL_avg = mean(DII_ALL, na.rm = TRUE), |
| | DII_NOETOH_avg = mean(DII_NOETOH, na.rm = TRUE), |
| | n_recalls = n(), |
| | .groups = "drop" |
| | ) |
| |
|
| | rv$results$dii_by_recall <- dii_scores |
| | rv$results$dii_by_subject <- dii_by_subject |
| | rv$results$dii_components <- dii_long %>% |
| | select(subject, RecallNo, Variable, Value, IND_DII_SCORE) %>% |
| | pivot_wider(names_from = Variable, values_from = c(Value, IND_DII_SCORE)) |
| | } |
| |
|
| | |
| | incProgress(0.95, detail = "Generating QA/QC report") |
| |
|
| | missing_counts <- input_mapped %>% |
| | group_by(subject, RecallNo) %>% |
| | summarise( |
| | missing = sum(is.na(orig_food_common_name)), |
| | total = n(), |
| | pct_missing = missing / total * 100, |
| | .groups = "drop" |
| | ) |
| |
|
| | rv$results$missing_counts <- missing_counts |
| |
|
| | rv$analysis_complete <- TRUE |
| | rv$processing <- FALSE |
| |
|
| | incProgress(1, detail = "Complete") |
| |
|
| | |
| | nav_select("main_nav", selected = "results") |
| | showNotification("Pipeline complete", type = "message") |
| |
|
| | }, error = function(e) { |
| | rv$processing <- FALSE |
| | showNotification(paste("Pipeline error:", e$message), type = "error", duration = 15) |
| | }) |
| | }) |
| | }) |
| |
|
| | |
| | |
| | |
| | output$summary_cards <- renderUI({ |
| | if (!rv$analysis_complete) { |
| | return(tags$div( |
| | class = "alert alert-info text-center", |
| | icon("chart-bar"), " Run the pipeline to view results." |
| | )) |
| | } |
| |
|
| | n_subjects <- nrow(rv$results$total_by_subject) |
| | mean_intake <- mean(rv$results$total_by_subject$pp_average_mg, na.rm = TRUE) |
| | n_classes <- length(unique(rv$results$class_by_subject$class)) |
| | n_unmapped <- length(rv$unmapped_foods) |
| |
|
| | layout_columns( |
| | col_widths = c(3, 3, 3, 3), |
| | value_box( |
| | title = "Subjects Analyzed", |
| | value = n_subjects, |
| | theme = "primary", |
| | showcase = icon("users") |
| | ), |
| | value_box( |
| | title = "Mean Polyphenol Intake", |
| | value = paste(round(mean_intake, 1), "mg/day"), |
| | theme = "success", |
| | showcase = icon("leaf") |
| | ), |
| | value_box( |
| | title = "Polyphenol Classes", |
| | value = n_classes, |
| | theme = "info", |
| | showcase = icon("layer-group") |
| | ), |
| | value_box( |
| | title = "Unmapped Foods", |
| | value = n_unmapped, |
| | theme = if (n_unmapped > 0) "warning" else "success", |
| | showcase = icon("triangle-exclamation") |
| | ) |
| | ) |
| | }) |
| |
|
| | |
| | |
| | |
| | output$plot_total_intake <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$total_by_subject) |
| |
|
| | df <- rv$results$total_by_subject %>% |
| | arrange(desc(pp_average_mg)) %>% |
| | head(30) |
| |
|
| | plot_ly(df, x = ~reorder(subject, pp_average_mg), y = ~pp_average_mg, |
| | type = "bar", marker = list(color = PRIMARY_COLOR)) %>% |
| | layout( |
| | xaxis = list(title = "Subject", tickangle = -45), |
| | yaxis = list(title = "Mean Polyphenol Intake (mg/day)"), |
| | margin = list(b = 100) |
| | ) |
| | }) |
| |
|
| | output$plot_intake_distribution <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$total_by_subject) |
| |
|
| | plot_ly(rv$results$total_by_subject, x = ~pp_average_mg, type = "histogram", |
| | marker = list(color = PRIMARY_COLOR, line = list(color = "white", width = 1))) %>% |
| | layout( |
| | xaxis = list(title = "Mean Polyphenol Intake (mg/day)"), |
| | yaxis = list(title = "Count") |
| | ) |
| | }) |
| |
|
| | output$table_total_intake <- renderDT({ |
| | req(rv$analysis_complete, rv$results$total_by_subject) |
| |
|
| | datatable( |
| | rv$results$total_by_subject %>% |
| | mutate(across(where(is.numeric), ~ round(.x, 2))), |
| | options = list( |
| | pageLength = 25, |
| | scrollX = TRUE, |
| | scrollY = "400px", |
| | dom = 'Bfrtip' |
| | ), |
| | class = "display compact stripe", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | output$plot_class_intake <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$class_by_subject) |
| |
|
| | class_summary <- rv$results$class_by_subject %>% |
| | group_by(class) %>% |
| | summarise(mean_intake = mean(Avg_class_intake_mg, na.rm = TRUE), .groups = "drop") %>% |
| | arrange(desc(mean_intake)) |
| |
|
| | colors <- get_viz_colors(nrow(class_summary)) |
| |
|
| | plot_ly(class_summary, x = ~reorder(class, mean_intake), y = ~mean_intake, |
| | type = "bar", marker = list(color = colors)) %>% |
| | layout( |
| | xaxis = list(title = "Polyphenol Class", tickangle = -45), |
| | yaxis = list(title = "Mean Intake (mg/day)"), |
| | margin = list(b = 150) |
| | ) |
| | }) |
| |
|
| | output$table_class_intake <- renderDT({ |
| | req(rv$analysis_complete, rv$results$class_by_subject) |
| |
|
| | datatable( |
| | rv$results$class_by_subject %>% |
| | mutate(across(where(is.numeric), ~ round(.x, 2))), |
| | options = list( |
| | pageLength = 25, |
| | scrollX = TRUE, |
| | scrollY = "400px", |
| | dom = 'Bfrtip' |
| | ), |
| | class = "display compact stripe", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | output$plot_food_treemap <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$food_contributors) |
| |
|
| | top_foods <- rv$results$food_contributors %>% |
| | filter(!is.na(fdd_ingredient), food_pp_average_mg1000kcal > 0) %>% |
| | head(50) |
| |
|
| | if (nrow(top_foods) == 0) { |
| | return(plotly_empty() %>% layout(title = "No data available")) |
| | } |
| |
|
| | plot_ly( |
| | top_foods, |
| | labels = ~fdd_ingredient, |
| | parents = "", |
| | values = ~food_pp_average_mg1000kcal, |
| | type = "treemap", |
| | textinfo = "label+value", |
| | marker = list( |
| | colors = get_viz_colors(nrow(top_foods)), |
| | line = list(width = 1, color = "white") |
| | ) |
| | ) %>% |
| | layout(margin = list(l = 0, r = 0, t = 30, b = 0)) |
| | }) |
| |
|
| | output$table_food_contributors <- renderDT({ |
| | req(rv$analysis_complete, rv$results$food_contributors) |
| |
|
| | datatable( |
| | rv$results$food_contributors %>% |
| | filter(!is.na(fdd_ingredient)) %>% |
| | mutate(across(where(is.numeric), ~ round(.x, 2))) %>% |
| | head(100), |
| | options = list( |
| | pageLength = 25, |
| | scrollX = TRUE, |
| | scrollY = "400px", |
| | dom = 'Bfrtip' |
| | ), |
| | class = "display compact stripe", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | output$dii_content <- renderUI({ |
| | if (!input$calculate_dii) { |
| | return(tags$div( |
| | class = "alert alert-secondary text-center my-4", |
| | icon("info-circle"), " DII calculation was not enabled. Enable it in the Input tab and re-run the pipeline." |
| | )) |
| | } |
| |
|
| | if (!rv$analysis_complete) { |
| | return(tags$div( |
| | class = "alert alert-info text-center my-4", |
| | icon("chart-bar"), " Run the pipeline to view DII scores." |
| | )) |
| | } |
| |
|
| | if (is.null(rv$results$dii_by_subject)) { |
| | return(tags$div( |
| | class = "alert alert-warning text-center my-4", |
| | icon("triangle-exclamation"), " DII scores could not be calculated. Check that your data contains the required nutrient columns." |
| | )) |
| | } |
| |
|
| | tagList( |
| | layout_columns( |
| | col_widths = c(6, 6), |
| | card( |
| | card_header("DII Score Distribution"), |
| | card_body( |
| | plotlyOutput("plot_dii_distribution", height = "350px") |
| | ) |
| | ), |
| | card( |
| | card_header("DII Scores by Subject"), |
| | card_body( |
| | plotlyOutput("plot_dii_by_subject", height = "350px") |
| | ) |
| | ) |
| | ), |
| | card( |
| | card_header( |
| | class = "d-flex justify-content-between align-items-center", |
| | tags$span("Subject-Level DII Scores"), |
| | downloadButton("download_dii", "Export CSV", class = "btn-sm btn-outline-primary") |
| | ), |
| | card_body( |
| | DTOutput("table_dii_scores") |
| | ) |
| | ), |
| | tags$div( |
| | class = "alert alert-info mt-3", |
| | tags$strong("About the DII: "), |
| | "The Dietary Inflammatory Index (DII) is a literature-derived score that assesses the inflammatory ", |
| | "potential of the diet. Negative scores indicate anti-inflammatory diets, while positive scores ", |
| | "indicate pro-inflammatory diets. This calculation uses 42 components including nutrients, ", |
| | "polyphenol subclasses, and specific anti-inflammatory foods." |
| | ) |
| | ) |
| | }) |
| |
|
| | |
| | output$plot_dii_distribution <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$dii_by_subject) |
| |
|
| | plot_ly(rv$results$dii_by_subject, x = ~DII_ALL_avg, type = "histogram", |
| | marker = list(color = PRIMARY_COLOR, line = list(color = "white", width = 1))) %>% |
| | layout( |
| | xaxis = list(title = "Average DII Score"), |
| | yaxis = list(title = "Number of Subjects"), |
| | shapes = list( |
| | list(type = "line", x0 = 0, x1 = 0, y0 = 0, y1 = 1, yref = "paper", |
| | line = list(color = "red", dash = "dash", width = 2)) |
| | ) |
| | ) |
| | }) |
| |
|
| | output$plot_dii_by_subject <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$dii_by_subject) |
| |
|
| | df <- rv$results$dii_by_subject %>% |
| | arrange(DII_ALL_avg) %>% |
| | head(30) |
| |
|
| | colors <- ifelse(df$DII_ALL_avg < 0, "#28a745", "#dc3545") |
| |
|
| | plot_ly(df, x = ~reorder(subject, DII_ALL_avg), y = ~DII_ALL_avg, |
| | type = "bar", marker = list(color = colors)) %>% |
| | layout( |
| | xaxis = list(title = "Subject", tickangle = -45), |
| | yaxis = list(title = "Average DII Score"), |
| | margin = list(b = 100) |
| | ) |
| | }) |
| |
|
| | output$table_dii_scores <- renderDT({ |
| | req(rv$analysis_complete, rv$results$dii_by_subject) |
| |
|
| | datatable( |
| | rv$results$dii_by_subject %>% |
| | mutate(across(where(is.numeric), ~ round(.x, 3))) %>% |
| | rename( |
| | Subject = subject, |
| | `DII (All Components)` = DII_ALL_avg, |
| | `DII (No Alcohol)` = DII_NOETOH_avg, |
| | `Number of Recalls` = n_recalls |
| | ), |
| | options = list( |
| | pageLength = 25, |
| | scrollX = TRUE, |
| | scrollY = "400px", |
| | dom = 'Bfrtip' |
| | ), |
| | class = "display compact stripe", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | output$download_dii <- downloadHandler( |
| | filename = function() paste0("dii_scores_", Sys.Date(), ".csv"), |
| | content = function(file) { |
| | write_csv(rv$results$dii_by_subject, file) |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | output$unmapped_summary <- renderUI({ |
| | if (is.null(rv$unmapped_foods) || length(rv$unmapped_foods) == 0) { |
| | return(tags$div( |
| | class = "alert alert-success", |
| | icon("check-circle"), " All foods were successfully mapped to FooDB." |
| | )) |
| | } |
| |
|
| | tags$div( |
| | class = "alert alert-warning", |
| | icon("triangle-exclamation"), |
| | sprintf(" %d unique food items could not be mapped.", length(rv$unmapped_foods)) |
| | ) |
| | }) |
| |
|
| | output$table_unmapped_foods <- renderDT({ |
| | req(rv$unmapped_foods) |
| |
|
| | if (length(rv$unmapped_foods) == 0) { |
| | return(NULL) |
| | } |
| |
|
| | datatable( |
| | data.frame(Unmapped_Food = rv$unmapped_foods), |
| | options = list( |
| | pageLength = 25, |
| | scrollX = TRUE, |
| | scrollY = "300px" |
| | ), |
| | class = "display compact stripe", |
| | rownames = FALSE |
| | ) |
| | }) |
| |
|
| | output$plot_missing_distribution <- renderPlotly({ |
| | req(rv$analysis_complete, rv$results$missing_counts) |
| |
|
| | plot_ly(rv$results$missing_counts, x = ~pct_missing, type = "histogram", |
| | marker = list(color = "#ffc107", line = list(color = "white", width = 1))) %>% |
| | layout( |
| | xaxis = list(title = "Unmapped Foods (%)"), |
| | yaxis = list(title = "Number of Recalls") |
| | ) |
| | }) |
| |
|
| | |
| | |
| | |
| | output$download_total <- downloadHandler( |
| | filename = function() paste0("polyphenol_total_intake_", Sys.Date(), ".csv"), |
| | content = function(file) { |
| | write_csv(rv$results$total_by_subject, file) |
| | } |
| | ) |
| |
|
| | output$download_class <- downloadHandler( |
| | filename = function() paste0("polyphenol_class_intake_", Sys.Date(), ".csv"), |
| | content = function(file) { |
| | write_csv(rv$results$class_by_subject, file) |
| | } |
| | ) |
| |
|
| | output$download_foods <- downloadHandler( |
| | filename = function() paste0("polyphenol_food_contributors_", Sys.Date(), ".csv"), |
| | content = function(file) { |
| | write_csv(rv$results$food_contributors, file) |
| | } |
| | ) |
| |
|
| | output$download_all <- downloadHandler( |
| | filename = function() paste0("polyphenol_results_", Sys.Date(), ".zip"), |
| | content = function(file) { |
| | |
| | temp_dir <- tempdir() |
| | on.exit(unlink(file.path(temp_dir, "*.csv"))) |
| |
|
| | files_to_zip <- c() |
| |
|
| | if (!is.null(rv$results$total_by_subject)) { |
| | f <- file.path(temp_dir, "total_intake_by_subject.csv") |
| | write_csv(rv$results$total_by_subject, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$total_by_recall)) { |
| | f <- file.path(temp_dir, "total_intake_by_recall.csv") |
| | write_csv(rv$results$total_by_recall, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$class_by_subject)) { |
| | f <- file.path(temp_dir, "class_intake_by_subject.csv") |
| | write_csv(rv$results$class_by_subject, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$class_by_recall)) { |
| | f <- file.path(temp_dir, "class_intake_by_recall.csv") |
| | write_csv(rv$results$class_by_recall, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$food_contributors)) { |
| | f <- file.path(temp_dir, "food_contributors.csv") |
| | write_csv(rv$results$food_contributors, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$unmapped_foods) && length(rv$unmapped_foods) > 0) { |
| | f <- file.path(temp_dir, "unmapped_foods.csv") |
| | write_csv(data.frame(unmapped_food = rv$unmapped_foods), f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$dii_by_subject)) { |
| | f <- file.path(temp_dir, "dii_scores_by_subject.csv") |
| | write_csv(rv$results$dii_by_subject, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| | if (!is.null(rv$results$dii_by_recall)) { |
| | f <- file.path(temp_dir, "dii_scores_by_recall.csv") |
| | write_csv(rv$results$dii_by_recall, f) |
| | files_to_zip <- c(files_to_zip, f) |
| | } |
| |
|
| | zip::zip(file, files_to_zip, mode = "cherry-pick") |
| | }, |
| | contentType = "application/zip" |
| | ) |
| | } |
| |
|