# Dot and whisker plot in PGFplots

Sometimes it is useful to present the coefficients of a regression with accompanying confidence intervals as a dot-and-whisker plot. See here for a motivation of this approach and some examples of what I am trying to achieve based on an R package dotwhisker designed to produce these plots. Here is an example from the linked R vignette: I would like to replicate similar dot-and-whisker plots in PGFPLOTS.

PGFPLOTS supports box-and-whisker plots natively -- here is an example. A full description of the box-and-whisker feature of PGF is in section 5.12.1 of manual version 1.16.

Here is my best attempt:

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\usepgfplotslibrary{statistics}

\begin{document}
\begin{tikzpicture}
\begin{axis}[
ytick={1,2,3},
yticklabels={Group A, Group B, Group C},
]

boxplot prepared={
lower whisker=0.35,
% lower quartile=,
median=0.6,
% upper quartile=,
upper whisker=0.85,
},
]
coordinates {};

boxplot prepared={
lower whisker=0.55,
% lower quartile=,
median=0.70,
% upper quartile=,
upper whisker=0.85,
},
]
coordinates {};

boxplot prepared={
lower whisker=0.85,
% lower quartile=,
median=0.9,
% upper quartile=,
upper whisker=0.95,
},
]
coordinates {};
\end{axis}
\end{tikzpicture}

\end{document}


There are several problems with my attempt:

1. I am adapting the box and whisker feature to represent something that is not a box whisker. This is perhaps the wrong way to approach things. My kludge treats the median as the coefficient estimate, and treats the upper and lower whiskers as the upper and lower confidence intervals. I comment out the quartiles as I do not have an equivalent for these, and I do not want the box.
2. Unlike the R package dotwhister, the graphics are not clear -- having vertical lines puts too much emphasis on the confidence bounds relative to the coefficient point estimate.
3. It is not scalable. I would like to analyse 50 groups. Everytime I update my data I do not want to manually update the box plot entries in PGFPLOTS. I would prefer to be able to read from a .csv containing 50 rows for each group and four columns: group name, coefficient estimate, upper confidence interval, lower confidence interval.

Here is some randomly generated data (upper and lower are symmetric around point_est):

\begin{filecontents*}{dotwhisker.csv}
group,point_est,upper,lower
SCBQD6600C,0.318940138,0.782642805,-0.144762529
GHECK1046A,0.541614386,1.425115639,-0.341886867
ICOOO3242S,0.662666177,1.143455809,0.181876544
PHOVQ7028A,0.148145345,0.239989182,0.056301508
HSJEK0588Y,0.564368703,0.997673282,0.131064125
CYVFG8255L,0.575908384,1.288811424,-0.136994656
ZDYRJ3242S,0.413789006,0.639376662,0.18820135
PXQSX1684J,0.418005222,0.974470232,-0.138459788
VTCRK0417U,0.4153322,1.020437688,-0.189773288
WSYWC4669M,0.366494326,0.756315385,-0.023326734
BZPKZ2934L,0.428421095,0.792023892,0.064818298
EGIPR1094A,0.350242033,0.598746704,0.101737362
PQTFK6203U,0.383561916,0.660697282,0.10642655
UYLGX7811M,0.509668823,1.037205877,-0.017868231
ICEHA2251J,0.643924109,1.452395674,-0.164547457
\end{filecontents*}

• To pre-suppose a comment, I know how to use R's tikzDevice package and would rather avoid that approach applied to the above R package for dot-whisker plots. I want to plot directly in PGFplots based on coefficient estimate and standard error. Nov 23, 2019 at 17:58
• I have no reason to prefer using the box whisker package in PGF. If it is easier to do in a scalable manner without box and whisker, please suggest an approach. Thanks. Nov 24, 2019 at 12:49
• Thanks fo adding the code! My main problem is that I am not really very firm in the backgrounds of this (even though I probably should) but if your question is if one could read out the values 0.55, 0.70 and 0.85 (or 0.55 and 0.15 for symmetric whiskers) from a csv file to create a series of \addplot+ [ boxplot prepared={ lower whisker=0.55, % lower quartile=, median=0.70, % upper quartile=, upper whisker=0.85, }, ] coordinates {}; which use these values, the answer is of course affirmative.
– user194703
Nov 24, 2019 at 12:57
• Yes that would be what I am after as a start. Read a .csv where each row is a separate group, and there are four columns 1. group name 2. "median" 3. "upper whisker" 4. "lower whisker". I use quotations as this is not what is being represented statistically, but it is how we would interpret in terms of the box and whisker plot semantics. However, I say this is a start because I'd then be unsatisfied with the vertical line representations which are more confusing than having dots, see R dotwhisker package. Hence an alternative to the box whisker package in PGF might be more viable. Nov 24, 2019 at 13:03
• Can you provide a sketch of how these "whiskers" should look like?
– user194703
Nov 24, 2019 at 13:07

Here is a code that goes through a csv file and adds these "whiskers". The dimensions of the vertical lines are determined by /pgfplots/boxplot/box extend=0.1 (and /pgfplots/boxplot/whisker extend).

\documentclass{article}
\usepackage{pgfplots}
\usepackage{tikzlings}%<- for the second part of the answer ;-)
\usetikzlibrary{shapes.callouts}%<- for the second part of the answer
\usepackage{pgfplotstable}
\usepackage{filecontents}
\begin{filecontents*}{whiskers.dat}
0.6 0.35 0.85
0.7 0.55 0.85
0.9 0.85 0.95
\end{filecontents*}

\pgfplotsset{compat=1.16}
\usepgfplotslibrary{statistics}
\newcounter{iloop}
\pgfplotstablegetelem{#2}{[index]#3}\of{#1}%
\let#4\pgfplotsretval
}

\begin{document}
\begin{tikzpicture}
\pgfplotstablegetrowsof{\datatable}
\pgfmathtruncatemacro{\numrows}{\pgfplotsretval}
\setcounter{iloop}{1}
\edef\MyLabels{Group A}
\loop\stepcounter{iloop}\edef\MyLabels{\MyLabels,Group \Alph{iloop}}%
\ifnum\value{iloop}<\numrows\repeat
\begin{axis}[
ytick={1,...,\numrows},
yticklabels/.expanded=\MyLabels,
/pgfplots/boxplot/box extend=0.1,
/pgfplots/boxplot/whisker extend=%
\pgfkeysvalueof{/pgfplots/boxplot/box extend}*4,
]
\pgfplotsinvokeforeach{0,...,\the\numexpr\numrows-1}
{
boxplot prepared={
lower whisker=\Lower,
% lower quartile=,
median=\Median,
% upper quartile=,
upper whisker=\Upper,
},
] coordinates {(#1+1,\Median)};
}
\temp
}

\end{axis}
\end{tikzpicture}
\bigskip\bigskip

\begin{tikzpicture}
\marmot[whiskers,teeth]
\node[ellipse callout, fill=white,
draw,
font=\sffamily,align=center,%inner sep=-2pt,
callout relative pointer={(-150:0.9)}] at (2,2.4) {How about\\ my whiskers?};
\end{tikzpicture}
\end{document}


Your larger data file works, too.

\documentclass{article}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\begin{filecontents*}{dotwhisker.csv}
group,point_est,upper,lower
RAMOF4414X,0.25858685,0.878367526,-0.161193826
DOYDL6809E,0.555485848,1.400010767,-0.089039071
YHYYL9849M,0.716888235,1.368127463,0.265649007
JMQIG4509E,0.030860459,0.453325401,-0.191604482
SQYRJ4202T,0.585824938,1.206768474,0.164881402
OYSCJ0457U,0.601211178,1.362723257,0.0396991
ZQAXZ5423I,0.385052008,0.892098437,0.078005579
MDWEF7504F,0.390673629,1.063439815,-0.082092558
IJUSO8696V,0.3871096,1.084017824,-0.109798624
BQSBP8990Q,0.321992434,0.908002585,-0.064017717
OCDWZ7363Q,0.40456146,0.981590932,0.027531988
OHEEJ6147W,0.300322711,0.814591182,-0.01394576
KCRLD3716J,0.344749221,0.875554365,0.013944077
MFPJM1799W,0.512891763,1.177304879,0.048478648
DIPEZ2121T,0.691898812,1.505729535,0.078068088
LYOUI9349J,0.477942901,1.060060156,0.095825646
VZFYT9091T,0.397798099,0.863976498,0.1316197
OEQZM1870D,0.317760917,0.817961252,0.017560582
VTGHR8934O,0.332565664,0.913827561,-0.048696233
KMCVQ3734I,0.983167036,1.675403499,0.490930572
ZALVK0344T,0.526817644,1.122164944,0.131470344
PNXOL7981C,0.401862821,0.962308107,0.041417534
PYIND4089E,0.411890426,0.88306747,0.140713381
PNZTF5825U,0.591917888,1.201046391,0.182789386
LPPRP3997H,0.361255951,1.008833148,-0.086321247
VBUVF6797C,0.210359018,0.815565496,-0.194847461
INHXV3841Z,0.614951144,1.31681457,0.113087718
RPWRJ7560V,0.295268855,0.810041955,-0.019504244
SCTCT3620G,0.49697234,1.070902402,0.123042278
MAYYI9754J,0.31281028,0.774220754,0.051399806
\end{filecontents*}

\pgfplotsset{compat=1.16}
\usepgfplotslibrary{statistics}
\newcounter{iloop}
\pgfplotstablegetelem{#2}{[index]#3}\of{#1}%
\let#4\pgfplotsretval
}

\begin{document}
\begin{tikzpicture}
\pgfplotstablegetrowsof{\datatable}
\pgfmathtruncatemacro{\numrows}{\pgfplotsretval}
\edef\MyLabels{\Label}
\setcounter{iloop}{1}
\loop
\edef\MyLabels{\MyLabels,\Label}%
\stepcounter{iloop}%
\ifnum\value{iloop}<\numrows\repeat
\begin{axis}[width=12cm,height=14cm,
ytick={1,...,\numrows},
yticklabels/.expanded=\MyLabels,
/pgfplots/boxplot/box extend=0.1,
/pgfplots/boxplot/whisker extend=%
\pgfkeysvalueof{/pgfplots/boxplot/box extend}*4,
]
\pgfplotsinvokeforeach{0,...,\the\numexpr\numrows-1}
{
boxplot prepared={
lower whisker=\Lower,
% lower quartile=,
median=\Median,
% upper quartile=,
upper whisker=\Upper,
},
] coordinates {(#1+1,\Median)};
}
\temp
}

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


• I updated an earlier dotwhisker.csv I provided to be symmetric contemporaneously to @Schrödinger'scat excellent answer which uses the original .csv. This answer works well regardless, except for automatically recognizing group names. Thanks. Nov 24, 2019 at 14:58
• Marmot is back! :) Nov 24, 2019 at 17:20
• Yes. I just generated those strings randomly as names along with generating the numerical data randomly. I wanted to emphasize that it might not be as simple as looping over a numeric or alphabetic counter such as 'group A', 'group B' etc. My MWE was misleading in this sense. Of course, instead of random strings I could have put cities, countries, species or some other large categorial variable. Nov 24, 2019 at 20:16
• @Sav-econ The labels are in.
– user194703
Nov 24, 2019 at 20:22
• @CarLaTeX YAY!! Nov 25, 2019 at 0:40