def read_tracefile(target, data, m,
                   trace_dir=None,
                   varnames='ppc', outvarnames=None, years=None, portionsize=1000, cwd=None, tune_length=5000, print_frequency=5, verbose=True):
    """Convert set of hard drive MCMC tracefiles into a single pandas dataframe.
    
    This function reads bit-by-bit into memory the large tracefiles
    generated by bs.fit(), capturing only desired variables.
    Typically, the tracefiles are too large to fit in memory in full,
    so desired variables should be a small subset of the full variable list.

    Positional arguments:
        target - (str) title of target society in df's columns
        data - (pandas dataframe) dataset originally analyzed
        m - (int) number of factors

    Optional keyword arguments:
        trace_dir - (str) source directory of the tracefiles; bs.fit()'s default used by default
        varnames - (str or list) variable selection method ('ppc' or 'factors') or list of varnames
        outvarnames - (list) variable names to overwrite the original varnames
        years - (numpy array) subset of data's index; full index by default
        portionsize - (int) size of the bit of trace read into memory at once; 1000 by default
        cwd - (str) current working directory path; inferred by default
        tune_length - (int) number of additional tune-in steps per chain; bs.fit()'s default by default
        print_frequency - (int) number of portions read without printing updates to user; 5 by default
        verbose - (bool) print messages to user; True by default

    Output: pandas dataframe
    
    """

    # Parameterize country selection & time period
    all_countries = data.columns.to_list()
    years = years if years else data.index.values
    n = len(years)
    order = all_countries.index(target)

    # Parameterize file paths and variable selection
    cwd = cwd if cwd else Path(__file__).parent.resolve()
    if not trace_dir:
            trace_dir = os.path.join(cwd, 'BSCOutput', 'L' + str(m) + target, 'trace')
    tracefiles = os.listdir(trace_dir)
    chains = len(tracefiles)
    tracefiles = [os.path.join(trace_dir, tracefile) for tracefile in tracefiles]

    # Select appropriate vairables
    if type(varnames) == list:
        if not all(type(statname) == str for statname in statnames):
            msg = "'varnames' must be str or list of str's"
            raise AttributeError(msg)
    elif varnames == 'ppc':
        varnames = [('synthetic__' + str(i) + '_' + str(order)) for i in range(n)] + ['sigma']
    elif varnames == 'factors':
        varnames = [('L_long__' + str(number)) for number in range(n*m)]
    elif type(varnames) != str:
        msg = "'varnames' must be list or str"
        raise AttributeError(msg)
    else:
        msg = "Variable selection method '{}' not recognised".format(varnames)
        raise NotImplementedError(msg)

    # Initialize pandas access to the trace files on hard drive
    readers = []
    for tracefile in tracefiles:
        reader = pd.read_csv(tracefile, chunksize=portionsize)
        readers.append(reader)

    # Initialize storage for carving out the relevant data sections
    dflists = [[] for tracefile in tracefiles]

    # Print target country to user
    if verbose:
        print('Reading tracefile of {}'.format(target))

    # Read data from hard drive in chunks and carve out relevant sections
    for i, portions in enumerate(zip(*readers)):
        # Skip the tune-in section of the trace
        if (i + 1) * portionsize <= tune_length:
            continue
        # Occasionally report progress to user
        if verbose and (i % print_frequency == 0):
            msg = '\tReached chunk #{}: draws {}-{}'.format(i, i*portionsize, (i+1)*portionsize)
            print(msg)
        # Record the relevant portion of each trace in memory
        for dflist, portion in zip(dflists, portions):
            dflist.append(portion[varnames])

    # Concatenate portions by trace
    data_by_trace = []
    for dflist in dflists:
        data_by_trace.append(pd.concat(dflist))
    # Aggregate data from all traces
    df = pd.concat(data_by_trace, ignore_index=True)
    if outvarnames:
        df.columns = outvarnames

    return df

