docs: def remap_snps(self, individual, target_assembly, complement_bases=True):
          """ Remap the SNP coordinates of an individual from one assembly to another.
  
          This method uses the assembly map endpoint of the Ensembl REST API service (via
          ``Resources``'s ``EnsemblRestClient``) to convert SNP coordinates / positions from one
          assembly to another. After remapping, the coordinates / positions for the individual's
          SNPs will be that of the target assembly.
  
          If the SNPs are already mapped relative to the target assembly, remapping will not be
          performed.
  
          Parameters
          ----------
          target_assembly : {'NCBI36', 'GRCh37', 'GRCh38', 36, 37, 38}
              assembly to remap to
          complement_bases : bool
              complement bases when remapping SNPs to the minus strand
  
          Returns
          -------
          chromosomes_remapped : list of str
              chromosomes remapped; empty if None
          chromosomes_not_remapped : list of str
              chromosomes not remapped; empty if None
  
          Notes
          -----
          An assembly is also know as a "build." For example:
  
          Assembly NCBI36 = Build 36
          Assembly GRCh37 = Build 37
          Assembly GRCh38 = Build 38
  
          See https://www.ncbi.nlm.nih.gov/assembly for more information about assemblies and
          remapping.
  
          References
          ----------
          ..[1] Ensembl, Assembly Map Endpoint,
            http://rest.ensembl.org/documentation/info/assembly_map
          """
          chromosomes_remapped = []
          chromosomes_not_remapped = []
  
          snps = individual.snps
  
          if snps is None:
              print("No SNPs to remap")
              return chromosomes_remapped, chromosomes_not_remapped
          else:
              chromosomes_not_remapped = list(snps["chrom"].unique())
  
          valid_assemblies = ["NCBI36", "GRCh37", "GRCh38", 36, 37, 38]
  
          if target_assembly not in valid_assemblies:
              print("Invalid target assembly")
              return chromosomes_remapped, chromosomes_not_remapped
  
          if isinstance(target_assembly, int):
              if target_assembly == 36:
                  target_assembly = "NCBI36"
              else:
                  target_assembly = "GRCh" + str(target_assembly)
  
          if individual.build == 36:
              source_assembly = "NCBI36"
          else:
              source_assembly = "GRCh" + str(individual.build)
  
          if source_assembly == target_assembly:
              return chromosomes_remapped, chromosomes_not_remapped
  
          assembly_mapping_data = self._resources.get_assembly_mapping_data(
              source_assembly, target_assembly
          )
  
          if assembly_mapping_data is None:
              return chromosomes_remapped, chromosomes_not_remapped
  
          for chrom in snps["chrom"].unique():
              # extract SNPs for this chrom for faster remapping
              temp = pd.DataFrame(snps.loc[snps["chrom"] == chrom])
  
              temp["remapped"] = False
  
              if chrom in assembly_mapping_data:
                  chromosomes_remapped.append(chrom)
                  chromosomes_not_remapped.remove(chrom)
                  mappings = assembly_mapping_data[chrom]
              else:
                  print(
                      "Chromosome " + chrom + " not remapped; "
                      "removing chromosome from SNPs for consistency"
                  )
                  snps = snps.drop(snps.loc[snps["chrom"] == chrom].index)
                  continue
  
              pos_start = int(temp["pos"].describe()["min"])
              pos_end = int(temp["pos"].describe()["max"])
  
              for mapping in mappings["mappings"]:
                  # skip if mapping is outside of range of SNP positions
                  if (
                      mapping["original"]["end"] <= pos_start
                      or mapping["original"]["start"] >= pos_end
                  ):
                      continue
  
                  orig_range_len = (
                      mapping["original"]["end"] - mapping["original"]["start"]
                  )
                  mapped_range_len = mapping["mapped"]["end"] - mapping["mapped"]["start"]
  
                  orig_region = mapping["original"]["seq_region_name"]
                  mapped_region = mapping["mapped"]["seq_region_name"]
  
                  if orig_region != mapped_region:
                      print("discrepant chroms")
                      continue
  
                  if orig_range_len != mapped_range_len:
                      print("discrepant coords")  # observed when mapping NCBI36 -> GRCh38
                      continue
  
                  # find the SNPs that are being remapped for this mapping
                  snp_indices = temp.loc[
                      ~temp["remapped"]
                      & (temp["pos"] >= mapping["original"]["start"])
                      & (temp["pos"] <= mapping["original"]["end"])
                  ].index
  
                  if len(snp_indices) > 0:
                      # remap the SNPs
                      if mapping["mapped"]["strand"] == -1:
                          # flip and (optionally) complement since we're mapping to minus strand
                          diff_from_start = (
                              temp.loc[snp_indices, "pos"] - mapping["original"]["start"]
                          )
                          temp.loc[snp_indices, "pos"] = (
                              mapping["mapped"]["end"] - diff_from_start
                          )
  
                          if complement_bases:
                              snps.loc[snp_indices, "genotype"] = temp.loc[
                                  snp_indices, "genotype"
                              ].apply(self._complement_bases)
                      else:
                          # mapping is on same (plus) strand, so just remap based on offset
                          offset = (
                              mapping["mapped"]["start"] - mapping["original"]["start"]
                          )
                          temp.loc[snp_indices, "pos"] = temp["pos"] + offset
  
                      # mark these SNPs as remapped
                      temp.loc[snp_indices, "remapped"] = True
  
              # update SNP positions for this chrom
              snps.loc[temp.index, "pos"] = temp["pos"]
  
          individual._set_snps(sort_snps(snps), int(target_assembly[-2:]))
  
          return chromosomes_remapped, chromosomes_not_remapped