Skip to content

Commit a3574bd

Browse files
committed
Add view details for multimatch sample, including map and dates. Add user ability to choose one group of samples/individual for the multimatch samples
1 parent 36162f7 commit a3574bd

6 files changed

+268
-45
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ vignettes/*.pdf
4242
.~lock*
4343

4444
# R Project
45-
*.Rproj
45+
# *.Rproj
4646
.Rbuildignore

DESCRIPTION

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ Imports:
1212
allelematch,
1313
tidyverse,
1414
shiny,
15-
zeallot
15+
zeallot,
16+
readxl,
17+
readODS

GenotypeCheck.Rproj

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Version: 1.0
2+
3+
RestoreWorkspace: No
4+
SaveWorkspace: No
5+
AlwaysSaveHistory: Default
6+
7+
EnableCodeIndexing: Yes
8+
UseSpacesForTab: Yes
9+
NumSpacesForTab: 2
10+
Encoding: UTF-8
11+
12+
RnwWeave: Sweave
13+
LaTeX: pdfLaTeX
14+
15+
AutoAppendNewline: Yes
16+
StripTrailingWhitespace: Yes
17+
18+
BuildType: Package
19+
PackageUseDevtools: Yes
20+
PackageInstallArgs: --no-multiarch --with-keep.source
21+
PackageCheckArgs: --as-cran
22+
PackageRoxygenize: rd,collate,namespace,vignette

R/AC_allKorr.ods

85.9 KB
Binary file not shown.

R/app.R

+186-35
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
library("shiny")
2+
library("DT")
23
library("zeallot")
3-
library("GenotypeCheck")
4+
library("leaflet")
5+
library("rgdal")
6+
#source("import_data.R")
7+
#library("GenotypeCheck")
48

59
# The standard locus names, mostly for faster testing
610
locus_names <- c("G10L", "G10L.1", "Mu05", "Mu05.1", "Mu09", "Mu09.1", "Mu10", "Mu10.1",
711
"Mu23", "Mu23.1", "Mu50", "Mu50.1", "Mu51", "Mu51.1", "Mu59", "Mu59.1")
812

13+
# Define coordinates system to convert from SWEREF99 (which the data is in) to WGS84 to render with leaflet
14+
SWEREF99 <- CRS("+init=epsg:3006")
15+
RT90 <- CRS("+init=epsg:3021")
16+
WGS84 <- CRS("+init=epsg:4326")
17+
UTM32N <- CRS("+init=epsg:32632")
18+
919
# Define UI
1020
ui <- fluidPage(
1121

@@ -16,21 +26,22 @@ ui <- fluidPage(
1626
sidebarLayout(
1727

1828
# Sidebar panel for inputs
19-
sidebarPanel(
29+
sidebarPanel(width = 3,
2030

2131
# Input: Select a file
2232
fileInput("file1", "Choose CSV File",
2333
multiple = FALSE,
2434
accept = c("text/csv",
2535
"text/comma-separated-values,text/plain",
26-
".csv")),
36+
".csv", ".xls", ".xlsx", ".ods")),
2737

2838

2939
# Input: Checkbox if file has header
3040
checkboxInput("header", "Header", TRUE),
3141

3242
tags$hr(),
3343
# Select allele mismatch value
44+
h4("TODO::: Show amUniqueProfile To User If a button is clicked"),
3445
textInput(inputId = "alleleMismatchValue", label = "Allowed Allele-mismatch", value = "3"),
3546

3647
h4("Type the column name of the specified columns."),
@@ -45,31 +56,61 @@ ui <- fluidPage(
4556
textInput(inputId = "northColumnName", label = "North Column", value = "Nord"),
4657
textInput(inputId = "eastColumnName", label = "East Column", value = "Ost"),
4758
textInput(inputId = "genderColumnName", label = "Gender Column", value = "Kon"),
48-
textInput(inputId = "presetIndColumnName", label = "PresetIndividual Column", value = ""),
59+
textInput(inputId = "presetIndColumnName", label = "PresetIndividual Column", value = "Individ"),
4960

5061
# Select Locus Columns
5162
textInput(inputId = "locusColumnNames", label = "Locus Columns (separated by ',')", value = paste0(locus_names, collapse = "", sep = ",")),
5263

5364
# Parse Data
5465
actionButton(inputId = "groupIndividuals", label = "GROUP INDIVIDUALS"),
5566

56-
tags$hr(),
5767
tags$hr(),
5868

59-
# Display some result data to the user, TODO: allow the user to handle these exceptations
69+
# Display some result data to the user
6070
textOutput(outputId = "amtMultipleMatches"),
6171
textOutput(outputId = "amtUnclassified"),
6272

63-
),
73+
div(h4("Handle Multiple Matches")),
6474

65-
# Main panel for displaying outputs ----
66-
mainPanel(
75+
textInput(inputId = "multipleMatchIndex", label = "View Details (Index of Multiple Matched Sample): ", placeholder = "tex 2"),
76+
DT::dataTableOutput("multipleMatchesTable"),
6777

68-
# Output: Data file ----
69-
tableOutput("contents")
78+
# TODO:: Allow the user to handle these (similar to matching new data)
79+
div(h4("Handle Unclassified Samples")),
7080

71-
)
81+
DT::dataTableOutput("unclassifiedTable"),
7282

83+
),
84+
85+
# Main panel for displaying outputs
86+
mainPanel(
87+
# Panel for handeling multiple matched data, will probably be similar to the panel for matching new data
88+
conditionalPanel("input.multipleMatchIndex != ''",
89+
# Showing Multimatch data for SEP123123 <-- Example
90+
h4(textOutput("multiMatchDataFor")),
91+
h5("The sample matched the following individuals: "),
92+
# Desired: map beside data, now it jumps down because of size, not that important
93+
sidebarLayout(
94+
sidebarPanel = sidebarPanel(width = 9,
95+
# Render the ones that were similar
96+
DT::dataTableOutput("multipleMatchedSingle"),
97+
),
98+
mainPanel = mainPanel(
99+
# render the map for the user to have all data when deciding which individual to add it to
100+
leafletOutput(outputId = "multiMatchMap"),
101+
),
102+
),
103+
# User choose and add to a group of samples/individual - information
104+
h5("If this ID is one of the listed above the sample will be added to that group of sample/individual, if not, the sample will create a new individual IF the new ID does not already exist, make sure it is unique if that is the desired action."),
105+
# Text box to type new id, either create new group or create a override id for every sample in that group
106+
textInput(inputId = "multipleMatchFix", label = "Set ID/Individual to group: "),
107+
actionButton(inputId = "multipleMatchFixConfirm", label = "Confirm/Save to data"),
108+
tags$hr(),
109+
),
110+
111+
# Output: Data file
112+
DT::dataTableOutput("contents")
113+
)
73114
)
74115
)
75116

@@ -83,54 +124,164 @@ server <- function(input, output, session) {
83124
multiple_matches <- list(index = character())
84125
unclassified <- list(index = character(), multilocus = character())
85126

127+
data <- list()
128+
86129
# Run allelematch and all GenotypeChecks the surrounding code when the click of the button
87130
observeEvent(input$groupIndividuals, {
88131
groupIndividuals()
132+
update_output_preprocess_data()
89133
})
90134

91135
groupIndividuals <- function() {
92-
# Render the table of all sample data
93-
output$contents <- renderTable({
94-
req(input$file1)
136+
req(input$file1)
95137

96-
# Read the locus data from the ui
97-
locus_columns <- strsplit(input$locusColumnNames, ",")[[1]]
138+
# Read the locus data from the ui
139+
locus_columns <- strsplit(input$locusColumnNames, ",")[[1]]
98140

99-
# Read all of columns for the additional data from the ui
100-
additional_data <<- list(date = input$dateColumnName, north = input$northColumnName, east = input$eastColumnName, gender = input$genderColumnName, preset_ind = input$presetIndColumnName)
101-
# If the user does not specify column they get removed here to not have empty objects later that mess things up
102-
additional_data <<- additional_data[additional_data != ""]
141+
# Read all of columns for the additional data from the ui
142+
additional_data <<- list(date = input$dateColumnName, north = input$northColumnName, east = input$eastColumnName, gender = input$genderColumnName, preset_ind = input$presetIndColumnName)
143+
# If the user does not specify column they get removed here to not have empty objects later that mess things up
144+
additional_data <<- additional_data[additional_data != ""]
103145

104-
index_column <- input$indexColumnName
146+
index_column <- input$indexColumnName
105147

106-
# Convert the numbers if header is deselected (and we are handeling columnindexes instead of columnnames)
107-
if (!input$header) {
108-
locus_columns <- sapply(locus_columns, as.numeric)
109-
additional_data <<- sapply(additional_data, as.numeric)
110-
index_column <- as.numeric(index_column)
111-
}
148+
# Convert the numbers if header is deselected (and we are handeling columnindexes instead of columnnames)
149+
if (!input$header) {
150+
locus_columns <- sapply(locus_columns, as.numeric)
151+
additional_data <<- sapply(additional_data, as.numeric)
152+
index_column <- as.numeric(index_column)
153+
}
112154

113-
# Unpack the different data returned by our wrapper of allelematch into temp variables
114-
c(search_data_temp, multiple_matches_temp, unclassified_temp) %<-% create_search_data(input$file1$datapath, index_column, additional_data, locus_columns, as.numeric(input$alleleMismatchValue))
155+
# Unpack the different data returned by our wrapper of allelematch into temp variables
156+
c(data_temp, search_data_temp, multiple_matches_temp, unclassified_temp) %<-% create_search_data(input$file1$datapath, index_column, additional_data, locus_columns, as.numeric(input$alleleMismatchValue))
115157

116-
# Change the session (server) data from the temp data
117-
search_data <<- search_data_temp
118-
multiple_matches <<- multiple_matches_temp
119-
unclassified <<- unclassified_temp
158+
# Change the session (server) data from the temp data
159+
search_data <<- search_data_temp
160+
multiple_matches <<- multiple_matches_temp
161+
unclassified <<- unclassified_temp
162+
data <<- data_temp
163+
}
120164

165+
update_output_preprocess_data <- function() {
166+
# Render the table of all sample data
167+
output$contents <- DT::renderDataTable(options = list(pageLength = 50, lengthMenu = c(10, 25, 50, 100, 250)), filter = "top",
168+
{
121169
# Show the indexes, multilocus and individual id data to the user
122-
return(search_data)
170+
search_data
123171
})
124172

125173
# Display the amount of problematic data. TODO: Allow user to handle this data and tell the program what to do
126174
output$amtMultipleMatches <- renderText(
127175
paste0("There were: ", length(multiple_matches), " samples that matched multiple individuals.")
128176
)
129177

178+
# Render only the ones that matched multiple so the user can choose one
179+
output$multipleMatchesTable <- DT::renderDataTable({
180+
number_indexes <- 1:length(multiple_matches)
181+
# If there are none, avoid an vector that look like c(1, 0)
182+
if (length(multiple_matches) == 0) {
183+
number_indexes <- c()
184+
}
185+
# Add id for the user to choose and view the details for one
186+
df <- data.frame(list(multipleMatchIndex = number_indexes))
187+
rownames(df) <- multiple_matches
188+
df
189+
})
190+
191+
# Show amount of unclassified samples in text
130192
output$amtUnclassified <- renderText(
131193
paste0("There were: ", length(unclassified$index), " samples that were unclassified.")
132194
)
133195
}
196+
197+
# Observe when user types an index to view details
198+
observeEvent(input$multipleMatchIndex, {
199+
# Make sure the data is generated/button pressed, and that the user did not delete the index
200+
req(input$multipleMatchIndex, input$groupIndividuals)
201+
202+
# If the index is parsable, continue
203+
if (!is.na(as.numeric(input$multipleMatchIndex)) & as.numeric(input$multipleMatchIndex) <= length(multiple_matches)) {
204+
# Figure out what "big" index we are handeling, SEP index
205+
showing_index <- multiple_matches[[as.numeric(input$multipleMatchIndex)]]
206+
207+
# Create a filter c(TRUE, TRUE, FALSE, FALSE, FALSE) to select only the ones that are in the group of samples that the multimatched sample was in
208+
# Get the multiple instaces of the sample, here in different groups
209+
search_data_filter <- search_data$index == showing_index
210+
# Get the id of the groups that the sample is a port of
211+
ids <- get_id(search_data[search_data_filter,])
212+
# Expand the filter to include everything with those id:s aswell
213+
search_data_filter <- search_data_filter | get_id(search_data) %in% ids
214+
215+
# Show information to user, which sample (SEP index)
216+
output$multiMatchDataFor <- renderText(paste0("Showing Data For ", showing_index))
217+
218+
# Render all samples that are part of the process to choose
219+
output$multipleMatchedSingle <- DT::renderDataTable({
220+
search_data[search_data_filter,]
221+
})
222+
223+
# If the user have specified map coordinates, continue
224+
if (!is.null(additional_data$north) & !is.null(additional_data$east)) {
225+
# Extract the long and lat from the data
226+
# The data have user defined names for the columns, hence the pull with the additional_data which is the link between the user
227+
# defined column-names and to us known names (north, east etc)
228+
coords <- list(lng = pull(data[search_data$index[search_data_filter],], additional_data$east),
229+
lat = pull(data[search_data$index[search_data_filter],], additional_data$north))
230+
# Create a spatialpointsdataframe with the coordinates, empty meta-data and the input GPS system
231+
p1 <- SpatialPointsDataFrame(coords, data = data.frame(list(temp = rep(NA, length(search_data_filter[search_data_filter == TRUE])))), proj4string = SWEREF99)
232+
# Convert to WGS84 to render to the map and extract the coordinates
233+
p2 <- spTransform(p1, WGS84) %>%
234+
coordinates()
235+
236+
# Render to the map
237+
output$multiMatchMap <- renderLeaflet({
238+
# Get the id:s for all points to be renderer
239+
ids <- get_id(search_data[search_data_filter,])
240+
# Change the id for the ones that have multiple ids. They have the same location and will be placed on top of each other
241+
ids[search_data$index[search_data_filter] %in% multiple_matches] <- "Multiple"
242+
# Read the dates for the relevant samples from the meta-data
243+
dates <- pull(data[search_data$index[search_data_filter],], additional_data$date)
244+
# Create all labels with the information we want to display
245+
label <- paste0("Index: ", search_data$index[search_data_filter], " ID: ", ids, " Date: ", dates)
246+
247+
# Render the map with leaflet and att markers
248+
leaflet() %>%
249+
# Get the map from openstreetmap
250+
addProviderTiles(provider = providers$OpenStreetMap,
251+
options = providerTileOptions(noWrap = TRUE)) %>%
252+
# Add popups (take alot of space but gives all information, can be closed) - uses the label with the information created earlier
253+
addPopups(lng = p2[,"lng"], lat = p2[,"lat"], popup = label, options = popupOptions(closeButton = TRUE)) %>%
254+
# Add markers that show the information both on click and on hover, cant disapear
255+
addMarkers(lng = p2[,"lng"], lat = p2[,"lat"], label = label, popup = label)
256+
})
257+
}
258+
}
259+
})
260+
261+
# Observe if user clicks the button to change the override id of the sample in question (that have been multimatched)
262+
observeEvent(input$multipleMatchFixConfirm, {
263+
# Make sure the user has written a new id
264+
req(input$multipleMatchFix)
265+
266+
# Figure out the search_data index, (SEP index)
267+
showing_index <- multiple_matches[[as.numeric(input$multipleMatchIndex)]]
268+
# Remove the sample from the list of samples that have multiple matches, it has been corrected by the user
269+
multiple_matches <<- multiple_matches[multiple_matches != showing_index]
270+
# Remove all duplicated of the specific sample, the order doesnt matter as override id is the important parameter for the id
271+
# We can remove the one with the correct id and set the override id of another without problem
272+
search_data <<- search_data[!(search_data$index == showing_index & duplicated(search_data$index)),]
273+
# Set the sample (now no duplicates, only one left) override id to be the id specified by the user
274+
search_data$override_id[search_data$index == showing_index] <<- input$multipleMatchFix
275+
# Set every sample that is in the same group to have a override id, maybe not necessary but to ensure the order generated by
276+
# allelelmatch doesnt change and would therefor place the user "controlled" one in a then different group
277+
search_data$override_id[get_id(search_data) == input$multipleMatchFix] <<- input$multipleMatchFix
278+
279+
# Update the visual information, the big table and the count of multimatches
280+
update_output_preprocess_data()
281+
# Reset the chosen multimatch index, the conditional panel will disapear until the user chooses a new sample that have been multimatched to handle
282+
updateTextInput(session, "multipleMatchIndex", value = "")
283+
})
134284
}
135285

286+
# Create a shiny app
136287
shinyApp(ui = ui, server = server)

0 commit comments

Comments
 (0)