Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Coefplot - plot Estimates on right axis

    Dear Statalisters

    I am very fond of the flexible coefplot and use it to graph all my models these days. However, for publication it would be nice to print the actual estimates in numbers next to the graph e.g. as y-axis labels on an alternative y-axis. I have struggled, but have so far failed.
    I want to use coefplot, because it can easily plot adjusted and unandjusted estimates on the same graph. I have posted an example below, which is based on the manual page of coefplot (http://repec.sowi.unibe.ch/stata/coe...g-started.html)

    Code:
    sysuse auto, clear
    regress price mpg trunk length turn if foreign==0
    estimates store D
    regress price mpg trunk length turn if foreign==1
    estimates store F
    coefplot (D, offset(0.05)) (F, offset(-0.05)), drop(_cons) xline(0) ///
    mlabel(string(@b,"%9.1f")+"("+string(@ll,"%9.1f")+"-"+string(@ul,"%9.1f")+")"+", {it:p} = "+string(@pval,"%9.3f")) ///
    mlabposition(3) mlabgap(*0.4) mlabsize(vsmall)
    The result is shown below. I tried to add a scatter plot at @at(internal variable for plot position), "some fixed x-value", and then add the mlabels to this. But no 2-way plot seems to accept fixed x-values. Moreover, yaxis labels cannot be filled with the coefplots internal variables, e.g. @b for point estimates. So I am at my wits end. Any suggestions?

    Thank you for time!


  • #2
    Not sure what you mean by an alternative y-axis, but you can move the marker labels using the -mlabgap()- option that you specify, as well as manipulate the space between the margin outside the y-axis title. Eventually, if alignment becomes an issue, just use the -text()- option or tw scatteri and manually enter the labels, specifying the exact positon that you want. Written up in

    Code:
    help tw scatteri

    Code:
    sysuse auto, clear
    regress price mpg trunk length turn if foreign==0
    estimates store D
    regress price mpg trunk length turn if foreign==1
    estimates store F
    coefplot (D, offset(0.15)) (F, offset(-0.15)), drop(_cons) xline(0) ///
    mlab(string(@b,"%9.1f")+"("+string(@ll,"%9.1f")+"-"+string(@ul,"%9.1f")+")"+", {it:p} = "+string(@pval,"%9.3f")) ///
    ysc(outergap(45)) mlabposition(9) mlabgap(6.5cm) mlabsize(small)

    Click image for larger version

Name:	Graph.png
Views:	1
Size:	25.8 KB
ID:	1613790

    Comment


    • #3
      Dear Andrew

      Thank you for your reply.
      You might be right - manually entering all values might be the only option. Because simply pushing the labels outside the graph does not look so nice,

      I havent heard of tw scatteri. Thank you for bringing my attention to the immediate forms of graphs! This could be really useful.

      In this case, -tw scatteri does not seem to accept the internal coefplot variables either. So i might have to read out the estimate and CI values into a matrix somehow and feed them to scatteri as mlabels - seems more work than just feeding them manually.

      Thanks again.

      Comment


      • #4
        May be easier to plot each coefficient separately with an invisible CI and marker, added to the original plot and offset the labels. Something along the lines:

        Code:
        sysuse auto, clear
        regress price mpg trunk length turn if foreign==0
        estimates store D
        regress price mpg trunk length turn if foreign==1
        estimates store F
        coefplot (D, offset(0.15) mc(blue) lc(blue)  mlabc(none)) (F, offset(-0.15) mc(red) lc(red) mlabc(none)) ///
        (D, keep(mpg) offset(0.15) mc(none) mlabc(black) mlabgap(2.5cm)) ///
        (F, keep(mpg) offset(-0.15) mc(none) mlabc(black) mlabgap(2.1cm)) ///
        (D, keep(trunk) offset(0.15) mc(none) mlabc(black) mlabgap(2.2cm)) , ///
        drop(_cons) xline(0) ///
        mlab(string(@b,"%9.1f")+"("+string(@ll,"%9.1f")+"-"+string(@ul,"%9.1f")+")"+", {it:p} = "+string(@pval,"%9.3f")) ///
        xsc(r(-1000 2000)) nokey
        Click image for larger version

Name:	Graph.png
Views:	1
Size:	17.4 KB
ID:	1613853

        Last edited by Andrew Musau; 09 Jun 2021, 02:42.

        Comment


        • #5
          Dear Andrew

          This is definitely an interesting idea to fiddle around with. I was thinking of feeding the list of models to a local then extracting the estimates and CIs, and finally constructing a string which can be fed either to a second y-axis label or to -iscatter (invisible with markerlabels). The extracting is rather simple. However - constructing strings containing double quotes (required for iscatter mlabels and y-axis labels) from macros proves once again more than difficult.

          As soon as macros contain string derived from code it seems impossible to get the resulting string into a new string variable with quotes around it - instead it shows the programming code

          Here my working example:

          Code:
          local x = 4+6 // x-coordinate for iscatter
          local y = 5+7 //y-coordinate for iscatter
          local o 1.5456381 // OR from a model
          local or round(`o', 0.01) //rounding the OR - could also use string(`o', "%9.2f") - would not work either
          
          *This works for bizarre reasons and shows quotes around OR - the ` in front of ", is unmatched!
          local string0 `x' " " `y' `", "OR=`o'"
          di `string0'
          
          di `or' //the rounded OR
          
          *This will not show the number in `or' - it will show the code
          
          local string1 `x' " " `y' `", "OR=`or'"
          di `string1'
          I have tried all kinds of fancy combinations `"`or"'" "``or''" and read the manual on macros and double quotes, but nothing works.

          Maybe this belongs to a new post though.

          Thank you for helping out!

          Comment


          • #6
            Well - it is still impossible to get the `or' number values to show in above code. But turns out you just can leave the `or' outside the double quotes and then use ` " " " ' to insert a quote as string in front. I tried that earlier and failed. This is due to the (undocumented?) fact that STATA will not allow this code combination as the FIRST sign in a local macro (where I tested it) . Luckily i shall have 2 numbers first. So the following works
            Code:
             local string1 `x' " " `y' `"""' ", OR="`or' `"""' di `string1'
            Result: 10 12, "OR=1.55"

            Still would be fun to see how to extract results of macros directly into a string inside double quotes.

            Hopefully i can present a finished solution later for the interested.

            Comment


            • #7
              Code:
              local x = 4+6 
              local y = 5+7 
              local o `:di %9.2f 1.5456381'
              local string1  `x' " " `y', `" "OR= `o'" "'
              Res.:

              Code:
              . di `string1'
              10 12  "OR= 1.55"

              Comment


              • #8
                Here is my attempt to print the coefficient values on the right axis:

                Code:
                sysuse auto, clear
                regress price mpg trunk length turn if foreign==0
                estimates store D
                regress price mpg trunk length turn if foreign==1
                estimates store F
                
                gen xpos = 1000
                coefplot D F, drop(_cons) xline(0) mlabel(string(@b,"%9.1f")) mlabcolor(none) ///
                    addplot(scatter @at xpos, ms(i) mlab(@mlbl) mlabcolor(black) mlabgap(3) mlabsize(vsmall)) ///
                    graphr(margin(r=10))
                This is not very elegant as you have to fiddle around with the values for xpos and the right margin of the graph, but it is the best I could come up with. If you want to right-align the values, you could do the following:

                Code:
                coefplot D F, drop(_cons) xline(0) mlabel(string(@b,"%9.1f")) mlabcolor(none) ///
                    addplot(scatter @at xpos, ms(i) mlab(@mlbl) mlabcolor(black) mlabpos(9) mlabgap(-10) mlabsize(vsmall)) ///
                    graphr(margin(r=10))

                Comment


                • #9
                  Dear Andrew

                  This was a clever way of defining a local. Thank you, also for your corrections of the quotes. Will this make it easier to save a string into a local? I am flabbergasted, how difficult it is to have STATA passing concatenated string as a string to another program. It keeps inserting the code that constructed the string macro rather than the string value. This makes it impossible to construct a string for an immediate scatter plot á la X Y "Number as text(more numbers), p=###". Because when inserting such a macro into -scatteri it is presented with -string(###,"%9.1f... etc, which it cannot understand. So i have given up on this way to create the lables.

                  I am honored to see the author of Coefplot visiting this thread. The proposed solution seemed to be the least cumbersome as it enables use of the internal variables of coefplot.
                  This is a clever method to make markerlabels and then "copy" them with the internal variable @mlbl to an added plot.


                  Below find a example with original data where i added the -preserve to leave the dataset unchanged. Also instead of using mlabcolor(none), (which leaves the original marker labels), I use a huge mlabgap value to displace the original marker labels outside the graph, thus only leaving the ones added with the invisible scatter plot.

                  Ironically, the labels are on the left side here, not right.

                  Code:
                  preserve
                  gen xpos=0.2
                  coefplot (i_cvd_dysp*, label(Univariable) offset (0.25) ///
                  mlabel(string(@b,"%9.1f")+"("+string(@ll,"%9.1f")+"-"+string(@ul,"%9.1f")+")"+", {it:p} = "+string(@pval,"%9.3f")) ///
                              mlabposition(3) mlabgap(100) mlabsize(vtiny)  ) ///
                  (adj_i_cvd_dysp*, label(Multivariable) offset (-0.25) ///
                      mlabel(string(@b,"%9.1f")+"("+string(@ll,"%9.1f")+"-"+string(@ul,"%9.1f")+")"+", {it:p} = "+string(@pval,"%9.3f")) ///
                              mlabposition(6) mlabgap(100) mlabsize(vtiny) ), ///
                  eform xline(1)  sort ///
                  keep(E_E__Avg TR_maxPG pulmpress tapse2) ///
                  addplot(scatter @at xpos, msymbol(i) ///
                  mlab(@mlbl) mlabposition(9) mlabcolor(black) mlabgap(2))
                  This produces the following graph which is a nice starting point for further adjustments.
                  Click image for larger version

Name:	CoefMarkesEx.png
Views:	1
Size:	32.7 KB
ID:	1614004



                  Thank you for your efforts, gentlemen!
                  Last edited by Nino Landler; 09 Jun 2021, 16:05.

                  Comment


                  • #10
                    I put some more though into this. A good way to plot the information would be to include it in the plot as tick labels on a secondary axis. This cannot be done directly in coefplot. However, you can use a first call to collect the information and then use a second call to include the labels in the plot. This requires a bit of programming. Here is a little command that returns the list of labels in s():

                    Code:
                    capt program drop coefplot_mlbl
                    *! version 1.0.0  10jun2021  Ben Jann
                    program coefplot_mlbl, sclass
                        _parse comma plots 0 : 0
                        syntax [, MLabel(passthru) * ]
                        if `"`mlabel'"'=="" local mlabel mlabel(string(@b))
                        preserve
                        qui coefplot `plots', `options' `mlabel' generate replace nodraw
                        sreturn clear
                        tempvar touse
                        qui gen byte `touse' = __at<.
                        mata: st_global("s(mlbl)", ///
                            invtokens((strofreal(st_data(.,"__at","`touse'")) :+ " " :+ ///
                            "`" :+ `"""' :+ st_sdata(.,"__mlbl","`touse'") :+ `"""' :+ "'")'))
                        sreturn local plots `"`plots'"'
                        sreturn local options `"`options'"'
                    end
                    You can use the program as follows:

                    Code:
                    . sysuse auto, clear
                    (1978 automobile data)
                    
                    . qui regress price mpg trunk length turn if foreign==0
                    
                    . estimates store D
                    
                    . qui regress price mpg trunk length turn if foreign==1
                    
                    . estimates store F
                    
                    . coefplot_mlbl D F, drop(_cons) xline(0)
                    
                    . sret list
                    
                    macros:
                                s(options) : "drop(_cons) xline(0)"
                                  s(plots) : "D F"
                                   s(mlbl) : ".8333333 `"-186.1083"' 1.166667 `"-55.72135"' 1.833333 .."
                    The idea is that you specify the full coefplot syntax to ensure that the returned labels are consistent with the graph you want to create. The labels are returned in s(mlbl); the coefplot command is returned in two parts in s(plots) and s(options). You can then type:

                    Code:
                    coefplot `s(plots)', `s(options)' ///
                        ymlabel(`s(mlbl)', angle(0) notick axis(2)) ///
                        yaxis(1 2) yscale(alt) yscale(axis(2) alt) ///
                        ylabel(none, axis(2)) yti("", axis(2))
                    Click image for larger version

Name:	g1.png
Views:	1
Size:	29.7 KB
ID:	1614119


                    Note that the s() returns will only available immediately after running coefplot_mlbl.

                    When calling coefplot_mlbl you can specify option mlabel() in the usual way to determine the contents of the labels:

                    Code:
                    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) ///
                        ylabel(none, axis(2)) yti("", axis(2))
                    Click image for larger version

Name:	g2.png
Views:	2
Size:	28.5 KB
ID:	1614120


                    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:

                    Code:
                    capt program drop coefplot_ymlbl
                    *! version 1.0.0  10jun2021  Ben Jann
                    program coefplot_ymlbl
                        _parse comma plots 0 : 0
                        syntax [, MLabel(str asis) * ]
                        _parse comma mlspec mlopts : mlabel
                        local mlopts = substr(`"`mlopts'"', 2, .) // remove leading comma
                        if `"`mlspec'"'!="" local mlabel mlabel(`mlspec')
                        else                local mlabel
                        coefplot_mlbl `plots', `options' `mlabel'
                        coefplot `plots',  ///
                            yaxis(1 2) yscale(alt) yscale(axis(2) alt) ///
                            ylabel(none, axis(2)) yti("", axis(2)) ///
                            ymlabel(`s(mlbl)', axis(2) angle(0) `mlopts') `options'
                    end
                    You could then type:

                    Code:
                    coefplot_ymlbl D F, drop(_cons) xline(0)
                    Click image for larger version

Name:	g3.png
Views:	1
Size:	29.7 KB
ID:	1614121


                    Or:

                    Code:
                    coefplot_ymlbl D F, drop(_cons) xline(0) ///
                        mlabel(string(@b,"%9.1f"), notick labsize(small) labcolor(blue))
                    Click image for larger version

Name:	g4.png
Views:	1
Size:	28.0 KB
ID:	1614122


                    Or:

                    Code:
                    coefplot_ymlbl D F, drop(_cons) xline(0) ///
                        mlabel("p = "+string(@pval,"%9.3f"), notick)
                    Click image for larger version

Name:	g5.png
Views:	2
Size:	28.5 KB
ID:	1614123


                    Command coefplot_mlbl does not make a difference between the models in the plot. If you want to style the values differently depending on model, you have to create multiple lists of labels. Here's a variant of the command to this end:

                    Code:
                    capt program drop coefplot_mlbl2
                    *! version 1.0.0  10jun2021  Ben Jann
                    program coefplot_mlbl2, sclass
                        _parse comma plots 0 : 0
                        syntax [, MLabel(passthru) * ]
                        if `"`mlabel'"'=="" local mlabel mlabel(string(@b))
                        preserve
                        qui coefplot `plots', `options' `mlabel' generate replace nodraw
                        sreturn clear
                        tempvar touse
                        qui gen byte `touse' = 0
                        local nplots = r(n_plots)
                        forv i = 1/`nplots' {
                            qui replace `touse' = __plot==`i' & __at<.
                            mata: st_global("s(mlbl`i')", ///
                                invtokens((strofreal(st_data(.,"__at","`touse'")) :+ " " :+ ///
                                "`" :+ `"""' :+ st_sdata(.,"__mlbl","`touse'") :+ `"""' :+ "'")'))
                        }
                        sreturn local plots `"`plots'"'
                        sreturn local options `"`options'"'
                    end
                    Example:

                    Code:
                    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) ///
                        ylabel(none, axis(2)) yti("", axis(2))
                    Click image for larger version

Name:	g6.png
Views:	1
Size:	28.5 KB
ID:	1614124


                    I hope this is helpful.
                    ben

                    Comment


                    • #11
                      "thought" I mean

                      Comment


                      • #12
                        Dearest Ben Jann

                        Helpful is not quite the term...
                        I am amazed! This was actually my first thought - using tick marks on an alt Y axis. But i do not have the skills to do this as I have not progressed past simple macro programming (and I even struggle with that). Also the ability to have the same colors for the estimates on the y-axis as in the plot was one I regretted to lose. How foresightful of you to even provide a program that re-enables that!

                        Also the wrapper is neat - i need to learn this, because my coefplot code is often huge with 10+ lines.

                        So I am beyond greatful!

                        And I think others will find this very useful also, as we all move away from using tables to visualize model results (I believe we will). Your program coefplot (along with eststo, and esttab) is surely one of the best extensions to STATA around.

                        I was planning to reference coefplot in my article anyway, but now I will also put You in the Acknowledment section of the final article

                        Do you think the ylabels kan be put on the inside of the graph (kan be useful sometimes). It does not seem to be the case - and it is not that important.

                        Many thanks again!

                        Kindest regards Nino

                        Comment


                        • #13
                          There is no option in Stata graphs to place axis labels on the inside, as far as I know, but you can work with negative gaps:

                          Code:
                          capt program drop coefplot_mlbl2
                          *! version 1.0.0  10jun2021  Ben Jann
                          program coefplot_mlbl2, sclass
                              _parse comma plots 0 : 0
                              syntax [, MLabel(passthru) * ]
                              if `"`mlabel'"'=="" local mlabel mlabel(string(@b))
                              preserve
                              qui coefplot `plots', `options' `mlabel' generate replace nodraw
                              sreturn clear
                              tempvar touse
                              qui gen byte `touse' = 0
                              local nplots = r(n_plots)
                              forv i = 1/`nplots' {
                                  qui replace `touse' = __plot==`i' & __at<.
                                  mata: st_global("s(mlbl`i')", ///
                                      invtokens((strofreal(st_data(.,"__at","`touse'")) :+ " " :+ ///
                                      "`" :+ `"""' :+ st_sdata(.,"__mlbl","`touse'") :+ `"""' :+ "'")'))
                              }
                              sreturn local plots `"`plots'"'
                              sreturn local options `"`options'"'
                          end
                          sysuse auto, clear
                          qui regress price mpg trunk length turn if foreign==0
                          estimates store D
                          qui regress price mpg trunk length turn if foreign==1
                          estimates store F
                          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 labgap(-10) axis(2) add custom labcolor(navy)) ///
                              ymlabel(`s(mlbl2)', angle(0) notick labgap(-10) axis(2) add custom labcolor(maroon)) ///
                              yaxis(1 2) yscale(alt) yscale(axis(2) alt) ///
                              ylabel(none, axis(2)) yti("", axis(2))
                          Click image for larger version

Name:	gr1.png
Views:	1
Size:	28.6 KB
ID:	1614242

                          Comment


                          • #14
                            By the way, based on the above discussion, I now added a section on "Marker labels as axis labels" to the online documentation of coefplot; see http://repec.sowi.unibe.ch/stata/coe...ers.html#h-3-5.

                            I also added a section called "Labels at end of confidence intervals" that illustrates how to place the maker labels next to the confidence spikes; see http://repec.sowi.unibe.ch/stata/coe...ers.html#h-3-4.

                            ben

                            Comment


                            • #15
                              Dear Ben

                              Brilliant! This has then really been a fruitfull thread, it would appear

                              Best regards Nino

                              Comment

                              Working...
                              X