Home > Statistics > Exploring results of nonparametric regression models

Exploring results of nonparametric regression models

In his blog post, Enrique Pinzon discussed how to perform regression when we don’t want to make any assumptions about functional form—use the npregress command. He concluded by asking and answering a few questions about the results using the margins and marginsplot commands.

Recently, I have been thinking about all the different types of questions that we could answer using margins after nonparametric regression, or really after any type of regression. margins and marginsplot are powerful tools for exploring the results of a model and drawing many kinds of inferences. In this post, I will show you how to ask and answer very specific questions and how to explore the entire response surface based on the results of your nonparametric regression.

The dataset that we will use includes three covariates—continuous variables x1 and x2 and categorical variable a with three levels. If you would like to follow along, you can use these data by typing use http://www.stata.com/users/kmacdonald/blog/npblog.

Let’s first fit our model.

. npregress kernel y x1 x2 i.a, vce(boot, rep(10) seed(111))
(running npregress on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Bandwidth
------------------------------------
             |      Mean     Effect
-------------+----------------------
          x1 |  .4795422   1.150255
          x2 |  .7305017    1.75222
           a |  .3872428   .3872428
------------------------------------

Local-linear regression                    Number of obs      =          1,497
Continuous kernel : epanechnikov           E(Kernel obs)      =            524
Discrete kernel   : liracine               R-squared          =         0.8762
Bandwidth         : cross validation
------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
           y |   Estimate   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
Mean         |
           y |   15.64238   .3282016    47.66   0.000     15.17505    16.38781
-------------+----------------------------------------------------------------
Effect       |
          x1 |   3.472485   .2614388    13.28   0.000     2.895202    3.846091
          x2 |   4.143544   .2003619    20.68   0.000     3.830656    4.441306
             |
           a |
   (2 vs 1)  |   1.512241   .2082431     7.26   0.000     .9880627    1.717007
   (3 vs 1)  |  -10.23723   .2679278   -38.21   0.000     -10.6352   -9.715858
------------------------------------------------------------------------------
Note: Effect estimates are averages of derivatives for continuous covariates
      and averages of contrasts for factor covariates.

Enrique discussed interpretation of these results in his blog, so I will not focus on it here. I should, however, point out that because my goal is to show you how to use margins, I used only 10 bootstrap replications (a ridiculously small number) when estimating standard errors. In real research, you would certainly want to use more replications both with the npregress command and the margins commands that follow.

The npregress output includes estimates of the effects of x1, x2, and the levels of a on our outcome, but these estimates are probably not enough to answer some of the important questions that we want to address in our research.

Below, I will first show you how you can explore the nonlinear response surface—the expected value of y at different combinations of x1, x2, and a. For instance, suppose your outcome variable is the response to a drug, and you want to know the expected value for a female whose weight is 150 pounds and whose cholesterol level is 220 milligrams per deciliter. What about for a male with the same characteristics? How do these expectations change across a range of weights and cholesterol levels?

I will also demonstrate how to answer questions about population averages, counterfactuals, treatment effects, and more. These are exactly the types of questions that policy makers ask. How, on average, does a variable affect the population they are interested in? For instance, suppose your outcome variable is income of individuals in their 20s. What is the expected value of income for this group, the population average? What is the expected value if, instead of having their observed education level, they were all high school graduates? What if they were all college graduates? What is the difference in these values—the effect of college education?

These are just a few examples of the types of questions that you could answer. I will continue with variable names x1, x2, and a below, but you can imagine relevant questions for your research.

Exploring the response surface

Let’s start at the beginning. We might want to know the expected value of the outcome at one specific point. To get the expected value of y when a=1, x1=2, and x2=5, we can type

. margins 1.a, at(x1=2 x2=5) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Adjusted predictions                            Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()
at           : x1              =           2
               x2              =           5

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
         1.a |   12.66575   .4428049    28.60   0.000     11.57809     13.0897
------------------------------------------------------------------------------

We predict that y=12.7 at this point.

We could evaluate it at another point, say, a=2, x1=2, and x2=5.

. margins 2.a, at(x1=2 x2=5) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Adjusted predictions                            Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()
at           : x1              =           2
               x2              =           5

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
         2.a |   14.80935   .6774548    21.86   0.000     13.48154    15.98991
------------------------------------------------------------------------------

With a=2, the expected value of y is now 14.8.

What if our interest is in the effect of going from a=1 to a=2 when x1=2 and x2=5? That is just a contrast—the difference in our two previous results. Using the r. contrast operator with margins, we can perform a hypothesis test of whether these two values are the same.

. margins r(1 2).a, at(x1=2 x2=5) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........
Contrasts of predictive margins
                                                Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()
at           : x1              =           2
               x2              =           5

------------------------------------------------
             |         df        chi2     P>chi2
-------------+----------------------------------
           a |          1       27.58     0.0000
------------------------------------------------

--------------------------------------------------------------
             |   Observed   Bootstrap          Percentile
             |   Contrast   Std. Err.     [95% Conf. Interval]
-------------+------------------------------------------------
           a |
   (2 vs 1)  |   2.143598   .4081472      1.335078    2.523469
--------------------------------------------------------------

The confidence interval for the difference does not include 0. Using a 5% significance level, we find that the expected value is significantly different for these two points of interest.

But we might be interested in more than these two points. Let’s continue holding x2 to 5 and look at a range of values for x1. And let’s estimate the expected values across all three levels of a. In other words, let’s look at a slice of the 3-dimensional response surface (at x2=5) and examine the relationship between the other two variables.

. margins a, at(x1=(1(1)4) x2=5) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Adjusted predictions                            Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

1._at        : x1              =           1
               x2              =           5

2._at        : x1              =           2
               x2              =           5

3._at        : x1              =           3
               x2              =           5

4._at        : x1              =           4
               x2              =           5

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
       _at#a |
        1 1  |   9.075231   2.017476     4.50   0.000     5.984868    12.61052
        1 2  |   14.35551   2.488854     5.77   0.000     9.944582    17.73349
        1 3  |  -.2925357   1.499151    -0.20   0.845    -1.991074    2.248606
        2 1  |   12.66575   .3285203    38.55   0.000     11.93955    13.03459
        2 2  |   14.80935   .8713405    17.00   0.000     12.92743    15.75046
        2 3  |    1.82419   .5384259     3.39   0.001     .6331934    2.422084
        3 1  |    17.1207   .2446792    69.97   0.000     16.89714    17.61579
        3 2  |   17.28286   .3536435    48.87   0.000     16.78102    17.89018
        3 3  |     6.2364   .2689665    23.19   0.000     5.695041      6.5686
        4 1  |   21.29851   .1441116   147.79   0.000     21.05866    21.54149
        4 2  |   18.96943   .2000661    94.82   0.000     18.50348    19.24232
        4 3  |   11.45945   .2037134    56.25   0.000     11.05172    11.69403
------------------------------------------------------------------------------

Better yet, let’s plot these values.

. marginsplot

We find that when x2=5, the expected value of y increases as x1 increases, and the expected value is lower for a=3 than for a=1 and a=2 across all levels of x1.

But is this pattern the same for other values of x2?

We have only three covariates. So we can explore the entire response surface easily. Let’s look at additional slices at other values of x2. Here’s the command:

. margins a, at(x1=(1(1)4) x2=(2 5 8)) reps(10)

This produces a lot of output, so I won’t show it. But here is the graph

. marginsplot, bydimension(x2) byopts(cols(3))

We can now see that the response surface changes as x2 changes. When x2=2, the expected value of y increases slightly as x1 increases, but there is almost no difference among the levels of a. For x2=8, the differences among levels of a are more pronounced and appear to have a different pattern, increasing with x1 and then beginning to level off.

Earlier, we typed r(1 2).a to test for a difference in expected values when a=1 and a=2. Similarly, we could type r(1 3).a to compare a=1 with a=3. We could make both of those comparisons by simply typing r.a. And we can do this across a range of values of x1 and x2. We just change a to r.a in our previous margins command.

. margins r.a, at(x1=(1(1)4) x2=(2 5 8)) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........
Contrasts of predictive margins
                                                Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

1._at        : x1              =           1
               x2              =           2

2._at        : x1              =           1
               x2              =           5

(output omitted)

--------------------------------------------------------------
             |   Observed   Bootstrap          Percentile
             |   Contrast   Std. Err.     [95% Conf. Interval]
-------------+------------------------------------------------
       a@_at |
(2 vs 1)  1  |  -.4316907   .2926308     -.6839362    .2414861
(2 vs 1)  2  |   5.280281   .7986439      3.821515    6.316996
(2 vs 1)  3  |   8.716763   9.371615     -7.472455    19.98571
(2 vs 1)  4  |  -1.388968   .1918396     -1.756544   -1.110874
(2 vs 1)  5  |   2.143598   .4487908      1.075932    2.563385
(2 vs 1)  6  |   12.88835   4.544753      6.060425    20.50299
(2 vs 1)  7  |  -2.384115   .2101401     -2.736413   -2.082235
(2 vs 1)  8  |   .1621524   .3430663     -.3141147    .6630604
(2 vs 1)  9  |   9.285226   1.292208      7.300806    10.96053
(2 vs 1) 10  |  -2.355113   .5453254     -2.893817   -1.346538
(2 vs 1) 11  |  -2.329082   .2355506     -2.717465   -1.844513
(2 vs 1) 12  |   7.264358   1.152335      5.603027    9.361632
(3 vs 1)  1  |  -3.622857   .6110854     -4.141618   -2.277811
(3 vs 1)  2  |  -9.367766   .9618594     -10.86855   -8.056798
(3 vs 1)  3  |  -4.751952    8.78832     -14.17876     12.3299
(3 vs 1)  4  |  -2.500585   .2942087     -2.999642   -2.113881
(3 vs 1)  5  |  -10.84156   .3575732     -11.46529    -10.4761
(3 vs 1)  6  |  -18.84966   2.783479     -21.87325   -11.56521
(3 vs 1)  7  |  -.3080399   .1685899     -.7749465   -.1793496
(3 vs 1)  8  |   -10.8843    .271959     -11.28382   -10.48519
(3 vs 1)  9  |  -22.70852   1.147957     -23.26496   -19.51463
(3 vs 1) 10  |   3.922399   .6064593      2.976073    4.793276
(3 vs 1) 11  |  -9.839058   .2572868     -10.13578   -9.339336
(3 vs 1) 12  |  -20.31851   1.294548      -22.1466   -17.29554
--------------------------------------------------------------

The legend at the top of the output tells us that 1._at corresponds to x1=1 and x2=2. The values parentheses, such as (2 vs 1), at the first of each line in the table tell us which values of a are compared in that line. Thus, the first line in the table provides a test comparing expected values of y for a=2 versus a=1 when x1=1 and x2=2. Admittedly, this is a lot to look at, and it is probably easier to interpret with a graph. We use marginsplot to graph these differences with their confidence intervals. This time, let’s use the yline(0) option to add a reference line at 0. This allows us to perform the test visually by checking whether the confidence interval for the difference includes 0.

. marginsplot, bydimension(x2) byopts(cols(3)) yline(0)

In this case, some of the confidence intervals are so narrow that they are hard to see. If we look closely at the blue point on the far left, we see that the confidence interval for the difference comparing a=2 versus a=1 when x1=1 and x2=2 (corresponding to the first line in the output above) does include 0. This indicates that there is not a significant difference in these expected values. We can examine each of the other points and confidence intervals in the same way. For example, looking at the red line and points in the third panel, we see that the effect of moving from a=1 to a=3 is negative and significantly different from 0 for x1 values of 2, 3, and 4. When x1 is 1, the point estimate of the effect is still negative, but that effect is not significantly different from 0 at the 95% level. But recall that we should dramatically increase the number of bootstrap replicates to make any real claims about confidence intervals.

So far, we have compared both a=2 with a=1 and a=3 with a=1. But we are not limited to making comparisons with a=1. We could compare 1 with 2 and 2 with 3, which often makes more sense if levels of a have a natural ordering. To do this, we just replace r. with ar. in our margins command. I won’t show that output, but you have the data and can try it if you like.

Population-averaged results

So far, we have talked about evaluating individual points on your response surface and how to perform tests comparing expected values at those points. Now, let’s switch gears and talk about population-averaged results.

We are going to need the dataset to be representative of the population. If that is not true of your data, you will want to stop with the analyses we did above. We will assume our data are representative so that we can answer a variety of questions based on average predictions.

First, what is the overall expected population mean from this response surface?

. margins, reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Predictive margins                              Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
       _cons |   15.64238    .318746    49.07   0.000     15.34973    16.24543
------------------------------------------------------------------------------

Whatever the process was that generated this, we believe 15.6 is the expected value in the population and [15.3, 16.2] is the confidence intervals for it.

Do the population averages differ when we first set everyone to have a=1, then set everyone to have a=2, and finally set everyone to have a=3? Let’s look at the expected means for all three.

. margins a, reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Predictive margins                              Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
           a |
          1  |   18.39275   .4847808    37.94   0.000     17.63322    19.31199
          2  |   19.90499   .6840056    29.10   0.000     18.17305    20.57171
          3  |   8.155515   .3239624    25.17   0.000      7.59187     8.70898
------------------------------------------------------------------------------

We get 18.4, 19.9, and 8.2. They sure don’t look the same. Let’s test them.

. margins r.a, reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........
Contrasts of predictive margins
                                                Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

------------------------------------------------
             |         df        chi2     P>chi2
-------------+----------------------------------
           a |
   (2 vs 1)  |          1       17.10     0.0000
   (3 vs 1)  |          1      618.86     0.0000
      Joint  |          2     1337.66     0.0000
------------------------------------------------

--------------------------------------------------------------
             |   Observed   Bootstrap          Percentile
             |   Contrast   Std. Err.     [95% Conf. Interval]
-------------+------------------------------------------------
           a |
   (2 vs 1)  |   1.512241   .3656855      1.051265    2.016566
   (3 vs 1)  |  -10.23723   .4115157     -10.82644     -9.4265
--------------------------------------------------------------

In the causal-inference or treatment-effects literature, the means would be considered potential-outcome means, and these differences would be the average treatment effects of a multivalued treatment. Here the average treatment effect of a=2 (compared with a=1) is 1.5.

We saw in the previous section that differences in expected values for levels of a varied across values of x2. Let’s estimate the potential-outcome means and treatment effects of a at different values of x2. Notice that these are still population averages because, unlike in the previous section, we are not setting x1 to any specific value. Instead, predictions use the observed values of x1 in the data.

. margins a, at(x2=(2 5 8)) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........

Predictive margins                              Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

1._at        : x2              =           2

2._at        : x2              =           5

3._at        : x2              =           8

------------------------------------------------------------------------------
             |   Observed   Bootstrap                          Percentile
             |     Margin   Std. Err.      z    P>|z|     [95% Conf. Interval]
-------------+----------------------------------------------------------------
       _at#a |
        1 1  |   3.707771   .6552861     5.66   0.000     4.619393     6.70335
        1 2  |   1.340815   .9491373     1.41   0.158     2.077606    5.010682
        1 3  |   3.677883    .935561     3.93   0.000      4.33317    7.380818
        2 1  |   17.16092     .27028    63.49   0.000      16.8047    17.75164
        2 2  |   17.22508   .1993421    86.41   0.000     16.91111    17.57034
        2 3  |   6.933778   .1929387    35.94   0.000     6.751693    7.332845
        3 1  |   30.61528   .9961323    30.73   0.000     28.66033    31.88367
        3 2  |   40.17685   1.687315    23.81   0.000     36.18359    41.73811
        3 3  |   10.97667   .9771489    11.23   0.000     9.510952    12.59133
------------------------------------------------------------------------------

Instead of looking at the output, let’s graph these potential-outcome means.

. marginsplot

The effect of a increases as x2 increases. The effect is largest when x2=8.

Now, we can test for differences at each level of x2

. margins r.a, at(x2=(2 5 8)) reps(10)
(running margins on estimation sample)

Bootstrap replications (10)
----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5
..........
Contrasts of predictive margins
                                                Number of obs     =      1,497
                                                Replications      =         10

Expression   : mean function, predict()

1._at        : x2              =           2

2._at        : x2              =           5

3._at        : x2              =           8

------------------------------------------------
             |         df        chi2     P>chi2
-------------+----------------------------------
       a@_at |
 (2 vs 1) 1  |          1       51.80     0.0000
 (2 vs 1) 2  |          1        0.13     0.7184
 (2 vs 1) 3  |          1       43.16     0.0000
 (3 vs 1) 1  |          1        0.01     0.9322
 (3 vs 1) 2  |          1      834.54     0.0000
 (3 vs 1) 3  |          1      245.45     0.0000
      Joint  |          6     2732.16     0.0000
------------------------------------------------

--------------------------------------------------------------
             |   Observed   Bootstrap          Percentile
             |   Contrast   Std. Err.     [95% Conf. Interval]
-------------+------------------------------------------------
       a@_at |
 (2 vs 1) 1  |  -2.366956   .3288861     -2.737075   -1.466043
 (2 vs 1) 2  |   .0641596   .1779406      -.224935    .3180577
 (2 vs 1) 3  |   9.561575   1.455483      6.847319    11.96769
 (3 vs 1) 1  |  -.0298883   .3513035     -.6345751    .4584875
 (3 vs 1) 2  |  -10.22714   .3540221     -10.89414   -9.808236
 (3 vs 1) 3  |  -19.63861   1.253521     -21.45499    -18.0223
--------------------------------------------------------------

Again, let’s look at the graph.

. marginsplot

The difference in means when a=3 and a=1, the treatment effect, is not significant when x2=2. Neither is the effect of a=2 versus a=1 when x2=5. All the other effects are significantly different from 0.

Conclusion

In this blog, we have explored the response surface of a nonlinear function, we have estimated a variety of population averages based on our nonparametric regression model, and we have performed several tests comparing expected values at specific points of the response surface and tests comparing population averages. Yet, we have only scratched the surface of the types of estimates and tests you can obtain using margins after npregress. There are additional contrasts operators that will allow you to test differences from a grand mean, differences from means of previous or subsequent levels, and more. See [R] contrast for details of the available contrast operators. You can also use marginsplot to see the results of margins commands from different angles. For instance, if we type marginsplot, bydimension(x1) instead of marginsplot, bydimension(x2), we see our nonlinear response surface from a different perspective. See [R] marginsplot for details and examples of this command.

Whether you use nonparametric regression or another model, margins and marginsplot are the solution for exploring the results, making inferences, and understanding relationships among the variables you are studying.