---
title: "The RNA Centric Analysis System Report"
author: "Bora Uyar, Dilmurat Yusuf, Ricardo Wurmus, Altuna Akalin"
date: "`r Sys.Date()`"
params: 
  query: 'testdata'
  gff: 'testdata'
  msigdb: 'testdata'
  annotationSummary: TRUE
  goAnalysis: TRUE
  msigdbAnalysis: TRUE
  motifAnalysis: TRUE
  genomeVersion: 'hg19'
  species: 'human'
  printProcessedTables: FALSE
  sampleN: 0
  workdir: '.'
---

```{r global_options, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE, fig.height = 4.5, fig.width = 8)
knitr::opts_knit$set(progress = FALSE, root.dir = params$workdir)
```

```{r chunk_eval_options}
annotationSummary <- params$annotationSummary
goAnalysis <- params$goAnalysis
msigdbAnalysis <- params$msigdbAnalysis
motifAnalysis <- params$motifAnalysis
provideMotifSummary <- FALSE #if params$motifAnalysis is TRUE and any motif is found, this will set to TRUE
goSummaryBP <- FALSE 
goSummaryMF <- FALSE
goSummaryCC <- FALSE 
```

```{r load_libraries, results='hide'}
library(RCAS)
```

# Introduction

RCAS is an automated system that provides dynamic genome annotations for custom input files that contain transcriptomic regions. Such transcriptomic regions could be, for instance, peak regions detected by CLIP-Seq analysis that detect protein-RNA interactions, RNA modifications (alias the epitranscriptome), CAGE-tag locations, or any other collection of target regions at the level of the transcriptome.

RCAS is designed as a reporting tool for the functional analysis of RNA-binding sites detected by high-throughput experiments. It takes as input a BED format file containing the genomic coordinates of the RNA binding sites and a GTF file that contains the genomic annotation features usually provided by publicly available databases such as Ensembl and UCSC. RCAS performs overlap operations between the genomic coordinates of the RNA binding sites and the genomic annotation features and produces in-depth annotation summaries such as the distribution of binding sites with respect to gene features (exons, introns, 5’/3’ UTR regions, exon-intron boundaries, promoter regions, and whole transcripts). Moreover, by detecting the collection of targeted transcripts, RCAS can carry out functional annotation tables for enriched gene sets (annotated by the Molecular Signatures Database) and GO terms. As one of the most important questions that arise during protein-RNA interaction analysis; RCAS has a module for detecting sequence motifs enriched in the targeted regions of the transcriptome. The final report of RCAS consists of high-quality dynamic figures and tables, which are readily applicable for publications or other academic usage.

```{r initiate_figure_table_counts}
figureCount <- 1
tableCount <- 1
```

# Input Settings
```{r printInputSettings}
inputParameterDesc <- c('Query BED file', 
                     'Target GTF file',
                     'Gene set Annotations',
                     'Annotation Summary Module',
                     'GO Analysis Module',
                     'Gene Set Enrichment Analysis Module',
                     'Motif Analysis Module',
                     'Genome Version',
                     'Species',
                     'Print Processed Tables?',
                     'Randomly sample query regions down to (N)',
                     'Working Directory')
inputParameterValues <- c(params$query, 
                          params$gff,
                          params$msigdb,
                          params$annotationSummary,
                          params$goAnalysis,
                          params$msigdbAnalysis,
                          params$motifAnalysis,
                          params$genomeVersion,
                          params$species,
                          params$printProcessedTables,
                          params$sampleN,
                          params$workdir)

inputSettings <- data.frame(parameters = inputParameterDesc,
                            values = inputParameterValues, 
                            stringsAsFactors = FALSE)

#inputSettings[inputSettings$values==TRUE,]$values <- 'ON'
#inputSettings[inputSettings$values==FALSE,]$values <- 'OFF'
#inputSettings[inputSettings$values==0,]$values <- 'OFF'

DT::datatable(data = inputSettings,
              extensions = 'FixedColumns',
              options = list(fixedColumns = TRUE, 
                         scrollX = TRUE, 
                         pageLength = 20,
                         dom = 't'))
```


```{r getInput, results='hide'}
if(params$query == 'testdata') {
  data(queryRegions) 
} else {
  cat('importing BED from filepath',params$query,'\n')
  queryRegions <- importBed(filePath = params$query, sampleN = params$sampleN)
}

if(params$gff == 'testdata') {
  data(gff)         
} else {
  cat('importing GTF from filepath',params$gff,'\n')
  gff <- importGtf(filePath = params$gff)
}

overlaps <- queryGff(queryRegions = queryRegions, gff = gff)
#data.table is used to do quick summary operations
overlaps.dt <- data.table(GenomicRanges::as.data.frame(overlaps)) 

#get all genes from the GTF data 
backgroundGenes <- unique(gff$gene_id)
#get genes that overlap query regions
targetedGenes <- unique(overlaps$gene_id)

```

```{r getTxdbFeatures, eval=annotationSummary}
txdbFeatures <- getTxdbFeaturesFromGRanges(gffData = gff)
```

```{r summaryOverlapsHeader, results='asis', eval=annotationSummary}
cat('# Annotation Summary for Query Regions \n')
```

```{r summarizeQueryRegionsFigureCaption, results='asis', eval=annotationSummary}
cat('## Distribution of query regions across gene features\n')

cat("**Figure",figureCount,":** The number of query regions that overlap different kinds of gene features are counted. The 'y' axis denotes the types of gene features included in the analysis and the 'x' axis denotes the percentage of query regions (out of total number of query regions denoted with 'n') that overlap at least one genomic interval that host the corresponding feature. Notice that the sum of the percentage values for different features don't add up to 100%, because some query regions may overlap multiple kinds of features \n")
```


```{r summarizeQueryRegions, eval=annotationSummary}
summary <- summarizeQueryRegions(queryRegions = queryRegions, 
                                 txdbFeatures = txdbFeatures)

df <- data.frame(summary)
df$percent <- round((df$count / length(queryRegions)), 3) * 100
p <- plot_ly( data = df, 
              x = rownames(df), 
              y = df$percent, 
              type = 'bar',
              text = paste("count:", df$count), 
              color = rownames(df)
              )
layout(p = p, 
       xaxis = list(title = 'features'),
       yaxis = list(title = paste("percentage of query regions,", 
                                  "n =", length(queryRegions)
                                  )
                    ), 
       margin = list(b = 150, r = 50),
       font = list(size = 14)
       )

if(params$printProcessedTables == TRUE) {
  write.table(x = df, file=paste0('Figure',figureCount,'.summarizeQueryRegions.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

figureCount <- figureCount + 1
```

```{r get_table_of_genes_tabcap, echo=FALSE, results='asis', eval=annotationSummary}
cat("## Interactive table of genes that overlap query regions\n")
cat("**Table",tableCount,":** Interactive table of top 100 genes that overlap query regions, grouped by gene features such as introns, exons, UTRs, etc.\n")
```

```{r getTargetedGenesTable, eval=annotationSummary} 
dt <- getTargetedGenesTable(queryRegions = queryRegions, 
                           txdbFeatures = txdbFeatures)
dt <- dt[order(transcripts, decreasing = TRUE)]

DT::datatable(dt[1:100], 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv','excel', 'pdf')),
          filter = 'bottom'
          )
if(params$printProcessedTables == TRUE) {
  write.table(x = dt, file=paste0('Table',tableCount,'.getTargetedGenesTable.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 
tableCount <- tableCount + 1
```

```{r query_gene_types_figcap, results='asis', eval=annotationSummary}
cat("## Distribution of query regions in the genome grouped by gene types\n")
cat("**Figure",figureCount,":** The number of query regions that overlap different kinds of gene types are counted. The 'x' axis denotes the types of genes included in the analysis and the 'y' axis denotes the percentage of query regions (out of total number of query regions denoted with 'n') that overlap at least one genomic interval that host the corresponding gene type. If the query regions don't overlap any known genes, they are classified as 'Unknown'.\n")
```

```{r query_gene_types, eval=annotationSummary}
biotype_col <- grep('gene_biotype', colnames(overlaps.dt), value = T)
df <- overlaps.dt[,length(unique(overlappingQuery)), by = biotype_col]
colnames(df) <- c("feature", "count")
df$percent <- round(df$count / length(queryRegions) * 100, 1)
df <- df[order(count, decreasing = TRUE)]
p <- plot_ly(data = df, 
             type = "bar",
             x = df$feature,
             y = df$percent,
             text = paste("count:", df$count), color=df$feature)
layout(p = p, 
       margin = list(l=100, r=100, b=150), 
       xaxis = list(showticklabels = TRUE,  tickangle = 90), 
       yaxis = list(title = paste("percentage of query regions,", 
                                  "n =", length(queryRegions))),
       font = list(size = 14))
if(params$printProcessedTables == TRUE) {
  write.table(x = df, file=paste0('Figure',figureCount,'.query_gene_types.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 
figureCount <- figureCount + 1
```

```{r transcriptBoundaryCoverage_figcap, results='asis', eval = annotationSummary}
cat("## Coverage profile of query regions at/around Transcription Start/End Sites\n")
cat("**Figure",figureCount,":** The depth of coverage of query regions at and around Transcription Start/End Sites\n")
```

```{r transcriptBoundaryCoverage, eval = annotationSummary}
cvgF <- getFeatureBoundaryCoverage(queryRegions = queryRegions, featureCoords = txdbFeatures$transcripts, flankSize = 1000, boundaryType = 'fiveprime', sampleN = 10000)
cvgT <- getFeatureBoundaryCoverage(queryRegions = queryRegions, featureCoords = txdbFeatures$transcripts, flankSize = 1000, boundaryType = 'threeprime', sampleN = 10000)

plotFeatureBoundaryCoverage(cvgF = cvgF, cvgT = cvgT, featureName = 'transcripts')

if(params$printProcessedTables == TRUE) {
  write.table(x = cvgF, file=paste0('Figure',figureCount,'.transcriptBoundaryCoverage.fiveprime.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
  write.table(x = cvgT, file=paste0('Figure',figureCount,'.transcriptBoundaryCoverage.threeprime.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

figureCount <- figureCount + 1
```

```{r exonIntronCoverage_figcap, results='asis', eval = annotationSummary}
cat("## Coverage profile of query regions at Exon - Intron Boundaries\n")
cat("**Figure",figureCount,":** The depth of coverage of query regions at exon - intron junctions\n")
```

```{r exonIntronCoverage, eval = annotationSummary}
#split the exons into corresponding transcripts
exons <- txdbFeatures$exons
exonCounts <- BiocGenerics::lengths(rtracklayer::split(exons, names(exons)))

#To find the internal exons, for each transcript, remove the first and last exons from the GRanges object
#thus we can make sure that each exon in the end we are looking at is adjacent to an intron
internalExons <- exons[exonCounts[names(exons)] != exons$exon_rank,]
internalExons <- internalExons[internalExons$exon_rank > 1,]

cvgF <- getFeatureBoundaryCoverage(queryRegions = queryRegions, featureCoords = internalExons, boundaryType = 'fiveprime', flankSize = 1000, sampleN = 10000)
cvgT <- getFeatureBoundaryCoverage(queryRegions = queryRegions, featureCoords = internalExons, boundaryType = 'threeprime', flankSize = 1000, sampleN = 10000)

plotFeatureBoundaryCoverage(cvgF = cvgF, cvgT = cvgT, featureName = 'Internal Exons')

if(params$printProcessedTables == TRUE) {
  write.table(x = cvgF, file=paste0('Figure',figureCount,'.exonIntronBoundaryCoverage.fiveprime.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
  write.table(x = cvgT, file=paste0('Figure',figureCount,'.exonIntronBoundaryCoverage.threeprime.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 
figureCount <- figureCount + 1
```

```{r coverageprofilelist_figcap, results='asis', eval=annotationSummary}
cat("## Coverage profile of query regions across the length of different gene features\n")
cat("**Figure",figureCount,":** The query regions are overlaid with the genomic coordinates of features. Each entry corresponding to a feature is divided into 100 bins of equal length and for each bin the number of query regions that cover the corresponding bin is counted. Features shorter than 100bp are excluded. Thus, a coverage profile is obtained based on the distribution of the query regions. Mean coverage score for each bin is represented with ribbons where the thickness of the ribbon indicates the 95% confidence interval (mean +- standard error of the mean x 1.96). The strandedness of the features are taken into account. The coverage profile is plotted in the 5' to 3' direction.\n")
```

```{r coverageprofilelist, eval=annotationSummary}
cvgList <- calculateCoverageProfileList(queryRegions = queryRegions, 
                                       targetRegionsList = txdbFeatures, 
                                       sampleN = 10000)

p <- plotly::plot_ly(data = cvgList, type = 'scatter', mode = 'lines')
for (f in unique(cvgList$feature)){
  data <- cvgList[cvgList$feature == f,]
  p <- add_trace(p = p, data = data, x = ~bins, y = ~meanCoverage, 
                 legendgroup = f, showlegend = FALSE, opacity = 1, color = f)
  p <- add_ribbons(p = p, data = data, x = ~bins, 
                   ymin = data$meanCoverage - data$standardError*1.96,
                   ymax = data$meanCoverage + data$standardError*1.96, 
                   legendgroup = f, 
                   name = f, color = f
                   )
}
layout(p, font = list(size = 14))

if(params$printProcessedTables == TRUE) {
  write.table(x = cvgList, file=paste0('Figure',figureCount,'.coverageprofilelist.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 
figureCount <- figureCount + 1
```

```{r plot_motif_results_figcap, results='asis', eval=motifAnalysis}
cat('# motifRG analysis results \n')
```

```{r motif_analysis, results='hide', eval=motifAnalysis}
#downsample the query regions to 10000 intervals for motif analysis
#as we don't need the complete set of intervals for motif discovery
#and the motif discovery part is a bottleneck for run time.
queryRegionsExt <- queryRegions

if(length(queryRegionsExt) > 10000) {
  queryRegionsExt <- queryRegionsExt[sample(x = 1:length(queryRegionsExt), size = 10000)]
}

#extend the queryRegions if shorter than 15 bp otherwise, motif analysis is not possible for such short segments
queryRegionsExt[width(queryRegionsExt) < 15] <- GenomicRanges::resize(x = queryRegionsExt[width(queryRegionsExt) < 15], 
                                                                      width = 15, 
                                                                      fix = 'center')

motifResults <- runMotifRG(queryRegions = queryRegionsExt, 
                           genomeVersion = params$genomeVersion, 
                           motifN = 4)
motifPWMs <- list()

if (length(motifResults$motifs) > 0) {
  querySeqs <- extractSequences(queryRegions = queryRegionsExt, 
                                genomeVersion = params$genomeVersion)
  querySeqs <- querySeqs[width(querySeqs) > 10,]
  
  patterns <- c()
  for (i in 1:length(motifResults$motifs)) {
    motifPattern <- motifResults$motifs[[i]]@pattern
    pwm.match <- refinePWMMotif(motifs = motifResults$motifs[[i]]@match$pattern,
                                seqs = querySeqs)
    motifPWMs[[length(motifPWMs)+1]] <- pwm.match
    patterns <- c(patterns, motifPattern)
  }
  names(motifPWMs) <- patterns

  provideMotifSummary <- TRUE
}
```

```{r motif_analysis_plot_figcap, results='asis', eval=motifAnalysis}
cat('## Top motifs discovered using motifRG \n')
```

```{r motif_analysis_plot, results='asis', eval=motifAnalysis}
if (length(motifPWMs) > 0) {
  cat("**Figure",figureCount,":** Top motifs discovered in the sequences of the query regions\n\n")
  for (i in 1:length(motifResults$motifs)) {
    motifPattern <- names(motifPWMs)[i]
    pwm.match <- motifPWMs[[motifPattern]]
    cat("**Motif",i,":** Consensus:",motifPattern,"\n")
    seqLogo(pwm.match$model$prob)
    cat('\n\n')
  }
  figureCount <- figureCount + 1
} else {
  cat('**Could not detect any motif enrichment in the given query regions**\n')
}
```

```{r summarize_stats_tablecap, results='asis', eval=provideMotifSummary}
cat('## motifRG motif discovery statistics \n')
cat("**Table",tableCount,":** motifRG motif discovery statistics. fg: foreground; bg: background; hits: number of motif hits; seq: number of sequences with motifs; frac: fraction of sequences that contain the motif compared to the all sequences; ratio: ratio of foreground motif fraction versus background motif fraction\n")
```

```{r motif_analysis_table, eval=provideMotifSummary}
summary <- getMotifSummaryTable(motifResults)
DT::datatable(summary, 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv','excel', 'pdf')),
          filter = 'bottom'
          )

if(params$printProcessedTables == TRUE) {
  write.table(x = summary, file=paste0('Table',tableCount,'.motif_analysis_table.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

tableCount <- tableCount + 1
```

```{r GO_section_header, results='asis', eval=goAnalysis}
cat("# GO Term Analysis Results\n")
```


```{r GO analysis, results='hide', eval=goAnalysis}

#run TopGO
goBP <- runTopGO(ontology = 'BP', 
                      species = params$species, 
                      backgroundGenes = backgroundGenes, 
                      targetedGenes = targetedGenes)

goMF <- runTopGO(ontology = 'MF', 
                      species = params$species, 
                      backgroundGenes = backgroundGenes, 
                      targetedGenes = targetedGenes)

goCC <- runTopGO(ontology = 'CC', 
                      species = params$species, 
                      backgroundGenes = backgroundGenes, 
                      targetedGenes = targetedGenes)

if (!is.null(goBP)) { goSummaryBP <- TRUE }
if (!is.null(goMF)) { goSummaryMF <- TRUE }
if (!is.null(goCC)) { goSummaryCC <- TRUE }
```

```{r goBP_tabcap, results='asis', eval=goSummaryBP}
cat("## Biological Processes\n")
cat("**Table",tableCount,":** Significant Biological Process GO terms (FDR < 0.1) enriched for genes that overlap query regions\n")  
goBP <- goBP[order(goBP$foldEnrichment, decreasing = TRUE),]
rownames(goBP) <- goBP$GO.ID
goBP <- subset(goBP, select = -c(Annotated,classicFisher, GO.ID))
DT::datatable(goBP[goBP$bh < 0.1,], 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv', 'excel', 'pdf')),
          filter = 'bottom'
          )

if(params$printProcessedTables == TRUE) {
  write.table(x = goBP, file=paste0('Table',tableCount,'.goBP.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

tableCount <- tableCount + 1
```

```{r goMF_tabcap, results='asis', eval=goSummaryMF}
cat("## Molecular Functions\n")
cat("**Table",tableCount,":** Significant Molecular Function GO terms (FDR < 0.1) enriched for genes that overlap query regions\n")  
goMF <- goMF[order(goMF$foldEnrichment, decreasing = TRUE),]
rownames(goMF) <- goMF$GO.ID
goMF <- subset(goMF, select = -c(Annotated,classicFisher, GO.ID))

DT::datatable(goMF[goMF$bh < 0.1,], 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv','excel', 'pdf')),
          filter = 'bottom'
          )

if(params$printProcessedTables == TRUE) {
  write.table(x = goMF, file=paste0('Table',tableCount,'.goMF.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

tableCount <- tableCount + 1
```

```{r goCC_tabcap, results='asis', eval=goSummaryCC}
cat("## Cellular Compartments\n")
cat("**Table",tableCount,":** Significant Cellular Compartment GO terms (FDR < 0.1) enriched for genes that overlap query regions\n")  
goCC <- goCC[order(goCC$foldEnrichment, decreasing = TRUE),]
rownames(goCC) <- goCC$GO.ID
goCC <- subset(goCC, select = -c(Annotated,classicFisher, GO.ID))

DT::datatable(goCC[goCC$bh < 0.1,], 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv','excel', 'pdf')),
          filter = 'bottom'
          )

if(params$printProcessedTables == TRUE) {
  write.table(x = goCC, file=paste0('Table',tableCount,'.goCC.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 
tableCount <- tableCount + 1
```

```{r GSEA_tabcap, results='asis', eval=msigdbAnalysis}
cat("# Gene Set Enrichment Analysis Results\n")
cat("**Table",tableCount,":** Significant MSigDB Gene Sets (FDR < 0.1) enriched for genes that overlap query regions\n")
```

```{r GSEA, eval=msigdbAnalysis}
if (params$species != 'human') {
  refGeneSets <- parseMsigdb(filePath = params$msigdb)
  geneSets <- createOrthologousGeneSetList(referenceGeneSetList = refGeneSets, 
                                                refGenomeVersion = 'hg19', 
                                                targetGenomeVersion = params$genomeVersion)
} else if (params$msigdb == 'testdata') {
  data(geneSets)
} else {
  geneSets <- parseMsigdb(filePath = params$msigdb)
}

resultsGSEA <- runGSEA(geneSetList = geneSets, 
                       species = params$species,
                       backgroundGenes = backgroundGenes, 
                       targetedGenes = targetedGenes)

rownames(resultsGSEA) = gsub(pattern = '_', replacement = ' ', x = rownames(resultsGSEA))
resultsGSEA <- subset(resultsGSEA, select = -c(treatmentSize, fisherPVal))

DT::datatable(resultsGSEA[resultsGSEA$BH < 0.1,], 
          extensions = c('Buttons', 'FixedColumns'), 
          options = list(fixedColumns = TRUE, 
                         scrollX = TRUE,
                         dom = 'Bfrtip',
                         buttons = c('copy', 'print', 'csv', 'excel', 'pdf')),
          filter = 'bottom'
          )

if(params$printProcessedTables == TRUE) {
  write.table(x = resultsGSEA, file=paste0('Table',tableCount,'.GSEA.data.tsv'), quote = FALSE, sep = '\t', row.names = TRUE)
} 

tableCount <- tableCount + 1
```

# Acknowledgements

RCAS is developed in the group of 
[Altuna Akalin](http://bioinformatics.mdc-berlin.de/team.html#altuna-akalin-phd)
(head of the Scientific Bioinformatics Platform) by
[Bora Uyar](http://bioinformatics.mdc-berlin.de/team.html#bora-uyar-phd)
(Bioinformatics Scientist),
[Dilmurat Yusuf](http://bioinformatics.mdc-berlin.de/team.html#dilmurat-yusuf-phd)
(Bioinformatics Scientist) and 
[Ricardo Wurmus](http://bioinformatics.mdc-berlin.de/team.html#ricardo-wurmus)
(System Administrator) at the Berlin Institute of Medical Systems Biology
([BIMSB](https://www.mdc-berlin.de/13800178/en/bimsb))
at the Max-Delbrueck-Center for Molecular Medicine
([MDC](https://www.mdc-berlin.de)) in Berlin.

RCAS is developed as a bioinformatics service as part of
the [RNA Bioinformatics Center](http://www.denbi.de/index.php/rbc),
which is one of the eight centers of
the German Network for Bioinformatics Infrastructure
([de.NBI](http://www.denbi.de/)).  

# Session Information
```{r sessionInfo}
sessionInfo()
```