docs: def read_attributes(self, attributes=None):
          '''
          Collect read attributes across reads in this PileupCollection into a
          pandas.DataFrame.
  
          Valid attributes are the following properties of a pysam.AlignedSegment
          instance. See:
              http://pysam.readthedocs.org/en/latest/api.html#pysam.AlignedSegment
          for the meaning of these attributes.
  
               * cigarstring
               * flag
               * inferred_length
               * is_duplicate
               * is_paired
               * is_proper_pair
               * is_qcfail
               * is_read1
               * is_read2
               * is_reverse
               * is_secondary
               * is_unmapped
               * mapping_quality
               * mate_is_reverse
               * mate_is_unmapped
               * next_reference_id
               * next_reference_start
               * query_alignment_end
               * query_alignment_length
               * query_alignment_qualities
               * query_alignment_sequence
               * query_alignment_start
               * query_length
               * query_name
               * reference_end
               * reference_id
               * reference_length
               * reference_start
               * template_length
  
          (Note: the above list is parsed into the _READ_ATTRIBUTE_NAMES class
          variable, so be careful when modifying it.)
  
          Additionally, for alignment "tags" (arbitrary key values associated
          with an alignment), a column of the form "TAG_{tag name}" is
          included.
  
          Finally, the column "pysam_alignment_record" gives the underlying
          `pysam.AlignedSegment` instances.
  
          Parameters
          ----------
          attributes (optional): list of strings
              List of columns to include. If unspecified, all columns are
              included in the result.
  
          Returns
          ----------
          pandas.DataFrame of read attributes.
  
          '''
          def include(attribute):
              return attributes is None or attribute in attributes
  
          reads = self.reads()
  
          possible_column_names = list(PileupCollection._READ_ATTRIBUTE_NAMES)
          result = OrderedDict(
              (name, [getattr(read, name) for read in reads])
              for name in PileupCollection._READ_ATTRIBUTE_NAMES
              if include(name))
  
          # Add tag columns.
          if reads:
              tag_dicts = [dict(x.get_tags()) for x in reads]
              tag_keys = set.union(
                  *[set(item.keys()) for item in tag_dicts])
              for tag_key in sorted(tag_keys):
                  column_name = "TAG_%s" % tag_key
                  possible_column_names.append(column_name)
                  if include(column_name):
                      result[column_name] = [d.get(tag_key) for d in tag_dicts]
  
          # Lastly, we include the underlying pysam alignment record.
          possible_column_names.append("pysam_alignment_record")
          if include("pysam_alignment_record"):
              result["pysam_alignment_record"] = reads
  
          # If particular attributes were requested, check that they're here.
          if attributes is not None:
              for attribute in attributes:
                  if attribute not in result:
                      raise ValueError(
                          "No such attribute: %s. Valid attributes are: %s"
                          % (attribute, " ".join(possible_column_names)))
              assert set(attributes) == set(result)
  
          return pandas.DataFrame(result)