docs: def group_by_allele(self, locus):
          '''
          Split the PileupCollection by the alleles suggested by the reads at the
          specified locus.
  
          If a read has an insertion immediately following the locus, then the
          insertion is included in the allele. For example, if locus is the
          1-base range [5,6), one allele might be "AGA", indicating that at
          locus 5 some read has an "A" followed by a 2-base insertion ("GA"). If
          a read has a deletion at the specified locus, the allele is the empty
          string.
  
          The given locus may include any number of bases. If the locus includes
          multiple bases, then the alleles consist of all bases aligning to that
          range in any read. Note that only sequences actually sequenced in a
          particular read are included. For example, if one read has "ATT" at a
          locus and another read has "GCC", then the alleles are "ATT" and
          "GCC", but not "GTT". That is, the bases in each allele are phased. For
          this reason, only reads that overlap the entire locus are included.
  
          If the locus is an empty interval (e.g. [5,5) ), then the alleles
          consist only of inserted bases. In this example, only bases inserted
          immediately after locus 5 would be included (but *not* the base
          actually at locus 5). In the previous insertion example, the allele
          would be "GA", indicating a 2-base insertion. Reads that have no
          insertion at that position (matches or deletions) would have the empty
          string as their allele.
  
          Parameters
          ----------
          locus : Locus
              The reference locus, encompassing 0 or more bases.
  
          Returns
          ----------
          A dict of string -> PileupCollection. The keys are nucleotide strings
          giving the bases sequenced at the locus, and the values are
          PileupCollection instances of the alignments that support that allele.
  
          '''
          locus = to_locus(locus)
          read_to_allele = None
          loci = []
          if locus.positions:
              # Our locus includes at least one reference base.
              for position in locus.positions:
                  base_position = Locus.from_interbase_coordinates(
                      locus.contig, position)
                  loci.append(base_position)
                  new_read_to_allele = {}
                  for element in self.pileups[base_position]:
                      allele_prefix = ""
                      key = alignment_key(element.alignment)
                      if read_to_allele is not None:
                          try:
                              allele_prefix = read_to_allele[key]
                          except KeyError:
                              continue
                      allele = allele_prefix + element.bases
                      new_read_to_allele[key] = allele
                  read_to_allele = new_read_to_allele
          else:
              # Our locus is between reference bases.
              position_before = Locus.from_interbase_coordinates(
                  locus.contig, locus.start)
              loci.append(position_before)
              read_to_allele = {}
              for element in self.pileups[position_before]:
                  allele = element.bases[1:]
                  read_to_allele[alignment_key(element.alignment)] = allele
  
          split = defaultdict(lambda: PileupCollection(pileups={}, parent=self))
          for locus in loci:
              pileup = self.pileups[locus]
              for e in pileup.elements:
                  key = read_to_allele.get(alignment_key(e.alignment))
                  if key is not None:
                      if locus in split[key].pileups:
                          split[key].pileups[locus].append(e)
                      else:
                          split[key].pileups[locus] = Pileup(locus, [e])
  
          # Sort by number of reads (descending). Break ties with the
          # lexicographic ordering of the allele string.
          def sorter(pair):
              (allele, pileup_collection) = pair
              return (-1 * pileup_collection.num_reads(), allele)
          return OrderedDict(sorted(split.items(), key=sorter))