Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I have several curves/datasets (obtained from Monte Carlo simulations) with x-dependent y errors I would like to plot with the errors somehow indicated. Since each curve consists of quite a large number of data points with rather small errors, using ordinary errorbars doesn't seem to be the most informative/aestethic solution. Instead, I think it would be nicer to indicate the error by (local) line thickness (or thickness in the y-direction). It could e.g. be done by plotting y(x)+dy(x) and y(x)-dy(x) and fill between the two curves. But how does one do that (in a reasonably easy way - remember: I have several curves!) in Pgfplots?

My question is maybe somewhat similar to this one, but I don't know how to do the table manipulations (within Pgfplots) needed in my case.

Here's a simplified example of how my datafiles look:

x y dy
0 2 0.1
1 4 0.5
2 3 0.2
3 3 0.3
share|improve this question

3 Answers 3

up vote 31 down vote accepted
+50

You can use stacked plots to draw the uncertainty bands before plotting the actual data line. First you'd say \addplot table [y expr=\thisrow{<data col>}-\thisrow{<error col}] {<datatable>}; to determine the lower bound, and then \addplot [fill=<colour>] table [y expr=2*\thisrow{<error col}] {<datatable>} \closedcycle; to fill the area between the lower and upper bound.

You can wrap these two \addplot commands in a macro to make generating the plots easier. If you define

\newcommand{\errorband}[5][]{ % x column, y column, error column, optional argument for setting style of the area plot
\pgfplotstableread[col sep=comma, skip first n=2]{#2}\datatable
    % Lower bound (invisible plot)
    \addplot [draw=none, stack plots=y, forget plot] table [
        x={#3},
        y expr=\thisrow{#4}-\thisrow{#5}
    ] {\datatable};

    % Stack twice the error, draw as area plot
    \addplot [draw=none, fill=gray!40, stack plots=y, area legend, #1] table [
        x={#3},
        y expr=2*\thisrow{#5}
    ] {\datatable} \closedcycle;

    % Reset stack using invisible plot
    \addplot [forget plot, stack plots=y,draw=none] table [x={#3}, y expr=-(\thisrow{#4}+\thisrow{#5})] {\datatable};
}

you can generate a plot with an error band using

\errorband[<plot options>]{<data file>}{<x column>}{<y column>}{<error column>}

Here's an example plotting the average northern hemisphere sea ice extent according to the NSIDC:

\documentclass{article}
\usepackage{pgfplots, pgfplotstable}


\begin{document}

\newcommand{\errorband}[5][]{ % x column, y column, error column, optional argument for setting style of the area plot
\pgfplotstableread[col sep=comma, skip first n=2]{#2}\datatable
    % Lower bound (invisible plot)
    \addplot [draw=none, stack plots=y, forget plot] table [
        x={#3},
        y expr=\thisrow{#4}-2*\thisrow{#5}
    ] {\datatable};

    % Stack twice the error, draw as area plot
    \addplot [draw=none, fill=gray!40, stack plots=y, area legend, #1] table [
        x={#3},
        y expr=4*\thisrow{#5}
    ] {\datatable} \closedcycle;

    % Reset stack using invisible plot
    \addplot [forget plot, stack plots=y,draw=none] table [x={#3}, y expr=-(\thisrow{#4}+2*\thisrow{#5})] {\datatable};
}




\begin{tikzpicture}
\begin{axis}[
    compat=1.5.1,
    no markers,
    enlarge x limits=false,
    ymin=0,
    xlabel=Day of the Year,
    ylabel=Sea Ice Extent\quad/\quad $10^6\,\mathrm{km}^2$,
    legend entries={
        $\pm$ 2 Standard Deviation,
        NH 1997 to 2000 Average,
        $\pm$ 2 Standard Deviation,
        SH 1997 to 2000 Average,
        NH 2012,
        SH 2012
    },
    legend reversed,
    legend pos=outer north east,
    legend cell align=left,
    x post scale=1.2
]

% Northern Hemisphere Average
\errorband[orange, opacity=0.5]{NH_seaice_extent_climatology_1979-2000.csv}{0}{3}{4}

% Northern Hemisphere 2012
\addplot [thick, orange!50!black] table [
    x index=0,
    y index=3,
    skip first n=2,
    col sep=comma,
] {NH_seaice_extent_climatology_1979-2000.csv};

% Southern Hemisphere Average
\errorband[cyan, opacity=0.5]{SH_seaice_extent_climatology_1979-2000.csv}{0}{3}{4}


% Southern Hemisphere 2012
\addplot [thick, cyan!50!black] table [
    x index=0,
    y index=3,
    skip first n=2,
    col sep=comma,
] {SH_seaice_extent_climatology_1979-2000.csv};




\addplot [ultra thick,red] table [
    col sep=comma,
    skip first n=367,
    x expr=\coordindex,
    y index=3
] {NH_seaice_extent_nrt.csv};

\addplot [ultra thick,blue] table [
    col sep=comma,
    skip first n=367,
    x expr=\coordindex,
    y index=3
] {SH_seaice_extent_nrt.csv};
%

\end{axis}
\end{tikzpicture}


\end{document}
share|improve this answer
    
Nice. A couple of follow-up questions: How does one prevent multiple stacking, i.e. when two or more \errorband's are used in the same plot? You write % data file or table in the \errorband definition, but I'm only able to use tables (through \pgfplotstableread) as inputs, not file names directly. It would be easier with just filenames (or else I would have to define a new table for each dataset...) Why? –  Sleort Aug 20 '12 at 15:00
    
@Sleort: It should work with filenames directly (you'll have to use the column names, x, y, and dy in your example file, instead of the column indices). If it doesn't please edit your question to include an example. For resetting the stack, you can add another invisible plot with the value -y-error. I've edited the macro to do this automatically. –  Jake Aug 20 '12 at 15:25
    
Yes, after you've added the \pgfplotstableread to the macro it works with filenames directly, but now it doesn't work with tables... (Well, I'm nitpicking, as the filename option is the only one I need at the moment. I'm just curious - and others might be interested.) Another minor flaw is that the new macro seems to choose ymin=0 (or close to that) by default, regardless of the input data. Easily fixed by a manual ymin adjustment, but nevertheless... –  Sleort Aug 20 '12 at 16:09
    
@Sleort: Sorry, the code wasn't meant to be the "general solution", at the moment it requires adjustment depending on the data source. Doing this "properly" would require a lot more work. I think it's a reasonably practical solution, though. –  Jake Aug 20 '12 at 21:47
1  
@Ingo: The legend entries appear in reverse order due to the legend reversed key: The orange error band was added first (\errorband[orange...), that's why it appears at the bottom of the legend. The definition of the legend entries (using the legend entries key) happens in the same order as the plots ($\pm$ Standard Deviation is specified first). –  Jake Mar 21 '13 at 10:25

You can use a mesh plot with varying line width. However, this causes unsmooth transitions from one line segment to the next. But perhaps it is feasible:

enter image description here

in case you have smaller data sets, markers can hide the transitions:

enter image description here

Here is the code:

\documentclass{standalone}

\usepackage{pgfplots}

\pgfplotsset{compat=1.5}

\begin{document}

\begin{tikzpicture}
% avoid false-positive compilation errors:
\def\pgfplotspointmetatransformed{1000}

    \begin{axis}[ymin=0,ymax=10]
    \addplot+[
        mesh,
        blue,
        %no marks,
        every mark/.append style={line width=1pt,mark size=4pt,fill=blue!80!black},
        shader=flat corner,
        line width=1pt+5pt*\pgfplotspointmetatransformed/1000
    ] 
    table[point meta=\thisrow{dy}] {
    x y dy
    0 2 0.1
    1 4 0.5
    2 3 0.2
    3 3 0.3
    };
    \end{axis}
\end{tikzpicture}
\end{document}

The key idea is that (a) a mesh plot draws individual line segments and (b) \pgfplotspointmetatransformed contains the point meta data in a fully normalized way: the smallest meta data entry (0.1 here) gets \pgfplotspointmetatransformed=0 and the largest one (0.5 here) gets \pgfplotspointmetatransformed=1000. The values in-between are interpolated linearly. Consequently, we can safely use them for line width as above.

Note that the options are evaluated in contexts where this point meta macro is unavailable. To this end, I defined it as 1000 globally (which should be fine for these contexts).

share|improve this answer

I have been playing around with the fillbetween library lately, and I thought that it would be ideal for this scenario.

The answer is inspired from Jake's answer above, but uses the fillbetween library introduded in pgfplots' version 1.10 instead of stacked plots.

Output:

Output


The errorband macro takes six mandatory arguments: data table name, x column, y column, error column, line and error band color, and error band opacity.

It works by creating invisible auxiliary plots for the upper and lower boundaries of the error, and naming them for use by the fillbetween library. fillbetween uses the color and opacity arguments as the error band settings. Finally, it plots y column on top of the error band using in provided color.

The auxiliary plots and error band fillbetween plots are forgotten so that they are not included in the legend. This makes it easy to use errorband immediately followed by \addlegendentry (or \legend at the end) to generate a legend.

(Data not shown.)

Solution:

\documentclass[x11names]{standalone}

\usepackage{pgfplots,pgfplotstable}
\usepgfplotslibrary{fillbetween}
\pgfplotsset{compat=1.10}

% Takes six arguments: data table name, x column, y column, error column,
% color and error bar opacity.
% ---
% Creates invisible plots for the upper and lower boundaries of the error,
% and names them. Then uses fill between to fill between the named upper and
% lower error boundaries. All these plots are forgotten so that they are not
% included in the legend. Finally, plots the y column above the error band.
\newcommand{\errorband}[6]{
\pgfplotstableread{#1}\datatable
  \addplot [name path=pluserror,draw=none,no markers,forget plot]
    table [x={#2},y expr=\thisrow{#3}+\thisrow{#4}] {\datatable};

  \addplot [name path=minuserror,draw=none,no markers,forget plot]
    table [x={#2},y expr=\thisrow{#3}-\thisrow{#4}] {\datatable};

  \addplot [forget plot,fill=#5,opacity=#6]
    fill between[on layer={},of=pluserror and minuserror];

  \addplot [#5,thick,no markers]
    table [x={#2},y={#3}] {\datatable};
}

\begin{document}
\begin{tikzpicture}%
  \begin{axis}[%
    width=10cm,
    height=10cm,
    scale only axis,
    xlabel={$x$},
    ylabel={$y$},
    enlarge x limits=false,
    grid=major,
    legend style={
      column sep=3pt,
      nodes={right},
      legend pos=south east,
    },
  ]

    \errorband{./data.dat}{0}{1}{2}{Firebrick2}{0.4}
    \addlegendentry{Data}

    \errorband{./data.dat}{0}{3}{4}{SpringGreen4}{0.4}
    \addlegendentry{More data}

  \end{axis}
\end{tikzpicture}%
\end{document}

Performance Considerations:

For those still reading, I assume that plotting the invisible plots just to name them makes this less efficient than it could be. If someone knows a way to replace this:

\addplot [name path=pluserror,draw=none,no markers,forget plot]
    table [x={#2},y expr=\thisrow{#3}+\thisrow{#4}] {\datatable};

with something like:

\path[name path=pluserror] table [x={#2},y expr=\thisrow{#3}+\thisrow{#4}] {\datatable};

it would be great. I assume \path does not actually waste time drawing, which would make it more efficient. Not sure how to do that or even if it would improve efficiency.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.