def line_plot(df, x='year', y='value', ax=None, legend=None, title=True,
              color=None, marker=None, linestyle=None, cmap=None,
              fill_between=None, final_ranges=None,
              rm_legend_label=[], **kwargs):
    """Plot data as lines with or without markers.

    Parameters
    ----------
    df : pd.DataFrame
        Data to plot as a long-form data frame
    x : string, optional
        The column to use for x-axis values
        default: year
    y : string, optional
        The column to use for y-axis values
        default: value
    ax : matplotlib.Axes, optional
    legend : bool or dictionary, optional
        Add a legend. If a dictionary is provided, it will be used as keyword
        arguments in creating the legend.
        default: None (displays legend only if less than 13 entries)
    title : bool or string, optional
        Display a default or custom title.
    color : string, optional
        A valid matplotlib color or column name. If a column name, common
        values will be provided the same color.
        default: None
    marker : string, optional
        A valid matplotlib marker or column name. If a column name, common
        values will be provided the same marker.
        default: None
    linestyle : string, optional
        A valid matplotlib linestyle or column name. If a column name, common
        values will be provided the same linestyle.
        default: None
    cmap : string, optional
        A colormap to use.
        default: None
    fill_between : boolean or dict, optional
        Fill lines between minima/maxima of the 'color' argument. This can only
        be used if also providing a 'color' argument. If this is True, then
        default arguments will be provided to `ax.fill_between()`. If this is a
        dictionary, those arguments will be provided instead of defaults.
        default: None
    final_ranges : boolean or dict, optional
        Add vertical line between minima/maxima of the 'color' argument in the
        last period plotted.  This can only be used if also providing a 'color'
        argument. If this is True, then default arguments will be provided to
        `ax.axvline()`. If this is a dictionary, those arguments will be
        provided instead of defaults.
        default: None
    rm_legend_label : string, list, optional
        Remove the color, marker, or linestyle label in the legend.
        default: []
    kwargs : Additional arguments to pass to the pd.DataFrame.plot() function
    """
    if ax is None:
        fig, ax = plt.subplots()

    # assign styling properties
    props = assign_style_props(df, color=color, marker=marker,
                               linestyle=linestyle, cmap=cmap)

    if fill_between and 'color' not in props:
        raise ValueError('Must use `color` kwarg if using `fill_between`')
    if final_ranges and 'color' not in props:
        raise ValueError('Must use `color` kwarg if using `final_ranges`')

    # reshape data for use in line_plot
    df = reshape_line_plot(df, x, y)  # long form to one column per line

    # determine index of column name in reshaped dataframe
    prop_idx = {}
    for kind, var in [('color', color), ('marker', marker),
                      ('linestyle', linestyle)]:
        if var is not None and var in df.columns.names:
            prop_idx[kind] = df.columns.names.index(var)

    # plot data, keeping track of which legend labels to apply
    no_label = [rm_legend_label] if isstr(rm_legend_label) else rm_legend_label

    for col, data in df.iteritems():
        pargs = {}
        labels = []
        # build plotting args and line legend labels
        for key, kind, var in [('c', 'color', color),
                               ('marker', 'marker', marker),
                               ('linestyle', 'linestyle', linestyle)]:
            if kind in props:
                label = col[prop_idx[kind]]
                pargs[key] = props[kind][label]
                if kind not in no_label:
                    labels.append(repr(label).lstrip("u'").strip("'"))
            else:
                pargs[key] = var
        kwargs.update(pargs)
        data = data.dropna()
        data.plot(ax=ax, **kwargs)
        if labels:
            ax.lines[-1].set_label(' '.join(labels))

    if fill_between:
        _kwargs = {'alpha': 0.25} if fill_between in [True, None] \
            else fill_between
        data = df.T
        columns = data.columns
        # get outer boundary mins and maxes
        allmins = data.groupby(color).min()
        intermins = (
            data.dropna(axis=1).groupby(color).min()  # nonan data
            .reindex(columns=columns)  # refill with nans
            .T.interpolate(method='index').T  # interpolate
        )
        mins = pd.concat([allmins, intermins]).min(level=0)
        allmaxs = data.groupby(color).max()
        intermaxs = (
            data.dropna(axis=1).groupby(color).max()  # nonan data
            .reindex(columns=columns)  # refill with nans
            .T.interpolate(method='index').T  # interpolate
        )
        maxs = pd.concat([allmaxs, intermaxs]).max(level=0)
        # do the fill
        for idx in mins.index:
            ymin = mins.loc[idx]
            ymax = maxs.loc[idx]
            ax.fill_between(ymin.index, ymin, ymax,
                            facecolor=props['color'][idx], **_kwargs)

    # add bars to the end of the plot showing range
    if final_ranges:
        # have to explicitly draw it to get the tick labels (these change once
        # you add the vlines)
        plt.gcf().canvas.draw()
        _kwargs = {'linewidth': 2} if final_ranges in [True, None] \
            else final_ranges
        first = df.index[0]
        final = df.index[-1]
        mins = df.T.groupby(color).min()[final]
        maxs = df.T.groupby(color).max()[final]
        ymin, ymax = ax.get_ylim()
        ydiff = ymax - ymin
        xmin, xmax = ax.get_xlim()
        xdiff = xmax - xmin
        xticks = ax.get_xticks()
        xlabels = ax.get_xticklabels()
        # 1.5% increase seems to be ok per extra line
        extra_space = 0.015
        for i, idx in enumerate(mins.index):
            xpos = final + xdiff * extra_space * (i + 1)
            _ymin = (mins[idx] - ymin) / ydiff
            _ymax = (maxs[idx] - ymin) / ydiff
            ax.axvline(xpos, ymin=_ymin, ymax=_ymax,
                       color=props['color'][idx], **_kwargs)
        # for equal spacing between xmin and first datapoint and xmax and last
        # line
        ax.set_xlim(xmin, xpos + first - xmin)
        ax.set_xticks(xticks)
        ax.set_xticklabels(xlabels)

    # build unique legend handles and labels
    handles, labels = ax.get_legend_handles_labels()
    handles, labels = np.array(handles), np.array(labels)
    _, idx = np.unique(labels, return_index=True)
    handles, labels = handles[idx], labels[idx]
    if legend is not False:
        _add_legend(ax, handles, labels, legend)

    # add default labels if possible
    ax.set_xlabel(x.title())
    units = df.columns.get_level_values('unit').unique()
    units_for_ylabel = len(units) == 1 and x == 'year' and y == 'value'
    ylabel = units[0] if units_for_ylabel else y.title()
    ax.set_ylabel(ylabel)

    # build a default title if possible
    if title:
        default_title = []
        for var in ['model', 'scenario', 'region', 'variable']:
            if var in df.columns.names:
                values = df.columns.get_level_values(var).unique()
                if len(values) == 1:
                    default_title.append('{}: {}'.format(var, values[0]))
        title = ' '.join(default_title) if title is True else title
        ax.set_title(title)

    return ax, handles, labels
Plot data as lines with or without markers.

    Parameters
    ----------
    df : pd.DataFrame
        Data to plot as a long-form data frame
    x : string, optional
        The column to use for x-axis values
        default: year
    y : string, optional
        The column to use for y-axis values
        default: value
    ax : matplotlib.Axes, optional
    legend : bool or dictionary, optional
        Add a legend. If a dictionary is provided, it will be used as keyword
        arguments in creating the legend.
        default: None (displays legend only if less than 13 entries)
    title : bool or string, optional
        Display a default or custom title.
    color : string, optional
        A valid matplotlib color or column name. If a column name, common
        values will be provided the same color.
        default: None
    marker : string, optional
        A valid matplotlib marker or column name. If a column name, common
        values will be provided the same marker.
        default: None
    linestyle : string, optional
        A valid matplotlib linestyle or column name. If a column name, common
        values will be provided the same linestyle.
        default: None
    cmap : string, optional
        A colormap to use.
        default: None
    fill_between : boolean or dict, optional
        Fill lines between minima/maxima of the 'color' argument. This can only
        be used if also providing a 'color' argument. If this is True, then
        default arguments will be provided to `ax.fill_between()`. If this is a
        dictionary, those arguments will be provided instead of defaults.
        default: None
    final_ranges : boolean or dict, optional
        Add vertical line between minima/maxima of the 'color' argument in the
        last period plotted.  This can only be used if also providing a 'color'
        argument. If this is True, then default arguments will be provided to
        `ax.axvline()`. If this is a dictionary, those arguments will be
        provided instead of defaults.
        default: None
    rm_legend_label : string, list, optional
        Remove the color, marker, or linestyle label in the legend.
        default: []
    kwargs : Additional arguments to pass to the pd.DataFrame.plot() function