-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.R
More file actions
510 lines (463 loc) · 24.7 KB
/
server.R
File metadata and controls
510 lines (463 loc) · 24.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# server.R - Main Server File
#
# Description:
# This file contains the core server logic, including data loading and reactive
# data preparation. It then sources the server logic for each individual tab
# from the 'server_tabs' directory.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
server <- function(input, output, session) {
# --- Reactive Values Store ---
# This is the central, consistent name for the reactive values object.
rv <- reactiveValues(
raw_data_full = NULL, # Holds the entire uploaded file
preprocessed_data = NULL,
transformed_cols = NULL,
original_for_plotting = NULL,
fuzzy_matching_results = NULL, # Store fuzzy matching results
pca_results = NULL, # Store PCA analysis results
lda_results = NULL # Store LDA analysis results
)
# Color scheme for NTG doses - custom colors for better visibility
# Replacing Set3 which has hard-to-see yellow
custom_colors <- c(
"0" = "#1f77b4", # Blue for control (0 mg/kg)
"2.5" = "#ff7f0e", # Orange for low dose (2.5 mg/kg)
"5" = "#2ca02c", # Green for medium dose (5 mg/kg)
"10" = "#d62728" # Red for high dose (10 mg/kg)
)
# --- Initial Data Loading ---
observeEvent(input$data_file, {
req(input$data_file)
withProgress(message = 'Loading data...', value = 0, {
raw_data_full <- tryCatch(
{
as.data.frame(data.table::fread(input$data_file$datapath))
},
error = function(e) {
showNotification(
paste("Error reading file:", e$message),
type = "error",
duration = 10
)
return(NULL)
}
)
req(raw_data_full)
# --- FUZZY MATCHING: Reconcile Column Names ---
cat("DEBUG: Starting fuzzy matching for column name reconciliation...\n")
# Initialize fuzzy matching results
rv$fuzzy_matching_results <- list(
renamed_columns = character(0),
missing_columns = character(0),
total_matched = 0,
total_expected = 0
)
# Wrap fuzzy matching in tryCatch to prevent breaking the app
tryCatch({
# Define expected column names and their variations
expected_columns <- list(
# Categorical variables
"Sex" = c("sex", "Sex", "gender", "Gender"),
"Pen_ID" = c("PenID", "pen_id", "Pen_ID", "penid", "PenId"),
"Cohort" = c("cohort", "Cohort", "group", "Group"),
"DOB" = c("dob", "DOB", "date_of_birth", "Date_of_Birth", "birth_date", "Birth_Date"),
"Date" = c("date", "Date", "experiment_date", "Experiment_Date", "test_date", "Test_Date"),
# Feature columns with spaces -> underscores
"Distance_Traveled" = c("Distance Traveled", "distance_traveled", "DistanceTraveled"),
"Body_Length" = c("Body Length", "body_length", "BodyLength"),
"bin_avg_60.scratch_time_secs" = c("bin_avg_60.scratch_time_secs", "Bin_avg_60.scratch_time_secs", "bin_avg_60_scratch_time_secs"),
"Speed_Variance" = c("Speed Variance", "speed_variance", "SpeedVariance"),
"Speed_Bin.10" = c("Speed Bin.10", "speed_bin.10", "SpeedBin.10"),
"Speed_Bin.15" = c("Speed Bin.15", "speed_bin.15", "SpeedBin.15"),
"Speed_Bin.20" = c("Speed Bin.20", "speed_bin.20", "SpeedBin.20"),
"Speed_Bin.25" = c("Speed Bin.25", "speed_bin.25", "SpeedBin.25"),
"Stride_Count.10" = c("Stride Count.10", "stride_count.10", "StrideCount.10"),
"Stride_Count.15" = c("Stride Count.15", "stride_count.15", "StrideCount.15"),
"Stride_Count.20" = c("Stride Count.20", "stride_count.20", "StrideCount.20"),
"Stride_Count.25" = c("Stride Count.25", "stride_count.25", "StrideCount.25"),
"Angular_Velocity.10" = c("Angular Velocity.10", "angular_velocity.10", "AngularVelocity.10"),
"Angular_Velocity.15" = c("Angular Velocity.15", "angular_velocity.15", "AngularVelocity.15"),
"Angular_Velocity.20" = c("Angular Velocity.20", "angular_velocity.20", "AngularVelocity.20"),
"Angular_Velocity.25" = c("Angular Velocity.25", "angular_velocity.25", "AngularVelocity.25"),
"Angular_Velocity_Variance.10" = c("Angular Velocity Variance.10", "angular_velocity_variance.10", "AngularVelocityVariance.10"),
"Angular_Velocity_Variance.15" = c("Angular Velocity Variance.15", "angular_velocity_variance.15", "AngularVelocityVariance.15"),
"Angular_Velocity_Variance.20" = c("Angular Velocity Variance.20", "angular_velocity_variance.20", "AngularVelocityVariance.20"),
"Angular_Velocity_Variance.25" = c("Angular Velocity Variance.25", "angular_velocity_variance.25", "AngularVelocityVariance.25"),
"Amplitude_Tail_Base.10" = c("Amplitude Tail Base.10", "amplitude_tail_base.10", "AmplitudeTailBase.10"),
"Amplitude_Tail_Base.15" = c("Amplitude Tail Base.15", "amplitude_tail_base.15", "AmplitudeTailBase.15"),
"Amplitude_Tail_Base.20" = c("Amplitude Tail Base.20", "amplitude_tail_base.20", "AmplitudeTailBase.20"),
"Amplitude_Tail_Base.25" = c("Amplitude Tail Base.25", "amplitude_tail_base.25", "AmplitudeTailBase.25"),
"Amplitude_Tail_Base_Variance.10" = c("Amplitude Tail Base Variance.10", "amplitude_tail_base_variance.10", "AmplitudeTailBaseVariance.10"),
"Amplitude_Tail_Base_Variance.15" = c("Amplitude Tail Base Variance.15", "amplitude_tail_base_variance.15", "AmplitudeTailBaseVariance.15"),
"Amplitude_Tail_Base_Variance.20" = c("Amplitude Tail Base Variance.20", "amplitude_tail_base_variance.20", "AmplitudeTailBaseVariance.20"),
"Amplitude_Tail_Base_Variance.25" = c("Amplitude Tail Base Variance.25", "amplitude_tail_base_variance.25", "AmplitudeTailBaseVariance.25"),
"Limb_Duty.10" = c("Limb Duty.10", "limb_duty.10", "LimbDuty.10"),
"Limb_Duty.15" = c("Limb Duty.15", "limb_duty.15", "LimbDuty.15"),
"Limb_Duty.20" = c("Limb Duty.20", "limb_duty.20", "LimbDuty.20"),
"Limb_Duty.25" = c("Limb Duty.25", "limb_duty.25", "LimbDuty.25"),
"Limb_Duty_Variance.10" = c("Limb Duty Variance.10", "limb_duty_variance.10", "LimbDutyVariance.10"),
"Limb_Duty_Variance.15" = c("Limb Duty Variance.15", "limb_duty_variance.15", "LimbDutyVariance.15"),
"Limb_Duty_Variance.20" = c("Limb Duty Variance.20", "limb_duty_variance.20", "LimbDutyVariance.20"),
"Limb_Duty_Variance.25" = c("Limb Duty Variance.25", "limb_duty_variance.25", "LimbDutyVariance.25"),
"Amplitude_Nose.10" = c("Amplitude Nose.10", "amplitude_nose.10", "AmplitudeNose.10"),
"Amplitude_Nose.15" = c("Amplitude Nose.15", "amplitude_nose.15", "AmplitudeNose.15"),
"Amplitude_Nose.20" = c("Amplitude Nose.20", "amplitude_nose.20", "AmplitudeNose.20"),
"Amplitude_Nose.25" = c("Amplitude Nose.25", "amplitude_nose.25", "AmplitudeNose.25"),
"Amplitude_Nose_Variance.10" = c("Amplitude Nose Variance.10", "amplitude_nose_variance.10", "AmplitudeNoseVariance.10"),
"Amplitude_Nose_Variance.15" = c("Amplitude Nose Variance.15", "amplitude_nose_variance.15", "AmplitudeNoseVariance.15"),
"Amplitude_Nose_Variance.20" = c("Amplitude Nose Variance.20", "amplitude_nose_variance.20", "AmplitudeNoseVariance.20"),
"Amplitude_Nose_Variance.25" = c("Amplitude Nose Variance.25", "amplitude_nose_variance.25", "AmplitudeNoseVariance.25"),
"Step_Length.10" = c("Step Length.10", "step_length.10", "StepLength.10"),
"Step_Length.15" = c("Step Length.15", "step_length.15", "StepLength.15"),
"Step_Length.20" = c("Step Length.20", "step_length.20", "StepLength.20"),
"Step_Length.25" = c("Step Length.25", "step_length.25", "StepLength.25"),
"Step_Length_Variance.10" = c("Step Length Variance.10", "step_length_variance.10", "StepLengthVariance.10"),
"Step_Length_Variance.15" = c("Step Length Variance.15", "step_length_variance.15", "StepLengthVariance.15"),
"Step_Length_Variance.20" = c("Step Length Variance.20", "step_length_variance.20", "StepLengthVariance.20"),
"Step_Length_Variance.25" = c("Step Length Variance.25", "step_length_variance.25", "StepLengthVariance.25"),
"Step_Width.10" = c("Step Width.10", "step_width.10", "StepWidth.10"),
"Step_Width.15" = c("Step Width.15", "step_width.15", "StepWidth.15"),
"Step_Width.20" = c("Step Width.20", "step_width.20", "StepWidth.20"),
"Step_Width.25" = c("Step Width.25", "step_width.25", "StepWidth.25"),
"Step_Width_Variance.10" = c("Step Width Variance.10", "step_width_variance.10", "StepWidthVariance.10"),
"Step_Width_Variance.15" = c("Step Width Variance.15", "step_width_variance.15", "StepWidthVariance.15"),
"Step_Width_Variance.20" = c("Step Width Variance.20", "step_width_variance.20", "StepWidthVariance.20"),
"Step_Width_Variance.25" = c("Step Width Variance.25", "step_width_variance.25", "StepWidthVariance.25"),
"Stride_Length.10" = c("Stride Length.10", "stride_length.10", "StrideLength.10"),
"Stride_Length.15" = c("Stride Length.15", "stride_length.15", "StrideLength.15"),
"Stride_Length.20" = c("Stride Length.20", "stride_length.20", "StrideLength.20"),
"Stride_Length.25" = c("Stride Length.25", "stride_length.25", "StrideLength.25"),
"Stride_Length_Variance.10" = c("Stride Length Variance.10", "stride_length_variance.10", "StrideLengthVariance.10"),
"Stride_Length_Variance.15" = c("Stride Length Variance.15", "stride_length_variance.15", "StrideLengthVariance.15"),
"Stride_Length_Variance.20" = c("Stride Length Variance.20", "stride_length_variance.20", "StrideLengthVariance.20"),
"Stride_Length_Variance.25" = c("Stride Length Variance.25", "stride_length_variance.25", "StrideLengthVariance.25"),
"Temporal_Symmetry.10" = c("Temporal Symmetry.10", "temporal_symmetry.10", "TemporalSymmetry.10"),
"Temporal_Symmetry.15" = c("Temporal Symmetry.15", "temporal_symmetry.15", "TemporalSymmetry.15"),
"Temporal_Symmetry.20" = c("Temporal Symmetry.20", "temporal_symmetry.20", "TemporalSymmetry.20"),
"Temporal_Symmetry.25" = c("Temporal Symmetry.25", "temporal_symmetry.25", "TemporalSymmetry.25"),
"Temporal_Symmetry_Variance.10" = c("Temporal Symmetry Variance.10", "temporal_symmetry_variance.10", "TemporalSymmetryVariance.10"),
"Temporal_Symmetry_Variance.15" = c("Temporal Symmetry Variance.15", "temporal_symmetry_variance.15", "TemporalSymmetryVariance.15"),
"Temporal_Symmetry_Variance.20" = c("Temporal Symmetry Variance.20", "temporal_symmetry_variance.20", "TemporalSymmetryVariance.20"),
"Temporal_Symmetry_Variance.25" = c("Temporal Symmetry Variance.25", "temporal_symmetry_variance.25", "TemporalSymmetryVariance.25"),
"Amplitude_Tail_Tip.10" = c("Amplitude Tail Tip.10", "amplitude_tail_tip.10", "AmplitudeTailTip.10"),
"Amplitude_Tail_Tip.15" = c("Amplitude Tail Tip.15", "amplitude_tail_tip.15", "AmplitudeTailTip.15"),
"Amplitude_Tail_Tip.20" = c("Amplitude Tail Tip.20", "amplitude_tail_tip.20", "AmplitudeTailTip.20"),
"Amplitude_Tail_Tip.25" = c("Amplitude Tail Tip.25", "amplitude_tail_tip.25", "AmplitudeTailTip.25"),
"Amplitude_Tail_Tip_Variance.10" = c("Amplitude Tail Tip Variance.10", "amplitude_tail_tip_variance.10", "AmplitudeTailTipVariance.10"),
"Amplitude_Tail_Tip_Variance.15" = c("Amplitude Tail Tip Variance.15", "amplitude_tail_tip_variance.15", "AmplitudeTailTipVariance.15"),
"Amplitude_Tail_Tip_Variance.20" = c("Amplitude Tail Tip Variance.20", "amplitude_tail_tip_variance.20", "AmplitudeTailTipVariance.20"),
"Amplitude_Tail_Tip_Variance.25" = c("Amplitude Tail Tip Variance.25", "amplitude_tail_tip_variance.25", "AmplitudeTailTipVariance.25")
)
# Get current column names
current_cols <- names(raw_data_full)
matched_cols <- character(0)
renamed_cols <- character(0)
used_columns <- character(0) # Track which columns have already been matched
# Apply fuzzy matching
for (expected_name in names(expected_columns)) {
variations <- expected_columns[[expected_name]]
# Find the best match using exact matching first, then fuzzy
best_match <- NULL
for (variation in variations) {
if (variation %in% current_cols && !variation %in% used_columns) {
best_match <- variation
break
}
}
# If no exact match, try fuzzy matching using string distance
if (is.null(best_match)) {
# Only consider columns that haven't been used yet
available_cols <- current_cols[!current_cols %in% used_columns]
if (length(available_cols) > 0) {
distances <- sapply(available_cols, function(col) {
min(sapply(variations, function(var) {
adist(col, var, ignore.case = TRUE)[1,1]
}))
})
# Find the column with minimum distance (threshold of 2 for more restrictive matches)
min_dist <- min(distances)
if (min_dist <= 2) {
candidate_match <- names(distances)[which.min(distances)]
# Additional check: don't match if the length difference is too large
length_diff <- abs(nchar(candidate_match) - nchar(expected_name))
if (length_diff <= 3) {
best_match <- candidate_match
}
}
}
}
# If we found a match, rename the column
if (!is.null(best_match) && best_match != expected_name) {
names(raw_data_full)[names(raw_data_full) == best_match] <- expected_name
matched_cols <- c(matched_cols, expected_name)
renamed_cols <- c(renamed_cols, paste(best_match, "->", expected_name))
used_columns <- c(used_columns, best_match)
cat("DEBUG: Renamed column:", best_match, "->", expected_name, "\n")
} else if (!is.null(best_match) && best_match == expected_name) {
matched_cols <- c(matched_cols, expected_name)
used_columns <- c(used_columns, best_match)
}
}
# Add missing categorical columns with dummy values if they don't exist
missing_categorical <- c("DOB", "Date")
for (col in missing_categorical) {
if (!col %in% names(raw_data_full)) {
raw_data_full[[col]] <- "Not Available"
cat("DEBUG: Added missing categorical column:", col, "with dummy values\n")
}
}
# Log the results
cat("DEBUG: Fuzzy matching completed.\n")
cat("DEBUG: Matched columns:", length(matched_cols), "out of", length(expected_columns), "expected\n")
if (length(renamed_cols) > 0) {
cat("DEBUG: Renamed columns:", paste(renamed_cols, collapse = ", "), "\n")
}
# Store fuzzy matching results for display in UI
tryCatch({
rv$fuzzy_matching_results <- list(
renamed_columns = renamed_cols,
missing_columns = missing_categorical[missing_categorical %in% names(raw_data_full) &
raw_data_full[[missing_categorical[1]]][1] == "Not Available"],
total_matched = length(matched_cols),
total_expected = length(expected_columns)
)
}, error = function(e) {
cat("DEBUG: Error storing fuzzy matching results:", e$message, "\n")
rv$fuzzy_matching_results <- list(
renamed_columns = character(0),
missing_columns = character(0),
total_matched = 0,
total_expected = 0
)
})
}, error = function(e) {
cat("DEBUG: Error in fuzzy matching, using original data:", e$message, "\n")
rv$fuzzy_matching_results <- list(
renamed_columns = character(0),
missing_columns = character(0),
total_matched = 0,
total_expected = 0
)
})
rv$raw_data_full <- raw_data_full
# DEBUG: Show what columns are in the original data after fuzzy matching
cat("DEBUG: Columns after fuzzy matching:", paste(names(raw_data_full), collapse = ", "), "\n")
cat("DEBUG: Looking for categorical variables: Sex, PenID, Cohort, DOB, Date\n")
categorical_vars <- c("Sex", "Pen_ID", "Cohort", "DOB", "Date", "PenID", "pen_id", "cohort", "dob", "date", "sex")
found_categorical <- categorical_vars[categorical_vars %in% names(raw_data_full)]
cat("DEBUG: Found categorical variables:", paste(found_categorical, collapse = ", "), "\n")
# Set preselected features: Distance_Traveled as first, bin_avg_60.scratch_time_secs as last
# Note: Use the renamed version after fuzzy matching (spaces converted to underscores)
start_feature <- "Distance_Traveled"
end_feature <- "bin_avg_60.scratch_time_secs"
# Check if the preselected features exist in the dataset
if (start_feature %in% names(raw_data_full)) {
selected_start <- start_feature
} else {
# Fallback to first available feature if Distance_Traveled not found
selected_start <- names(raw_data_full)[1]
cat("DEBUG: Distance_Traveled not found, using first feature:", selected_start, "\n")
}
if (end_feature %in% names(raw_data_full)) {
selected_end <- end_feature
} else {
# Fallback to last available feature if bin_avg_60.scratch_time_secs not found
selected_end <- names(raw_data_full)[length(names(raw_data_full))]
cat("DEBUG: bin_avg_60.scratch_time_secs not found, using last feature:", selected_end, "\n")
}
updateSelectInput(
session,
"start_col_selector",
choices = names(raw_data_full),
selected = selected_start
)
updateSelectInput(
session,
"end_col_selector",
choices = names(raw_data_full),
selected = selected_end
)
cat("DEBUG: Preselected features - Start:", selected_start, "End:", selected_end, "\n")
# Show simple notification
if (length(renamed_cols) > 0 || length(rv$fuzzy_matching_results$missing_columns) > 0) {
showNotification(
"Data loaded with automatic column adjustments. See 'Column Matching Results' below for details.",
type = "message",
duration = 5
)
} else {
showNotification("Data loaded successfully!", type = "message")
}
})
})
# --- Display Fuzzy Matching Results ---
output$fuzzy_matching_results <- renderUI({
req(rv$fuzzy_matching_results)
tryCatch({
results <- rv$fuzzy_matching_results
if (length(results$renamed_columns) == 0 && length(results$missing_columns) == 0) {
return(
div(
class = "alert alert-success",
h5("✅ No Column Adjustments Needed"),
p("All column names matched the expected format perfectly!")
)
)
}
# Create the results display
div(
class = "panel panel-info",
div(
class = "panel-heading",
h4("📋 Column Matching Results", style = "margin: 0;")
),
div(
class = "panel-body",
# Summary
div(
class = "alert alert-info",
h5("📊 Summary"),
p(strong("Matched:"), results$total_matched, "out of", results$total_expected, "expected columns"),
if (length(results$renamed_columns) > 0) {
p(strong("Renamed:"), length(results$renamed_columns), "columns")
},
if (length(results$missing_columns) > 0) {
p(strong("Added:"), length(results$missing_columns), "missing columns with placeholder values")
}
),
# Renamed columns
if (length(results$renamed_columns) > 0) {
div(
h5("🔄 Renamed Columns"),
div(
class = "table-responsive",
style = "max-height: 200px; overflow-y: auto;",
tags$table(
class = "table table-striped table-condensed",
tags$thead(
tags$tr(
tags$th("Original Name", style = "width: 50%;"),
tags$th("New Name", style = "width: 50%;")
)
),
tags$tbody(
lapply(results$renamed_columns, function(rename) {
parts <- strsplit(rename, " -> ")[[1]]
tags$tr(
tags$td(parts[1], style = "font-family: monospace;"),
tags$td(parts[2], style = "font-family: monospace; color: #337ab7;")
)
})
)
)
)
)
},
# Missing columns
if (length(results$missing_columns) > 0) {
div(
h5("➕ Added Missing Columns"),
div(
class = "alert alert-warning",
p("The following columns were missing and have been added with placeholder values:"),
tags$ul(
lapply(results$missing_columns, function(col) {
tags$li(strong(col), " = 'Not Available'")
})
)
)
)
}
)
)
}, error = function(e) {
cat("DEBUG: Error rendering fuzzy matching results:", e$message, "\n")
return(div(
class = "alert alert-warning",
h5("⚠️ Display Error"),
p("Could not display column matching results. Check console for details.")
))
})
})
# --- Reactive for Subsetting Data by Selected Features ---
data_with_selected_features <- reactive({
req(rv$raw_data_full, input$start_col_selector, input$end_col_selector)
raw_df <- rv$raw_data_full
all_names <- names(raw_df)
start_idx <- which(all_names == input$start_col_selector)
end_idx <- which(all_names == input$end_col_selector)
validate(need(
length(start_idx) == 1 && length(end_idx) == 1 && start_idx <= end_idx,
"Please select a valid start and end column range."
))
feature_names <- all_names[start_idx:end_idx]
metadata_cols <- all_names[1:(start_idx - 1)]
# DEBUG: Show what features are being selected
cat("DEBUG data_with_selected_features: Selected features:", paste(feature_names, collapse = ", "), "\n")
cat("DEBUG data_with_selected_features: Metadata columns:", paste(metadata_cols, collapse = ", "), "\n")
required_cols <- c("MouseID", "Tx", "Timepoint")
missing_cols <- setdiff(required_cols, metadata_cols)
if (length(missing_cols) > 0) {
showNotification(
paste(
"Data is missing required metadata column. Missing:",
paste(missing_cols, collapse = ", ")
),
type = "error",
duration = 10
)
return(NULL)
}
# Create proper timepoint sorting (D00 to D21)
timepoint_levels <- unique(raw_df$Timepoint)
# Extract numeric part and sort
timepoint_nums <- as.numeric(gsub("D", "", timepoint_levels))
sorted_timepoints <- timepoint_levels[order(timepoint_nums)]
raw_df %>%
select(all_of(metadata_cols), all_of(feature_names)) %>%
filter(as.numeric(Tx) %in% CONFIG$genotypes) %>%
mutate(
Tx = factor(Tx, levels = as.character(CONFIG$genotypes)),
Timepoint = factor(
Timepoint,
levels = sorted_timepoints
)
)
})
# --- Observer to reset analysis if feature range changes ---
observeEvent(
c(input$start_col_selector, input$end_col_selector),
{
if (!is.null(rv$preprocessed_data)) {
rv$preprocessed_data <- NULL
rv$original_for_plotting <- NULL
rv$pca_results <- NULL
showNotification(
"Feature range has changed. Please click 'Run Selected Steps' to re-process the data.",
type = "warning",
duration = 8
)
}
},
ignoreInit = TRUE
)
# --- UI Logic for Conditional Panels ---
# This reactive output is used by conditionalPanel in ui.R to show/hide UI elements
# based on whether the preprocessed data is available.
output$preprocessed_data_exists <- reactive({
!is.null(rv$preprocessed_data)
})
# It's important to keep this output suspended so it doesn't try to render
# anywhere, but its value can still be accessed by the UI for the conditionalPanel.
outputOptions(output, "preprocessed_data_exists", suspendWhenHidden = FALSE)
# --- Source Server Logic for Each Tab ---
source(
file.path("server_tabs", "tab_data_exploration_server.R"),
local = TRUE
)
source(file.path("server_tabs", "tab_pca_server.R"), local = TRUE)
source(file.path("server_tabs", "tab_lda_server.R"), local = TRUE)
source(file.path("server_tabs", "tab_correlation_server.R"), local = TRUE)
source(file.path("server_tabs", "tab_summary_server.R"), local = TRUE)
}