def rule2columns(rule,lhs,rhs,history,grammar,automaton,w,run,begin=0,end=None):
    """
    Given a rule and a bunch of info about run, windows, grammar, etc, 
    builds a dict for a dataframe for the rule
    Dataframe includes how rule probability changed during training, 
    for each run of Windows
    for each training window

    Arguments
    rule     : the rule string (eg 'lhs->e rhs')
    lhs,rhs  : left and righthand sides of rule. If FSA, rhs = (emission, operation)
    history  : a list of dicts from an EM function including expected counts
    grammar  : 'copy' or 'no copy'
    automaton: 'fsa' or 'trans_probs'
    w        : training window number
    run      : run-th time we did EM
    begin    : usually 0, can be set later so you can fill in blanks in no-copy grammar, 
                which doens't need to iterate
    end      : usually len(history), but if filling in blanks in no-copy grammar, len(copy history)

    Returns
    list of rule dicts with expected counts, probabilities, run, iteration, 
    training window, grammar, the parts of the rule, and which automaton the rules belong to 

    """

    if end==None:
        end=len(history)

    rule_set=[]
    # go through the history and extract values at each step
    for i in range(begin,end):
        this_rule={' rule':rule}

        # the normal case: we go through the whole history
        if i < len(history):
            p=history[i][automaton][lhs][rhs] # rule prob

            # operations
            if i>0 and lhs in history[i]['scs']: # if we actually have this lhs recorded in this history step
                this_rule['expected TC']=sum(history[i]['tcs'][lhs][rhs].values()) # sum the expected values from the corpus
                this_rule['expected SC']=sum(history[i]['scs'][lhs].values()) # sum the expected values from the corpus

            # bigrams
            elif i>0 and lhs in history[i]['ucs']: # if we actually have this lhs recorded in this history step
                this_rule['expected BC']=sum(history[i]['bcs'][lhs][rhs].values()) # sum the expected values from the corpus
                this_rule['expected UC']=sum(history[i]['ucs'][lhs].values()) # sum the expected values from the corpus

        else:  # in the weird case, we're filling in the blank no-copy cells with the values from the final iteration
            p=history[-1][automaton][lhs][rhs] 
            if lhs in history[begin-1]['scs']:
                this_rule['expected TC']=sum(history[-1]['tcs'][lhs][rhs].values())
                this_rule['expected SC']=sum(history[-1]['scs'][lhs].values())

            elif lhs in history[i]['ucs']:
                this_rule['expected BC']=sum(history[-1]['bcs'][lhs][rhs].values())
                this_rule['expected UC']=sum(history[-1]['ucs'][lhs].values())


        this_rule["prob"]=p
        this_rule['run']=run+1
        this_rule['training window']=w+1
        this_rule['iteration']=i
        this_rule['grammar']=grammar
        this_rule['lhs']=lhs
        this_rule['rhs']=rhs
        if automaton == 'fsa':
            this_rule['automaton']='operations'
        elif automaton == 'trans_probs':
            this_rule['automaton']='transitions'
        rule_set.append(this_rule)

    return rule_set