docs: def get_atlas_per_gene_mutation_df(self, gene_id):
          """Create a single data frame which summarizes a gene and its mutations.
  
          Args:
              gene_id (str): Gene ID in the base model
  
          Returns:
              DataFrame: Pandas DataFrame of the results
  
          """
          # TODO: also count: number of unique mutations (have to consider position, amino acid change)
          # TODO: keep track of strain with most mutations, least mutations
          # TODO: keep track of strains that conserve the length of the protein, others that extend or truncate it
          # need statistical test for that too (how long is "extended"/"truncated"?)
          # TODO: number of strains with at least 1 mutations
          # TODO: number of strains with <5% mutated, 5-10%, etc
  
          g = self.reference_gempro.genes.get_by_id(gene_id)
  
          single, fingerprint = g.protein.sequence_mutation_summary(alignment_type='seqalign')
  
          structure_type_suffix = 'NA'
          appender = []
  
          for k, strains in single.items():
              # Mutations in the strain
              to_append = {}
              orig_res = k[0]
              resnum = int(k[1])
              mutated_res = k[2]
              num_strains_mutated = len(strains)
              strain_ids = [str(x.split(g.id + '_')[1]) for x in strains]
              to_append['ref_residue'] = orig_res
              to_append['ref_resnum'] = resnum
              to_append['strain_residue'] = mutated_res
              to_append['num_strains_mutated'] = num_strains_mutated
              to_append['strains_mutated'] = ';'.join(strain_ids)
              to_append['at_disulfide_bridge'] = False
  
              # Residue properties
              origres_props = ssbio.protein.sequence.properties.residues.residue_biochemical_definition(orig_res)
              mutres_props = ssbio.protein.sequence.properties.residues.residue_biochemical_definition(mutated_res)
              to_append['ref_residue_prop'] = origres_props
              to_append['strain_residue_prop'] = mutres_props
  
              # Grantham score - score a mutation based on biochemical properties
              grantham_s, grantham_txt = ssbio.protein.sequence.properties.residues.grantham_score(orig_res, mutated_res)
              to_append['grantham_score'] = grantham_s
              to_append['grantham_annotation'] = grantham_txt
  
              # Get all per residue annotations - predicted from sequence and calculated from structure
              to_append.update(g.protein.get_residue_annotations(seq_resnum=resnum, use_representatives=True))
  
              # Check structure type
              if g.protein.representative_structure:
                  if g.protein.representative_structure.is_experimental:
                      to_append['structure_type'] = 'EXP'
                  else:
                      to_append['structure_type'] = 'HOM'
  
                  # At disulfide bond?
                  repchain = g.protein.representative_chain
                  repchain_annotations = g.protein.representative_structure.chains.get_by_id(repchain).seq_record.annotations
                  if 'SSBOND-biopython' in repchain_annotations:
                      structure_resnum = g.protein.map_seqprop_resnums_to_structprop_resnums(resnums=resnum,
                                                                                             use_representatives=True)
                      if resnum in structure_resnum:
                          ssbonds = repchain_annotations['SSBOND-biopython']
                          ssbonds_res = []
                          for x in ssbonds:
                              ssbonds_res.append(x[0])
                              ssbonds_res.append(x[1])
  
                          if structure_resnum in ssbonds_res:
                              to_append['at_disulfide_bridge'] = True
  
              appender.append(to_append)
  
          if not appender:
              return pd.DataFrame()
  
          cols = ['ref_residue', 'ref_resnum', 'strain_residue', 'num_strains_mutated', 'strains_mutated',
                  'ref_residue_prop', 'strain_residue_prop', 'grantham_score', 'grantham_annotation',
                  'at_disulfide_bridge',
                  'seq_SS-sspro', 'seq_SS-sspro8', 'seq_RSA-accpro', 'seq_RSA-accpro20', 'seq_TM-tmhmm',
                  'struct_SS-dssp', 'struct_RSA-dssp', 'struct_ASA-dssp',
                  'struct_CA_DEPTH-msms', 'struct_RES_DEPTH-msms',
                  'struct_PHI-dssp', 'struct_PSI-dssp',
                  'struct_resnum', 'struct_residue'
                  'strains_mutated']
  
          df_gene_summary = pd.DataFrame.from_records(appender, columns=cols)
  
          # Drop columns that don't have anything in them
          df_gene_summary.dropna(axis=1, how='all', inplace=True)
  
          df_gene_summary.sort_values(by='ref_resnum', inplace=True)
          df_gene_summary = df_gene_summary.set_index('ref_resnum')
          return df_gene_summary