Announcement

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

  • How to create a 2×2 multi-panel graph combining horizontal bar charts and coefficient plots

    Hi everyone,

    I would like to create in Stata a figure similar to the one shown at the end of this post, using the example dataset provided below (generated with dataex).

    The figure consists of four panels arranged in a 2×2 layout:

    • The top panels show horizontal bar charts with the share of workers in three categories:
    (i) wage increases, (ii) same wage, and (iii) wage declines
    Each category has two bars, corresponding to two groups: BF and Non-BF.

    • The bottom panels show coefficient plots with point estimates and 95% confidence intervals.
    These coefficients represent the difference (in percentage points) between BF and Non-BF for each category.

    • The left column corresponds to job stayers, while the right column corresponds to job movers.

    • The y-axis categories are shared across panels and ordered from top to bottom as:
    wage increases, same wage, and wage declines.

    Ideally, I would like:

    1. The bars in the top panels to be horizontal and grouped by category.
    2. The coefficient plots in the bottom panels to also be horizontal (with x-axis in percentage points).
    3. The y-axis categories to align perfectly across the top and bottom panels.
    4. A vertical reference line at zero in the bottom panels.
    5. A clean layout where the two columns correspond to "Job Stayers" and "Job Movers".

    Could you please advise on how to implement this type of multi-panel figure in Stata?

    Many thanks in advance!

    Best,
    Otavio

    Code:
    * Example generated by -dataex-. For more info, type help dataex
    clear
    input str8 panel str6 group str20 category float(share coef ci_low ci_high)
    
    "Stayers" "NonBF" "wage increases" 0.50 0.05 0.02 0.07
    "Stayers" "BF" "wage increases" 0.55 0.05 0.02 0.07
    
    "Stayers" "NonBF" "same wage" 0.30 -0.02 -0.04 0.00
    "Stayers" "BF" "same wage" 0.25 -0.02 -0.04 0.00
    
    "Stayers" "NonBF" "wage declines" 0.20 0.03 0.01 0.05
    "Stayers" "BF" "wage declines" 0.20 0.03 0.01 0.05
    
    "Movers" "NonBF" "wage increases" 0.40 0.04 0.02 0.06
    "Movers" "BF" "wage increases" 0.45 0.04 0.02 0.06
    
    "Movers" "NonBF" "same wage" 0.25 -0.01 -0.03 0.01
    "Movers" "BF" "same wage" 0.20 -0.01 -0.03 0.01
    
    "Movers" "NonBF" "wage declines" 0.35 0.02 0.00 0.04
    "Movers" "BF" "wage declines" 0.35 0.02 0.00 0.04
    
    end
    Click image for larger version

Name:	mock_horizontal_textlabels.png
Views:	1
Size:	83.8 KB
ID:	1785608

  • #2
    This was perhaps overlooked for various reasons, including possibly a guess that it was just the same question as, or a similar question to, https://www.statalist.org/forums/for...erent-y-scales

    It is puzzling and challenging on several levels.

    Wanting a different scale for difference and share makes perfect sense.

    Wanting a different scale for movers and stayers doesn't make sense to me. It's then hard to compare values that seem as if they should be compared.

    Putting movers and stayers in separate graphs doesn't make sense to me. Same reason.

    The distinction between BF and nonBF, whatever that means, applies to one pair of graphs but not the other. I am happy that matches the economics, but it makes graph design awkward.

    It seems that you had some Stata graph code but you don't show it. Showing 0.5 and 1.5 makes no sense if you have just have 3 categories. And you don't need 0 1 2 either.

    In terms of your ideals (NJC is me)

    1. The bars in the top panels to be horizontal and grouped by category.

    NJC: I don't see that bars are the best design here. See your other thread.

    2. The coefficient plots in the bottom panels to also be horizontal (with x-axis in percentage points).

    NJC: OK

    3. The y-axis categories to align perfectly across the top and bottom panels.

    NJC: OK

    4. A vertical reference line at zero in the bottom panels.

    NJC: OK

    5. A clean layout where the two columns correspond to "Job Stayers" and "Job Movers".

    NJC: Disagree, for reasons stated.


    I got this far after much messing around.

    Detail: "decrease" is a perfect antonym in English for "increase". "decline" has similar but not quite identical meaning.

    Code:
    * Example generated by -dataex-. For more info, type help dataex
    clear
    input str8 panel str6 group str20 category float(share coef ci_low ci_high)
    
    "Stayers" "NonBF" "wage increases" 0.50 0.05 0.02 0.07
    "Stayers" "BF" "wage increases" 0.55 0.05 0.02 0.07
    
    "Stayers" "NonBF" "same wage" 0.30 -0.02 -0.04 0.00
    "Stayers" "BF" "same wage" 0.25 -0.02 -0.04 0.00
    
    "Stayers" "NonBF" "wage decreases" 0.20 0.03 0.01 0.05
    "Stayers" "BF" "wage decreases" 0.20 0.03 0.01 0.05
    
    "Movers" "NonBF" "wage increases" 0.40 0.04 0.02 0.06
    "Movers" "BF" "wage increases" 0.45 0.04 0.02 0.06
    
    "Movers" "NonBF" "same wage" 0.25 -0.01 -0.03 0.01
    "Movers" "BF" "same wage" 0.20 -0.01 -0.03 0.01
    
    "Movers" "NonBF" "wage decreases" 0.35 0.02 0.00 0.04
    "Movers" "BF" "wage decreases" 0.35 0.02 0.00 0.04
    
    end
    
    drop if missing(panel)
    
    label def panel2 1 Stayers 2 Movers
    encode panel, gen(panel2) label(panel2)
    
    gen change = cond(strpos(category, "inc"), 1, cond(strpos(category, "same"), 2, 3))
    label def change 1 `" "wage" "increases" "' 2 `" "wage" "same" "' 3 `" "wage" "decreases" "'
    label val change change
    gen change2 = cond(panel2 == 1, change + 0.15, change - 0.15)
    
    separate share, by(panel2)
    separate share1, by(group)
    separate share2, by(group)
    
    local col1 stc1
    local col2 stc2
    
    #delimit ;
    twoway
    || scatter change2 share11, ms(Oh) mc(`col1') msize(large)
    || scatter change2 share12, ms(+) mc(`col1') msize(large)
    || scatter change2 share21, ms(Oh) mc(`col2') msize(large)
    || scatter change2 share22, ms(+) mc(`col2') msize(large)
    legend(order(1 "Stayers BF" 2 "Stayers NonBF" 3 "Movers BF" 4 "Movers NonBF")  row(2) pos(6))
    yla(1 `" "wage" "increases" "' 2 `" "wage" "same" "' 3 `" "wage"  "decreases" "', labsize(medium) tlc(none)) ytitle("")
    xla(0 "0" 0.2(0.2)0.6, format(%02.1f)) ysc(r(0.75 3.25)) xsc(alt) xtitle(Share) name(ott1, replace) saving(ott1, replace) ;
    
    twoway
    || scatter change2 coef if panel2 == 1, mc(`col1') msize(large)
    || rspike ci_* change2 if panel2 == 1, horizontal lc(`col1')
    || scatter change2 coef if panel2 == 2, mc(`col2') msize(large)
    || rspike ci_* change2 if panel2 == 2, horizontal lc(`col2')
    xtitle(Difference (p.p.)) ysc(r(0.75 3.25)) xsc(alt) xli(0) legend(order(1 "Stayers" 3 "Movers") row(2) pos(6))
    yla(1 `" "wage" "in  
     creases" "' 2 `" "wage" "same" "' 3 `" "wage"  "decreases" "', labsize(medium) tlc(none)) ytitle("")
    xla(-0.05 "-0.05" 0 0.05 "0.05" 0.1 "0.1") name(ott2, replace) saving(ott2, replace) ;
    
    #delimit cr
    
    graph combine ott1 ott2

    Click image for larger version

Name:	ott.png
Views:	0
Size:	0
ID:	1785640
    Last edited by Nick Cox; 08 Apr 2026, 08:34.

    Comment


    • #3
      Click image for larger version

Name:	ott.png
Views:	1
Size:	33.2 KB
ID:	1785642


      Here's the graph again.

      Comment


      • #4
        Many thanks, Nick Cox !! This is amazing.

        Comment

        Working...
        X