How markers are rendered is determined by the employed graph scheme. Use marker options to change the default look. The marker options can be specified for each series separately, but also as global options (in which case they are taken as defaults for the individual series):
. sysuse auto, clear (1978 automobile data) . keep if rep78>=3 (10 observations deleted) . regress mpg headroom i.rep i.foreign (output omitted) . estimates store m1 . regress mpg headroom i.rep##i.foreign (output omitted) . estimates store m2 . coefplot (m1, msymbol(D) mlcolor(magenta) mfcolor(magenta*.3)) /// > (m2, msymbol(S)) /// > , mfcolor(white) msize(large)Code
The overall style of markers and their confidence intervals can also be
changed using the pstyle()
option (pstyles are
named composite styles provided by the graph scheme). By default, coefplot
uses the first pstyle for the first series, the second pstyle
for the second series, and so on. Here is an example in which the styles are changed:
. coefplot (m1, pstyle(p3)) (m2, pstyle(p4))
Code
Specifying pstyle()
changes the pstyle both for markers and confidence spikes. To use a different
pstyle for the confidence spikes, specify option
cipts(pstyle()):
. coefplot (m1, ciopts(pstyle(p2))) (m2, pstyle(p3) ciopts(pstyle(p4)))
Code
To print markers without confidence intervals, you can specify the
noci
option; to print only the confidence intervals without markers, type
cionly:
. sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn (output omitted) . coefplot (., noci label(Markers only)) /// > (., cionly label(CIs only) key(ci)) /// > , drop(_cons)Code
Series without markers are omitted from the legend. This is why option
key(ci) has been
specified. The option requests generating a legend key for the confidence
interval.
To add the values of the point estimates as marker labels, use the
mlabel
option, possibly together with
format()
to set the display format:
. sysuse auto, clear (1978 automobile data) . keep if rep78>=3 (10 observations deleted) . regress mpg headroom i.rep##i.foreign (output omitted) . coefplot, xline(0) mlabel format(%9.2g) mlabposition(12) mlabgap(*2)Code
Stata graphs do not support background colors for marker labels, which makes
labels unreadable if you place them on top of the markers using
mlabposition(0).
However, here is a workaround. The trick is to add a second "confidence
interval" that is a bar of fixed width:
. mata: st_matrix("e(box)", (st_matrix("e(b)"):-2 \ st_matrix("e(b)"):+2)) . coefplot, xline(0) mlabel format(%9.2g) mlabposition(0) msymbol(i) /// > ci(95 box) ciopts(recast(. rbar) barwidth(. 0.35) color(. white))Code
The dot in the suboptions within
ciopts()
specifies the "default" style; see
help stylelists.
Instead of physically adding the coordinates of the bars as matrix
e(box) to the estimation set, the coordinates can also be
generated on the fly by the
transform()
option. In the example below, a second set of confidence limits is requested
(the specified level does not matter as long as its unique)
that is then transformed to b±2 with the help of
transform()
(also see
Accessing internal temporary variables):
. sysuse auto, clear (1978 automobile data) . keep if rep78>=3 (10 observations deleted) . regress mpg headroom i.rep##i.foreign (output omitted) . coefplot, xline(0) mlabel format(%9.2g) mlabposition(0) msymbol(i) /// > ci(95 99) transform(* = "cond(@==@ll2, @b-2, cond(@==@ul2, @b+2, @))") /// > ciopts(recast(. rbar) barwidth(. 0.35) color(. white))Code
Here is a further example where a box is placed around the numbers:
. coefplot, xline(0) mlabel format(%9.2g) mlabposition(0) msymbol(i) ///
> ci(95 99) transform(* = "cond(@==@ll2, @b-2, cond(@==@ul2, @b+2, @))") ///
> ciopts(recast(. rbar) barwidth(. 0.35) fcolor(. white) lwidth(. medium))
CodeA bit unfortunate might be that due to the box the exact location of a coefficient can no longer be seen in the graph. Here is an example where an additional vertical spike is added to mark the point estimates.
. coefplot, xline(0) mlabel format(%9.2g) mlabposition(0) msymbol(i) ///
> ci(95 (b b) 99) transform(* = "cond(@==@ll3, @b-2, cond(@==@ul3, @b+2, @))") ///
> ciopts(recast(. rcap rbar) msize(. 7 .) barwidth(. . 0.35) ///
> fcolor(. . white) lwidth(. . medium))
Code
In the example, a confidence interval of zero width is used to produce the vertical
spikes ((b b) means to include a confidence interval whose
lower and upper limits are both equal to e(b)).
The plot might still not be optimal since for the first coefficient, the confidence interval is hidden behind the marker label box. Plotting the confidence intervals as bars can, for example, solve this problem:
. coefplot, xline(0) mlabel format(%9.2g) mlabposition(0) msymbol(i) ///
> ci(95 (b b) 99) transform(* = "cond(@==@ll3, @b-2, cond(@==@ul3, @b+2, @))") ///
> ciopts(recast(rbar rcap rbar) bstyle(ci2 . .) msize(. 7 .) ///
> barwidth(0.5 . 0.35) fcolor(. . white) lwidth(. . medium))
Code
The default for the
mlabel option is
to display the values of the point estimates as marker labels. To display
p-values instead of point estimates, for example, you could type
mlabel(@pval) (see
Accessing internal temporary variables
for available @-variables). Furthermore, a string expression
may be provided as an argument to
mlabel()
to construct more complicated marker labels, as in the following example:
. sysuse auto, clear (1978 automobile data) . keep if rep78>=3 (10 observations deleted) . regress mpg headroom i.rep##i.foreign (output omitted) . coefplot, xline(0) mlabposition(1) mlabgap(*2) /// > mlabel("{it:p} = " + string(@pval,"%9.3f"))Code
Here is a more complicated example that displays different symbols as marker labels depending on the size of the p-value:
. coefplot, xline(0) mlabposition(1) ///
> mlabel(cond(@pval<.001, "***", ///
> cond(@pval<.01, "**", ///
> cond(@pval<.05, "*", ///
> cond(@pval<.1, "+", ""))))) ///
> note("+ p < .1, * p < .05, ** p < .01, *** p < .001")
Code
To create custom labels for specific markers, you can use the
mlabels()
option. For example, the following graph includes information on hypotheses
(positive effect or null effect) as marker labels:
. sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn if foreign==0 (output omitted) . estimates store domestic . regress price mpg trunk length turn if foreign==1 (output omitted) . estimates store foreign . coefplot (domestic, mlabels(length = 1 "+" * = 11 "0")) /// > (foreign, mlabels(trunk length = 1 "+" * = 11 "0")) /// > , drop(_cons) xline(0) note("Hypotheses: + positive effect; 0 no effect")Code
Of course, you can also attach labels only to some of the coefficients and you can use marker label options to affect the rendering of the labels:
. coefplot ///
> (domestic, mlabels(trunk = 12 "I don't like that this is not significant") ///
> mlabangle(45) mlabgap(2) mlabsize(medium) mlabcolor(red)) ///
> (foreign, mlabels(length = 1 "This is the {bf:only} significant effect!") ///
> mlabtextstyle(small_label)) ///
> , drop(_cons) xline(0)
Code
As seen above, marker labels will always be placed at the values of the point estimates.
In some situations, however, it may be preferable to place the labels, say,
at the upper end of the confidence intervals. coefplot has no
option to change the anchor of marker labels, but here is a workaround
using the addplot() option
with internal temporary variables:
. sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn if foreign==0 (output omitted) . estimates store domestic . regress price mpg trunk length turn if foreign==1 (output omitted) . estimates store foreign . coefplot domestic foreign, drop(_cons) xline(0) /// > addplot(scatter @at @ul, ms(i) mlabel(@b) mlabformat("%9.1f") /// > mlabcolor(black) mlabpos(2))Code
The approach works well if you just want to print the values of the estimates.
For more complex labels, first create the labels employing the usual
mlabel()
option, but suppress their display by setting the color to none. The created
labels will be stored in temporary variable
@mlbl. You can then use this
variable in the addplot() option:
. coefplot domestic foreign, drop(_cons) xline(0) ///
> mlabel("{it:p} = " + string(@pval,"%9.3f")) mlabcolor(none) ///
> addplot(scatter @at @ul, ms(i) mlabel(@mlbl) mlabcolor(black) mlabpos(2))
Code
If you want to use different styles for the labels depending on model,
include multiple plots in the
addplot() option.
The relevant internal variable to select the models is called
@plot
. coefplot domestic foreign, drop(_cons) xline(0) ///
> mlabel("{it:p} = " + string(@pval,"%9.3f")) mlabcolor(none) ///
> addplot(scatter @at @ul if @plot==1, ms(i) mlabel(@mlbl) pstyle(p1) ///
> || scatter @at @ul if @plot==2, ms(i) mlabel(@mlbl) pstyle(p2))
Code
Note that the approach also works with custom labels generated by
option mlabels():
. coefplot (domestic, mlabels(length = 0 "+" trunk = 0 "0")) ///
> (foreign, mlabels(trunk length = 0 "+")) ///
> , drop(_cons) xline(0) note("Hypotheses: + positive effect; 0 no effect") ///
> mlabcolor(none) ///
> addplot(scatter @at @ul, ms(i) mlabel(@mlbl) mlabcolor(black))
Code
Nino Landler asked on Statalist
whether it is possible to print the values of the estimates on the right
axis of a plot. This is not directly possible, but you can write a little program
to call coefplot without drawing a graph and collect the labels. The
labels can then be included as axis labels in a second call to coefplot.
The program could look about as follows:
. capt program drop coefplot_mlbl . *! version 1.0.0 10jun2021 Ben Jann . program coefplot_mlbl, sclass 1. _parse comma plots 0 : 0 2. syntax [, MLabel(passthru) * ] 3. if `"`mlabel'"'=="" local mlabel mlabel(string(@b)) 4. preserve 5. qui coefplot `plots', `options' `mlabel' generate replace nodraw 6. sreturn clear 7. tempvar touse 8. qui gen byte `touse' = __at<. 9. mata: st_global("s(mlbl)", /// > invtokens((strofreal(st_data(.,"__at","`touse'")) :+ " " :+ /// > "`" :+ `"""' :+ st_sdata(.,"__mlbl","`touse'") :+ `"""' :+ "'")')) 10. sreturn local plots `"`plots'"' 11. sreturn local options `"`options'"' 12. end . sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn if foreign==0 (output omitted) . estimates store D . regress price mpg trunk length turn if foreign==1 (output omitted) . estimates store F . coefplot_mlbl D F, drop(_cons) xline(0) . sreturn list macros: s(options) : "drop(_cons) xline(0)" s(plots) : "D F" s(mlbl) : ".8333333 `"-186.1083"' 1.166667 `"-55.72135"' 1.833333 `"-60.."Code
The idea is that you specify the full coefplot syntax
already when collecting the labels to ensure that the
collected labels are consistent with the graph you want to create. The
labels are returned in s(mlbl); the corresponding
coefplot command is returned in two parts in
s(plots) and s(options). To draw the graph including
the labels you can type:
. coefplot_mlbl D F, drop(_cons) xline(0) . coefplot `s(plots)', `s(options)' /// > ymlabel(`s(mlbl)', angle(0) notick axis(2)) /// > yaxis(1 2) yscale(alt) yscale(axis(2) alt noline) /// > ylabel(none, axis(2)) yti("", axis(2))Code
Note that the s() returns will only be available immediately after
running coefplot_mlbl.
The precise contents of the labels can be determined by specifying
option mlabel()
(or mlabels()) in
the usual way when calling coefplot_mlbl:
. coefplot_mlbl D F, drop(_cons) xline(0) mlabel("p = "+string(@pval,"%9.3f")) . coefplot `s(plots)', `s(options)' /// > ymlabel(`s(mlbl)', angle(0) notick axis(2)) /// > yaxis(1 2) yscale(alt) yscale(axis(2) alt noline) /// > ylabel(none, axis(2)) yti("", axis(2))Code
The syntax of the final coefplot command is quite complicated
because a secondary axis has to be created. An idea could be to write a
little wrapper so that you can produce many plots without having to
repeat these options. Here is a corresponding program:
. capt program drop coefplot_ymlbl . *! version 1.0.0 10jun2021 Ben Jann . program coefplot_ymlbl 1. _parse comma plots 0 : 0 2. syntax [, MLabel(str asis) * ] 3. _parse comma mlspec mlopts : mlabel 4. local mlopts = substr(`"`mlopts'"', 2, .) // remove leading comma 5. if `"`mlspec'"'!="" local mlabel mlabel(`mlspec') 6. else local mlabel 7. coefplot_mlbl `plots', `options' `mlabel' 8. coefplot `plots', /// > yaxis(1 2) yscale(alt) yscale(axis(2) alt noline) /// > ylabel(none, axis(2)) yti("", axis(2)) /// > ymlabel(`s(mlbl)', axis(2) notick angle(0) `mlopts') `options' 9. endCode
You can then type
. coefplot_ymlbl D F, drop(_cons) xline(0)
Codeor, for example,
. coefplot_ymlbl D F, drop(_cons) xline(0) mlabel("p = "+string(@pval,"%9.3f"))
CodeOf course you may need to tweak the program a bit so that it creates the graphs you have in mind.
The coefplot_mlbl program defined above does not make a difference
between the models in the plot. If you want to style the labels
differently depending on model, you need to modify the program so that it
returns multiple lists of labels. Here is an example:
. capt program drop coefplot_mlbl2 . *! version 1.0.0 10jun2021 Ben Jann . program coefplot_mlbl2, sclass 1. _parse comma plots 0 : 0 2. syntax [, MLabel(passthru) * ] 3. if `"`mlabel'"'=="" local mlabel mlabel(string(@b)) 4. preserve 5. qui coefplot `plots', `options' `mlabel' generate replace nodraw 6. sreturn clear 7. tempvar touse 8. qui gen byte `touse' = 0 9. local nplots = r(n_plots) 10. forv i = 1/`nplots' { 11. qui replace `touse' = __plot==`i' & __at<. 12. mata: st_global("s(mlbl`i')", /// > invtokens((strofreal(st_data(.,"__at","`touse'")) :+ " " :+ /// > "`" :+ `"""' :+ st_sdata(.,"__mlbl","`touse'") :+ `"""' :+ "'")')) 13. } 14. sreturn local plots `"`plots'"' 15. sreturn local options `"`options'"' 16. end . coefplot_mlbl2 D F, drop(_cons) xline(0) mlabel("p = "+string(@pval,"%9.3f")) . coefplot `s(plots)', `s(options)' /// > ymlabel(`s(mlbl1)', angle(0) notick axis(2) add custom labcolor(navy)) /// > ymlabel(`s(mlbl2)', angle(0) notick axis(2) add custom labcolor(maroon)) /// > yaxis(1 2) yscale(alt) yscale(axis(2) alt noline) /// > ylabel(none, axis(2)) yti("", axis(2))Code
To scale the size of markers use the
weight() option.
In the following example, the sizes of the marker symbols are proportional to the
inverse of the standard errors:
. sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn (output omitted) . coefplot, weight(1/@se) ms(oh) drop(_cons) xline(0)Code
@se is an internal variable provided by coefplot. See
Accessing internal temporary variables
in the help file for the list of available internal variables. Additional
internal variables can be made available through the
aux() option. Here
is an example in which the markers for group means are scaled by the number of
observations in each group:
. sysuse auto, clear (1978 automobile data) . version 15: mean price, over(rep78) Mean estimation Number of obs = 69 1: rep78 = 1 2: rep78 = 2 3: rep78 = 3 4: rep78 = 4 5: rep78 = 5 -------------------------------------------------------------- Over | Mean Std. err. [95% conf. interval] -------------+------------------------------------------------ price | 1 | 4564.5 369.5 3827.174 5301.826 2 | 5967.625 1265.494 3442.372 8492.878 3 | 6429.233 643.5995 5144.95 7713.516 4 | 6071.5 402.9585 5267.409 6875.591 5 | 5913 788.6821 4339.209 7486.791 -------------------------------------------------------------- . matrix list e(_N) e(_N)[1,5] price: price: price: price: price: 1 2 3 4 5 r1 2 8 30 18 11 . coefplot, ciopts(recast(rcap)) aux(_N) weight(@aux1) mfcolor(*.6)Code
The mean
command returns the group sizes in vector e(_N) from where they
can be read by typing aux(_N).
If you apply the weight()
option in a graph that contains multiple series, marker sizes will be computed
for each series separately. Consider the following example:
. sysuse auto, clear (1978 automobile data) . regress price mpg trunk length turn if foreign==0 (output omitted) . estimates store domestic . regress price mpg trunk length turn if foreign==1 (output omitted) . estimates store foreign . coefplot domestic foreign, weight(1/@se) ms(oh) drop(_cons) xline(0)Code
The sizes of the marker can be compared within series, that is, within series they
are proportional to the inverse of the standard errors. Between series, however, this
relation does not hold. Here is a picture in which the same information is displayed
while applying weight()
to all markers at once (see the help file for information on options
asequation
and swapnames):
. coefplot (domestic foreign), asequation swapnames nolabel ///
> weight(1/@se) ms(oh) drop(_cons) xline(0)
Code
As is evident, to be comparable, the sizes of the markers for
domestic have to be made a bit larger. A trick to make marker
sizes comparable across series is to include all data in each series, so
that the same information is used to determine the marker sizes, but then
suppress the extra estimates by setting them to missing using the
transform() option:
. coefplot (foreign, transform(* = .) \ domestic) ///
> (domestic, transform(* = .) \ foreign) ///
> , weight(1/@se) ms(oh) drop(_cons) xline(0)
(transform missing for some coefficients or CIs)
Code