helper function to plot ensemble histograms

    Parameters
    ----------
    ensemble : varies
        the ensemble argument can be a pandas.DataFrame or derived type or a str, which
        is treated as a filename.  Optionally, ensemble can be a list of these types or
        a dict, in which case, the keys are treated as facecolor str (e.g., 'b', 'y', etc).
    facecolor : str
        the histogram facecolor.  Only applies if ensemble is a single thing
    plot_cols : enumerable
        a collection of columns (in form of a list of parameters, or a dict with keys for
        parsing plot axes and values of parameters) from the ensemble(s) to plot.  If None,
        (the union of) all cols are plotted. Default is None
    filename : str
        the name of the pdf to create.  If None, return figs without saving.  Default is None.
    func_dict : dict
        a dictionary of unary functions (e.g., np.log10_ to apply to columns.  Key is
        column name.  Default is None
    sync_bins : bool
        flag to use the same bin edges for all ensembles. Only applies if more than
        one ensemble is being plotted.  Default is True
    deter_vals : dict
        dict of deterministic values to plot as a vertical line. key is ensemble columnn name
    std_window : float
        the number of standard deviations around the mean to mark as vertical lines.  If None,
        nothing happens.  Default is None
    deter_range : bool
        flag to set xlims to deterministic value +/- std window.  If True, std_window must not be None.
        Default is False

def ensemble_helper(ensemble,bins=10,facecolor='0.5',plot_cols=None,
                    filename=None,func_dict = None,
                    sync_bins=True,deter_vals=None,std_window=None,
                    deter_range=False,**kwargs):
    """helper function to plot ensemble histograms

    Parameters
    ----------
    ensemble : varies
        the ensemble argument can be a pandas.DataFrame or derived type or a str, which
        is treated as a filename.  Optionally, ensemble can be a list of these types or
        a dict, in which case, the keys are treated as facecolor str (e.g., 'b', 'y', etc).
    facecolor : str
        the histogram facecolor.  Only applies if ensemble is a single thing
    plot_cols : enumerable
        a collection of columns (in form of a list of parameters, or a dict with keys for
        parsing plot axes and values of parameters) from the ensemble(s) to plot.  If None,
        (the union of) all cols are plotted. Default is None
    filename : str
        the name of the pdf to create.  If None, return figs without saving.  Default is None.
    func_dict : dict
        a dictionary of unary functions (e.g., np.log10_ to apply to columns.  Key is
        column name.  Default is None
    sync_bins : bool
        flag to use the same bin edges for all ensembles. Only applies if more than
        one ensemble is being plotted.  Default is True
    deter_vals : dict
        dict of deterministic values to plot as a vertical line. key is ensemble columnn name
    std_window : float
        the number of standard deviations around the mean to mark as vertical lines.  If None,
        nothing happens.  Default is None
    deter_range : bool
        flag to set xlims to deterministic value +/- std window.  If True, std_window must not be None.
        Default is False

    """
    logger = pyemu.Logger("ensemble_helper.log")
    logger.log("pyemu.plot_utils.ensemble_helper()")
    ensembles = _process_ensemble_arg(ensemble,facecolor,logger)

    #apply any functions
    if func_dict is not None:
        logger.log("applying functions")
        for col,func in func_dict.items():
            for fc,en in ensembles.items():
                if col in en.columns:
                    en.loc[:,col] = en.loc[:,col].apply(func)
        logger.log("applying functions")


    #get a list of all cols (union)
    all_cols = set()
    for fc, en in ensembles.items():
        cols = set(en.columns)
        all_cols.update(cols)
    if plot_cols is None:
        plot_cols = {i: [v] for i, v in (zip(all_cols, all_cols))}
    else:
        if isinstance(plot_cols,list):
            splot_cols = set(plot_cols)
            plot_cols = {i: [v] for i, v in (zip(plot_cols, plot_cols))}
        elif isinstance(plot_cols,dict):
            splot_cols = []
            for label,pcols in plot_cols.items():
                splot_cols.extend(list(pcols))
            splot_cols = set(splot_cols)
        else:
            logger.lraise("unrecognized plot_cols type: {0}, should be list or dict".
                          format(type(plot_cols)))

        missing = splot_cols - all_cols
        if len(missing) > 0:
            logger.lraise("the following plot_cols are missing: {0}".
                          format(','.join(missing)))

    logger.statement("plotting {0} histograms".format(len(plot_cols)))

    fig = plt.figure(figsize=figsize)
    if "fig_title" in kwargs:
        plt.figtext(0.5,0.5,kwargs["fig_title"])
    else:
        plt.figtext(0.5, 0.5, "pyemu.plot_utils.ensemble_helper()\n at {0}"
                    .format(str(datetime.now())), ha="center")
    #plot_cols = list(plot_cols)
    #plot_cols.sort()
    labels = list(plot_cols.keys())
    labels.sort()
    logger.statement("saving pdf to {0}".format(filename))
    figs = []

    ax_count = 0

    #for label,plot_col in plot_cols.items():
    for label in labels:
        plot_col = plot_cols[label]
        logger.log("plotting reals for {0}".format(label))
        if ax_count % (nr * nc) == 0:
            plt.tight_layout()
            #pdf.savefig()
            #plt.close(fig)
            figs.append(fig)
            fig = plt.figure(figsize=figsize)
            axes = get_page_axes()
            [ax.set_yticks([]) for ax in axes]
            ax_count = 0

        ax = axes[ax_count]
        ax.set_title("{0}) {1}".format(abet[ax_count],label),loc="left")
        if sync_bins:
            mx,mn = -1.0e+30,1.0e+30
            for fc,en in ensembles.items():
                # for pc in plot_col:
                #     if pc in en.columns:
                #         emx,emn = en.loc[:,pc].max(),en.loc[:,pc].min()
                #         mx = max(mx,emx)
                #         mn = min(mn,emn)
                emn = en.loc[:,plot_col].values.min()
                emx = en.loc[:, plot_col].values.max()
                mx = max(mx, emx)
                mn = min(mn,emn)
            plot_bins = np.linspace(mn,mx,num=bins)
            logger.statement("{0} min:{1:5G}, max:{2:5G}".format(label,mn,mx))
        else:
            plot_bins=bins
        for fc,en in ensembles.items():
            #for pc in plot_col:
            #    if pc in en.columns:
            #        try:
            #            en.loc[:,pc].hist(bins=plot_bins,facecolor=fc,
            #                                    edgecolor="none",alpha=0.5,
            #                                    normed=True,ax=ax)
            #        except Exception as e:
            #            logger.warn("error plotting histogram for {0}:{1}".
            #                        format(pc,str(e)))
            vals = en.loc[:,plot_col].values.flatten()
            #print(plot_bins)
            #print(vals)
            ax.hist(vals,bins=plot_bins,edgecolor="none",alpha=0.5,normed=True,facecolor=fc)
            v = None
            if deter_vals is not None:
                for pc in plot_col:
                    if pc in deter_vals:
                        ylim = ax.get_ylim()
                        v = deter_vals[pc]
                        ax.plot([v,v],ylim,"k--",lw=1.5)
                        ax.set_ylim(ylim)


            if std_window is not None:
                try:
                    ylim = ax.get_ylim()
                    mn, st = en.loc[:,pc].mean(), en.loc[:,pc].std() * (std_window / 2.0)

                    ax.plot([mn - st, mn - st], ylim, color=fc, lw=1.5,ls='--')
                    ax.plot([mn + st, mn + st], ylim, color=fc, lw=1.5,ls='--')
                    ax.set_ylim(ylim)
                    if deter_range and v is not None:
                        xmn = v - st
                        xmx = v + st
                        ax.set_xlim(xmn,xmx)
                except:
                    logger.warn("error plotting std window for {0}".
                                format(pc))
        ax.grid()

        ax_count += 1

    for a in range(ax_count, nr * nc):
        axes[a].set_axis_off()
        axes[a].set_yticks([])
        axes[a].set_xticks([])

    plt.tight_layout()
    #pdf.savefig()
    #plt.close(fig)
    figs.append(fig)
    if filename is not None:
        plt.tight_layout()
        with PdfPages(filename) as pdf:
            for fig in figs:
                pdf.savefig(fig)
                plt.close(fig)
    logger.log("pyemu.plot_utils.ensemble_helper()")