Find the user specified categories in the map and create a dictionary to contain the
    relevant data for each type within the categories. Multiple categories will have their
    types combined such that each possible combination will have its own entry in the
    dictionary.

    :type imap: dict
    :param imap: The input mapping file data keyed by SampleID
    :type header: list
    :param header: The header line from the input mapping file. This will be searched for
                   the user-specified categories
    :type categories: list
    :param categories: The list of user-specified category column name from mapping file
    :rtype: dict
    :return: A sorted dictionary keyed on the combinations of all the types found within
             the user-specified categories. Each entry will contain an empty DataCategory
             namedtuple. If no categories are specified, a single entry with the key
             'default' will be returned

def gather_categories(imap, header, categories=None):
    """
    Find the user specified categories in the map and create a dictionary to contain the
    relevant data for each type within the categories. Multiple categories will have their
    types combined such that each possible combination will have its own entry in the
    dictionary.

    :type imap: dict
    :param imap: The input mapping file data keyed by SampleID
    :type header: list
    :param header: The header line from the input mapping file. This will be searched for
                   the user-specified categories
    :type categories: list
    :param categories: The list of user-specified category column name from mapping file
    :rtype: dict
    :return: A sorted dictionary keyed on the combinations of all the types found within
             the user-specified categories. Each entry will contain an empty DataCategory
             namedtuple. If no categories are specified, a single entry with the key
             'default' will be returned
    """
    # If no categories provided, return all SampleIDs
    if categories is None:
        return {"default": DataCategory(set(imap.keys()), {})}

    cat_ids = [header.index(cat)
               for cat in categories if cat in header and "=" not in cat]

    table = OrderedDict()
    conditions = defaultdict(set)
    for i, cat in enumerate(categories):
        if "=" in cat and cat.split("=")[0] in header:
            cat_name = header[header.index(cat.split("=")[0])]
            conditions[cat_name].add(cat.split("=")[1])

    # If invalid categories or conditions identified, return all SampleIDs
    if not cat_ids and not conditions:
        return {"default": DataCategory(set(imap.keys()), {})}

    #If only category column given, return column-wise SampleIDs
    if cat_ids and not conditions:
        for sid, row in imap.items():
            cat_name = "_".join([row[cid] for cid in cat_ids])
            if cat_name not in table:
                table[cat_name] = DataCategory(set(), {})
            table[cat_name].sids.add(sid)
        return table

    # Collect all condition names
    cond_ids = set()
    for k in conditions:
        try:
            cond_ids.add(header.index(k))
        except ValueError:
            continue
    idx_to_test = set(cat_ids).union(cond_ids)

    # If column name and condition given, return overlapping SampleIDs of column and
    # condition combinations
    for sid, row in imap.items():
        if all([row[header.index(c)] in conditions[c] for c in conditions]):
            key = "_".join([row[idx] for idx in idx_to_test])
            try:
                assert key in table.keys()
            except AssertionError:
                table[key] = DataCategory(set(), {})
            table[key].sids.add(sid)
    try:
        assert len(table) > 0
    except AssertionError:
        return {"default": DataCategory(set(imap.keys()), {})}
    else:
        return table