Predict on peturbations of a feature vector
    estimator: a fitted sklearn estimator
    index: the index of the example to perturb
    bins: a dictionary of column:bins arrays
    columns: list of columns if bins doesn't cover all columns
    TODO make this work when index is multiple rows

def perturb(estimator, X, bins, columns=None):
    """
    Predict on peturbations of a feature vector
    estimator: a fitted sklearn estimator
    index: the index of the example to perturb
    bins: a dictionary of column:bins arrays
    columns: list of columns if bins doesn't cover all columns
    TODO make this work when index is multiple rows
    """
    if columns is None:
        if len(bins) != X.shape[1]:
            raise ValueError("Must specify columns when not perturbing all columns")
        else:
            columns = X.columns

    n = np.concatenate(([0], np.cumsum([len(b) for b in bins])))

    X_test = np.empty((n[-1]*X.shape[0], X.shape[1]))
    r = pd.DataFrame(columns=['value', 'feature', 'index'], index=np.arange(n[-1]*X.shape[0]))
    for j, index in enumerate(X.index):
        X_test[j*n[-1]:(j+1)*n[-1], :] = X.values[j, :]
        for i, c in enumerate(columns):
            s = slice(j*n[-1] + n[i], j*n[-1] + n[i+1])
            r['value'].values[s] = bins[i]
            r['feature'].values[s] = c
            r['index'].values[s] = [index]*(n[i+1]-n[i])
            X_test[s, (X.columns == c).argmax()] = bins[i]

    y = estimator.predict_proba(X_test)[:, 1]
    r['y'] = y
    return r