Skip to content

crispr_analysis_utils.accession

A guide's accession encodes its protospacer, so the same target gets the same id in every library and table, and two different protospacers can never share one. spacer_of decodes it back.

The protospacer is the key, not the ordered sequence. Two guides differing only 5' of the protospacer — one lab prepends a G for the U6 promoter, another does not — are two oligos aimed at one site, so they share an accession and differ in guide_alignment's guide_id. The accession answers "same target?"; guide_id answers "same oligo?".

Nothing in a sequence says where the protospacer ends, so a sequence with its PAM still attached is accepted and gets a different accession, silently. One spacer in sixteen ends in GG. Decide which bases are the protospacer first; guide_genome_annotation.tsv's spacer column already has.

The encoding

For an uppercase protospacer:

  1. Start from 1, and for each base multiply by 4 and add 0 for A, 1 for C, 2 for G or 3 for T. The leading 1 keeps AAA apart from AA.
  2. Write the result in Crockford's base 32, alphabet 0123456789ABCDEFGHJKMNPQRSTVWXYZ, after GU.

Decoding ignores case and reads I and L as 1 and O as 0. A 20-nt protospacer gives 9 characters after GU; a 19-nt one gives 8.

ACGT packs as 0b1_00_01_10_11, which is 283, which is 8, 27 in base 32: the accession is GU8V.

crispr_analysis_utils.accession

Accession ids for guides, encoded from the protospacer.

An accession is GU followed by the protospacer packed two bits per base behind a leading 1 bit, in Crockford's base 32. The packing is reversible, so two different protospacers can never share an accession -- not in a library, and not among every guide the lab will ever order.

The protospacer is the key, not the ordered sequence, so the same target gets the same accession whatever a lab adds at the 5' end for its promoter and whatever it calls the guide. The exact sequence that was ordered is guide_alignment's guide_id; this answers the other question, which is whether two guides hit the same place.

normalize_spacer

normalize_spacer(spacer: str) -> str

Strip and uppercase a protospacer, and check it is one.

Parameters:

  • spacer (str) –

    The protospacer, 5' to 3', without the PAM and without any base added 5' of it for the promoter.

Returns:

  • str –

    spacer stripped and in uppercase.

Raises:

  • TypeError –

    If spacer is not a string, such as the NaN pandas reads for an empty cell.

  • ValueError –

    If spacer is empty or holds anything but A, C, G and T.

Notes

The PAM is genomic, so it is not part of a protospacer and must not be passed here. A sequence ending in its PAM is accepted and gives a different accession, silently: one spacer in sixteen ends in GG, so there is no way to tell from the sequence alone. Decide which bases are the protospacer before calling this.

Source code in src/crispr_analysis_utils/accession.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def normalize_spacer(spacer: str) -> str:
    """Strip and uppercase a protospacer, and check it is one.

    Parameters
    ----------
    spacer
        The protospacer, 5' to 3', without the PAM and without any base added
        5' of it for the promoter.

    Returns
    -------
    str
        `spacer` stripped and in uppercase.

    Raises
    ------
    TypeError
        If `spacer` is not a string, such as the NaN pandas reads for an empty
        cell.
    ValueError
        If `spacer` is empty or holds anything but A, C, G and T.

    Notes
    -----
    The PAM is genomic, so it is not part of a protospacer and must not be
    passed here. A sequence ending in its PAM is accepted and gives a
    different accession, silently: one spacer in sixteen ends in ``GG``, so
    there is no way to tell from the sequence alone. Decide which bases are
    the protospacer before calling this.
    """
    if not isinstance(spacer, str):
        raise TypeError(
            f"A protospacer must be a string, not {type(spacer).__name__} ({spacer!r})."
        )
    stripped = spacer.strip()
    if not stripped:
        raise ValueError("A protospacer must not be empty.")
    normalized = stripped.upper()
    unknown = "".join(sorted(set(normalized) - set(_BASES)))
    if unknown:
        raise ValueError(
            f"Protospacer {spacer!r} holds {unknown!r}: a protospacer is written "
            "with A, C, G and T."
        )
    return normalized

guide_accession

guide_accession(spacer: str) -> str

Return the accession of a protospacer.

Parameters:

  • spacer (str) –

    The protospacer, as normalize_spacer takes it.

Returns:

  • str –

    GU and the encoded bases: 9 characters for a 20-nt protospacer, 8 for 19.

Raises:

  • TypeError –

    If spacer is not a string.

  • ValueError –

    If spacer is empty or holds anything but A, C, G and T.

See Also

spacer_of : The inverse. guide_accessions : Accession a whole library.

Examples:

>>> guide_accession("AAAAGCCAACATGAATGCAG")
'GU102A170WJ'
>>> guide_accession(" aaaagccaacatgaatgcag ")
'GU102A170WJ'
Source code in src/crispr_analysis_utils/accession.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def guide_accession(spacer: str) -> str:
    """Return the accession of a protospacer.

    Parameters
    ----------
    spacer
        The protospacer, as `normalize_spacer` takes it.

    Returns
    -------
    str
        ``GU`` and the encoded bases: 9 characters for a 20-nt protospacer,
        8 for 19.

    Raises
    ------
    TypeError
        If `spacer` is not a string.
    ValueError
        If `spacer` is empty or holds anything but A, C, G and T.

    See Also
    --------
    spacer_of : The inverse.
    guide_accessions : Accession a whole library.

    Examples
    --------
    >>> guide_accession("AAAAGCCAACATGAATGCAG")
    'GU102A170WJ'
    >>> guide_accession(" aaaagccaacatgaatgcag ")
    'GU102A170WJ'
    """
    packed = 1
    for base in normalize_spacer(spacer):
        packed = packed * 4 + _BASES.index(base)

    characters = ""
    while packed:
        packed, remainder = divmod(packed, 32)
        characters = _ALPHABET[remainder] + characters
    return _PREFIX + characters

spacer_of

spacer_of(accession: str) -> str

Return the protospacer an accession encodes.

Parameters:

  • accession (str) –

    An accession from guide_accession. Case is ignored, and I, L and O read as 1, 1 and 0.

Returns:

  • str –

    The protospacer, 5' to 3'.

Raises:

  • TypeError –

    If accession is not a string.

  • ValueError –

    If accession does not begin with GU and base 32 characters, or encodes nothing.

Examples:

>>> spacer_of("GU102A170WJ")
'AAAAGCCAACATGAATGCAG'
Source code in src/crispr_analysis_utils/accession.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def spacer_of(accession: str) -> str:
    """Return the protospacer an accession encodes.

    Parameters
    ----------
    accession
        An accession from `guide_accession`. Case is ignored, and I, L and O
        read as 1, 1 and 0.

    Returns
    -------
    str
        The protospacer, 5' to 3'.

    Raises
    ------
    TypeError
        If `accession` is not a string.
    ValueError
        If `accession` does not begin with ``GU`` and base 32 characters, or
        encodes nothing.

    Examples
    --------
    >>> spacer_of("GU102A170WJ")
    'AAAAGCCAACATGAATGCAG'
    """
    if not isinstance(accession, str):
        raise TypeError(
            f"An accession must be a string, not {type(accession).__name__} "
            f"({accession!r})."
        )
    text = accession.strip().upper()
    if not text.startswith(_PREFIX) or len(text) == len(_PREFIX):
        raise ValueError(
            f"Accession {accession!r} does not begin with {_PREFIX!r} and its "
            "encoded bases."
        )

    packed = 0
    for character in text[len(_PREFIX) :]:
        if character not in _VALUES:
            raise ValueError(
                f"Accession {accession!r} holds {character!r}, which is not a "
                "base 32 character."
            )
        packed = packed * 32 + _VALUES[character]

    spacer = ""
    while packed > 1:
        packed, remainder = divmod(packed, 4)
        spacer = _BASES[remainder] + spacer
    if packed != 1 or not spacer:
        raise ValueError(f"Accession {accession!r} encodes no protospacer.")
    return spacer

guide_accessions

guide_accessions(spacers: Iterable[str]) -> list[str]

Return every protospacer's accession, in order.

Repeats of one protospacer share an accession; different ones never do.

Parameters:

  • spacers (Iterable[str]) –

    Protospacers, as guide_accession takes them: a list, or a pandas Series.

Returns:

  • list of str –

    One accession per protospacer.

Raises:

  • TypeError –

    If a protospacer is not a string. The message names its position.

  • ValueError –

    If a protospacer is empty or holds anything but A, C, G and T. The message names its position.

Source code in src/crispr_analysis_utils/accession.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def guide_accessions(spacers: Iterable[str]) -> list[str]:
    """Return every protospacer's accession, in order.

    Repeats of one protospacer share an accession; different ones never do.

    Parameters
    ----------
    spacers
        Protospacers, as `guide_accession` takes them: a list, or a pandas
        Series.

    Returns
    -------
    list of str
        One accession per protospacer.

    Raises
    ------
    TypeError
        If a protospacer is not a string. The message names its position.
    ValueError
        If a protospacer is empty or holds anything but A, C, G and T. The
        message names its position.
    """
    accessions = []
    for position, spacer in enumerate(spacers):
        try:
            accessions.append(guide_accession(spacer))
        except (TypeError, ValueError) as error:
            raise type(error)(f"Spacer at position {position}: {error}") from None
    return accessions