Announcement

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

  • Different test results using lincom and margins, over() with levels of three-way interaction

    I am trying to test for wage differentials over job types defined by the 8 levels of a three-way interaction. I can't figure out why the results are different when I use a lincom test of a specific combination of coefficients versus margins, over() to test each job type against the reference category.

    I'm using xtreg, fe in Stata 16.1 to fit a fixed-effects (within person) regression of log wages on the three-way interaction (PT hours * 1yr tenure * unionized) and controls. Below I have included an example using the nlswork dataset, which has the same basic structure as my data.

    I would appreciate any clarification or guidance you could offer on the appropriate command(s) for obtaining the predicted wage differential between job_type=1 (nonunion, FT, < 1yr tenure) and the other levels (e.g. job_type=4, nonunion, PT, >= 1yr tenure). I haven't found a comparable example using margins, over() in the manual or previous forum posts.


    Code:
    * Contrasting levels of a three-way interaction
    // Use NLS sample data
    webuse nlswork, clear
    
    // Generate indicators for thresholds of continuous variables
    gen pt_hrs = cond(hours < 35, 1, cond(!mi(hours), 0, .))
    gen tenure_1yr = cond(tenure < 1, 0, cond(!mi(tenure), 1, .))
    
    // Generate variable for 8 levels of three-way interaction
    egen job_type = group(union pt_hrs tenure_1yr), label
    tab job_type
    
    // Fixed-effects regression of ln(wage) on three-way interaction and controls
    xtset id year
    xtreg ln_wage age ttl_exp i.south i.c_city i.union##i.pt_hrs##i.tenure_1yr i.occ_code, fe vce(robust)
    
    // Wald test of (exponentiated) combination of coefficients
    lincom 1.pt_hrs + 1.tenure_1yr + 1.pt_hrs#1.tenure_1yr, ef
    * Predicted differential is 7.58% (s.e. = 1.5%)
    
    // Test using margins, over() with job_type == 1 (0 0 0) as reference
    margins, over(r.job_type) post
    lincom _b[r4vs1.job_type], ef
    * Predicted differential is 10.1% (s.e. = 1.56%)
    Attached Files
    Last edited by Peter Fugiel; 26 Sep 2020, 15:32.

  • #2
    The difference you see is because of what -margins- is trying to calculate and what -lincom- is trying to calculate.. -margins- is calculating average predicted values of ln(wage) averaged over all of the observations within each job-type. But those observations have variation on the values of all those other variables in your -xtreg- command. So you average predicted values over those observations, which are influenced by all of the variables in the model. Note that these calculations, because you used the -over()- option, are not adjusted for the full sample distribution of those other variables--each of those predicted margins is separately adjusted to the distributions of those variables for each job type.

    By contrast, the -lincom- expression you calculate is a straightforward calculation on the coefficients. But the calculations in the regression lead to these coefficients being adjusted for the distribution of all model variables in the full estimation sample.

    So you are comparing estimates that adjust for the other model variables differently. If you change your code to eliminate all those other variables, then you do get the same results either way:

    Code:
    . // Fixed-effects regression of ln(wage) on three-way interaction and controls
    . xtset id year
           panel variable:  idcode (unbalanced)
            time variable:  year, 68 to 88, but with gaps
                    delta:  1 unit
    
    . // xtreg ln_wage age ttl_exp i.south i.c_city i.union##i.pt_hrs##i.tenure_1yr i.occ_code, fe vce(robust)
    . xtreg ln_wage i.union##i.pt_hrs##i.tenure_1yr
    
    Random-effects GLS regression                   Number of obs     =     18,976
    Group variable: idcode                          Number of groups  =      4,134
    
    R-sq:                                           Obs per group:
         within  = 0.0529                                         min =          1
         between = 0.2016                                         avg =        4.6
         overall = 0.1129                                         max =         12
    
                                                    Wald chi2(7)      =    1407.99
    corr(u_i, X)   = 0 (assumed)                    Prob > chi2       =     0.0000
    
    -----------------------------------------------------------------------------------------
                    ln_wage |      Coef.   Std. Err.      z    P>|z|     [95% Conf. Interval]
    ------------------------+----------------------------------------------------------------
                    1.union |   .1309315   .0145117     9.02   0.000      .102489     .159374
                   1.pt_hrs |  -.0524859   .0108201    -4.85   0.000    -.0736929    -.031279
                            |
               union#pt_hrs |
                       1 1  |  -.0073441   .0311837    -0.24   0.814    -.0684631    .0537749
                            |
               1.tenure_1yr |   .1616999   .0070489    22.94   0.000     .1478842    .1755156
                            |
           union#tenure_1yr |
                       1 1  |  -.0300582   .0156652    -1.92   0.055    -.0607613     .000645
                            |
          pt_hrs#tenure_1yr |
                       1 1  |  -.0196444   .0131779    -1.49   0.136    -.0454726    .0061839
                            |
    union#pt_hrs#tenure_1yr |
                     1 1 1  |   .1582699   .0354985     4.46   0.000     .0886941    .2278457
                            |
                      _cons |   1.597183    .008193   194.94   0.000     1.581125    1.613241
    ------------------------+----------------------------------------------------------------
                    sigma_u |  .34923411
                    sigma_e |    .267007
                        rho |   .6310992   (fraction of variance due to u_i)
    -----------------------------------------------------------------------------------------
    
    . 
    . // Wald test of (exponentiated) combination of coefficients
    . lincom 1.pt_hrs + 1.tenure_1yr + 1.pt_hrs#1.tenure_1yr, ef
    
     ( 1)  1.pt_hrs + 1.tenure_1yr + 1.pt_hrs#1.tenure_1yr = 0
    
    ------------------------------------------------------------------------------
         ln_wage |     exp(b)   Std. Err.      z    P>|z|     [95% Conf. Interval]
    -------------+----------------------------------------------------------------
             (1) |   1.093703   .0109946     8.91   0.000     1.072365    1.115466
    ------------------------------------------------------------------------------
    
    . * Predicted differential is 7.58% (s.e. = 1.5%)
    . 
    . // Test using margins, over() with job_type == 1 (0 0 0) as reference
    . margins, over(r.job_type) post
    
    Contrasts of predictive margins                 Number of obs     =     18,976
    Model VCE    : Conventional
    
    Expression   : Linear prediction, predict()
    over         : job_type
    
    -----------------------------------------------------
                      |         df        chi2     P>chi2
    ------------------+----------------------------------
             job_type |
    (0 0 1 vs 0 0 0)  |          1      526.22     0.0000
    (0 1 0 vs 0 0 0)  |          1       23.53     0.0000
    (0 1 1 vs 0 0 0)  |          1       79.39     0.0000
    (1 0 0 vs 0 0 0)  |          1       81.40     0.0000
    (1 0 1 vs 0 0 0)  |          1      778.90     0.0000
    (1 1 0 vs 0 0 0)  |          1        6.78     0.0092
    (1 1 1 vs 0 0 0)  |          1      364.10     0.0000
               Joint  |          7     1407.99     0.0000
    -----------------------------------------------------
    
    -------------------------------------------------------------------
                      |            Delta-method
                      |   Contrast   Std. Err.     [95% Conf. Interval]
    ------------------+------------------------------------------------
             job_type |
    (0 0 1 vs 0 0 0)  |   .1616999   .0070489      .1478842    .1755156
    (0 1 0 vs 0 0 0)  |  -.0524859   .0108201     -.0736929    -.031279
    (0 1 1 vs 0 0 0)  |   .0895696   .0100527      .0698667    .1092724
    (1 0 0 vs 0 0 0)  |   .1309315   .0145117       .102489     .159374
    (1 0 1 vs 0 0 0)  |   .2625732   .0094082      .2441334    .2810131
    (1 1 0 vs 0 0 0)  |   .0711015   .0273005      .0175935    .1246095
    (1 1 1 vs 0 0 0)  |   .3413688   .0178901      .3063048    .3764328
    -------------------------------------------------------------------
    
    . lincom _b[r4vs1.job_type], ef
    
     ( 1)  r4vs1.job_type = 0
    
    ------------------------------------------------------------------------------
                 |     exp(b)   Std. Err.      z    P>|z|     [95% Conf. Interval]
    -------------+----------------------------------------------------------------
             (1) |   1.093703   .0109946     8.91   0.000     1.072365    1.115466
    ------------------------------------------------------------------------------

    Comment


    • #3
      Alternatively, you can also get the results to agree if you include the other variables in the model but then use -margins- in a way that adjusts for the full-sample distribution of all the covariates:

      Code:
      . xtreg ln_wage age ttl_exp i.south i.c_city i.union##i.pt_hrs##i.tenure_1yr i.occ_code, fe vce(robust)
      
      Fixed-effects (within) regression               Number of obs     =     18,901
      Group variable: idcode                          Number of groups  =      4,126
      
      R-sq:                                           Obs per group:
           within  = 0.1872                                         min =          1
           between = 0.3736                                         avg =        4.6
           overall = 0.2934                                         max =         12
      
                                                      F(23,4125)        =      76.11
      corr(u_i, Xb)  = 0.1976                         Prob > F          =     0.0000
      
                                              (Std. Err. adjusted for 4,126 clusters in idcode)
      -----------------------------------------------------------------------------------------
                              |               Robust
                      ln_wage |      Coef.   Std. Err.      t    P>|t|     [95% Conf. Interval]
      ------------------------+----------------------------------------------------------------
                          age |  -.0128293   .0016901    -7.59   0.000    -.0161428   -.0095157
                      ttl_exp |   .0383339   .0022144    17.31   0.000     .0339925    .0426753
                      1.south |  -.0630837   .0206182    -3.06   0.002    -.1035065    -.022661
                     1.c_city |   .0245228   .0114368     2.14   0.032     .0021006    .0469451
                      1.union |   .1020516   .0164943     6.19   0.000     .0697139    .1343893
                     1.pt_hrs |   .0025544   .0142808     0.18   0.858    -.0254436    .0305524
                              |
                 union#pt_hrs |
                         1 1  |  -.0392912   .0414755    -0.95   0.344    -.1206056    .0420231
                              |
                 1.tenure_1yr |   .0933306   .0074617    12.51   0.000     .0787016    .1079596
                              |
             union#tenure_1yr |
                         1 1  |  -.0360954   .0164085    -2.20   0.028    -.0682648   -.0039259
                              |
            pt_hrs#tenure_1yr |
                         1 1  |  -.0228084   .0167728    -1.36   0.174    -.0556923    .0100754
                              |
      union#pt_hrs#tenure_1yr |
                       1 1 1  |   .1659487    .047206     3.52   0.000     .0733994    .2584979
                              |
                     occ_code |
                           2  |   .0021942   .0182128     0.12   0.904    -.0335127    .0379011
                           3  |  -.0794512   .0148901    -5.34   0.000    -.1086438   -.0502587
                           4  |  -.1118976   .0269338    -4.15   0.000    -.1647023    -.059093
                           5  |     .00951   .0255428     0.37   0.710    -.0405676    .0595876
                           6  |  -.0200826   .0206797    -0.97   0.332     -.060626    .0204609
                           7  |  -.4315012   .0520664    -8.29   0.000    -.5335795   -.3294229
                           8  |  -.1382763   .0197638    -7.00   0.000    -.1770241   -.0995285
                           9  |   .0066819   .0930453     0.07   0.943     -.175737    .1891007
                          10  |  -.1597528   .0675659    -2.36   0.018    -.2922184   -.0272872
                          11  |    .009136   .0381731     0.24   0.811    -.0657039    .0839758
                          12  |  -.0177634   .0369168    -0.48   0.630    -.0901402    .0546134
                          13  |  -.0568937   .0323615    -1.76   0.079    -.1203397    .0065524
                              |
                        _cons |   1.859806   .0411746    45.17   0.000     1.779082    1.940531
      ------------------------+----------------------------------------------------------------
                      sigma_u |  .35308998
                      sigma_e |  .24759396
                          rho |  .67037145   (fraction of variance due to u_i)
      -----------------------------------------------------------------------------------------
      
      . // xtreg ln_wage i.union##i.pt_hrs##i.tenure_1yr
      . 
      . // Wald test of (exponentiated) combination of coefficients
      . lincom 1.pt_hrs + 1.tenure_1yr + 1.pt_hrs#1.tenure_1yr, ef
      
       ( 1)  1.pt_hrs + 1.tenure_1yr + 1.pt_hrs#1.tenure_1yr = 0
      
      ------------------------------------------------------------------------------
           ln_wage |     exp(b)   Std. Err.      t    P>|t|     [95% Conf. Interval]
      -------------+----------------------------------------------------------------
               (1) |   1.075813   .0150424     5.23   0.000     1.046722    1.105712
      ------------------------------------------------------------------------------
      
      . * Predicted differential is 7.58% (s.e. = 1.5%)
      . 
      . // Test using margins, over() with job_type == 1 (0 0 0) as reference
      . // margins, over(r.job_type) post
      . margins union#pt_hrs#tenure_1yr, post
      
      Predictive margins                              Number of obs     =     18,901
      Model VCE    : Robust
      
      Expression   : Linear prediction, predict()
      
      -----------------------------------------------------------------------------------------
                              |            Delta-method
                              |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
      ------------------------+----------------------------------------------------------------
      union#pt_hrs#tenure_1yr |
                       0 0 0  |   1.669574   .0064164   260.20   0.000     1.656998     1.68215
                       0 0 1  |   1.762905   .0036269   486.06   0.000     1.755796    1.770014
                       0 1 0  |   1.672129   .0121805   137.28   0.000     1.648255    1.696002
                       0 1 1  |   1.742651   .0112386   155.06   0.000     1.720624    1.764678
                       1 0 0  |   1.771626   .0147189   120.36   0.000     1.742778    1.800475
                       1 0 1  |   1.828861   .0079964   228.71   0.000     1.813189    1.844534
                       1 1 0  |   1.734889   .0367947    47.15   0.000     1.662773    1.807005
                       1 1 1  |   1.935265    .028922    66.91   0.000     1.878579    1.991951
      -----------------------------------------------------------------------------------------
      
      . lincom _b[0.union#1.pt_hrs#1.tenure_1yr]-_b[0.union#0.pt_hrs#0.tenure_1yr], ef
      
       ( 1)  - 0bn.union#0bn.pt_hrs#0bn.tenure_1yr + 0bn.union#1.pt_hrs#1.tenure_1yr = 0
      
      ------------------------------------------------------------------------------
                   |     exp(b)   Std. Err.      z    P>|z|     [95% Conf. Interval]
      -------------+----------------------------------------------------------------
               (1) |   1.075813   .0150424     5.23   0.000     1.046731    1.105703
      ------------------------------------------------------------------------------

      Comment


      • #4
        Thanks Clyde Schechter for your reply. If I understand you, the difference is due to margins, over() using only a subset of observations in adjusting for covariates whereas the direct lincom test uses the full estimation sample. Because I am interested in drawing inferences about the full sample, it seems like the margins, over() treatment is not appropriate.

        Can I ask if there is a more efficient way of obtaining the same 7 contrasts from the margins, over() command but using the full distribution of the covariates? I see that the output from margins, pwcompare(effects) is consistent with the regression estimates, but I would prefer to test only the contrasts of interest (i.e. the first seven rows below).

        Code:
        . margins union#pt_hrs#tenure_1yr, pwcompare(effects) post
        
        Pairwise comparisons of predictive margins      Number of obs     =     18,901
        Model VCE    : Robust
        
        Expression   : Linear prediction, predict()
        
        -----------------------------------------------------------------------------------------
                                |            Delta-method    Unadjusted           Unadjusted
                                |   Contrast   Std. Err.      z    P>|z|     [95% Conf. Interval]
        ------------------------+----------------------------------------------------------------
        union#pt_hrs#tenure_1yr |
            (0 0 1) vs (0 0 0)  |   .0933306   .0074617    12.51   0.000     .0787059    .1079553
            (0 1 0) vs (0 0 0)  |   .0025544   .0142808     0.18   0.858    -.0254354    .0305442
            (0 1 1) vs (0 0 0)  |   .0730765   .0139823     5.23   0.000     .0456716    .1004814
            (1 0 0) vs (0 0 0)  |   .1020516   .0164943     6.19   0.000     .0697234    .1343798
            (1 0 1) vs (0 0 0)  |   .1592868   .0115318    13.81   0.000     .1366849    .1818887
            (1 1 0) vs (0 0 0)  |   .0653148   .0377986     1.73   0.084    -.0087691    .1393987
            (1 1 1) vs (0 0 0)  |   .2656902   .0305161     8.71   0.000     .2058797    .3255007
            (0 1 0) vs (0 0 1)  |  -.0907762    .013807    -6.57   0.000    -.1178373    -.063715
            (0 1 1) vs (0 0 1)  |   -.020254   .0131581    -1.54   0.124    -.0460434    .0055353
            (1 0 0) vs (0 0 1)  |    .008721   .0159952     0.55   0.586     -.022629    .0400711
            (1 0 1) vs (0 0 1)  |   .0659562   .0098984     6.66   0.000     .0465558    .0853567
            (1 1 0) vs (0 0 1)  |  -.0280158   .0377003    -0.74   0.457    -.1019071    .0458755
            (1 1 1) vs (0 0 1)  |   .1723596   .0302562     5.70   0.000     .1130586    .2316606
            (0 1 1) vs (0 1 0)  |   .0705221   .0153204     4.60   0.000     .0404947    .1005495
            (1 0 0) vs (0 1 0)  |   .0994972   .0200462     4.96   0.000     .0602074     .138787
            (1 0 1) vs (0 1 0)  |   .1567324   .0161608     9.70   0.000     .1250578    .1884071
            (1 1 0) vs (0 1 0)  |   .0627604   .0386749     1.62   0.105    -.0130411    .1385618
            (1 1 1) vs (0 1 0)  |   .2631358   .0313885     8.38   0.000     .2016155    .3246561
            (1 0 0) vs (0 1 1)  |   .0289751   .0196101     1.48   0.140      -.00946    .0674102
            (1 0 1) vs (0 1 1)  |   .0862103   .0156779     5.50   0.000     .0554821    .1169385
            (1 1 0) vs (0 1 1)  |  -.0077617   .0379846    -0.20   0.838    -.0822102    .0666867
            (1 1 1) vs (0 1 1)  |   .1926137   .0300754     6.40   0.000      .133667    .2515603
            (1 0 1) vs (1 0 0)  |   .0572352    .015243     3.75   0.000     .0273594     .087111
            (1 1 0) vs (1 0 0)  |  -.0367368   .0397373    -0.92   0.355    -.1146205    .0411468
            (1 1 1) vs (1 0 0)  |   .1636386   .0330025     4.96   0.000     .0989548    .2283223
            (1 1 0) vs (1 0 1)  |   -.093972   .0380016    -2.47   0.013    -.1684538   -.0194903
            (1 1 1) vs (1 0 1)  |   .1064034   .0308554     3.45   0.001      .045928    .1668788
            (1 1 1) vs (1 1 0)  |   .2003754    .041917     4.78   0.000     .1182197    .2825311
        -----------------------------------------------------------------------------------------
        
        .
        . lincom _b[0.union#1.pt_hrs#1.tenure_1yr]-_b[0.union#0.pt_hrs#0.tenure_1yr], ef
        
         ( 1)  - 0bn.union#0bn.pt_hrs#0bn.tenure_1yr + 0bn.union#1.pt_hrs#1.tenure_1yr = 0
        
        ------------------------------------------------------------------------------
                     |     exp(b)   Std. Err.      z    P>|z|     [95% Conf. Interval]
        -------------+----------------------------------------------------------------
                 (1) |   1.075813   .0150424     5.23   0.000     1.046731    1.105703
        ------------------------------------------------------------------------------

        Comment


        • #5
          You could use -margins, post- without the -pwcompare- option and then use seven -lincom- commands corresponding to the four differences you are interested in. For example:

          Code:
          margins, at(union = (0) pt_hrs = (0 1) tenure_1yr = (0 1)) ///
              at(union = (1) pt_hrs = (0 1) tenure_1yr = (0 1)) post
          forvalues j = 2/8 {
              lincom _b[`j'._at]  - _b[1._at], ef
          }
          will limit the output to the combinations that interest you and then you do seven -lincom- commands like the one shown above to compare each of those to the 0, 0, 0 job type.

          I don't think you can do this easily with the -pwcompare- option, however, because when you -post- after -pwcompare- the comparisons are not posted. Rather the underlying margins themselves are. So you still have to -lincom- the differences between the base levels, but the output of -margins, pwcompare- does not give you a handle on the correct levels of _at to refer to the particular -margins- you want. So I think you're stuck with using -margins- as illustrated, without pwcompare, and -lincom-ing the differences that interest you.
          Last edited by Clyde Schechter; 26 Sep 2020, 19:46.

          Comment


          • #6
            I think that settles it. I'll use -lincom- after -xtreg- or -margins, at() post- for each contrast of interest from the full model.

            Thank you for answering my questions and clarifying some of the finer points of the various -margins- options. Sometimes searching for an elegant solution leads me away from the more reliable though piecemeal approach.

            Comment

            Working...
            X