*! version 1.0 2023-10-03 Mark Chatfield

Title

box_logscale -- Box plots on the log scale (generalising Tukey's definition of whiskers)

Syntax

box_logscale yvars [if] [in] [weight],
[horizontal
label(numlist)
mtick(numlist)
graph_box_options]

where yvars is a varlist

options

horizontal draw horizontal box plots

label(numlist) labels numeric axis with user-specified original-scale numlist

mtick(numlist) minor values ticked along the numeric axis according to user-specified original-scale numlist

graph_box_options choose from almost all of the options

See graph box for what weights are allowed

Description

box_logscale presents box plots on a logarithmic scale.
It does this by log10 transforming the yvars, then running graph box or graph hbox.
The numeric axis, however, is labelled with nice original-scale numbers.
Optionally, you can specify what nice original-scale numbers you want as labels using label()
and/or minor ticks using mtick().

N.B. If you want to use some graph_box_options such as ylabel(), yline(), text(),
you will need to specify log10-scale numbers. See last example below.

Remarks

This is an implementation of Nick Cox's advice in
"How can I best get box plots on logarithmic scales?",
which should save you time and brain power. It can be a better approach than: graph box, yscale(log).
Box plots where whiskers are instead based on percentiles can be produced with stripplot.

box_logscale differs from graph box, yscale(log)

i) considerably in how whiskers (and therefore outside values) are defined

ii) considerably in how the numeric axis is labelled

iii) occasionally, and often in a minor way, in how the quartiles which make up the box (and middle line) are defined

This is because:

graph box, yscale(log) applies the usual definitions to untransformed data,
then plots the resulting statistics on a log-scaled axis.

box_logscale log-transforms the data first, applies the usual definitions,
plots the resulting statistics but labels on the numeric axis are carefully chosen
such that they correspond to nice original-scale values
̶ and with a little magic- these nice original-scale values appear on the numeric axis.

Cox (2018) discusses labelling log-scaled axes in detail. His paper is well worth reading.
When max/min is sufficiently large, I label using powers of 10 such as ..., 0.1, 1, 10, 100, 1,000, ... .
Otherwise, if max/min is not too small, I label like ..., 0.1, 0.2, 0.5, 1, 2, 5, 10, ... .
Labelling that I consider "nice" may not be labelling that you consider "nice".
You might prefer to specify different value labels and minor ticks, by specifying e.g. label() and mtick().
You can attach "labels" to numbers by using ylabel() if you are careful - see last example below.

Detail

box_logscale calculates quartiles of log10(y) in the usual manner described in [G-2] graph box.
Call them q1log, q2log, q3log.
Then the following are calculated: Ulog = q3log + 1.5*(q3log - q1log) and Llog = q1log - 1.5*(q3log - q1log).
Then adjacent values for log10(y) are defined in the usual manner described in [G-2] graph box, i.e.
the upper adjacent value is the largest value of log10(y) not exceeding Ulog, and
the lower adjacent value is the smallest value of log10(y) exceeding Llog.
The plot is then drawn, and labels on the numeric axis are carefully chosen
such that they correspond to (nice, possibly user specified) original-scale values
- I make these nice values appear on the numeric axis.

This has the effect of looking like a plot of the untransformed data, where the box shows
q1 = 10^q1log, q2 = 10^q2log and q3 = 10^q3log, and whiskers are drawn between the box and adjacent values,
where adjacent values are now defined as: the upper adjacent value is the largest value of y not exceeding U,
and the lower adjacent value is the smallest value of y exceeding L, where
U = 10^Ulog = q3 * (q3/q1)^1.5, and
L = 10^Llog = q1 / (q3/q1)^1.5.
It could be argued this results in a generalisation of Tukey's definition of whiskers (Tukey 1977).

Minor point: q1 and q3 can differ from the first and third quartiles of y because
order statistics sometimes need to be averaged to calculate quartiles.
This happens when N is a multiple of 4. In that case q1 and q3 are calculating a geometric mean
rather than an arithmetic mean. The same is true of the median, when N is a multiple of 2.

When some values ≤0, box_logscale alerts the user and does not produce a graph.

Examples

Let's generate a dataset with a lognormal variable.

. `"clear"'

. `"set seed 999"'

. `"set obs 999"'

. `"generate y = 10^rnormal(0,0.3)"'

Before showing off box_logscale, let's see the result of using Stata's graph box command
when we ask for the numeric axis to have a log scale. Yuk! So many high outside values and no low outside values.
And a badly labelled numeric axis.

. graph box y, yscale(log)

In contrast, box_logscale shows just a few high outside values and just a few low outside values.
It also labels the numeric axis nicely.

. box_logscale y

If you want just .1, 1 and 10 on the numeric axis. And minor ticks at .2(.1).9 and 2(1)9.

. box_logscale y, label(.1 1 10) mtick(.2(.1).9 2(1)9)

If you want just a line at y=0.5, and labels "0.1" and "0.5" on the numeric axis rather than the numbers .1 and .5.

. `"box_logscale y, yline(`=log10(0.5)') ylabel(`=log10(0.1)' "0.1" `=log10(0.5)' "0.5" `=log10(1)' "1" `=log10(10)' "10") mtick(.2(.1).9 2(1)9) "'

If you have stripplot installed, you can create box plots on the log scale using percentile-based whiskers.

. `"stripplot y, box pctile(0.35) yscale(log) vertical ms(none) outside(ms(o)) note("whiskers to 0.35% and 99.65% percentiles")"'

With stripplot, i) you would need to improve labels,
ii) you can only have one over() if you have just one yvar,
iii) you can do some cool things like plot all datapoints as well as a box.

References

Cox, N.J. "How can I best get box plots on logarithmic scales?"
https://www.stata.com/support/faqs/graphics/box-plots-and-logarithmic-scales/

Cox, N.J. 2018. Speaking Stata: Logarithmic binning and labelling. Stata Journal 18: 262–286.

Cox, N.J. 2020. Software update for niceloglabels. Stata Journal 20: 1028-1030.

Tukey, J.W. 1977. Exploratory Data Analysis. Reading, MA: Addison-Wesley.

Author

Mark Chatfield, The University of Queensland, Australia.

m.chatfield@uq.edu.au

Acknowledgements

I have incorporated some of the syntax from Nick Cox's niceloglabels command (Cox 2018, 2020)
as well as his mylabels and myticks commands. This is to avoid dependency on his commands being installed.
But they are fabuluous, and worth checking out.

Also see

Help: niceloglabels (if installed), mylabels (if installed), myticks (if installed), stripplot (if installed)