Skip to content

Commit bef50b5

Browse files
authored
Add files via upload
1 parent 132a7a5 commit bef50b5

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed

app.R

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
####
2+
# Version: 0.10.2
3+
# Author: Christian Jaeger (christian.jaeger@uk-halle.de)
4+
# 20250312
5+
####
6+
7+
## Main Changes:
8+
# 0.10.2 Added Export Button
9+
################
10+
11+
## Beschreibung:
12+
# Diese Shiny-App ermöglicht das Hochladen und Zusammenführen von zwei CSV- oder TXT-Dateien.
13+
# Falls die Dateien keine Spaltennamen enthalten, können diese aus einer zugehörigen SQL-Dump-Datei extrahiert werden.
14+
# Der Nutzer kann auswählen, welche Spalten als ID für den Merge-Prozess verwendet werden sollen.
15+
# Zudem kann zwischen verschiedenen Join-Typen (Inner, Outer, Left, Right) gewählt werden.
16+
# Das zusammengeführte Ergebnis wird als interaktive Tabelle angezeigt und kann als CSV-Datei gespeichert werden.
17+
# Optional kann der Nutzer einen eigenen Dateinamen für den Export vergeben.
18+
################
19+
20+
library(shiny)
21+
library(readr)
22+
library(writexl)
23+
library(stringr)
24+
library(DT)
25+
library(shinyjs)
26+
27+
extract_column_names <- function(sql_file) {
28+
sql_text <- readLines(sql_file, warn = FALSE)
29+
table_start <- grep("CREATE TABLE", sql_text)
30+
if (length(table_start) == 0) return(NULL)
31+
32+
sql_text <- sql_text[(table_start + 1):length(sql_text)]
33+
table_end <- grep("\\) ENGINE", sql_text)[1] # Ensure capturing all column definitions
34+
if (is.na(table_end)) return(NULL)
35+
36+
column_lines <- sql_text[1:(table_end - 1)]
37+
column_names <- str_extract_all(column_lines, "`([^`]*)`")
38+
column_names <- unlist(column_names)
39+
column_names <- column_names[!is.na(column_names)]
40+
column_names <- gsub("`", "", column_names)
41+
42+
return(column_names)
43+
}
44+
45+
clean_column_names <- function(col_names) {
46+
col_names <- make.names(col_names, unique = TRUE)
47+
col_names <- gsub("\\.+(\\d+)$", "", col_names) # Entfernt ...[Zahl] komplett
48+
return(col_names)
49+
}
50+
51+
rename_columns <- function(df, file_name) {
52+
file_suffix <- tools::file_path_sans_ext(basename(file_name))
53+
colnames(df) <- paste0(clean_column_names(colnames(df)), "_", file_suffix)
54+
return(df)
55+
}
56+
57+
# UI der Shiny-App
58+
ui <- fluidPage(
59+
useShinyjs(),
60+
tags$head(tags$title("CSV/SQL - Merger - DIZ & Biomedical Data Science")),
61+
titlePanel(
62+
div(
63+
tags$img(src = "Logo_DIZ_DE.jpg", height = "80px", style = "margin-right: 10px;"),
64+
div(
65+
h1("CSV/SQL - Merger für File Preprocessings - 0.10.2", style = "margin-bottom: 0px;"),
66+
h4("Ein Service des Datenintegrationszentrums (DIZ) und der AG (Bio-) Medical Data Science",
67+
style = "margin-top: 5px; color: gray; font-weight: normal;")
68+
)
69+
)
70+
),
71+
72+
sidebarLayout(
73+
sidebarPanel(
74+
checkboxInput("has_headers1", "Datei 1 enthält Spaltennamen", value = FALSE),
75+
76+
fileInput("file1", "Lade CSV- oder TXT-Datei 1 hoch", accept = c(".csv", ".txt")),
77+
fileInput("sql1", "Lade zugehörige SQL-Dump-Datei 1 hoch", accept = ".sql"),
78+
hr(),
79+
checkboxInput("has_headers2", "Datei 2 enthält Spaltennamen", value = FALSE),
80+
fileInput("file2", "Lade CSV- oder TXT-Datei 2 hoch", accept = c(".csv", ".txt")),
81+
fileInput("sql2", "Lade zugehörige SQL-Dump-Datei 2 hoch", accept = ".sql"),
82+
83+
uiOutput("select_id1"),
84+
uiOutput("select_id2"),
85+
86+
hr(),
87+
selectInput("join_type", "Join-Typ auswählen:",
88+
choices = list("Inner Join" = "inner",
89+
"Outer Join" = "outer",
90+
"Left Join" = "left",
91+
"Right Join" = "right")),
92+
actionButton("merge", "Merge-this-IDs"),
93+
hr(),
94+
textInput("filename", "Name der Datei: (optional)", value = ""),
95+
downloadButton("download", "Download Merged CSV"),
96+
downloadButton("download_excel", "Download Merged Excel"),
97+
hr(),
98+
tags$a(href = "readme.html", "Dokumentation öffnen", target = "_blank"),
99+
br(), br(),
100+
h4("Kontakt"),
101+
tags$p("Fragen? Schreiben Sie an: "),
102+
tags$a(href = "mailto:christian.jaeger@uk-halle.de", "christian.jaeger@uk-halle.de"),
103+
),
104+
mainPanel(
105+
h3("Vorschau der Dateien"),
106+
fluidRow(
107+
column(6, DTOutput("preview1")),
108+
column(6, DTOutput("preview2"))
109+
),
110+
h3("Zusammengeführte Datei"),
111+
DTOutput("merged_table")
112+
)
113+
)
114+
)
115+
116+
server <- function(input, output, session) {
117+
observe({
118+
toggleState("sql1", condition = !input$has_headers1)
119+
toggleState("sql2", condition = !input$has_headers2)
120+
})
121+
122+
data1 <- reactive({
123+
req(input$file1)
124+
if (!grepl("\\.csv$|\\.txt$", input$file1$name, ignore.case = TRUE)) {
125+
showNotification("Fehler: Ungültiges Dateiformat für Datei 1.", type = "error")
126+
return(NULL)
127+
}
128+
if (input$has_headers1) {
129+
df <- read_csv(input$file1$datapath, show_col_types = FALSE)
130+
} else {
131+
req(input$sql1)
132+
col_names <- extract_column_names(input$sql1$datapath)
133+
if (is.null(col_names)) return(NULL)
134+
df <- read_csv(input$file1$datapath, col_names = col_names, skip = 0, show_col_types = FALSE)
135+
}
136+
rename_columns(df, input$file1$name)
137+
})
138+
139+
data2 <- reactive({
140+
req(input$file2)
141+
if (!grepl("\\.csv$|\\.txt$", input$file2$name, ignore.case = TRUE)) {
142+
showNotification("Fehler: Ungültiges Dateiformat für Datei 2.", type = "error")
143+
return(NULL)
144+
}
145+
if (input$has_headers2) {
146+
df <- read_csv(input$file2$datapath, show_col_types = FALSE)
147+
} else {
148+
req(input$sql2)
149+
col_names <- extract_column_names(input$sql2$datapath)
150+
if (is.null(col_names)) return(NULL)
151+
df <- read_csv(input$file2$datapath, col_names = col_names, skip = 0, show_col_types = FALSE)
152+
}
153+
rename_columns(df, input$file2$name)
154+
})
155+
156+
output$preview1 <- renderDT({
157+
req(data1())
158+
datatable(data1(), options = list(scrollX = TRUE))
159+
})
160+
161+
output$preview2 <- renderDT({
162+
req(data2())
163+
datatable(data2(), options = list(scrollX = TRUE))
164+
})
165+
166+
output$select_id1 <- renderUI({
167+
req(data1())
168+
selectInput("id1", "Wähle ID-Spalte Datei 1:", choices = names(data1()))
169+
})
170+
171+
output$select_id2 <- renderUI({
172+
req(data2())
173+
selectInput("id2", "Wähle ID-Spalte Datei 2:", choices = names(data2()))
174+
})
175+
176+
merged_data <- reactiveVal()
177+
178+
observeEvent(input$merge, {
179+
req(input$id1, input$id2)
180+
181+
join_type <- input$join_type
182+
183+
merged <- switch(join_type,
184+
"inner" = merge(data1(), data2(), by.x = input$id1, by.y = input$id2),
185+
"outer" = merge(data1(), data2(), by.x = input$id1, by.y = input$id2, all = TRUE),
186+
"left" = merge(data1(), data2(), by.x = input$id1, by.y = input$id2, all.x = TRUE),
187+
"right" = merge(data1(), data2(), by.x = input$id1, by.y = input$id2, all.y = TRUE))
188+
189+
merged_data(merged)
190+
})
191+
192+
output$merged_table <- renderDT({
193+
req(merged_data())
194+
datatable(merged_data(), options = list(scrollX = TRUE))
195+
})
196+
197+
output$download <- downloadHandler(
198+
filename = function() {
199+
if (input$filename != "") {
200+
paste0(input$filename, ".csv")
201+
} else {
202+
paste0(tools::file_path_sans_ext(input$file1$name), "_",
203+
tools::file_path_sans_ext(input$file2$name), ".csv")
204+
}
205+
},
206+
content = function(file) {
207+
req(merged_data())
208+
write_csv(merged_data(), file)
209+
}
210+
)
211+
212+
output$download_excel <- downloadHandler(
213+
filename = function() {
214+
if (input$filename != "") {
215+
paste0(input$filename, ".xlsx")
216+
} else {
217+
paste0(tools::file_path_sans_ext(input$file1$name), "_",
218+
tools::file_path_sans_ext(input$file2$name), ".xlsx")
219+
}
220+
},
221+
content = function(file) {
222+
req(merged_data())
223+
write_xlsx(merged_data(), file)
224+
}
225+
)
226+
}
227+
228+
shinyApp(ui, server)

www/Logo_DIZ_DE.jpg

83.6 KB
Loading

www/readme.html

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!DOCTYPE html>
2+
<html lang="de">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>CSV/SQL Merger - Dokumentation</title>
7+
<style>
8+
body { font-family: Arial, sans-serif; line-height: 1.6; margin: 40px; }
9+
h1, h2, h3 { color: #333; }
10+
pre { background: #f4f4f4; padding: 10px; border-radius: 5px; }
11+
code { font-weight: bold; }
12+
a { color: #007bff; text-decoration: none; }
13+
a:hover { text-decoration: underline; }
14+
</style>
15+
</head>
16+
<body>
17+
<h1>CSV/SQL Merger - Dokumentation</h1>
18+
<h2>Überblick</h2>
19+
<p>Diese Shiny-App ermöglicht das <strong>Hochladen, Verknüpfen und Speichern</strong> von CSV- oder TXT-Dateien mit zugehörigen SQL-Dump-Dateien. Sie wurde entwickelt, um <strong>alte JAM-Py-Datenbankexporte</strong> zu verarbeiten und daraus verknüpfte Tabellen zu erstellen, die als Importdateien für <strong>eLabFTW-Ressourcen</strong> genutzt werden können.</p>
20+
21+
<h2>Funktionen</h2>
22+
<ul>
23+
<li><strong>Import von CSV- oder TXT-Dateien</strong></li>
24+
<li><strong>Optionaler Import von SQL-Dump-Dateien</strong>, um Spaltennamen zu extrahieren</li>
25+
<li><strong>ID-basierte Verknüpfung</strong> von Tabellen</li>
26+
<li><strong>Unterstützung verschiedener Join-Typen</strong> (Inner, Outer, Left, Right)</li>
27+
<li><strong>Interaktive Vorschau der Daten</strong></li>
28+
<li><strong>Export der verknüpften Tabelle als CSV</strong> mit wählbarem Dateinamen</li>
29+
</ul>
30+
31+
<h2>Nutzungsszenario</h2>
32+
<ol>
33+
<li><strong>Export aus JAM-Py</strong>: Besteht aus TXT-Dateien mit Rohdaten und einer SQL-Dump-Datei mit Tabellenstrukturen.</li>
34+
<li><strong>Datenverknüpfung in Shiny</strong>: Die Shiny-App liest die Dateien ein, verknüpft Tabellen über gemeinsame IDs und erstellt eine bereinigte Datentabelle.</li>
35+
<li><strong>Weiterverarbeitung für eLabFTW</strong>: Die resultierende Datei kann mit einer weiteren Shiny-App in das eLabFTW-Format überführt werden.</li>
36+
</ol>
37+
38+
<h2>Installation & Nutzung</h2>
39+
<h3>Voraussetzungen</h3>
40+
<p>R (>= 4.0) und folgende R-Pakete müssen installiert sein:</p>
41+
<pre><code>install.packages(c("shiny", "readr", "writexl", "stringr", "DT", "shinyjs"))</code></pre>
42+
43+
<h3>Start der Anwendung</h3>
44+
<ol>
45+
<li>Repository clonen oder Dateien herunterladen</li>
46+
<li>R oder RStudio öffnen</li>
47+
<li>Mit folgendem Befehl die App starten:</li>
48+
</ol>
49+
<pre><code>runApp("Pfad/zur/ShinyApp")</code></pre>
50+
51+
<h3>Bedienung der App</h3>
52+
<ol>
53+
<li><strong>Dateien hochladen</strong>: Wähle zwei CSV/TXT-Dateien aus. Falls die Spaltennamen nicht enthalten sind, kann eine zugehörige SQL-Datei hochgeladen werden.</li>
54+
<li><strong>IDs für den Merge auswählen</strong>: Wähle die Spalten, über die die Tabellen verknüpft werden sollen.</li>
55+
<li><strong>Join-Typ wählen</strong>: Entscheide, ob ein Inner-, Outer-, Left- oder Right-Join verwendet werden soll.</li>
56+
<li><strong>Merge starten</strong>: Die Tabellen werden verknüpft und als interaktive Tabelle dargestellt.</li>
57+
<li><strong>Ergebnis speichern</strong>: Die zusammengeführte Tabelle kann mit einem individuell wählbaren Namen als CSV gespeichert werden.</li>
58+
</ol>
59+
60+
<h2>Technische Details</h2>
61+
<ul>
62+
<li><strong>SQL-Dump-Datei-Handling</strong>: Die App extrahiert Spaltennamen aus <code>CREATE TABLE</code>-Statements der SQL-Datei.</li>
63+
<li><strong>Dynamische Join-Verarbeitung</strong>: Merge-Operationen werden über <code>switch()</code> dynamisch gesteuert.</li>
64+
<li><strong>Interaktive Tabellenanzeige</strong>: <code>DT</code> sorgt für eine scrollbare, durchsuchbare Darstellung der Daten.</li>
65+
<li><strong>Benutzerfreundliche UI</strong>: <code>shinyjs</code> ermöglicht das Deaktivieren und Aktivieren von UI-Elementen basierend auf Benutzerauswahl.</li>
66+
</ul>
67+
68+
<h2>Kontakt</h2>
69+
<p>Für Fragen oder Verbesserungsvorschläge:</p>
70+
<p><strong>Christian Jaeger</strong><br>
71+
E-Mail: <a href="mailto:christian.jaeger@uk-halle.de">christian.jaeger@uk-halle.de</a></p>
72+
73+
<hr>
74+
<p><em>Letzte Aktualisierung: 12. März 2025</em></p>
75+
</body>
76+
</html>

0 commit comments

Comments
 (0)