Author: Benjamin Jean-Marie Tremblay

# Motif import, export, and manipulation

# Introduction

This vignette will introduce the `universalmotif` class and its structure, the import and export of motifs in R, basic motif manipulation, creation, and visualisation. For an introduction to sequence motifs, see the introductory vignette. For sequence-related utilities, see the sequences vignette. For motif comparisons and P-values, see the motif comparisons and P-values vignette. For the multi-source curation step that picks up where these import functions leave off (building a single, deduplicated, and annotated motif library out of several public databases), see the curated motif database vignette.

# The universalmotif class and conversion utilities

## The universalmotif class

The `universalmotif` package stores motifs using the `universalmotif` class. The most basic `universalmotif` object exposes the `name`, `alphabet`, `type`, `strand`, `icscore`, `consensus`, and `motif` slots; furthermore, the `pseudocount` and `bkg` slots are also stored but not shown. `universalmotif` class motifs can be PCM, PPM, PWM, or ICM type.

    library(universalmotif)
    data(examplemotif)
    examplemotif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>   T A T A   W A   W
    #> A 0 1 0 1 0.5 1 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> T 1 0 1 0 0.5 0 0.5

A brief description of all the available slots:

- `name`: motif name
- `altname`: (optional) alternative motif name
- `family`: (optional) a word representing the transcription factor or matrix family
- `organism`: (optional) organism of origin
- `motif`: the actual motif matrix
- `alphabet`: motif alphabet
- `type`: motif ‘type’, one of PCM, PPM, PWM, ICM; see the introductory vignette
- `icscore`: (generated automatically) Sum of information content for the motif
- `nsites`: (optional) number of sites the motif was created from
- `pseudocount`: this value is added to the motif matrix during certain type conversions; this is necessary to avoid `-Inf` values from appearing in PWM type motifs
- `bkg`: a named vector of probabilities which represent the background letter frequencies
- `bkgsites`: (optional) total number of background sequences from motif creation
- `consensus`: (generated automatically) for DNA/RNA/AA motifs, the motif consensus
- `strand`: strand motif can be found on
- `pval`: (optional) P-value from de novo motif search
- `qval`: (optional) Q-value from de novo motif search
- `eval`: (optional) E-value from de novo motif search
- `multifreq`: (optional) higher-order motif representations.
- `extrainfo`: (optional) any extra motif information that cannot fit in the existing slots

The other slots will be shown as they are filled.

    library(universalmotif)
    data(examplemotif)

    ## The various slots can be accessed individually using `[`

    examplemotif["consensus"]
    #> [1] "TATAWAW"

    ## To change a slot, use `[<-`

    examplemotif["family"] <- "My motif family"
    examplemotif
    #>
    #>        Motif name:   motif
    #>            Family:   My motif family
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>   T A T A   W A   W
    #> A 0 1 0 1 0.5 1 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> T 1 0 1 0 0.5 0 0.5

Though the slots can easily be changed manually with `[<-`, a number of safeguards have been put in place for some of the slots which will prevent incorrect values from being introduced.

    library(universalmotif)
    data(examplemotif)

    ## The consensus slot is dependent on the motif matrix

    examplemotif["consensus"]
    #> [1] "TATAWAW"

    ## Changing this would mean it no longer matches the motif

    examplemotif["consensus"] <- "GGGAGAG"
    #> Error in `.local()`:
    #> ! this slot is unmodifiable with [<-

    ## Another example of trying to change a protected slot:

    examplemotif["strand"] <- "x"
    #> Error:
    #> ! * strand must be one of +, -, +-

Below the exposed metadata slots, the actual ‘motif’ matrix is shown. Each position is its own column, with row names showing the alphabet letters and column names showing the consensus letter at each position.

## Converting to and from another package’s class

The `universalmotif` package aims to unify most of the motif-related Bioconductor packages by providing the `convert_motifs()` function. This allows for easy transition between supported packages (see `?convert_motifs` for a complete list of supported packages). Should you ever come across a motif class from another Bioconductor package which is not supported by the `universalmotif` package, but believe it should be, then feel free to bring it up with me.

The `convert_motifs` function is embedded in most of the `universalmotif` functions, meaning that compatible motif classes from other packages can be used without needing to manually convert them first. However, keep in mind some conversions are final. Furthermore, internally, all motifs regardless of class are handled as `universalmotif` objects, even if the returning class is not. This will result in at times slightly different objects (though usually no information should be lost).

    library(universalmotif)
    library(MotifDb)
    data(examplemotif)
    data(MA0003.2)

    ## convert from a `universalmotif` motif to another class

    convert_motifs(examplemotif, "TFBSTools-PWMatrix")
    #> Note: motif [motif] has an empty nsites slot, using 100.
    #> An object of class PWMatrix
    #> ID:
    #> Name: motif
    #> Matrix Class: Unknown
    #> strand: *
    #> Pseudocounts: 1
    #> Tags:
    #> list()
    #> Background:
    #>    A    C    G    T
    #> 0.25 0.25 0.25 0.25
    #> Matrix:
    #>           T         A         T         A          W         A          W
    #> A -6.658211  1.989247 -6.658211  1.989247  0.9928402  1.989247  0.9928402
    #> C -6.658211 -6.658211 -6.658211 -6.658211 -6.6582115 -6.658211 -6.6582115
    #> G -6.658211 -6.658211 -6.658211 -6.658211 -6.6582115 -6.658211 -6.6582115
    #> T  1.989247 -6.658211  1.989247 -6.658211  0.9928402 -6.658211  0.9928402

    ## convert to universalmotif

    convert_motifs(MA0003.2)
    #>
    #>        Motif name:   TFAP2A
    #>    Alternate name:   MA0003.2
    #>            Family:   Helix-Loop-Helix
    #>          Organism:   9606
    #>          Alphabet:   DNA
    #>              Type:   PCM
    #>           Strands:   +
    #>          Total IC:   12.9
    #>       Pseudocount:   1
    #>         Consensus:   NNNNGCCYSAGGSCA
    #>      Target sites:   5098
    #>        Extra info:   [centrality_logp] -4343
    #>                      [family] Helix-Loop-Helix
    #>                      [medline] 10497269
    #>                      ...
    #>
    #>      N    N    N    N    G    C    C    Y    S    A    G    G    S    C    A
    #> A 1387 2141  727 1517   56    0    0   62  346 3738  460    0  116  451 3146
    #> C 1630 1060 1506  519 1199 5098 4762 1736 2729  236    0    0 1443 3672  690
    #> G  851  792  884  985 3712    0    0   85 1715  920 4638 5098 3455  465  168
    #> T 1230 1105 1981 2077  131    0  336 3215  308  204    0    0   84  510 1094

    ## convert between two packages

    convert_motifs(MotifDb[1], "TFBSTools-ICMatrix")
    #> Note: motif [ABF1] has an empty nsites slot, using 100.
    #> [[1]]
    #> An object of class ICMatrix
    #> ID: M0001_1.02
    #> Name: ABF1
    #> Matrix Class: Unknown
    #> strand: *
    #> Pseudocounts: 1
    #> Schneider correction: FALSE
    #> Tags:
    #> $dataSource
    #> [1] "cisbp_1.02"
    #>
    #> Background:
    #>    A    C    G    T
    #> 0.25 0.25 0.25 0.25
    #> Matrix:
    #>            T          T          A          T          C          A          C
    #> A 0.02119039 0.02119039 1.64861232 0.02119039 0.02119039 1.64861232 0.02119039
    #> C 0.02119039 0.02119039 0.02119039 0.02119039 1.64861232 0.02119039 1.64861232
    #> G 0.02119039 0.02119039 0.02119039 0.02119039 0.02119039 0.02119039 0.02119039
    #> T 1.64861232 1.64861232 0.02119039 1.64861232 0.02119039 0.02119039 0.02119039
    #>            T
    #> A 0.02119039
    #> C 0.02119039
    #> G 0.02119039
    #> T 1.64861232

# Importing and exporting motifs

## Importing

The `universalmotif` package offers a number of `read_*()` functions to allow for easy import of various motif formats. These include:

- `read_cisbp()`: CIS-BP (Weirauch et al. 2014)
- `read_homer()`: `HOMER` (Heinz et al. 2010)
- `read_jaspar()`: JASPAR (Khan et al. 2018)
- `read_matrix()`: generic reader for simply formatted motifs
- `read_meme()`: `MEME` (Bailey et al. 2009)
- `read_motifs()`: native `universalmotif` format (not recommended; use `saveRDS()` instead)
- `read_transfac()`: TRANSFAC (Wingender et al. 1996)
- `read_uniprobe()`: UniPROBE (Hume et al. 2015)

These functions should work natively with these formats, but if you are generating your own motifs in one of these formats then it must adhere quite strictly to the format. An example of each of these is included in this package (see `system.file("extdata", package="universalmotif")`). If you know of additional motif formats which are not supported in the `universalmotif` package that you believe should be, or of any mistakes in the way the `universalmotif` package parses supported formats, then please let me know.

## Exporting

Compatible motif classes can be written to disk using:

- `write_homer()`
- `write_jaspar()`
- `write_matrix()`
- `write_meme()`
- `write_motifs()`
- `write_transfac()`

The `write_matrix()` function, similar to its `read_matrix()` counterpart, can write motifs as simple matrices with an optional header. Additionally, please keep in mind format limitations. For example, multiple `MEME` motifs written to a single file will all share the same alphabet, with identical background letter frequencies.

# Modifying motifs and related functions

## Converting motif type

Any `universalmotif` object can transition between PCM, PPM, PWM, and ICM types seamlessly using the `convert_type()` function. The only exception to this is if the ICM calculation is performed with sample correction, or as relative entropy. If this occurs, then back conversion to another type will be inaccurate (and `convert_type()` would not warn you, since it won’t know this has taken place).

    library(universalmotif)
    data(examplemotif)

    ## This motif is currently a PPM:

    examplemotif["type"]
    #> [1] "PPM"

When converting to PCM, the `nsites` slot is needed to tell it how many sequences it originated from. If empty, 100 is used.

    convert_type(examplemotif, "PCM")
    #> Note: motif [motif] has an empty nsites slot, using 100.
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PCM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>     T   A   T   A  W   A  W
    #> A   0 100   0 100 50 100 50
    #> C   0   0   0   0  0   0  0
    #> G   0   0   0   0  0   0  0
    #> T 100   0 100   0 50   0 50

For converting to PWM, the `pseudocount` slot is used to determine if any correction should be applied:

    examplemotif["pseudocount"]
    #> [1] 1
    convert_type(examplemotif, "PWM")
    #> Note: motif [motif] has an empty nsites slot, using 100.
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PWM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>       T     A     T     A     W     A     W
    #> A -6.66  1.99 -6.66  1.99  0.99  1.99  0.99
    #> C -6.66 -6.66 -6.66 -6.66 -6.66 -6.66 -6.66
    #> G -6.66 -6.66 -6.66 -6.66 -6.66 -6.66 -6.66
    #> T  1.99 -6.66  1.99 -6.66  0.99 -6.66  0.99

You can either change the `pseudocount` slot manually beforehand, or pass one to `convert_type()`.

    convert_type(examplemotif, "PWM", pseudocount = 1)
    #> Note: motif [motif] has an empty nsites slot, using 100.
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PWM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>       T     A     T     A     W     A     W
    #> A -6.66  1.99 -6.66  1.99  0.99  1.99  0.99
    #> C -6.66 -6.66 -6.66 -6.66 -6.66 -6.66 -6.66
    #> G -6.66 -6.66 -6.66 -6.66 -6.66 -6.66 -6.66
    #> T  1.99 -6.66  1.99 -6.66  0.99 -6.66  0.99

There are a couple of additional options for ICM conversion: `nsize_correction` and `relative_entropy`. The former uses the `TFBSTools:::schneider_correction()` function (and thus requires that the `TFBSTools` package be installed) for sample size correction. The latter uses the `bkg` slot to calculate information content. See the IntroductionToSequenceMotifs vignette for an overview on the various types of ICM calculations.

    examplemotif["nsites"] <- 10
    convert_type(examplemotif, "ICM", nsize_correction = FALSE)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   ICM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>      Target sites:   10
    #>
    #>   T A T A   W A   W
    #> A 0 2 0 2 0.5 2 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> T 2 0 2 0 0.5 0 0.5

    convert_type(examplemotif, "ICM", nsize_correction = TRUE)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   ICM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>      Target sites:   10
    #>
    #>      T    A    T    A    W    A    W
    #> A 0.00 1.75 0.00 1.75 0.38 1.75 0.38
    #> C 0.00 0.00 0.00 0.00 0.00 0.00 0.00
    #> G 0.00 0.00 0.00 0.00 0.00 0.00 0.00
    #> T 1.75 0.00 1.75 0.00 0.38 0.00 0.38

    examplemotif["bkg"] <- c(A = 0.4, C = 0.1, G = 0.1, T = 0.4)
    convert_type(examplemotif, "ICM", relative_entropy = TRUE)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   ICM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>      Target sites:   10
    #>
    #>      T    A    T    A    W    A    W
    #> A 0.00 1.32 0.00 1.32 0.16 1.32 0.16
    #> C 0.00 0.00 0.00 0.00 0.00 0.00 0.00
    #> G 0.00 0.00 0.00 0.00 0.00 0.00 0.00
    #> T 1.32 0.00 1.32 0.00 0.16 0.00 0.16

## Merging motifs

The `universalmotif` package includes the `merge_motifs()` / `merge_motifs2()` functions to combine motifs (the latter is a newer, leaner version). Motifs are first aligned, and the best match found before the motif matrices are averaged. The implementation for this is identical to that used by `compare_motifs()` / `compare_motifs2()` (see the motif comparisons vignette for more information).

    library(universalmotif)

    m1 <- create_motif("TTAAACCCC", name = "1")
    m2 <- create_motif("AACC", name = "2")
    m3 <- create_motif("AACCCCGG", name = "3")

    view_motifs(c(m1, m2, m3),
      show.positions.once = FALSE, show.names = FALSE)

    view_motifs(merge_motifs(c(m1, m2, m3), method = "PCC"))

This functionality can also be automated to reduce the number of overly similar motifs in larger datasets via the `merge_similar()` / `merge_similar2()` functions.

    library(universalmotif)
    library(MotifDb)

    motifs <- filter_motifs(MotifDb, family = "bHLH")[1:100]
    #> motifs converted to class 'universalmotif'
    length(motifs)
    #> [1] 100

    motifs <- merge_similar(motifs)
    length(motifs)
    #> [1] 63

Comparison and merging parameters can be fine-tuned as users wish. See the `compare_motifs()` / `compare_motifs2()` and `merge_motifs()` / `merge_motifs2()` documentation for more details, as well as the “Motif comparisons and P-values” vignette.

## Motif reverse complement

Get the reverse complement of a motif.

    library(universalmotif)
    data(examplemotif)

    ## Quickly switch to the reverse complement of a motif

    ## Original:

    examplemotif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>   T A T A   W A   W
    #> A 0 1 0 1 0.5 1 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> T 1 0 1 0 0.5 0 0.5

    ## Reverse complement:

    motif_rc(examplemotif)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   WTWTATA
    #>
    #>     W T   W T A T A
    #> A 0.5 0 0.5 0 1 0 1
    #> C 0.0 0 0.0 0 0 0 0
    #> G 0.0 0 0.0 0 0 0 0
    #> T 0.5 1 0.5 1 0 1 0

## Switching between DNA and RNA alphabets

Since not all motif formats or programs support RNA alphabets by default, the `switch_alph()` function can quickly go between DNA and RNA motifs.

    library(universalmotif)
    data(examplemotif)

    ## DNA --> RNA

    switch_alph(examplemotif)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   RNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   UAUAWAW
    #>
    #>   U A U A   W A   W
    #> A 0 1 0 1 0.5 1 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> U 1 0 1 0 0.5 0 0.5

    ## RNA --> DNA

    motif <- create_motif(alphabet = "RNA")
    motif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   RNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.04
    #>       Pseudocount:   0
    #>         Consensus:   UGUUCAURGS
    #>
    #>      U   G    U    U    C    A    U    R    G    S
    #> A 0.19 0.0 0.00 0.14 0.06 0.98 0.09 0.36 0.22 0.00
    #> C 0.02 0.0 0.00 0.00 0.82 0.00 0.01 0.18 0.04 0.57
    #> G 0.16 0.9 0.13 0.01 0.12 0.02 0.04 0.46 0.55 0.41
    #> U 0.64 0.1 0.87 0.85 0.00 0.00 0.86 0.00 0.18 0.01

    switch_alph(motif)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.04
    #>       Pseudocount:   0
    #>         Consensus:   TGTTCATRGS
    #>
    #>      T   G    T    T    C    A    T    R    G    S
    #> A 0.19 0.0 0.00 0.14 0.06 0.98 0.09 0.36 0.22 0.00
    #> C 0.02 0.0 0.00 0.00 0.82 0.00 0.01 0.18 0.04 0.57
    #> G 0.16 0.9 0.13 0.01 0.12 0.02 0.04 0.46 0.55 0.41
    #> T 0.64 0.1 0.87 0.85 0.00 0.00 0.86 0.00 0.18 0.01

## Motif trimming

Get rid of low information content edges on motifs, such as `NNCGGGCNN` to `CGGGC`. The ‘amount’ of trimming can also be controlled by setting a minimum required information content, as well as the direction of trimming (by default both edges are trimmed).

    library(universalmotif)

    motif <- create_motif("NNGCSGCGGNN")
    motif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   13
    #>       Pseudocount:   0
    #>         Consensus:   NNGCSGCGGNN
    #>
    #>      N    N G C   S G C G G    N    N
    #> A 0.25 0.25 0 0 0.0 0 0 0 0 0.25 0.25
    #> C 0.25 0.25 0 1 0.5 0 1 0 0 0.25 0.25
    #> G 0.25 0.25 1 0 0.5 1 0 1 1 0.25 0.25
    #> T 0.25 0.25 0 0 0.0 0 0 0 0 0.25 0.25

    trim_motifs(motif)
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   13
    #>       Pseudocount:   0
    #>         Consensus:   GCSGCGG
    #>      Target sites:   100
    #>
    #>   G C   S G C G G
    #> A 0 0 0.0 0 0 0 0
    #> C 0 1 0.5 0 1 0 0
    #> G 1 0 0.5 1 0 1 1
    #> T 0 0 0.0 0 0 0 0
    trim_motifs(motif, trim.from = "right")
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   13
    #>       Pseudocount:   0
    #>         Consensus:   NNGCSGCGG
    #>      Target sites:   100
    #>
    #>      N    N G C   S G C G G
    #> A 0.25 0.25 0 0 0.0 0 0 0 0
    #> C 0.25 0.25 0 1 0.5 0 1 0 0
    #> G 0.25 0.25 1 0 0.5 1 0 1 1
    #> T 0.25 0.25 0 0 0.0 0 0 0 0

## Rounding motifs

Round off near-zero probabilities.

    motif1 <- create_motif("ATCGATGC", pseudocount = 10, type = "PPM", nsites = 100)
    motif2 <- round_motif(motif1)
    view_motifs(c(motif1, motif2))

# Motif creation

Though `universalmotif` class motifs can be created using the `new` constructor, the `universalmotif` package provides the `create_motif()` function which aims to provide a simpler interface to motif creation. The `universalmotif` class was initially designed to work natively with DNA, RNA, and amino acid motifs. Currently though, it can handle any custom alphabet just as easily. The only downside to custom alphabets is the lack of support for certain slots such as the `consensus` and `strand` slots.

The `create_motif()` function will be introduced here only briefly; see `?create_motif` for details.

## From a PCM/PPM/PWM/ICM matrix

Should you wish to make use of the `universalmotif` functions starting from a motif class unsupported by `convert_motifs()`, you can instead manually create `universalmotif` class motifs using the `create_motif()` function and the motif matrix.

    motif.matrix <- matrix(c(0.7, 0.1, 0.1, 0.1,
                             0.7, 0.1, 0.1, 0.1,
                             0.1, 0.7, 0.1, 0.1,
                             0.1, 0.7, 0.1, 0.1,
                             0.1, 0.1, 0.7, 0.1,
                             0.1, 0.1, 0.7, 0.1,
                             0.1, 0.1, 0.1, 0.7,
                             0.1, 0.1, 0.1, 0.7), nrow = 4)

    motif <- create_motif(motif.matrix, alphabet = "RNA", name = "My motif",
                          pseudocount = 1, nsites = 20, strand = "+")

    ## The 'type', 'icscore' and 'consensus' slots will be filled for you

    motif
    #>
    #>        Motif name:   My motif
    #>          Alphabet:   RNA
    #>              Type:   PPM
    #>           Strands:   +
    #>          Total IC:   4.68
    #>       Pseudocount:   1
    #>         Consensus:   AACCGGUU
    #>      Target sites:   20
    #>
    #>     A   A   C   C   G   G   U   U
    #> A 0.7 0.7 0.1 0.1 0.1 0.1 0.1 0.1
    #> C 0.1 0.1 0.7 0.7 0.1 0.1 0.1 0.1
    #> G 0.1 0.1 0.1 0.1 0.7 0.7 0.1 0.1
    #> U 0.1 0.1 0.1 0.1 0.1 0.1 0.7 0.7

As a brief aside: if you have a motif formatted simply as a matrix, you can still use it with the `universalmotif` package functions natively without creating a motif with `create_motif()`, as `convert_motifs()` also has the ability to handle motifs formatted simply as matrices. However it is much safer to first format the motif beforehand with `create_motif()`.

## From sequences or character strings

If all you have is a particular consensus sequence in mind, you can easily create a full motif using `create_motif()`. This can be convenient if you’d like to create a quick motif to use with an external program such as from the `MEME` suite or `HOMER`. Note that ambiguity letters can be used with single strings.

    motif <- create_motif("CCNSNGG", nsites = 50, pseudocount = 1)

    ## Now to disk:
    ## write_meme(motif, "meme_motif.txt")

    motif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   8.39
    #>       Pseudocount:   1
    #>         Consensus:   CCNSNGG
    #>      Target sites:   50
    #>
    #>      C    C    N   S    N    G    G
    #> A 0.00 0.00 0.22 0.0 0.22 0.00 0.00
    #> C 0.99 0.99 0.26 0.5 0.26 0.00 0.00
    #> G 0.00 0.00 0.26 0.5 0.26 0.99 0.99
    #> T 0.00 0.00 0.26 0.0 0.26 0.00 0.00

## Generating random motifs

If you wish, it is easy to create random motifs. The values within the motif are generated using `rgamma()` to avoid creating low information content motifs. If background probabilities are not provided, then they are generated with `rpois()`.

    create_motif()
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   12.04
    #>       Pseudocount:   0
    #>         Consensus:   TGCTGRCATR
    #>
    #>      T    G    C    T    G    R    C    A    T    R
    #> A 0.00 0.14 0.14 0.00 0.03 0.40 0.05 0.73 0.07 0.40
    #> C 0.07 0.10 0.85 0.22 0.00 0.00 0.88 0.00 0.01 0.01
    #> G 0.00 0.76 0.01 0.04 0.97 0.49 0.04 0.21 0.01 0.59
    #> T 0.93 0.00 0.00 0.74 0.00 0.11 0.03 0.06 0.91 0.00

You can change the probabilities used to generate the values within the motif matrix:

    create_motif(bkg = c(A = 0.2, C = 0.4, G = 0.2, T = 0.2))
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.33
    #>       Pseudocount:   0
    #>         Consensus:   CCGYTAACCA
    #>
    #>      C    C    G    Y    T    A    A    C C    A
    #> A 0.16 0.00 0.01 0.07 0.01 0.71 0.97 0.01 0 0.62
    #> C 0.57 0.76 0.16 0.46 0.11 0.05 0.03 0.91 1 0.07
    #> G 0.09 0.01 0.84 0.01 0.03 0.21 0.00 0.01 0 0.25
    #> T 0.17 0.23 0.00 0.46 0.84 0.02 0.00 0.08 0 0.06

With a custom alphabet:

    create_motif(alphabet = "QWERTY")
    #>
    #>        Motif name:   motif
    #>          Alphabet:   EQRTWY
    #>              Type:   PPM
    #>          Total IC:   18.38
    #>       Pseudocount:   0
    #>
    #>   [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
    #> E 0.03 0.00 0.99 0.00 0.00 0.01 0.03 0.03 0.03  0.00
    #> Q 0.00 0.24 0.00 0.98 0.00 0.37 0.09 0.66 0.01  0.92
    #> R 0.06 0.01 0.00 0.00 0.01 0.00 0.07 0.01 0.00  0.02
    #> T 0.91 0.72 0.00 0.00 0.00 0.52 0.00 0.17 0.01  0.06
    #> W 0.00 0.03 0.01 0.01 0.00 0.00 0.01 0.08 0.68  0.00
    #> Y 0.00 0.00 0.00 0.00 0.99 0.10 0.80 0.05 0.26  0.00

# Motif visualisation

## Motif logos

There are several packages which offer motif visualisation capabilities, such as `seqLogo`, `motifStack`, and `ggseqlogo`. The `universalmotif` package has its own implementation via the function `view_motifs()` / `view_motifs2()`, which renders motifs using the `ggplot2` package (similar to `ggseqlogo`). Here I will briefly show how to use these to visualise `universalmotif` class motifs.

    library(universalmotif)
    data(examplemotif)

    ## With the native `view_motifs` function:
    view_motifs(examplemotif)

The `view_motifs()` / `view_motifs2()` functions generates `ggplot` objects; feel free to manipulate them as such. For example, flipping the position numbers for larger motifs (where the text spacing can become tight):

    view_motifs(create_motif(15)) +
      ggplot2::theme(
        axis.text.x = ggplot2::element_text(angle = 90, hjust = 1)
      )

A large number of options are available for tuning the way motifs are plotted in `view_motifs()` / `view_motifs2()`. Visit the documentation for more information.

Using the other Bioconductor packages to view `universalmotif` motifs is fairly easy as well:

    ## For all the following examples, simply passing the functions a PPM is
    ## sufficient
    motif <- convert_type(examplemotif, "PPM")
    ## Only need the matrix itself
    motif <- motif["motif"]

    ## seqLogo:
    seqLogo::seqLogo(motif)

    ## motifStack:
    motifStack::plotMotifLogo(motif)
    #> also installing the dependency 'jpeg'
    #> Loading required namespace: Cairo
    #> Warning in checkValidSVG(doc, warn = warn): This picture may not have been
    #> generated by Cairo graphics; errors may result
    #> Loading required namespace: Cairo
    #> Warning in checkValidSVG(doc, warn = warn): This picture may not have been
    #> generated by Cairo graphics; errors may result
    #> Loading required namespace: Cairo
    #> Warning in checkValidSVG(doc, warn = warn): This picture may not have been
    #> generated by Cairo graphics; errors may result
    #> Loading required namespace: Cairo
    #> Warning in checkValidSVG(doc, warn = warn): This picture may not have been
    #> generated by Cairo graphics; errors may result

    ## ggseqlogo:
    ggseqlogo::ggseqlogo(motif)
    #> Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
    #> of ggplot2 3.3.4.
    #> ℹ The deprecated feature was likely used in the ggseqlogo package.
    #>   Please report the issue at <https://github.com/omarwagih/ggseqlogo/issues>.
    #> This warning is displayed once per session.
    #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
    #> generated.
    #> Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
    #> ℹ Please use tidy evaluation idioms with `aes()`.
    #> ℹ See also `vignette("ggplot2-in-packages")` for more information.
    #> ℹ The deprecated feature was likely used in the ggseqlogo package.
    #>   Please report the issue at <https://github.com/omarwagih/ggseqlogo/issues>.
    #> This warning is displayed once per session.
    #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
    #> generated.

## Stacked motif logos

The `motifStack` package allows for a number of different motif stacking visualisations. The `universalmotif` package, while not capable of emulating most of these, still offers basic stacking via `view_motifs()` / `view_motifs2()`. The motifs are aligned using `compare_motifs()` / `compare_motifs2()`.

    library(universalmotif)
    library(MotifDb)

    motifs <- convert_motifs(MotifDb[50:54])
    view_motifs(motifs, show.positions.once = FALSE, names.pos = "right")

## Plot arbitrary text logos

The logo plotting capabilities of `view_motifs()` / `view_motifs2()` can be used for any kind of arbitrary text logo. All you need is a numeric matrix (the heights of the characters), with the desired characters as row names. The following example is taken from the `view_logo()` documentation.

    library(universalmotif)
    data(examplemotif)

    ## Start from a numeric matrix:
    toplot <- examplemotif["motif"]

    # Adjust the character heights as you wish (negative values are possible):
    toplot[4] <- 2
    toplot[20] <- -0.5

    # Mix and match the number of characters per letter/position:
    rownames(toplot)[1] <- "AA"

    toplot <- toplot[c(1, 4), ]

    toplot
    #>    T A T A    W A   W
    #> AA 0 1 0 1  0.5 1 0.5
    #> T  2 0 1 0 -0.5 0 0.5

    view_logo(toplot)

# Higher-order motifs

Though PCM, PPM, PWM, and ICM type motifs are still widely used today, a few ‘next generation’ motif formats have been proposed. These wish to add another layer of information to motifs: positional interdependence. To illustrate this, consider the following sequences:

(#tab:seqs2) Example sequences.
# | Sequence
--- | ---
1 | CAAAACC
2 | CAAAACC
3 | CAAAACC
4 | CTTTTCC
5 | CTTTTCC
6 | CTTTTCC

This becomes the following PPM:

(#tab:ppm2) Position Probability Matrix.
Position | 1 | 2 | 3 | 4 | 5 | 6 | 7
--- | --- | --- | --- | --- | --- | --- | ---
A | 0.0 | 0.5 | 0.5 | 0.5 | 0.5 | 0.0 | 0.0
C | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 1.0
G | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
T | 0.0 | 0.5 | 0.5 | 0.5 | 0.5 | 0.0 | 0.0

Based on the PPM representation, all three of CAAAACC, CTTTTCC, and CTATACC are equally likely. Though looking at the starting sequences, should CTATACC really be considered equally likely? For transcription factor binding sites, this sometimes is not the case. By incorporating this type of information into the motif, it can allow for increased accuracy in motif searching. A few example implementations of this include: TFFM by Mathelier and Wasserman (2013), BaMM by Siebert and Soding (2016), and KSM by Guo et al. (2018).

The `universalmotif` package implements its own, rather simplified, version of this concept. Plainly, the standard PPM has been extended to include `k`-letter frequencies, with `k` being any number higher than 1. For example, the 2-letter version of the table @ref(tab:ppm2) motif would be:

(#tab:multi) 2-letter probability matrix.
Position | 1 | 2 | 3 | 4 | 5 | 6
--- | --- | --- | --- | --- | --- | ---
AA | 0.0 | 0.5 | 0.5 | 0.5 | 0.0 | 0.0
AC | 0.0 | 0.0 | 0.0 | 0.0 | 0.5 | 0.0
AG | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
AT | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
CA | 0.5 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
CC | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0
CG | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
CT | 0.5 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
GA | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
GC | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
GG | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
GT | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
TA | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
TC | 0.0 | 0.0 | 0.0 | 0.0 | 0.5 | 0.0
TG | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0
TT | 0.0 | 0.5 | 0.5 | 0.5 | 0.0 | 0.0

This format shows the probability of each letter combined with the probability of the letter in the next position. The seventh column has been dropped, since it is not needed: the information in the sixth column is sufficient, and there is no eighth position to draw 2-letter probabilities from. Now, the probability of getting CTATACC is no longer equal to CTTTTCC and CAAAACC. This information is kept in the `multifreq` slot of `universalmotif` class motifs. To add this information, use the `add_multifreq()` function.

    library(universalmotif)

    motif <- create_motif("CWWWWCC", nsites = 6)
    sequences <- DNAStringSet(rep(c("CAAAACC", "CTTTTCC"), 3))
    motif.k2 <- add_multifreq(motif, sequences, add.k = 2)

    ## Alternatively:
    # motif.k2 <- create_motif(sequences, add.multifreq = 2)

    motif.k2
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   10
    #>       Pseudocount:   0
    #>         Consensus:   CWWWWCC
    #>      Target sites:   6
    #>    k-letter freqs:   2
    #>
    #>   C   W   W   W   W C C
    #> A 0 0.5 0.5 0.5 0.5 0 0
    #> C 1 0.0 0.0 0.0 0.0 1 1
    #> G 0 0.0 0.0 0.0 0.0 0 0
    #> T 0 0.5 0.5 0.5 0.5 0 0

To plot these motifs, use `view_motifs()`:

    view_motifs(motif.k2, use.freq = 2)

This information is most useful with functions such as `scan_sequences()` and `enrich_motifs()`. Though other tools in the `universalmotif` package can work with `multifreq` motifs (such as `motif_pvalue()`, `compare_motifs()`), keep in mind they are not as well supported as regular motifs (getting P-values from `multifreq` motifs is exponentially slower, and P-values from using `compare_motifs()` for `multifreq` motifs are not available by default). See the sequences vignette for using `scan_sequences()` with the `multifreq` slot.

# Tidy motif manipulation with the `universalmotif_df` data structure

For those who enjoy using the tidyverse functions for data handling, motifs can additionally be represented as the modified `data.frame` format: `universalmotif_df`. This format allows one to modify motif slots for multiple motifs simultaneously using the `universalmotif_df` columns, and then return to a list of motifs afterwards to resume use with `universalmotif` package functions. A few key functions have been provided in relation to this format:

- `to_df()`: Generate a `universalmotif_df` object from a list of motifs.
- `update_motifs()`: After modifying the `universalmotif_df` object, apply these modifications to the actual `universalmotif` objects (contained within the `motif` column).
- `to_list()`: Return to a list of `universalmotif` objects for use with `universalmotif` package functions. Note that it is not required to use `update_motifs()` before using `to_list()`, as modifications will be checked for and applied if found.
- `requires_update()`: Boolean check as to whether the `universalmotif` objects and the `universalmotif_df` columns differ and require either a `update_motifs()` or `to_list()` call to re-sync them.

    library(universalmotif)
    library(MotifDb)

    ## Obtain a `universalmotif_df` object
    motifs <- to_df(MotifDb)
    head(motifs)
    #>          motif      name    altname family    organism   consensus alphabet
    #> 1   <mot:ABF1>      ABF1 M0001_1.02   ABF1 Scerevisiae    TTATCACT      DNA
    #> 2   <mot:AFT1>      AFT1 M0002_1.02    AFT Scerevisiae NTTGCACCCNN      DNA
    #> 3   <mot:AFT2>      AFT2 M0003_1.02    AFT Scerevisiae  NHNNCACCYN      DNA
    #> 4 <mot:PK19..> PK19363.1 M0004_1.02    AP2     Csativa    CGCCGCCR      DNA
    #> 5  <mot:ORA59>     ORA59 M0005_1.02    AP2   Athaliana    MGCCGCCN      DNA
    #> 6   <mot:WIN1>      WIN1 M0006_1.02    AP2   Athaliana  NCRCCGCNNN      DNA
    #>   strand   icscore type pseudocount          bkg dataSource
    #> 1     +- 13.697468  PPM           1 0.25, 0..... cisbp_1.02
    #> 2     +- 11.323910  PPM           1 0.25, 0..... cisbp_1.02
    #> 3     +-  5.809302  PPM           1 0.25, 0..... cisbp_1.02
    #> 4     +-  8.926117  PPM           1 0.25, 0..... cisbp_1.02
    #> 5     +- 11.351632  PPM           1 0.25, 0..... cisbp_1.02
    #> 6     +-  6.509679  PPM           1 0.25, 0..... cisbp_1.02
    #>
    #> [Hidden empty columns: nsites, bkgsites, pval, qval, eval.]

Some tidy manipulation:

    library(dplyr)

    motifs <- motifs %>%
      mutate(bkg = case_when(
        organism == "Athaliana" ~ list(c(A = 0.32, C = 0.18, G = 0.18, T = 0.32)),
        TRUE ~ list(c(A = 0.25, C = 0.25, G = 0.25, T = 0.25))
      ))
    head(filter(motifs, organism == "Athaliana"))
    #>            motif      name    altname family  organism  consensus alphabet
    #> 1 *  <mot:ORA59>     ORA59 M0005_1.02    AP2 Athaliana   MGCCGCCN      DNA
    #> 2 *   <mot:WIN1>      WIN1 M0006_1.02    AP2 Athaliana NCRCCGCNNN      DNA
    #> 3 * <mot:AT1G..> AT1G22985 M0007_1.02    AP2 Athaliana  NNGCCGNNN      DNA
    #> 4 *   <mot:TEM1>      TEM1 M0008_1.02    AP2 Athaliana   WATGTTGC      DNA
    #> 5 *  <mot:ERF11>     ERF11 M0009_1.02    AP2 Athaliana NNGCCGNNNN      DNA
    #> 6 * <mot:RAP2.6>    RAP2.6 M0010_1.02    AP2 Athaliana   NNGCCGNN      DNA
    #>   strand   icscore type pseudocount          bkg dataSource
    #> 1     +- 11.351632  PPM           1 0.32, 0..... cisbp_1.02
    #> 2     +-  6.509679  PPM           1 0.32, 0..... cisbp_1.02
    #> 3     +-  5.155725  PPM           1 0.32, 0..... cisbp_1.02
    #> 4     +- 11.182383  PPM           1 0.32, 0..... cisbp_1.02
    #> 5     +-  5.148803  PPM           1 0.32, 0..... cisbp_1.02
    #> 6     +-  4.227144  PPM           1 0.32, 0..... cisbp_1.02
    #>
    #> [Hidden empty columns: nsites, bkgsites, pval, qval, eval.]
    #> [Rows marked with * are changed. Run update_motifs() or to_list() to apply
    #>   changes.]

Feel free to add columns as well. You can add one-dimensional vectors which will be added to the `extrainfo` slots of motifs. (Note that they will be coerced to character vectors!)

    motifs <- motifs %>%
      mutate(MotifIndex = 1:n())
    head(motifs)
    #>            motif      name    altname family    organism   consensus alphabet
    #> 1 *   <mot:ABF1>      ABF1 M0001_1.02   ABF1 Scerevisiae    TTATCACT      DNA
    #> 2 *   <mot:AFT1>      AFT1 M0002_1.02    AFT Scerevisiae NTTGCACCCNN      DNA
    #> 3 *   <mot:AFT2>      AFT2 M0003_1.02    AFT Scerevisiae  NHNNCACCYN      DNA
    #> 4 * <mot:PK19..> PK19363.1 M0004_1.02    AP2     Csativa    CGCCGCCR      DNA
    #> 5 *  <mot:ORA59>     ORA59 M0005_1.02    AP2   Athaliana    MGCCGCCN      DNA
    #> 6 *   <mot:WIN1>      WIN1 M0006_1.02    AP2   Athaliana  NCRCCGCNNN      DNA
    #>   strand   icscore type pseudocount          bkg dataSource MotifIndex
    #> 1     +- 13.697468  PPM           1 0.25, 0..... cisbp_1.02          1
    #> 2     +- 11.323910  PPM           1 0.25, 0..... cisbp_1.02          2
    #> 3     +-  5.809302  PPM           1 0.25, 0..... cisbp_1.02          3
    #> 4     +-  8.926117  PPM           1 0.25, 0..... cisbp_1.02          4
    #> 5     +- 11.351632  PPM           1 0.32, 0..... cisbp_1.02          5
    #> 6     +-  6.509679  PPM           1 0.32, 0..... cisbp_1.02          6
    #>
    #> [Hidden empty columns: nsites, bkgsites, pval, qval, eval.]
    #> [Rows marked with * are changed. Run update_motifs() or to_list() to apply
    #>   changes.]

    to_list(motifs)[[1]]
    #>
    #>        Motif name:   ABF1
    #>    Alternate name:   M0001_1.02
    #>            Family:   ABF1
    #>          Organism:   Scerevisiae
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   13.7
    #>       Pseudocount:   1
    #>         Consensus:   TTATCACT
    #>        Extra info:   [dataSource] cisbp_1.02
    #>                      [MotifIndex] 1
    #>
    #>      T    T    A    T    C    A    C    T
    #> A 0.01 0.01 0.97 0.01 0.01 0.97 0.01 0.01
    #> C 0.01 0.01 0.01 0.01 0.97 0.01 0.97 0.01
    #> G 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01
    #> T 0.97 0.97 0.01 0.97 0.01 0.01 0.01 0.97

If during the course of your manipulation you’ve generated temporary columns which you wish to drop, you can set `extrainfo = FALSE` to discard all extra columns. Be careful though, this will discard any previously existing `extrainfo` data as well.

    to_list(motifs, extrainfo = FALSE)[[1]]
    #> Discarding unknown slot(s) 'dataSource', 'MotifIndex' (set `extrainfo=TRUE`
    #>   to preserve these).
    #>
    #>        Motif name:   ABF1
    #>    Alternate name:   M0001_1.02
    #>            Family:   ABF1
    #>          Organism:   Scerevisiae
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   13.7
    #>       Pseudocount:   1
    #>         Consensus:   TTATCACT
    #>
    #>      T    T    A    T    C    A    C    T
    #> A 0.01 0.01 0.97 0.01 0.01 0.97 0.01 0.01
    #> C 0.01 0.01 0.01 0.01 0.97 0.01 0.97 0.01
    #> G 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01
    #> T 0.97 0.97 0.01 0.97 0.01 0.01 0.01 0.97

# Miscellaneous motif utilities

A number of convenience functions are included for manipulating motifs.

## DNA/RNA/AA consensus functions

For DNA, RNA and AA motifs, the `universalmotif` package will automatically generate a `consensus` string slot. Furthermore, `create_motif()` can generate motifs from consensus strings. The internal functions for these have been made available:

- `consensus_to_ppm()`
- `consensus_to_ppmAA()`
- `get_consensus()`
- `get_consensusAA()`

    library(universalmotif)

    get_consensus(c(A = 0.7, C = 0.1, G = 0.1, T = 0.1))
    #> [1] "A"

    consensus_to_ppm("G")
    #> [1] 0.001 0.001 0.997 0.001

## Filter through lists of motifs

Filter a list of motifs, using the `universalmotif` slots with `filter_motifs()`.

    library(universalmotif)
    library(MotifDb)

    ## Let us extract all of the Arabidopsis and C. elegans motifs

    motifs <- filter_motifs(MotifDb, organism = c("Athaliana", "Celegans"))
    #> motifs converted to class 'universalmotif'

    ## Only keeping motifs with sufficient information content and length:

    motifs <- filter_motifs(motifs, icscore = 10, width = 10)

    head(summarise_motifs(motifs))
    #>        name    altname family  organism     consensus alphabet strand  icscore
    #> 1      ERF1 M0025_1.02    AP2 Athaliana    NMGCCGCCRN      DNA     +- 12.40700
    #> 2    ATERF6 M0027_1.02    AP2 Athaliana    NTGCCGGCGB      DNA     +- 11.77649
    #> 3    ATCBF3 M0032_1.02    AP2 Athaliana    ATGTCGGYNN      DNA     +- 10.66970
    #> 4 AT2G18300 M0155_1.02   bHLH Athaliana  NNNGCACGTGNN      DNA     +- 11.50133
    #> 5   bHLH104 M0159_1.02   bHLH Athaliana    GGCACGTGCC      DNA     +- 16.05350
    #> 6    hlh-16 M0173_1.02   bHLH  Celegans NNNCAATATKGNN      DNA     +- 10.32432
    #>   nsites
    #> 1     NA
    #> 2     NA
    #> 3     NA
    #> 4     NA
    #> 5     NA
    #> 6     NA

## Generate random motif matches

Get a random set of sequences which are created using the probabilities of the motif matrix, in effect generating motif sites, with `sample_sites()`.

    library(universalmotif)
    data(examplemotif)

    sample_sites(examplemotif)
    #> DNAStringSet object of length 100:
    #>       width seq
    #>   [1]     7 TATATAT
    #>   [2]     7 TATAAAT
    #>   [3]     7 TATATAT
    #>   [4]     7 TATATAA
    #>   [5]     7 TATATAA
    #>   ...   ... ...
    #>  [96]     7 TATAAAA
    #>  [97]     7 TATAAAA
    #>  [98]     7 TATATAA
    #>  [99]     7 TATAAAA
    #> [100]     7 TATATAA

## Motif shuffling

Shuffle a set of motifs with `shuffle_motifs()`. The original shuffling implementation is taken from the `linear` shuffling method of `shuffle_sequences()`, described in the sequences vignette.

    library(universalmotif)
    library(MotifDb)

    motifs <- convert_motifs(MotifDb[1:50])
    head(summarise_motifs(motifs))
    #>        name    altname family    organism   consensus alphabet strand   icscore
    #> 1      ABF1 M0001_1.02   ABF1 Scerevisiae    TTATCACT      DNA     +- 13.697468
    #> 2      AFT1 M0002_1.02    AFT Scerevisiae NTTGCACCCNN      DNA     +- 11.323910
    #> 3      AFT2 M0003_1.02    AFT Scerevisiae  NHNNCACCYN      DNA     +-  5.809302
    #> 4 PK19363.1 M0004_1.02    AP2     Csativa    CGCCGCCR      DNA     +-  8.926117
    #> 5     ORA59 M0005_1.02    AP2   Athaliana    MGCCGCCN      DNA     +- 11.351632
    #> 6      WIN1 M0006_1.02    AP2   Athaliana  NCRCCGCNNN      DNA     +-  6.509679

    motifs.shuffled <- shuffle_motifs(motifs, k = 3)
    head(summarise_motifs(motifs.shuffled))
    #>                   name   consensus alphabet strand   icscore
    #> 1      ABF1 [shuffled]    TGNCNNNN      DNA     +-  3.870597
    #> 2      AFT1 [shuffled] RCCNNNATNNG      DNA     +-  6.230797
    #> 3      AFT2 [shuffled]  CCGGTGCCRG      DNA     +- 15.685415
    #> 4 PK19363.1 [shuffled]    TCGCCTCC      DNA     +- 12.254109
    #> 5     ORA59 [shuffled]    NHNGTCNN      DNA     +-  5.760136
    #> 6      WIN1 [shuffled]  TCCRNCNCCC      DNA     +-  6.119395

## Scoring and match functions

Motif matches in a set of sequences are typically obtained using logodds scores. Several functions are exposed to reveal some of the internal work that goes on.

- `get_matches()`: show all possible sequence matches above a certain score
- `get_scores()`: obtain all possible scores from all possible sequence matches
- `motif_score()`: translate score thresholds to logodds scores
- `prob_match()`: return probabilities for sequence matches
- `score_match()`: return logodds scores for sequence matches

    library(universalmotif)
    data(examplemotif)
    examplemotif
    #>
    #>        Motif name:   motif
    #>          Alphabet:   DNA
    #>              Type:   PPM
    #>           Strands:   +-
    #>          Total IC:   11.54
    #>       Pseudocount:   1
    #>         Consensus:   TATAWAW
    #>
    #>   T A T A   W A   W
    #> A 0 1 0 1 0.5 1 0.5
    #> C 0 0 0 0 0.0 0 0.0
    #> G 0 0 0 0 0.0 0 0.0
    #> T 1 0 1 0 0.5 0 0.5

    ## Get the min and max possible scores:
    motif_score(examplemotif)
    #>      0%    100%
    #> -46.606  11.929

    ## Show matches above a score of 10:
    get_matches(examplemotif, 10)
    #> [1] "TATAAAA" "TATATAA" "TATAAAT" "TATATAT"

    ## Get the probability of a match:
    prob_match(examplemotif, "TTTTTTT", allow.zero = FALSE)
    #> [1] 6.103516e-05

    ## Score a specific sequence:
    score_match(examplemotif, "TTTTTTT")
    #> [1] -14.012

    ## Take a look at the distribution of scores:
    plot(density(get_scores(examplemotif), bw = 5))

## Type conversion functions

While `convert_type()` will take care of switching the current type for `universalmotif` objects, the individual type conversion functions are also available for personal use. These are:

- `icm_to_ppm()`
- `pcm_to_ppm()`
- `ppm_to_icm()`
- `ppm_to_pcm()`
- `ppm_to_pwm()`
- `pwm_to_ppm()`

These functions take a one-dimensional vector. To use these for matrices:

    library(universalmotif)

    m <- create_motif(type = "PCM")["motif"]
    m
    #>    G  A  K  M  A  T  K  T  A  K
    #> A 12 99  1 28 79  0  0  1 98  2
    #> C  0  0  1 67  4 13  0  0  0  1
    #> G 88  0 55  0 15  4 54  1  1 59
    #> T  0  1 43  5  2 83 46 98  1 38

    apply(m, 2, pcm_to_ppm)
    #>         G    A    K    M    A    T    K    T    A    K
    #> [1,] 0.12 0.99 0.01 0.28 0.79 0.00 0.00 0.01 0.98 0.02
    #> [2,] 0.00 0.00 0.01 0.67 0.04 0.13 0.00 0.00 0.00 0.01
    #> [3,] 0.88 0.00 0.55 0.00 0.15 0.04 0.54 0.01 0.01 0.59
    #> [4,] 0.00 0.01 0.43 0.05 0.02 0.83 0.46 0.98 0.01 0.38

Additionally, `position_icscore()` can be used to get the total information content per position:

    library(universalmotif)

    position_icscore(c(0.7, 0.1, 0.1, 0.1))
    #> [1] 0.6307803

# Session info

    #> R version 4.6.0 (2026-04-24)
    #> Platform: x86_64-pc-linux-gnu
    #> Running under: Ubuntu 24.04.4 LTS
    #>
    #> Matrix products: default
    #> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
    #> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
    #>
    #> locale:
    #>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
    #>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
    #>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
    #>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
    #>  [9] LC_ADDRESS=C               LC_TELEPHONE=C
    #> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
    #>
    #> time zone: Etc/UTC
    #> tzcode source: system (glibc)
    #>
    #> attached base packages:
    #> [1] stats4    stats     graphics  grDevices utils     datasets  methods
    #> [8] base
    #>
    #> other attached packages:
    #>  [1] TFBSTools_1.51.0
    #>  [2] cowplot_1.2.0
    #>  [3] dplyr_1.2.1
    #>  [4] ggtree_4.3.0
    #>  [5] ggplot2_4.0.3
    #>  [6] MotifDb_1.55.0
    #>  [7] BSgenome.Athaliana.TAIR.TAIR9_1.3.1000
    #>  [8] BSgenome_1.81.0
    #>  [9] BiocIO_1.23.3
    #> [10] rtracklayer_1.73.0
    #> [11] GenomeInfoDb_1.49.1
    #> [12] GenomicRanges_1.65.0
    #> [13] Biostrings_2.81.2
    #> [14] Seqinfo_1.3.0
    #> [15] XVector_0.53.0
    #> [16] IRanges_2.47.2
    #> [17] S4Vectors_0.51.3
    #> [18] BiocGenerics_0.59.6
    #> [19] generics_0.1.4
    #> [20] universalmotif_1.31.32
    #> [21] bookdown_0.46
    #>
    #> loaded via a namespace (and not attached):
    #>   [1] DBI_1.3.0                   bitops_1.0-9
    #>   [3] rlang_1.2.0                 magrittr_2.0.5
    #>   [5] ade4_1.7-24                 RSQLite_3.53.1
    #>   [7] matrixStats_1.5.0           compiler_4.6.0
    #>   [9] png_0.1-9                   systemfonts_1.3.2
    #>  [11] vctrs_0.7.3                 pwalign_1.9.1
    #>  [13] pkgconfig_2.0.3             crayon_1.5.3
    #>  [15] fastmap_1.2.0               motifStack_1.57.0
    #>  [17] labeling_0.4.3              caTools_1.18.3
    #>  [19] splitstackshape_1.4.8.1     Rsamtools_2.29.0
    #>  [21] rmarkdown_2.31              UCSC.utils_1.9.0
    #>  [23] DirichletMultinomial_1.55.0 bit_4.6.0
    #>  [25] purrr_1.2.2                 xfun_0.57
    #>  [27] ggseqlogo_0.2.2             cachem_1.1.0
    #>  [29] cigarillo_1.3.0             aplot_0.2.9
    #>  [31] jsonlite_2.0.0              blob_1.3.0
    #>  [33] DelayedArray_0.39.3         BiocParallel_1.47.0
    #>  [35] jpeg_0.1-11                 parallel_4.6.0
    #>  [37] R6_2.6.1                    bslib_0.11.0
    #>  [39] RColorBrewer_1.1-3          jquerylib_0.1.4
    #>  [41] Rcpp_1.1.1-1.1              SummarizedExperiment_1.43.0
    #>  [43] knitr_1.51                  base64enc_0.1-6
    #>  [45] BiocBaseUtils_1.15.1        Matrix_1.7-5
    #>  [47] tidyselect_1.2.1            dichromat_2.0-0.1
    #>  [49] abind_1.4-8                 yaml_2.3.12
    #>  [51] codetools_0.2-20            curl_7.1.0
    #>  [53] lattice_0.22-9              tibble_3.3.1
    #>  [55] Biobase_2.73.1              treeio_1.37.0
    #>  [57] withr_3.0.2                 S7_0.2.2
    #>  [59] evaluate_1.0.5              gridGraphics_0.5-1
    #>  [61] pillar_1.11.1               MatrixGenerics_1.25.0
    #>  [63] ggfun_0.2.0                 grImport2_0.3-3
    #>  [65] RCurl_1.98-1.18             scales_1.4.0
    #>  [67] tidytree_0.4.7              gtools_3.9.5
    #>  [69] glue_1.8.1                  gdtools_0.5.1
    #>  [71] lazyeval_0.2.3              seqLogo_1.79.0
    #>  [73] maketools_1.3.2             tools_4.6.0
    #>  [75] TFMPvalue_1.0.0             sys_3.4.3
    #>  [77] data.table_1.18.4           GenomicAlignments_1.49.0
    #>  [79] ggiraph_0.9.6               buildtools_1.0.0
    #>  [81] fs_2.1.0                    XML_3.99-0.23
    #>  [83] grid_4.6.0                  tidyr_1.3.2
    #>  [85] ape_5.8-1                   nlme_3.1-169
    #>  [87] patchwork_1.3.2             restfulr_0.0.16
    #>  [89] cli_3.6.6                   rappdirs_0.3.4
    #>  [91] fontBitstreamVera_0.1.1     S4Arrays_1.13.0
    #>  [93] gtable_0.3.6                yulab.utils_0.2.4
    #>  [95] sass_0.4.10                 digest_0.6.39
    #>  [97] fontquiver_0.2.1            SparseArray_1.13.2
    #>  [99] ggplotify_0.1.3             rjson_0.2.23
    #> [101] htmlwidgets_1.6.4           farver_2.1.2
    #> [103] memoise_2.0.1               htmltools_0.5.9
    #> [105] lifecycle_1.0.5             httr_1.4.8
    #> [107] bit64_4.8.2                 fontLiberation_0.1.0
    #> [109] MASS_7.3-65

# References

Bailey, T. L., M. Boden, F. A. Buske, et al. 2009. “MEME SUITE: Tools for Motif Discovery and Searching.” Nucleic Acids Research 37: W202–8.
Guo, Y., K. Tian, H. Zeng, X. Guo, and D. K. Gifford. 2018. “A Novel k-Mer Set Memory (KSM) Motif Representation Improves Regulatory Variant Prediction.” Genome Research 28: 891–900.
Heinz, S., C. Benner, N. Spann, et al. 2010. “Simple Combinations of Lineage-Determining Transcription Factors Prime Cis-Regulatory Elements Required for Macrophage and b Cell Identities.” Molecular Cell 38: 576–89.
Hume, M. A., L. A. Barrera, S. S. Gisselbrecht, and M. L. Bulyk. 2015. “UniPROBE, Update 2015: New Tools and Content for the Online Database of Protein-Binding Microarray Data on Protein-DNA Interactions.” Nucleic Acids Research 43: D117–22.
Khan, A., O. Fornes, A. Stigliani, et al. 2018. “JASPAR 2018: Update of the Open-Access Database of Transcription Factor Binding Profiles and Its Web Framework.” Nucleic Acids Research 46: D260–66.
Mathelier, A., and W. W. Wasserman. 2013. “The Next Generation of Transcription Factor Binding Site Prediction.” PLoS Computational Biology 9: e1003214.
Siebert, M., and J. Soding. 2016. “Bayesian Markov Models Consistently Outperform PWMs at Predicting Motifs in Nucleotide Sequences.” Nucleic Acids Research 44: 6055–69.
Weirauch, M. T., A. Yang, M. Albu, et al. 2014. “Determination and Inference of Eukaryotic Transcription Factor Sequence Specificity.” Cell 158: 1431–43.
Wingender, E., P. Dietze, H. Karas, and R. Knuppel. 1996. “TRANSFAC: A Database on Transcription Factors and Their DNA Binding Sites.” Nucleic Acids Research 24: 238–41.

1. benjamin.tremblay@uwaterloo.ca↩︎