<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def combine_sj_out( fns, external_db, total_jxn_cov_cutoff=20, define_sample_name=None, verbose=False, ):
 """Combine SJ.out.tab files from STAR by filtering based on coverage and comparing to an external annotation to discover novel junctions. Parameters fns : list of strings Filenames of SJ.out.tab files to combine. external_db : str Filename of splice junction information from external database. The file should have a header and contained the following columns 'gene', 'chrom', 'start', 'end', 'strand', 'chrom:start', 'chrom:end', 'donor', 'acceptor', 'intron'. total_jxn_cov_cutoff : int Discard junctions with less than this many reads summed over all samples. define_sample_name : function A function mapping the SJ.out.tab filenames to sample names. Returns ------- countDF : pandas.DataFrame Number of unique junction spanning reads for each junction that passed filtering criteria. annotDF : pandas.DataFrame Annotation information for junctions that passed filtering criteria. stats : list of strings Human readable statistics. """

    if verbose:
        import sys
    # I'll start by figuring out which junctions we will keep.
    counts = _total_jxn_counts(fns) 
    jxns = set(counts[counts >= total_jxn_cov_cutoff].index)
    if verbose:
        sys.stderr.write('Counting done\n')

    stats = []
    sj_outD = _make_sj_out_dict(fns, jxns=jxns,
                                define_sample_name=define_sample_name)
    stats.append('Number of junctions in SJ.out file per sample')
    for k in sj_outD.keys():
        stats.append('{0}\t{1:,}'.format(k, sj_outD[k].shape[0]))
    stats.append('')
    if verbose:
        sys.stderr.write('Dict done\n')

    sj_outP, annotDF = _make_sj_out_panel(sj_outD, total_jxn_cov_cutoff)
    stats.append('SJ.out panel size\t{0}'.format(sj_outP.shape))
    stats.append('')
    if verbose:
        sys.stderr.write('Panel done\n')

    extDF, ext_stats = read_external_annotation(external_db)
    stats += ext_stats
    stats.append('')
    if verbose:
        sys.stderr.write('DB read done\n')

    countsDF, annotDF, filter_stats = _filter_jxns_donor_acceptor(sj_outP, 
                                                                  annotDF, 
                                                                  extDF)
    if verbose:
        sys.stderr.write('Filter done\n')

    annotDF = _find_novel_donor_acceptor_dist(annotDF, extDF)
    if verbose:
        sys.stderr.write('Dist done\n')

    stats += filter_stats
    return countsDF, annotDF, stats