Scott's Mixtape · Economics & Policy
TIER 4 Mon, 20 Apr 2026 11:17:24 +0000
Part 2! ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ | | ---|---|--- | | | Forwarded this email? Subscribe here for more --- # Making a shiny to illustrate the TWFE continuous weights ### Part 2! | | scott cunningham --- | Apr 20 --- | --- --- | | | --- | | --- | | --- | | --- | | READ IN APP --- Brantly Callaway, Andrew Goodman-Bacon and Pedro Sant'Anna (hereafter CBS) have a new article conditionally accepted at _American Economic Review_ on continuous treatment difference-in-differences. The paper has three main ingredients: * an introduction and formalization of various causal parameters related to a treatment "dosage" appropriate for the difference-in-differences framework. This is not what I'll discuss today. * an introduction of an estimator that one can use to estimate some of those causal parameters when you have a continuous treatment dosage and a difference-in-differences treatment assignment. That is not what I will discuss today either. * a decomposition of traditional two-way fixed effects (TWFE) estimator using Frisch-Waugh-Lovell. This is what I will talk about today. There are four decompositions in the paper, and today I will only talk about one of them: the levels. In the last substack on this, I worked through that one. Difference-in-Differences --- ## Decomposing the TWFE regression coefficient with continuous treatment dosage using FWL | | scott cunningham| | * ---|---|--- | Apr 15 Technically, today's post has nothing to do with Claude Code. It's purely algebraic Frisch-Waugh-Lovell, and thus because it's about continuous treatment diff-in-diff, it fits under the diff-in-diff banner, and therefore is subject to my randomized paywall. So I flipped a coin three times, it came up heads twice, therefore it's paywalled. And so paywalled it shall be. But first, let me tell you what you're going to be missing if you are not a paying subscriber. | | Read full story --- Here it is formally: And so with all that out of the way, I will move on to the next item on my agenda which is to use Claude Code to create a shiny app that helps all of us better understand just what is going on in that formula. I have a walk through of that here in a 35 minute video, which resulted in this shiny app that you can use now to help you better understand the TWFE decomposition and the whereabouts of **negative weights** that it uses to calculate its coefficients. This is my first shiny app, and technically Claude Code made it so it isn't even my shiny app, but I thought it was fun. I'm hosting it on my website. Scott's Mixtape Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber. Upgrade to paid * * * ### Reviewing our three ingredients | | ---|---|--- There it is! My new CBS shiny app for the level decomposition. Let me help you navigate it. First, notice there are four tables labeled **Level** , Scaled level, Causal response and Scaled 2x2. If you click on the others, they have a "Coming soon" page. The only working right now is the Level one as that's the only one so far we have discussed. The pictures at the bottom are from the deck: | Cbs Table1 Weights744KB ∙ PDF file ---|--- | Download --- Let me briefly walk you through it. There are multiple ingredients in each of the decompositions and this slide illustrates them: | | ---|---|--- The things on the left are the weight ingredients and the things on the right are the particular decomposition. So we are doing level weight, and therefore we have three ingredients to it: the mean of the treatment dosage in the data (E[D]), the variance, and the density. We integrate over the doses using the density formula. Remember our TWFE formula from earlier: There are two pieces inside this weight -- the weight associated with units that have zero dose, and those that have positive dose. Look closely at Table 1, row 2, labeled "Levels" and you'll see it. | | ---|---|--- Back to the ingredients. Here's the first one: the mean, E[D]: | | ---|---|--- Notice that in this case the mean dose (a tariff in this case) is 0.164. We will hang on to that. Then there is the variance. The variance recall is the square of the standard deviation measuring the spread of the dose around the mean we just identified. And it is equal to 0.0202, or 0.02 for short. The variance scales the weight and appears in the denominator. | | ---|---|--- And then there is the density, _f_D(l)._ This is what we will be integrating over. The dosage is presented as continuous, but if it was multi-valued, we'd just take weighted averages. When the density is high, there are many units with that value and when it is low, there are few. The fact that it is high at zero in the image below means that there are a large number of units with zero dose. When we work with the density formula, we calculate the density at a given dose, _l_. So if the dose was _l=0.10_ , we just calculate the density associated with it. | | ---|---|--- And just to be sure we all see it, the x-axis always has the dose. The y-axis has the frequency, or count, or number of units, in the first two ingredients, and the density in the third. * * * ### Using our three ingredients in the shiny app Alright, so let's look again real quick at the shiny app page for the level decomposition. For instance, let's say I want to know the density at 0.365. That is, for industries with the tariff value of 0.365, what is the density value? I simply slide the slider button on the top left labeled "Dose _l_ :" to 0.365. And notice, it automatically calculates it. The value of the density at that point is **0.834**. You can see it on the far right of the "Six ingredients" formula, and you can also see that it populates the image labeled **" Plug in the numbers at the chosen **_**l**_**". ** | | ---|---|--- And so given the mean of 0.165, the variance of 0.0202 and a density value (which unlike the mean and variance is not a constant but changes at each dose) of 0.834, we get: And that's it! That's how the level formula works. You can either move the slider left and right to find each industry's density value, or you can move your cursor over the density and that'll show you the density associated with that dosage. Either way. But the formula stays the same. * * * ### Sign flip Now the most interesting thing in this decomposition is the sign flip. Notice what happens when the dosage is exactly equal to the mean. The mean, recall, is _E[D],_ equalling 0.164: So, when you have a unit or set of units whose dose is exactly the mean, then the weight on them interestingly is zero. Now in our case, because the treatment is a **continuous dose** for which the probability of any exact value is zero, I don't show the weight at that level as there is no one with _exactly_ the mean dose. But, you can see what happens if you move the slider left and right -- when industries have an _above average_ dose, they are **positively weighted** , but when they have a _below average dose_ , they are **negatively weighted.** To illustrate this, I filmed myself a second time. In the above video, I actually had a discrepancy which took me a bit to understand. Claude had minimum dosage at 0.027 and a maximum dosage of 0.552. But the Gaussian kernel smeared a little probability mass to the left of the smallest observed dose. So the kernel put a teeny tiny bit of density at 0.005. The weight is technically computable but substantively empty -- it's a weight on a dose group with no industries in it. So in the new one, you'll still see the sign flip, but now there are vertical dashed lines at the lowest to highest doses. It isn't in the video, but if you watch the video, you'll notice that is where I start to realize it. I think it was probably fine to have left it there, but I felt like the smoothing that the kernel function was doing was confusing me, and therefore I changed it. The top panel uses a kernel smoother of 0.005, but the bottom one was a little under the largest kernel smoothing value possible. Just so you can see. | | ---|---|--- | | ---|---|--- Anyway, the TWFE estimator integrates from the smallest to highest doses, and when industries have doses below the mean, we are in that zone to the left in that kind of orange hue-ish looking range, all of which are **negative weights** , and when the dose is above the mean, we are above zero in that turquoise blue and that's **positive weights**. * * * ### Wrapping it up! And there you go! That's the shiny app, plus two video walk-throughs, the first one which you can use to better see how to use Claude Code to make a shiny app, and the second was walking you through interpreting the shiny app. I hope you found this helpful! Both for using Claude Code to make a shiny app, and for interpreting the TWFE decomposition when you're regression is in its "level" formulation (which will probably be most of the time tbh). And of course, if you found this useful, consider becoming a paying subscriber! Scott's Mixtape Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber. Upgrade to paid You're currently a free subscriber to Scott's Mixtape Substack. For the full experience, upgrade your subscription. Upgrade to paid --- | | | Like --- | | Comment --- | | Restack --- (C) 2026 scott cunningham 910 North 17th Street, Waco, Texas 76707 Unsubscribe