def chain(args):
    """
    %prog chain gffile > chained.gff

    Fill in parent features by chaining child features and return extent of the
    child coordinates.
    """
    valid_merge_op = ('sum', 'min', 'max', 'mean', 'collapse')

    p = OptionParser(chain.__doc__)
    p.add_option("--key", dest="attrib_key", default=None,
                help="Attribute to use as `key` for chaining operation")
    p.add_option("--chain_ftype", default="cDNA_match",
                help="GFF feature type to use for chaining operation")
    p.add_option("--parent_ftype", default=None,
                help="GFF feature type to use for the chained coordinates")
    p.add_option("--break", dest="break_chain", action="store_true",
                help="Break long chains which are non-contiguous")
    p.add_option("--transfer_attrib", dest="attrib_list",
                help="Attributes to transfer to parent feature; accepts comma" + \
                " separated list of attribute names [default: %default]")
    p.add_option("--transfer_score", dest="score_merge_op", choices=valid_merge_op,
                help="Transfer value stored in score field to parent feature." + \
                " Score is reported based on chosen operation")
    p.set_outfile()

    opts, args = p.parse_args(args)

    if len(args) != 1:
        sys.exit(not p.print_help())

    gffile, = args
    attrib_key = opts.attrib_key
    attrib_list = opts.attrib_list
    score_merge_op = opts.score_merge_op
    break_chain = opts.break_chain

    chain_ftype = opts.chain_ftype
    parent_ftype = opts.parent_ftype if opts.parent_ftype else chain_ftype

    gffdict = {}
    fw = must_open(opts.outfile, "w")
    gff = Gff(gffile)
    if break_chain:
        ctr, prev_gid = dict(), None
    for g in gff:
        if g.type != chain_ftype:
            print(g, file=fw)
            continue

        id = g.accn
        gid = id
        if attrib_key:
            assert attrib_key in g.attributes.keys(), \
                "Attribute `{0}` not present in GFF3".format(attrib_key)
            gid = g.get_attr(attrib_key)
        curr_gid = gid
        if break_chain:
            if prev_gid != curr_gid:
                if curr_gid not in ctr:
                    ctr[curr_gid] = 0
                else:
                    ctr[curr_gid] += 1
            gid = "{0}:{1}".format(gid, ctr[curr_gid])
        gkey = (g.seqid, gid)
        if gkey not in gffdict:
            gffdict[gkey] = { 'seqid': g.seqid,
                            'source': g.source,
                            'strand': g.strand,
                            'type': parent_ftype,
                            'coords': [],
                            'children': [],
                            'score': [],
                            'attrs': DefaultOrderedDict(set)
                          }
            gffdict[gkey]['attrs']['ID'].add(gid)

        if attrib_list:
            for a in attrib_list.split(","):
                if a in g.attributes:
                    [gffdict[gkey]['attrs'][a].add(x) for x in g.attributes[a]]
                    del g.attributes[a]

        if break_chain:
            _attrib = "Alias" if attrib_list and ("Name" not in attrib_list) else "Name"
            gffdict[gkey]['attrs'][_attrib].add(curr_gid)

        gffdict[gkey]['coords'].append((g.start, g.end))
        if score_merge_op:
            if is_number(g.score):
                gffdict[gkey]['score'].append(float(g.score))
                g.score = "."

        g.attributes["Parent"] = [gid]
        g.attributes["ID"] = ["{0}-{1}".\
                format(gid, len(gffdict[gkey]['children']) + 1)]
        g.type = valid_gff_parent_child[g.type]
        g.update_attributes()
        gffdict[gkey]['children'].append(g)
        if break_chain:
            prev_gid = curr_gid

    for gkey, v in sorted(gffdict.items()):
        gseqid, key = gkey
        seqid = v['seqid']
        source = v['source']
        type = v['type']
        strand = v['strand']
        start, stop = range_minmax(gffdict[gkey]['coords'])

        score = "."
        if score_merge_op:
            v['score'].sort()
            if score_merge_op == "sum":
                score = sum(v['score'])
            elif score_merge_op == "min":
                score = min(v['score'])
            elif score_merge_op == "max":
                score = max(v['score'])
            elif score_merge_op == "mean":
                score = sum(v['score'], 0.0)/len(v['score'])
            elif score_merge_op == "collapse":
                score = ",".join((str(x) for x in v['score']))

        g = GffLine("\t".join(str(x) for x in [seqid, source, type, start, stop, \
            score, strand, ".", None]))
        g.attributes = v['attrs']
        g.update_attributes()

        print(g, file=fw)

        for child in gffdict[gkey]['children']:
            print(child, file=fw)

    fw.close()
def chain ( args ) : valid_merge_op = ( 'sum' , 'min' , 'max' , 'mean' , 'collapse' ) p = OptionParser ( chain . __doc__ ) p . add_option ( "--key" , dest = "attrib_key" , default = None , help = "Attribute to use as `key` for chaining operation" ) p . add_option ( "--chain_ftype" , default = "cDNA_match" , help = "GFF feature type to use for chaining operation" ) p . add_option ( "--parent_ftype" , default = None , help = "GFF feature type to use for the chained coordinates" ) p . add_option ( "--break" , dest = "break_chain" , action = "store_true" , help = "Break long chains which are non-contiguous" ) p . add_option ( "--transfer_attrib" , dest = "attrib_list" , help = "Attributes to transfer to parent feature; accepts comma" + " separated list of attribute names [default: %default]" ) p . add_option ( "--transfer_score" , dest = "score_merge_op" , choices = valid_merge_op , help = "Transfer value stored in score field to parent feature." + " Score is reported based on chosen operation" ) p . set_outfile ( ) opts , args = p . parse_args ( args ) if len ( args ) != 1 : sys . exit ( not p . print_help ( ) ) gffile , = args attrib_key = opts . attrib_key attrib_list = opts . attrib_list score_merge_op = opts . score_merge_op break_chain = opts . break_chain chain_ftype = opts . chain_ftype parent_ftype = opts . parent_ftype if opts . parent_ftype else chain_ftype gffdict = { } fw = must_open ( opts . outfile , "w" ) gff = Gff ( gffile ) if break_chain : ctr , prev_gid = dict ( ) , None for g in gff : if g . type != chain_ftype : print ( g , file = fw ) continue id = g . accn gid = id if attrib_key : assert attrib_key in g . attributes . keys ( ) , "Attribute `{0}` not present in GFF3" . format ( attrib_key ) gid = g . get_attr ( attrib_key ) curr_gid = gid if break_chain : if prev_gid != curr_gid : if curr_gid not in ctr : ctr [ curr_gid ] = 0 else : ctr [ curr_gid ] += 1 gid = "{0}:{1}" . format ( gid , ctr [ curr_gid ] ) gkey = ( g . seqid , gid ) if gkey not in gffdict : gffdict [ gkey ] = { 'seqid' : g . seqid , 'source' : g . source , 'strand' : g . strand , 'type' : parent_ftype , 'coords' : [ ] , 'children' : [ ] , 'score' : [ ] , 'attrs' : DefaultOrderedDict ( set ) } gffdict [ gkey ] [ 'attrs' ] [ 'ID' ] . add ( gid ) if attrib_list : for a in attrib_list . split ( "," ) : if a in g . attributes : [ gffdict [ gkey ] [ 'attrs' ] [ a ] . add ( x ) for x in g . attributes [ a ] ] del g . attributes [ a ] if break_chain : _attrib = "Alias" if attrib_list and ( "Name" not in attrib_list ) else "Name" gffdict [ gkey ] [ 'attrs' ] [ _attrib ] . add ( curr_gid ) gffdict [ gkey ] [ 'coords' ] . append ( ( g . start , g . end ) ) if score_merge_op : if is_number ( g . score ) : gffdict [ gkey ] [ 'score' ] . append ( float ( g . score ) ) g . score = "." g . attributes [ "Parent" ] = [ gid ] g . attributes [ "ID" ] = [ "{0}-{1}" . format ( gid , len ( gffdict [ gkey ] [ 'children' ] ) + 1 ) ] g . type = valid_gff_parent_child [ g . type ] g . update_attributes ( ) gffdict [ gkey ] [ 'children' ] . append ( g ) if break_chain : prev_gid = curr_gid for gkey , v in sorted ( gffdict . items ( ) ) : gseqid , key = gkey seqid = v [ 'seqid' ] source = v [ 'source' ] type = v [ 'type' ] strand = v [ 'strand' ] start , stop = range_minmax ( gffdict [ gkey ] [ 'coords' ] ) score = "." if score_merge_op : v [ 'score' ] . sort ( ) if score_merge_op == "sum" : score = sum ( v [ 'score' ] ) elif score_merge_op == "min" : score = min ( v [ 'score' ] ) elif score_merge_op == "max" : score = max ( v [ 'score' ] ) elif score_merge_op == "mean" : score = sum ( v [ 'score' ] , 0.0 ) / len ( v [ 'score' ] ) elif score_merge_op == "collapse" : score = "," . join ( ( str ( x ) for x in v [ 'score' ] ) ) g = GffLine ( "\t" . join ( str ( x ) for x in [ seqid , source , type , start , stop , score , strand , "." , None ] ) ) g . attributes = v [ 'attrs' ] g . update_attributes ( ) print ( g , file = fw ) for child in gffdict [ gkey ] [ 'children' ] : print ( child , file = fw ) fw . close ( )
%prog chain gffile > chained.gff

    Fill in parent features by chaining child features and return extent of the
    child coordinates.