Announcement

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

  • How to ensure that points are overlaid on a graph with two axes, where one axis is a linear transformation of the other

    In this toy example, I have a y-axis variable, weight, and an x-axis variable, mpg. I create a percent change version of weight (relative to mpg==26). Thus, the percent change version has a correlation of 1.0 with the original weight variable because it is just a linear transformation. I would like to plot the data with two axes, one for the count and the other for the percentage change, with either one dot or the dots exactly overlaid, so that the axes perfectly correspond to each other. How can I do this? There is an issue with the below code where Stata scales the axes differently.

    Code:
    sysuse auto2, clear
    
    *get one value per mpg
    collapse (mean) weight, by(mpg)
    
    *get mean value when mpg is 26
    sum weight if mpg==26
    local baseline = `r(mean)'
    
    *calculate percent change relative to baseline
    gen perc_change = 100*((weight - `baseline')/`baseline')
    
    *confirm that perc_change is a linear transformation of weight
    corr perc_change weight
    
    twoway    (scatter weight mpg, yaxis(1)) ///
            (scatter perc_change mpg, yaxis(2))

    Screenshot 2026-02-12 at 4.55.37 PM.png

    You can see that the percent change scaling is more narrow than the original variable.
    Last edited by Todd Jones; 12 Feb 2026, 16:09.

  • #2
    I believe the trick is to "lock in" the minimums and the maximums for both y axes in ylabel definitions:

    Code:
    sum weight
    local weight_max = r(max)
    local weight_min = r(min)
    
    sum perc_change
    local perc_change_max = r(max)
    local perc_change_min = r(min)
    
    twoway    (scatter weight mpg, yaxis(1)) ///
            (scatter perc_change mpg, yaxis(2)), ///
            ylabel(`weight_max' 2193.33 `weight_min', axis(1)) ///
            ylabel(`perc_change_max' 0 `perc_change_min', axis(2))

    Comment


    • #3
      Here is another approach. I inverted the relationship and called up mylabels from the Stata Journal. That is, there is no need to plot two y axis variables and overlay them, because that is a roundabout way to plot one variable. The need is to get another scale with different axis labels.

      Code:
      . search mylabels, sj
      
      Search of official help files, FAQs, Examples, and Stata Journals
      
      SJ-24-1 gr0092_1  . . . . . . . . . . . . . . . . Software update for mylabels
              (help nicelabels, mylabels, myticks if installed) . . . . .  N. J. Cox
              Q1/24   SJ 24(1):182--184
              fixes a bug that could bite if the options myscale() and
              clean were specified together
      
      SJ-22-4 gr0092  . . . . . . . . . . . . Speaking Stata: Automating axis labels
              (help nicelabels, mylabels, myticks if installed) . . . . .  N. J. Cox
              Q4/22   SJ 22(4):975--995
              provides commands to handle two common problems with graph
              axis labels: decide in advance on some "nice" numbers to
              use on one or both axes and show particular labels on some
              transformed scale

      Code:
      sysuse auto2, clear
      
      local ytitle : var label weight
      
      *get one value per mpg
      collapse (mean) weight, by(mpg)
      
      *get mean value when mpg is 26
      sum weight if mpg==26
      local baseline = `r(mean)'
      
      *calculate percent change relative to baseline
      gen perc_change = 100*((weight - `baseline')/`baseline')
      
      *confirm that perc_change is a linear transformation of weight
      corr perc_change weight
      
      mylabels -50(50)100, myscale(`baseline' * (1 + (@ / 100))) local(newla)
      
      twoway scatter weight  mpg, ms(O) yaxis(1 2)  ytitle(`ytitle') ytitle("% change from baseline", axis(2)) yla(`newla', axis(2)) yline(`baseline', lp(solid) lc(gs12))
      Click image for larger version

Name:	todd.png
Views:	1
Size:	41.2 KB
ID:	1784826

      Last edited by Nick Cox; 13 Feb 2026, 03:15.

      Comment


      • #4
        Thank you, Evgeny and Nick.

        Nick, thank you for pointing out mylabels - I really like this approach. If I want to do this with many plots in a loop, it looks like the main thing is to programatically determine which range should replace -50(50)100?

        Comment


        • #5
          Indeed, but the cognate command nicelabels can help there, References as in #3.


          Code:
          nicelabels perc_change , local(newla)
          
          mylabels `newla', myscale(`baseline' * (1 + (@ / 100))) local(newla)
          
          twoway scatter weight mpg, ms(O) yaxis(1 2) ytitle(`ytitle') ytitle("% change from baseline", axis(2)) yla(`newla', axis(2)) yline(`baseline', lp(solid) lc(gs12))

          Comment


          • #6
            Amazing - thank you very much!

            Comment


            • #7
              The command names aren't very consistent. Perhaps it should be mynicelabels. but it isn't.

              Comment

              Working...
              X