Wikki13 commited on
Commit
156485b
·
verified ·
1 Parent(s): efcac0b

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +556 -45
app.R CHANGED
@@ -1,58 +1,569 @@
 
1
  library(shiny)
2
- library(bslib)
3
  library(dplyr)
4
- library(ggplot2)
5
-
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
9
-
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
13
- sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
18
- ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ),
24
- plotOutput("scatter")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  )
26
 
 
 
27
  server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
- })
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
 
 
 
43
 
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
49
- )
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- p
53
- },
54
- res = 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
- shinyApp(ui, server)
 
1
+ #app.R
2
  library(shiny)
3
+ library(BioAge)
4
  library(dplyr)
5
+ #library(RSQLite)
6
+ library(googlesheets4)
7
+ library(googledrive)
8
+ library(shinyvalidate)
9
+ library(shinyjs)
10
+ library(plotly)
11
+ library(lubridate)
12
+ library(pander)
13
+ library(tidyr)
14
+ library(shiny.router)
15
+
16
+
17
+
18
+ #options(gargle_oauth_cache = ".secrets")
19
+
20
+ createReactiveDataset <- function() {
21
+ return(reactiveVal(data.frame(date = as.Date(character()), quantity = integer(), item = character())))
22
+ }
23
+
24
+ # Define dataset and its reactive variable
25
+ dataset <- createReactiveDataset()
26
+
27
+ # Dummy dataset for development
28
+ localDataset <- reactiveVal(data.frame(
29
+ date = as.Date(c('2024-12-01', '2024-12-02')),
30
+ quantity = c(10, 20),
31
+ item = c('Apples', 'Oranges')
32
+ ))
33
+
34
+ # For local development, bypass authentication
35
+ isLocal <- TRUE
36
+
37
+ home_route <- route("/", function() {
38
+ fluidPage(
39
+ h1("Home Page"),
40
+ DT::dataTableOutput("dataTable")
41
+ )
42
+ })
43
+
44
+
45
+ checkRange <- function(value, min, max){
46
+ if(!is.na(value) & (value < min || value > max)){
47
+ paste0("Please specify a number that is between ", min, " and ", max, ", thank you!")
48
+ } else {
49
+ ""
50
+ }
51
+ }
52
+
53
+ search_string = ""
54
+ query_params <- parseQueryString(search_string)
55
+
56
+ #if !is.null()
57
+ emiglio = query_params$useremail
58
+ nomignolo = paste0(query_params$firstname, " ", query_params$lastname)
59
+
60
+ phone_prefix_regex <- "^\\+?\\d{1,3}$"
61
+ phone_number_regex <- "^[0-9]{10,10}$"
62
+
63
+ validatePhone <- function(value, n_char_min){
64
+ if(value != ""){
65
+
66
+ if(grepl("^[0-9]{1,100}$", value)){
67
+ if (nchar(value) == n_char_min) {
68
+ ""
69
+ } else{
70
+ paste0("Please specify a number that contains ", n_char_min, " digits")
71
+ }
72
+ }else{
73
+ "Please enter only digits"
74
+ }
75
+ } else{
76
+ ""
77
+ }
78
+
79
+ }
80
+
81
+
82
+ ui <- fluidPage(
83
+ tags$head(
84
+ tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"),
85
+ tags$style(HTML("
86
+ .control-label {
87
+ color: black !important;
88
+ }
89
+
90
+ .strongy{
91
+ background-color: #c8e6c9;
92
+ padding: 10px
93
+ }
94
+
95
+ .form-control{
96
+ border-color: grey !important
97
+ }
98
+
99
+ #loading {
100
+ display: none;
101
+ text-align: center;
102
+ }
103
+ .loader {
104
+ border: 6px solid #f3f3f3; /* Light grey */
105
+ border-top: 6px solid #3498db; /* Blue */
106
+ border-radius: 50%;
107
+ width: 50px;
108
+ height: 50px;
109
+ animation: spin 2s linear infinite;
110
+ margin: 20px auto;
111
+ }
112
+ @keyframes spin {
113
+ 0% { transform: rotate(0deg); }
114
+ 100% { transform: rotate(360deg); }
115
+ }
116
+ "))
117
  ),
118
+ useShinyjs(),
119
+ titlePanel("Biological Age Calculator"),
120
+
121
+ sidebarLayout(
122
+ sidebarPanel(
123
+ fluidRow(
124
+ column(6, textInput("Name", "Enter name", query_params$firstname)),
125
+ column(6, textInput("Surname", "Enter your last name", query_params$lastname))
126
+ ),
127
+ fluidRow(
128
+ column(6, textInput("phone_prefix", "Enter Country Code", "+91")),
129
+ column(6, textInput("phone_number", "Enter your phone number", ""))
130
+ ),
131
+ dateInput("dob", "Date of Birth*", value="1990-01-01"),
132
+ dateInput("bloodTestDate", "Date of TEST", value = Sys.Date()),
133
+ numericInput("albumin", "Albumin (g/dL)", value = NA, min = 0),
134
+ verbatimTextOutput("values"),
135
+ numericInput("lymph", "Lymphocytes (%)", value = NA, min = 0),
136
+ numericInput("mcv", "Mean Cell Volume (MCV)", value = NA, min = 0),
137
+ numericInput("glucose", "Glucose (mg/L)", value = NA, min = 0),
138
+ numericInput("rdw", "Red Cell Dist Width (RDW)", value = NA, min = 0),
139
+ numericInput("creat", "Creatinine (mg/dL)", value = NA, min = 0),
140
+ numericInput("crp", "CRP (mg/L)", value = NA, min = 0),
141
+ numericInput("alp", "Alkaline Phosphatase (U/L)", value = NA, min = 0),
142
+ numericInput("wbc", "White Blood Cells (cells/mL)", value = NA, min = 0),
143
+ actionButton("submit", "Submit", title = "Fill in the phone number field to enable")
144
+ ),
145
+ mainPanel(
146
+ tabsetPanel(
147
+ tabPanel("Calculator",
148
+ div(id = "loading", class = "loader", style = "display: none"), # Loading spinner
149
+ uiOutput("results"),
150
+ uiOutput("message")),
151
+ tabPanel("My historical data - Plot",
152
+ uiOutput("notlogged"),
153
+ actionLink("openLinkButton", "Create an account here / log in here", href = "", style = "background-color:#c8e6c9; padding: 10px; font-size: 2em"),
154
+ plotOutput("biologicalAgePlot"))
155
+ ,
156
+ tabPanel("My historical data - Table",
157
+ tableOutput("table"),
158
+ actionLink("openLinkButton", "Create an account here / log in here", href = "", style = "background-color:#c8e6c9; padding: 10px; font-size: 2em"),
159
+ uiOutput("notlogged1"))
160
+ )
161
+ )
162
+ )
163
  )
164
 
165
+
166
+
167
  server <- function(input, output, session) {
168
+ sheet_id <- "https://docs.google.com/spreadsheets/d/1xQpghper_5FCWkYFByIpdVKFmBofgM7uVrG_bsaGW58/edit?gid=0#gid=0"
169
+ values <- reactiveVal(data.frame())
170
+
171
+ router_server(
172
+ list(home_route)
173
+ )
174
+
175
+ # Dummy dataset
176
+ #dataset <- data.frame(date = as.Date(c('2024-12-01', '2024-12-02')),
177
+ #quantity = c(10, 20),
178
+ #item = c('Apples', 'Oranges'))
179
+
180
+ # Render the dataset in a table
181
+ #output$dataTable <- DT::renderDataTable({
182
+ #dataset
183
+ #})
184
 
185
+
186
+ observeEvent(input$submit, {
187
+ shinyjs::show(id = "loading") # Show loading spinner
188
+
189
+ # Collecting inputs
190
+ name <- paste0(input$Name, " ", input$Surname)
191
+ ageAtTestDate <- round(as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25, 1)
192
+ biological_age <- runif(1, min = ageAtTestDate - 5, max = ageAtTestDate + 5) # Simulated calculation
193
+
194
+ bioage_color <- if (biological_age < ageAtTestDate) "green" else if (biological_age > ageAtTestDate) "red" else "yellow"
195
+
196
+ # Show the BioAge Modal
197
+ show_bioage_modal(name, ageAtTestDate, biological_age, bioage_color)
198
+
199
+ output$results <- renderUI({
200
+ paste("Thank you for submitting your details. Your biological age will be calculated based on the provided data.")
201
+ })
202
+
203
+ })
204
+
205
+ # Define show_bioage_modal function outside observeEvent
206
+ show_bioage_modal <- function(name, ageAtTestDate, biological_age, bioage_color) {
207
+ diff <- round(abs(biological_age - ageAtTestDate), 2)
208
+ message <- if (biological_age < ageAtTestDate) {
209
+ tags$p(
210
+ paste0(
211
+ name,", your healthy choices show! your BioAge is ", diff, " years younger than your calendar Age."
212
+ ),
213
+ style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
214
+ )
215
+ } else if (biological_age > ageAtTestDate) {
216
+ tags$p(
217
+ paste0(
218
+ name,", your BioAge is ", diff, " years higher than your Calendar Age. Don’t worry—small, consistent steps in the right direction can help you close the gap!"
219
+ ),
220
+ style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
221
+ )
222
+ } else {
223
+ tags$p(
224
+ paste0(
225
+ name,", your BioAge matches your Calendar Age, indicating balanced health markers."
226
+ ),
227
+ style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
228
+ )
229
+ }
230
+
231
+ showModal(
232
+ modalDialog(
233
+ title = NULL,
234
+ div(
235
+ style = "background-color: #0E406B; color: white; padding: 24px; text-align: center;
236
+ border-top-left-radius: 5px; border-top-right-radius: 5px; margin: -15px -15px 0 -15px; font-family: 'Archia', sans-serif;",
237
+ h3(
238
+ paste0(name,"'s BioAge Results"),
239
+ style = "margin: 0; padding: 0; font-size: 26px; text-align: center; font-family: 'Archia', sans-serif; font-variant: common-ligatures; color: white; line-height: 1;"
240
+ )
241
+ ),
242
+ div(
243
+ style = "background-color: white; text-align: center; font-family: 'Archia', sans-serif; padding-top: 20px;",
244
+ fluidRow(
245
+ column(5, div(
246
+ tags$p("Calendar Age", style = "font-weight: bold; font-size: 20px; color: black;"),
247
+ tags$p(paste(ageAtTestDate, "years"), style = "font-size: 24px; font-weight: bold; color: black;")
248
+ )),
249
+ column(2, div(
250
+ tags$p("→", style = "font-size: 59px; font-weight: bold; color: darkblue; margin-top: 8px;")
251
+ )),
252
+ column(5, div(
253
+ tags$p("BioAge", style = "font-weight: bold; font-size: 20px; color: black;"),
254
+ tags$p(
255
+ paste(round(biological_age, 2), "years"),
256
+ style = sprintf("font-size: 24px; font-weight: bold; color: %s;", bioage_color)
257
+ )
258
+ ))
259
+ )
260
+ ),
261
+ div(
262
+ style = "text-align: center; margin-top: 20px; color: #333333; margin-bottom: 0px;",
263
+ message
264
+ ),
265
+ div(
266
+ style = "text-align: center;",
267
+ tags$p("Complete report sent to your WhatsApp",
268
+ tags$i(class = "fa-brands fa-whatsapp fa-beat",
269
+ style = "color: #25D366; margin-left: 5px; font-size: 16px;"),
270
+ style = "font-size: 14px; color: black; margin-bottom: 0px;"),
271
+ tags$p("xxxxxxxx4321", style = "font-size: 14px; color: black; margin-bottom: -16px;")
272
+ ),
273
+ easyClose = FALSE,
274
+ footer = tags$button(
275
+ "Close",
276
+ onclick = "Shiny.setInputValue('close_modal', true);",
277
+ style = "background-color: #86D1F1; color: black; border: none; padding: 10px 20px; border-radius: 5px; font-size: 16px; cursor: pointer;"
278
  )
279
+ )
280
+ )
281
+ }
282
 
283
+ output$message <- renderUI({
284
+ HTML("<div class='biological-age-text'>
285
+ <h2>What is Biological Age?</h2>
286
+ <p>Biological age refers to how old a person seems based on the functioning and condition of their
287
+ body systems, rather than the time since birth (chronological age). It is influenced by genetics,
288
+ lifestyle, and environmental factors, and can provide a more accurate representation of an
289
+ individual's health and longevity prospects.</p>
290
+ <h3 class = 'strongy'>Our tool can estimate your biological age using even one blood test parameter. However, for the most accurate results, we recommend including as many parameters as possible.</h3>
291
+ <h2>Here is what each of the parameters mean for your health:</h2>
292
+ <ol>
293
+ <li><strong>Albumin:</strong> A protein in the blood that helps maintain fluid balance and transport hormones,
294
+ vitamins, and drugs; low levels can indicate liver or kidney disease.</li>
295
+ <li><strong>Creatinine:</strong> A waste product from muscle metabolism; its blood level is a marker of kidney
296
+ function, with high levels indicating potential kidney impairment.</li>
297
+ <li><strong>Glucose:</strong> The main sugar found in the blood and the body's primary energy source; levels
298
+ are used to diagnose and monitor diabetes.</li>
299
+ <li><strong>C-reactive Protein (CRP):</strong> A substance produced by the liver in response to inflammation;
300
+ high CRP levels can indicate infection or chronic inflammatory diseases.</li>
301
+ <li><strong>Lymphocyte (Lymphs):</strong> A type of white blood cell involved in the immune response;
302
+ changes in lymphocyte levels can indicate infections, autoimmune diseases, or blood disorders.</li>
303
+ <li><strong>Mean Cell Volume (MCV):</strong> The average size of red blood cells; it helps diagnose and classify
304
+ anemias, with high MCV indicating macrocytic anemia and low MCV indicating microcytic
305
+ anemia.</li>
306
+ <li><strong>Red Cell Distribution Width (RDW):</strong> A measure of the variation in red blood cell size;
307
+ increased RDW can indicate anemia and has been associated with cardiovascular diseases.</li>
308
+ <li><strong>Alkaline Phosphatase:</strong> An enzyme found in the liver, bones, and other tissues; high levels
309
+ can indicate liver disease, bone disorders, or bile duct obstruction.</li>
310
+ <li><strong>White Blood Cells (WBCs):</strong> Cells in the immune system that help defend against infections;
311
+ abnormal levels can indicate infection, inflammation, or immune system disorders.</li>
312
+ </ol>
313
+
314
+ <h2>Based on scientific data:</h2>
315
+ <p>We have based this calculation of your biological age on scientific data from the National Health
316
+ and Nutrition Examination Survey (NHANES). The package uses published biomarker
317
+ algorithms to calculate three biological aging measures: Klemera-Doubal Method (KDM)
318
+ biological age, phenotypic age, and homeostatic dysregulation.</p>
319
+
320
+ <h2>Citation:</h2>
321
+ <p>Kwon, D., Belsky, D.W. A toolkit for quantification of biological age from blood chemistry and organ function
322
+ test data: BioAge. GeroScience 43, 2795–2808 (2021).</p>
323
+
324
+ <h2>Disclaimer:</h2>
325
+ <p>These results are solely for informational use and shouldn't replace professional medical advice,
326
+ diagnosis, or treatment. Always seek the guidance of a healthcare provider for any medical
327
+ conditions or before making changes to your health regimen, including starting new diets,
328
+ exercises, or supplements, or altering medication. Never discontinue medication or follow any
329
+ health advice without consulting your healthcare provider.</p></div>")
330
+ })
331
+
332
 
333
+
334
+ observeEvent(input$submit, {
335
+ # Handle form submission
336
+ output$results <- renderUI({
337
+ paste("Thank you for submitting your details. Your biological age will be calculated based on the provided data.")
338
+ })
339
+ })
340
+
341
+ output$notlogged <- renderUI({
342
+ if (is.null(emiglio)){
343
+ HTML("<div class='biological-age-text'>
344
+ <h2>Please log in to View your history</h2>")
345
+ } else {
346
+ ""
347
+ }
348
+
349
+ })
350
+
351
+ output$notlogged1 <- renderUI({
352
+ if (is.null(emiglio)){
353
+ HTML("<div class='biological-age-text'>
354
+ <h2>Please log in to View your history</h2>")
355
+ } else {
356
+ ""
357
+ }
358
+
359
+ })
360
+
361
+
362
+ output$biologicalAgePlot <- renderPlot(
363
+ if (!is.null(emiglio)){
364
+ if(nrow(values %>% filter(email == emiglio))>0){
365
+ values %>%
366
+ filter(email == emiglio) %>%
367
+ select(c(age, age_bio, date)) %>%
368
+ group_by(date) %>%
369
+ mutate(age = mean(age, na.rm = T), age_bio = mean(age_bio, na.rm = T)) %>%
370
+ rename(
371
+ `Actual age` = age,
372
+ `Biological age` = age_bio
373
+ ) %>%
374
+ mutate(Date = as_date(date)) %>%
375
+ pivot_longer(cols = c(1:2), names_to = "name", values_to = "value") %>%
376
+ ggplot(aes(
377
+ x = Date,
378
+ y = value,
379
+ color = name
380
+ )
381
+ )+
382
+ geom_line(size = 1.3)+
383
+ theme_bw()+
384
+ scale_color_discrete(name = "")+
385
+ scale_y_continuous(breaks = round(seq(min(c(values$age, values$age_bio)), max(c(values$age, values$age_bio)), by = 2),0))
386
+ }
387
+ }
388
+ )
389
+
390
+ output$table <- renderTable(
391
+ if (!is.null(emiglio)){
392
+
393
+ if(nrow(values %>% filter(email == emiglio))>0){
394
+ values %>%
395
+ filter(email == emiglio) %>%
396
+ mutate_at(c(1, 11), round, 1) %>%
397
+ select(c(15, 2:10, 1, 11)) %>%
398
+ #arrange(-date) %>%
399
+ mutate(date = as.character(as.Date(date)))%>%
400
+ rename(
401
+ Creatinine = creat,
402
+ `Lymphocyte (Lymphs)` = lymph,
403
+ `CRP (C-reactive Protein)` = crp,
404
+ `White Blood Cells` = wbc,
405
+ `Mean Cell Volume` = mcv,
406
+ `Red cells distribution width` = rdw,
407
+ Glucose = glucose,
408
+ `Alkaline Phosphatase` = alp,
409
+ `Actual Age` = age,
410
+ `Biological Age` = age_bio
411
+ )
412
+
413
+ }
414
+ }
415
  )
416
+
417
+ observe({
418
+ iv <- InputValidator$new()
419
+ iv$add_rule("albumin", checkRange, 2, 8)
420
+ iv$add_rule("creat", checkRange, .2, 2)
421
+ iv$add_rule("rdw", checkRange, 10, 20)
422
+ iv$add_rule("crp", checkRange, 0, 50)
423
+ iv$add_rule("lymph", checkRange, 0, 100)
424
+ iv$add_rule("glucose", checkRange, 40, 400)
425
+ iv$add_rule("wbc", checkRange, 2, 25)
426
+ iv$add_rule("alp", checkRange, 0, 300)
427
+ iv$add_rule("mcv", checkRange, 70, 120)
428
+ iv$add_rule("phone_number", validatePhone, 10)
429
+
430
+ iv$enable()
431
+
432
+ output$values <- renderPrint({
433
+ req(iv$is_valid())
434
+ })
435
+ })
436
+
437
+ observeEvent(input$submit, {
438
+ shinyjs::show(id = "loading") # Show loading spinner
439
+
440
+ nome = paste0(input$Name, " ", input$Surname)
441
+
442
+ ageAtTestDate <- round(as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25, 1)
443
+
444
+ if (!is.null(input$dob)) {
445
+ allInputs <- data.frame(
446
+ age = as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25,
447
+ albumin = input$albumin,
448
+ lymph = input$lymph,
449
+ mcv = input$mcv,
450
+ glucose = input$glucose,
451
+ rdw = input$rdw,
452
+ creat = input$creat,
453
+ crp = input$crp,
454
+ alp = input$alp,
455
+ wbc = input$wbc
456
+ )
457
+ }
458
+
459
+ if (!is.null(input$phone_number) && !is.null(phone_number_regex) &&
460
+ grepl(phone_prefix_regex, input$phone_prefix) &&
461
+ grepl(phone_number_regex, input$phone_number)) {
462
+ validated_phone <- paste(input$phone_prefix, input$phone_number)
463
+ } else {
464
+ validated_phone = NA
465
+ }
466
+
467
+ mrkrs = colnames(allInputs[, colSums(is.na(allInputs)) < nrow(allInputs)])
468
+
469
+ train = phenoage_calc(NHANES3, biomarkers = mrkrs)
470
+
471
+ biological_age <- phenoage_calc(allInputs, biomarkers = mrkrs, fit = train$fit)$data$phenoage
472
+
473
+ allInputs_tosave = allInputs %>%
474
+ mutate(
475
+ age_bio = biological_age,
476
+ email = ifelse(is.null(emiglio), "", emiglio),
477
+ name = nome,
478
+ phone = validated_phone,
479
+ date= input$bloodTestDate
480
+ )
481
+
482
+ biologicalAge <- if (!is.null(input$dob)) {
483
+ paste("Your biological age is approximately:", round(biological_age, 1), "years")
484
+ } else {
485
+ "Please enter at least your date of birth"
486
+ }
487
+
488
+ observeEvent(input$close_modal, {
489
+ removeModal()
490
+ })
491
+
492
+ output$results <- renderUI(
493
+ HTML(
494
+ paste0(
495
+ "<div style='background-color: #c8e6c9; padding: 10px;'>",
496
+ "<h2>Hello ", nome, "</h2>",
497
+ "<p>Your biological age is ", round(biological_age, 1), " years </p>",
498
+ "<p>Your actual age is ", ageAtTestDate, " years</p></div>"
499
+ )
500
+ )
501
+ )
502
+
503
+ # Append data to Google Sheet
504
+ if (nrow(allInputs_tosave) == 1) {
505
+ if (nrow(values()) == 0) {
506
+ sheet_write(data = allInputs_tosave, ss = sheet_id, sheet = "longevity")
507
+ } else {
508
+ sheet_append(data = allInputs_tosave, ss = sheet_id, sheet = "longevity")
509
+ }
510
+ }
511
+
512
+ # Update values to reflect the new data
513
+ values(read_sheet(ss = sheet_id, sheet = "longevity"))
514
+
515
+ shinyjs::hide(id = "loading") # Hide loading spinner
516
+
517
+ # Generate biological age plot
518
+ output$biologicalAgePlot <- renderPlot({
519
+ if (!is.null(emiglio)) {
520
+ if (nrow(values() %>% filter(email == emiglio)) > 0) {
521
+ values() %>%
522
+ filter(email == emiglio) %>%
523
+ select(c(age, age_bio, date)) %>%
524
+ group_by(date) %>%
525
+ mutate(age = mean(age, na.rm = TRUE), age_bio = mean(age_bio, na.rm = TRUE)) %>%
526
+ rename(`Actual age` = age, `Biological age` = age_bio) %>%
527
+ mutate(Date = as_date(date)) %>%
528
+ pivot_longer(cols = c(1:2), names_to = "name", values_to = "value") %>%
529
+ ggplot(aes(x = Date, y = value, color = name)) +
530
+ geom_line(size = 1.3) +
531
+ theme_bw() +
532
+ scale_color_discrete(name = "") +
533
+ scale_y_continuous(breaks = round(seq(min(c(values()$age, values()$age_bio)),
534
+ max(c(values()$age, values()$age_bio)), by = 2), 0))
535
+ }
536
+ }
537
+ })
538
+
539
+ # Generate biological age table
540
+ output$table <- renderTable({
541
+ if (!is.null(emiglio)) {
542
+ if (nrow(values() %>% filter(email == emiglio)) > 0) {
543
+ values() %>%
544
+ filter(email == emiglio) %>%
545
+ mutate_at(c(1, 11), round, 1) %>%
546
+ select(c(15, 2:10, 1, 11)) %>%
547
+ mutate(date = as.character(as.Date(date))) %>%
548
+ rename(
549
+ Creatinine = creat,
550
+ `Lymphocyte (Lymphs)` = lymph,
551
+ `CRP (C-reactive Protein)` = crp,
552
+ `White Blood Cells` = wbc,
553
+ `Mean Cell Volume` = mcv,
554
+ `Alkaline Phosphatase` = alp,
555
+ Glucose = glucose,
556
+ `Red cells distribution width` = rdw,
557
+ `Actual Age` = age,
558
+ `Biological Age` = age_bio
559
+ )
560
+ }
561
+ }
562
+ })
563
+ })
564
+
565
+
566
+
567
  }
568
 
569
+ shinyApp(ui = ui, server = server)