{"id":3630,"date":"2025-05-07T07:02:23","date_gmt":"2025-05-07T07:02:23","guid":{"rendered":"https:\/\/mailitics.com\/index.php\/2025\/05\/07\/regression-discontinuity-design-how-it-works-and-when-to-use-it\/"},"modified":"2025-05-07T07:02:23","modified_gmt":"2025-05-07T07:02:23","slug":"regression-discontinuity-design-how-it-works-and-when-to-use-it","status":"publish","type":"post","link":"https:\/\/mailitics.com\/index.php\/2025\/05\/07\/regression-discontinuity-design-how-it-works-and-when-to-use-it\/","title":{"rendered":"Regression Discontinuity Design: How It Works and When to Use It"},"content":{"rendered":"<p>    Regression Discontinuity Design: How It Works and When to Use It<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n    <!-- no image --><br \/>\n \t<BR><br \/>\n<BR><\/BR><\/p>\n<div>\n<h1 class=\"wp-block-heading\">Regression Discontinuity Design: How It Works and When to Use It<\/h1>\n<p class=\"wp-block-paragraph\">You\u2019re an avid data scientist and experimenter. You know that randomisation is the summit of Mount Evidence Credibility, and you also know that when you can\u2019t randomise, you resort to observational data and <a href=\"https:\/\/towardsdatascience.com\/tag\/causal-inference\/\" title=\"Causal Inference\">Causal Inference<\/a> techniques. At your disposal are various methods for spinning up a control group \u2014 difference-in-differences, inverse propensity score weighting, and others. With an assumption here or there (some shakier than others), you estimate the causal effect and drive decision-making. But if you thought it couldn\u2019t get more exciting than \u201cvanilla\u201d causal inference, read on.<\/p>\n<p class=\"wp-block-paragraph\">Personally, I\u2019ve often found myself in at least two scenarios where \u201cjust doing causal inference\u201d wasn\u2019t straightforward. The common denominator in these two scenarios? A missing control group \u2014 at first glance, that is.<\/p>\n<p class=\"wp-block-paragraph\">First, <strong>the cold-start scenario:<\/strong> the company wants to break into an uncharted opportunity space. Often there is no experimental data to learn from, nor has there been any change (read: \u201cexogenous shock\u201d), from the business or product side, to leverage in the more common causal inference frameworks like difference-in-differences (and other cousins in the pre-post paradigm).<\/p>\n<p class=\"wp-block-paragraph\">Second, <strong>the unfeasible randomisation scenario:<\/strong> the organisation is perfectly intentional about testing an idea, but randomisation is not feasible\u2014or not even wanted. Even emulating a natural experiment might be constrained legally, technically, or commercially (especially when it\u2019s about pricing), or when interference bias arises in the marketplace.<\/p>\n<p class=\"wp-block-paragraph\">These situations open up the space for a \u201cdifferent\u201d type of causal inference. Although the method we\u2019ll focus on here is not the only one suited for the job, I\u2019d love for you to tag along in this deep dive into <a href=\"https:\/\/towardsdatascience.com\/tag\/regression-discontinuity\/\" title=\"Regression Discontinuity\">Regression Discontinuity<\/a> Design (RDD).<\/p>\n<p class=\"wp-block-paragraph\">In this post, I\u2019ll give you a crisp view of <em>how<\/em> and <em>why<\/em> RDD works. Inevitably, this will involve a bit of math \u2014 a pleasant sight for some \u2014 but I\u2019ll do my best to keep it accessible with classic examples from the literature.<\/p>\n<p class=\"wp-block-paragraph\">We\u2019ll also see how RDD can tackle a thorny causal inference challenge in e-commerce and online marketplaces: the impact of listing position on listing performance. In this practical section we\u2019ll cover key modelling considerations that practitioners often face: parametric versus non-parametric RDD, choosing the right bandwidth parameter, and more. So, grab yourself a cup of of coffee and let\u2019s jump in!<\/p>\n<h2 class=\"wp-block-heading\">Outline<\/h2>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#how-why\" data-type=\"internal\" data-id=\"#how-why\">How and why RDD Works: Key Concepts<\/a><\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#continuity-assumption\">Core assumption explained: Continuity<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#instruments\" data-type=\"internal\" data-id=\"#instruments\">RDD and instrumental variables<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#Backdoorfrontdoor\" data-type=\"internal\" data-id=\"#Backdoorfrontdoor\">Why the backdoor doesn\u2019t work<\/a><\/li>\n<\/ul>\n<\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#rdd-usecase\" data-type=\"internal\" data-id=\"#rdd-usecase\">RDD in Action: Search Ranking and listing performance Example<\/a><\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#the-setup\" data-type=\"internal\" data-id=\"#the-setup\">Practical Setup &amp; Variables<\/a><\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#estimating-rdd\">Estimating RDD<\/a><\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#parametric\" data-type=\"internal\" data-id=\"#parametric\">Parametric vs. Non-parametric RDD<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#covariates\" data-type=\"internal\" data-id=\"#covariates\">Adding covariates<\/a><\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#polydegree\" data-type=\"internal\" data-id=\"#polydegree\">Polynomial degree<\/a> and <a href=\"https:\/\/towardsdatascience.com\/#bandwidth\" data-type=\"internal\" data-id=\"#bandwidth\">bandwidth<\/a>\n<\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#interpretation\" data-type=\"internal\" data-id=\"#interpretation\">Interpreting results<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#placebo\" data-type=\"internal\" data-id=\"#placebo\">Placebo testing<\/a><\/li>\n<\/ul>\n<\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#analysis-recap\" data-type=\"internal\" data-id=\"#analysis-recap\">Analysis recap<\/a> and;<\/li>\n<\/ul>\n<\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#conclusions\">Conclusion<\/a><\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\" id=\"how-why\">How and why RDD works\u00a0<\/h2>\n<p class=\"wp-block-paragraph\">Regression Discontinuity Design exploits cutoffs \u2014 thresholds \u2014 to recover the effect of a treatment on an outcome. More precisely, it looks for a sharp change in the probability of treatment assignment on a \u2018running\u2019 variable. If treatment assignment depends solely on the running variable, and the cutoff is arbitrary, i.e. exogenous, then we can treat the units around it as randomly assigned. The difference in outcomes just above and below the cutoff gives us the causal effect.<\/p>\n<p class=\"wp-block-paragraph\">For example, a scholarship awarded only to students scoring above 90, creates a cutoff based on test scores. That the cutoff is 90 is arbitrary \u2014 it could have been 80 for that matter; the line had just to be drawn somewhere. Moreover, scoring 91 vs. 89 makes the whole difference as for the treatment: either you get it or not. But regarding <em>capability<\/em>, the two groups of students that scored 91 and 89 are not really different, are they? And those who scored 89.9 versus 90.1 \u2014\u00a0if you insist? <\/p>\n<p class=\"wp-block-paragraph\">Making the cutoff could come down to randomness, when it\u2019s just a bout a few points. Maybe the student drank too much coffee right before the test \u2014 or too little. Maybe they got bad news the night before, were thrown off by the weather, or anxiety hit at the worst possible moment. It\u2019s this randomness that makes the cutoff so <em>instrumental<\/em> in RDD.<\/p>\n<p class=\"wp-block-paragraph\">Without a cutoff, you don\u2019t have an RDD\u00a0\u2014\u00a0just a scatterplot and a dream. But, the cutoff by itself is not equipped with all it takes to identify the causal effect. Why it works hinges on one core identification assumption: continuity.<\/p>\n<h3 class=\"wp-block-heading\" id=\"continuity-assumption\">The continuity assumption, and parallel worlds<\/h3>\n<p class=\"wp-block-paragraph\">If the cutoff is the cornerstone of the technique, then its importance comes entirely from the continuity assumption. The idea is a simple, counterfactual one: had there been no treatment, then there would\u2019ve been no effect.<\/p>\n<p class=\"wp-block-paragraph\">To ground the idea of continuity, let\u2019s jump straight into a classic example from public health: does legal alcohol access increase mortality?<\/p>\n<p class=\"wp-block-paragraph\">Imagine two worlds where everyone and everything is the same. Except for one thing: a law that sets the minimum legal drinking age at 18 years (we\u2019re in Europe, folks).<\/p>\n<p class=\"wp-block-paragraph\">In the world with the law (the factual world), we\u2019d expect alcohol consumption to jump right after age 18. Alcohol-related deaths should jump too, if there is a link.<\/p>\n<p class=\"wp-block-paragraph\">Now, take the counterfactual world where there is no such law; there should be no such jump. Alcohol consumption and mortality would likely follow a smooth trend across age groups.<\/p>\n<p class=\"wp-block-paragraph\">Now, that\u2019s a good thing for identifying the causal effect;\u00a0the absence of a jump in deaths in the counterfactual world is the <em>necessary<\/em> condition to interpret a jump in the factual world as the impact of the law.<\/p>\n<p class=\"wp-block-paragraph\">Put simply: if there is no treatment, there shouldn\u2019t be a jump in deaths. If there is, then something other than our treatment is causing it, and the RDD is not valid.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"538\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/RDD_-_Parallel_worlds-3-1024x538.png?resize=1024%2C538&#038;ssl=1\" alt=\"\" class=\"wp-image-603475\"><figcaption class=\"wp-element-caption\">Two parallel worlds. From left to right; one where there is no minimum age to consume alcohol legally, and one where there is: 18 years.<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">The continuity assumption can be written in the potential outcomes framework as:<\/p>\n<p class=\"wp-block-paragraph\">begin{equation}<br \/>lim_{x to c^-} mathbb{E}[Y_i(0) mid X_i = x] = lim_{x to c^+} mathbb{E}[Y_i(0) mid X_i = x]<br \/>label{eq: continuity_po} <br \/>end{equation}<\/p>\n<p class=\"wp-block-paragraph\">Where (Y_i(0)) is the potential outcome, say, risk of death of subject (\/mathbb{i}) under no treatment.<\/p>\n<p class=\"wp-block-paragraph\">Notice that the right-hand side is a quantity of the counterfactual world; not one that can be observed in the factual world, where subjects are treated if they fall above the cutoff.<\/p>\n<p class=\"wp-block-paragraph\">Unfortunately for us, we only have access to the factual world, so the assumption cannot be tested directly. But, luckily, we can proxy it. We will see placebo groups achieve this later in the post. But first, we start by identifying <em>what<\/em> can break the assumption:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Confounders:<\/strong> something other than the treatment happens at the cutoff that also impacts the outcome. For instance, adolescents resorting to alcohol to alleviate the crushing pressure of being an adult now \u2014 something that has nothing to do with the law on the minimum age to consume alcohol (in the no-law world), but that does confound the effect we are after, happening at the same age \u2014 the cutoff, that is.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Manipulating the running variable:<\/strong> <br \/>When units can influence their position with regard to the cutoff, it may be that units who did so are inherently different from those who did not. Hence, cutoff manipulation can result in selection bias: a form of confounding. Especially if treatment assignment is binding, subjects may try their best to get one version of the treatment over the other. <\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Hopefully, it\u2019s clear what constitutes a RDD: the running variable, the cutoff, and most importantly, reasonable grounds to defend that continuity holds. With that, you\u2019ve gotten yourself a neat and effective causal inference design for questions that can\u2019t be answered by an A\/B test, nor by some of the more common causal inference techniques like diff-in-diff, nor with stratification.<\/p>\n<p class=\"wp-block-paragraph\">In the next section, we continue shaping our understanding of how RDD works; how does RDD \u201ccontrol\u201d confounding relationships? What exactly does it estimate? Can we not just control for the running variable too? These are questions that we tackle next.<\/p>\n<h3 class=\"wp-block-heading\" id=\"instruments\">RDD and instruments<\/h3>\n<p class=\"wp-block-paragraph\">If you are already familiar with instrumental variables (IV), you may see the similarities: both RDD and IV leverage an exogenous variable that does not cause the outcome directly, but does influence the treatment assignment, which in turn may influence the outcome. In IV this is a third variable Z; in RDD it\u2019s the running variable that serves as an instrument.<\/p>\n<p class=\"wp-block-paragraph\">Wait. A third variable; maybe. But an exogenous one? That\u2019s less clear. <\/p>\n<p class=\"wp-block-paragraph\">In our example of alcohol consumption, it is not hard to imagine that age \u2014 the running variable \u2014 is a confounder. As age increases, so might tolerance for alcohol, and with it the level of consumption. That\u2019s a stretch, maybe, but not implausible.<\/p>\n<p class=\"wp-block-paragraph\">Since treatment (legal minimum age) depends on age \u2014 only units above 18 are treated \u2014 treated and untreated units are inherently different. If age also influences the outcome, through a mechanism like the one sketched above, we got ourselves an apex confounder.<\/p>\n<p class=\"wp-block-paragraph\">Still, the running variable plays a key role. To understand why, we need to look at how RDD and instruments leverage the frontdoor criterion to identify causal effects.<\/p>\n<h4 class=\"wp-block-heading\" id=\"Backdoorfrontdoor\"><em>Backdoor vs. frontdoor<\/em><\/h4>\n<p class=\"wp-block-paragraph\">Perhaps almost instinctively, one may answer with <em>controlling<\/em> for the running variable; that\u2019s what stratification taught us. The running variable is confounder, so we include it in our regression, and close the backdoor. But doing so would cause some trouble.  <\/p>\n<p class=\"wp-block-paragraph\">Remember, treatment assignment depends on the running variable so that everyone above the cutoff is treated with <em>all<\/em> certainty, and <em>certainly not <\/em>below it. So, if we control for the running variable, we run into two very related problems:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Violation of the Positivity assumption: <\/strong>this assumption says that treated units should have a non-zero probability to receive the opposite treatment, and vice versa. Intuitively, conditioning on the running variable is like saying: \u201cLet\u2019s estimate the effect of being above the minimum age for alcohol consumption, while holding age fixed at 14.\u201d That does not make sense. At any given value of running variable, treatment is either always 1 or always 0. So, there\u2019s no variation in treatment conditional on the running variable to support such a question.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Perfect collinearity at the cutoff:<\/strong>\u00a0in estimating the treatment effect, the model has no way to separate the effect of crossing the cutoff from the effect of being at a particular value of X. The result? No estimate, or a forcefully dropped variable from the model design matrix. <em>Singular design matrix<\/em>, <em>does not have full rank<\/em>, these should sound familiar to most practitioners.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">So no \u2014 conditioning on the running variable doesn\u2019t make the running variable the exogenous instrument that we\u2019re after. Instead, the running variable becomes exogenous by pushing it to the limit\u2014quite literally. There where the running variable approaches the cutoff from either side, the units are the same with respect to the running variable. Yet, falling just above or below makes the difference as for getting treated or not. This makes the running variable a valid instrument, if treatment assignment is the only thing that happens at the cutoff. Judea Pearl refers to instruments as meeting the front-door criterion.<\/p>\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/DAG_-_IV-2-1.png?ssl=1\" alt=\"\" class=\"wp-image-602951\"><figcaption class=\"wp-element-caption\">X is the running variable, D the treatment assignment, Y the outcome, and U is a set of unobserved influences on the outcome. The causal effect of D on Y is unidentified in the above marginal model, for X being a confounder, and U potentially too. Conditioning on X violates the positivity assumption. Instead, conditioning X on its limits towards cutoff (c0), controls for the backdoor path: X to Y directly, and through U.<\/figcaption><\/figure>\n<h3 class=\"wp-block-heading\"><em>LATE, not ATE<\/em><\/h3>\n<p class=\"wp-block-paragraph\">So, in essence, we\u2019re controlling for the running variable \u2014 but only near the cutoff. That\u2019s why RDD identifies the <em>local<\/em> average treatment effect (LATE), a special flavour of the average treatment effect (ATE). The LATE looks like:<\/p>\n<p class=\"wp-block-paragraph\">$$delta_{SRD}=Ebig[Y^1_i \u2013 Y_i^0mid X_i=c_0]$$<\/p>\n<p class=\"wp-block-paragraph\">The <em>local<\/em> bit refers to the partial scope of the population we are estimating the ATE for, which is the subpopulation around the cutoff. In fact, the further away the data point is from the cutoff, the more the running variable acts as a confounder, working <em>against<\/em> the RDD instead of in its favour.<\/p>\n<p class=\"wp-block-paragraph\">Back to the context of the minimum age for legal alcohol consumption example. Adolescents who are 17 years and 11 months old are really not so different from those that are 18 years and 1 month old, on average. If anything, a month or two difference in age is not going to be what sets them apart. Isn\u2019t that the essence of conditioning on, or holding a variable constant? What sets them apart is that the latter group can consume alcohol legally for being above the cutoff, and not the former. <\/p>\n<p class=\"wp-block-paragraph\">This setup enables us to estimate the LATE for the units around the cutoff and with that, the effect of the minimum age policy on alcohol-related deaths.<\/p>\n<p class=\"wp-block-paragraph\">We\u2019ve seen how the continuity assumption has to hold to make the cutoff an interesting point along the running variable in identifying the causal effect of a treatment on the outcome. Namely, by letting the jump in the outcome variable be entirely attributable to the treatment. If continuity holds, the treatment is as-good-as-random near the cutoff, allowing us to estimate the local average treatment effect.<\/p>\n<p class=\"wp-block-paragraph\">In the next section, we\u2019ll walk through the practical setup of a real-world RDD: we identify the key concepts; the running variable and cutoff, treatment, outcome, covariates, and finally, we estimate the RDD after discussing some crucial modelling choices, and end the section with a placebo test.<\/p>\n<h2 class=\"wp-block-heading\" id=\"rdd-usecase\">RDD in Action: Search Ranking and listing performance Example<\/h2>\n<p class=\"wp-block-paragraph\">In e-commerce and online marketplaces, the starting point of the buyer experience is searching for a listing. Think of the visitor typing  \u201cNikon F3 analogue camera\u201d in the search bar. Upon carrying out this action, algorithms frantically sort through the inventory looking for the best matching listings to populate the search results page.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Time and attention are two scarce resources.<\/strong> So, it is in the interest of everyone involved \u2014 the buyer, the seller and the platform \u2014\u00a0to reserve the most prominent positions on the page for the matches with the highest anticipated chance to become successful trades.<\/p>\n<p class=\"wp-block-paragraph\">Additionally, position effects in consumer behaviour suggest that users infer <strong>higher credibility and desirability from items \u201cranked\u201d at the top<\/strong>. Think about high-tier products being placed at eye-height or above in supermarkets, and highlighted items on an e-commerce platform, at the top of the homepage. <\/p>\n<p class=\"wp-block-paragraph\">So, the question then becomes: how does positioning on the search results page influence a listing\u2019s chances to be sold?<\/p>\n<p class=\"wp-block-paragraph\"><em>Hypothesis<\/em>:<br \/>If a listing is ranked higher on the search results page, then it will have a higher chance of being sold, because higher-ranked listings get more visibility and attention from users.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Intermezzo: business or theory?<\/summary>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">As with any good hypothesis, we need a bit of theory to ground it. Good for us is that we are not trying to find the cure for cancer. Our theory is about well-understood psychological phenomena and behavioural patterns, to put it overly sophisticated.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">Think of <em><a href=\"https:\/\/thedecisionlab.com\/biases\/primacy-effect\" data-type=\"link\" data-id=\"https:\/\/thedecisionlab.com\/biases\/primacy-effect\">primacy effect<\/a><\/em>, <em><a href=\"https:\/\/thedecisionlab.com\/reference-guide\/philosophy\/anchoring\">anchoring bias<\/a><\/em> and the <a href=\"https:\/\/www.mcw.edu\/-\/media\/MCW\/Education\/Academic-Affairs\/OEI\/Faculty-Quick-Guides\/Cognitive-Load-Theory.pdf\">resource theory of attention<\/a>. These are well ideas in behavioural and cognitive psychology that back up our plan here.<\/p>\n<p class=\"wp-block-paragraph\">Kicking off the conversation with a product manager will be more fun this way. Personally, I also get excited when I have to brush up on some psychology. <\/p>\n<p class=\"wp-block-paragraph\">But I\u2019ve found through and through that a theory is really secondary to any initiative in my industry (tech). Except for a research team and project, arguably. And it\u2019s fair to say it helps us stay on-purpose: what we are doing is to bring business forward, not mother science.\u00a0<\/p>\n<\/blockquote>\n<\/details>\n<p class=\"wp-block-paragraph\">Knowing the answer has real business value. Product and commercial teams could use it to design new paid features that help sellers get their listings on higher positions \u2014 a win for both the business and the user. It could also clarify the value of on-site real estate like banner positions and ad slots, helping drive growth in B2B advertising.<\/p>\n<p class=\"wp-block-paragraph\">The question is about incrementality: would\u2019ve listing (mathbb{j}) been sold, had it been ranked 1st on the results page, instead of 15th. So, we want to make a causal statement. That\u2019s hard for at least two reasons: <\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">A\/B testing comes with a price, and;<\/li>\n<li class=\"wp-block-list-item\">there are confounders we need to deal with if we resort to observational methods. <\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Let\u2019s expand on that.<\/p>\n<h3 class=\"wp-block-heading\">The cost of A\/B testing<\/h3>\n<p class=\"wp-block-paragraph\">One experiment design could randomise the fetched listings across the page slots, independent of the listing relevance. Breaking the inherent link between relevance and position, we would learn the effect of position on listing performance. It\u2019s an interesting idea \u2014 but a costly one.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">While it\u2019s a reasonable design for statistical inference, this setup is kind of terrible for the user and business. The user might have found what they needed\u2014maybe even made a purchase. But instead, maybe half of the inventory they would have seen was remotely a good match because of our experiment. This suboptimal user experience likely hurts engagement in both the short and long term \u2014 especially for new users who are still to see what value the platform holds for them.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">Can we think of a way to mitigate this loss? Still committed to A\/B testing, one could expose a smaller set of users to the experiment. While it will scale down the consequences, it may also stand in the way of reaching sufficient statistical power by lowering the sample size. Moreover, even small audiences can be responsible for substantial revenue for some companies still \u2014\u00a0those with millions of users. So, cutting the exposed audience is not a silver bullet either.<\/p>\n<p class=\"wp-block-paragraph\">Naturally, the way to go is to leave the platform and its users undisturbed \u2014\u00a0 and still find a way to answer the question at hand.\u00a0Causal inference is the right mindset for this, but the question is: how do we do that exactly? <\/p>\n<h3 class=\"wp-block-heading\">Confounders<\/h3>\n<p class=\"wp-block-paragraph\">Listings don\u2019t just make it to the top of the page on a good day; it\u2019s their quality, relevance, and the sellers\u2019 reputation that promote the ranking of a listing. Let\u2019s call these three variables <strong>W<\/strong>. <\/p>\n<p class=\"wp-block-paragraph\">What makes <strong>W<\/strong> tricky is that it influences both the ranking of the listing and also the probability that the listing gets clicked, a proxy for performance.<\/p>\n<p class=\"wp-block-paragraph\">In other words, <strong>W<\/strong> affects both our treatment (position) and outcome (click), helping itself with the status of <em>confounder<\/em>.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"538\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/DAG_-_Fork-4-1024x538.png?resize=1024%2C538&#038;ssl=1\" alt=\"\" class=\"wp-image-603412\"><figcaption class=\"wp-element-caption\">A variable, or set thereof, W, is a confounder when it influences both, the treatment (rank, position) and outcome of interest (click).<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">Therefore, our task is to find a design that\u2019s fit for purpose; one that effectively controls the confounding effect of <strong>W<\/strong>.<\/p>\n<h3 class=\"wp-block-heading\"><em>You don\u2019t choose regression discontinuity \u2014 it chooses you<\/em><\/h3>\n<p class=\"wp-block-paragraph\">Not all causal inference designs are just sitting around waiting to be picked. Sometimes they show up when you least need them, and sometimes you get lucky when you need them most \u2014 like today.<\/p>\n<p class=\"wp-block-paragraph\">It looks like we can use the page cutoff to identify the causal impact of position on clicks-through rate.<\/p>\n<h4 class=\"wp-block-heading\">Abrupt cutoff in search results pagination<\/h4>\n<p class=\"wp-block-paragraph\">Let\u2019s unpack the listing recommendation mechanism to see exactly how. Here\u2019s what happens under the hood when a results page is generated for a search:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Fetch listings matching the query<\/strong><br \/>A coarse set of listings is pulled from the inventory, based on filters like location, radius, and category, etc.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Score listings on personal relevance<\/strong><strong><br \/><\/strong>This step uses user history and listing quality proxies to predict what the user is most likely to click.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Rank listings by score<\/strong><strong><br \/><\/strong>Higher scores get higher ranks. Business rules mix in ads and commercial content with organic results.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Populate pages<br \/><\/strong>Listings are slotted by absolute relevance score. A results page ends at the <em>k<\/em><sup>th<\/sup> listing, so the <em>k<sub>+1<\/sub><\/em><sup>th<\/sup> listing appears at the top of the <em>next<\/em> page. This is is going to be crucial to our design.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Impressions and user interaction<br \/><\/strong>Users see the results in order of relevance. If a listing catches their eye, they might click and view more details: one step closer to the trade.<\/li>\n<\/ol>\n<h3 class=\"wp-block-heading\" id=\"the-setup\">Practical setup and variables<\/h3>\n<p class=\"wp-block-paragraph\">So, what is exactly our design? Next, we walk through the reasoning and identification of the key ingredients of our design.<\/p>\n<h4 class=\"wp-block-heading\">The running variable<\/h4>\n<p class=\"wp-block-paragraph\">In our setup, the running variable is the relevance score (s_j) for listing j. This score is a continuous, complex function of both user and listing properties:<\/p>\n<p class=\"wp-block-paragraph\">$$s_j = f(u_i, l_j)$$<\/p>\n<p class=\"wp-block-paragraph\">The listing\u2019s rank (r_j) is simply a rank transformation of (s_j), defined as:<\/p>\n<p class=\"wp-block-paragraph\">$$r_i = sum_{j=1}^{n} mathbf{1}(s_j leq s_i)$$<\/p>\n<p class=\"wp-block-paragraph\">Practically speaking, this means that for analytic purposes\u2014such as fitting models, making local comparisons, or identifying cutoff points\u2014knowing a listing\u2019s rank conveys nearly the same information as knowing its underlying relevance score, and vice versa.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Details: Relevance score vs. rank<\/summary>\n<p class=\"wp-block-paragraph\">The relevance score (s_j) reflects how well a listing matches a specific user\u2019s query, given parameters like location, price range, and other filters. But this score is relative\u2014it only has meaning within the context of the listings returned for that particular search.<\/p>\n<p class=\"wp-block-paragraph\">In contrast, rank (or position) is absolute. It directly determines a listing\u2019s visibility. I think of rank as a standardising transformation of (s_j). For example, Listing A in search Z might have the highest score of 5.66, while Listing B in search K tops out at 0.99. These raw scores aren\u2019t comparable across searches\u2014but both listings are ranked first in their respective result sets. That makes them equivalent in terms of what really matters here: how visible they are to users.<\/p>\n<\/details>\n<h4 class=\"wp-block-heading\">The cutoff, and treatment<\/h4>\n<p class=\"wp-block-paragraph\">If a listing just misses the first page, it doesn\u2019t fall to the bottom of page two \u2014 it is artificially bumped to the top. That\u2019s a lucky break. Normally, only the most relevant listings appear at the top, but here a listing of merely moderate relevance ends up in a prime slot \u2014albeit on the second page \u2014 purely due to the arbitrary position of the page break. Formally, the treatment assignment (D_j) goes like:<\/p>\n<p class=\"wp-block-paragraph\">$$D_j = begin{cases} 1 &amp; text{if } r_j &gt; 30 \\ 0 &amp; text{otherwise} end{cases}$$<\/p>\n<p class=\"wp-block-paragraph\"><em>(Note on global rank: Rank 31 isn\u2019t just the first listing on page two; it is still the 31st listing overall)<\/em><\/p>\n<p class=\"wp-block-paragraph\">The strength of this setup lies in what happens near the cutoff: a listing ranked 30 may be nearly identical in relevance to one ranked 31. A small scoring fluctuation \u2014 or a high-ranking outlier \u2014 can push a listing over the threshold, flipping its treatment status. This local randomness is what makes the setup valid for RDD.<\/p>\n<h4 class=\"wp-block-heading\">The outcome: Impression-to-click<\/h4>\n<p class=\"wp-block-paragraph\">Finally, we operationalise the outcome of interest as the click-though rate from impressions to clicks. Remember that all listings are \u2018impressed\u2019 when when the page is populated. The click is the binary indicator of the desired user behaviour.<\/p>\n<p class=\"wp-block-paragraph\">In summary, this is our setup:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Outcome:<\/strong> impression-to-click conversion<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Treatment: <\/strong>Landing on the first vs. second page<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Running variable<\/strong>: listing rank; page cutoff at 30\u00a0<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Next we walk through how to estimate the RDD.\u00a0<\/p>\n<h3 class=\"wp-block-heading\" id=\"estimating-rdd\">Estimating RDD<\/h3>\n<p class=\"wp-block-paragraph\">In this section, we\u2019ll estimate the causal parameter, interpret it, and connect them back to our core hypothesis: how position affects listing visibility.<\/p>\n<p class=\"wp-block-paragraph\">Here\u2019s what we\u2019ll cover:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Meet the data:<\/strong> Intro to the dataset<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Covariates:<\/strong> Why and how to include them<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Modelling choices<\/strong>: parametric RDD vs. not. Choosing the polynomial degree and bandwidth.<\/li>\n<li class=\"wp-block-list-item\"><strong>Placebo-testing<\/strong><\/li>\n<li class=\"wp-block-list-item\"><strong>Density continuity testing<\/strong><\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\">Meet the data<\/h3>\n<p class=\"wp-block-paragraph\">We\u2019re working with impressions data from one of Adevinta\u2019s (ex-eBay Classifieds Group) marketplaces. It\u2019s real data, which makes the whole exercise feel grounded. That said, values and relationships are censored and scrambled where necessary to protect its strategic value.<\/p>\n<p class=\"wp-block-paragraph\">An important note to how we interpret the RDD estimates and drive decisions, is how the data was collected: only those searches where the user saw both the first and second page were included. <\/p>\n<p class=\"wp-block-paragraph\">This way, we partial out the page fixed effect if any, but the reality is that many users don\u2019t make it to the second page at all. So there is a big volume gap. We discuss the repercussion in the analysis recap.<\/p>\n<p class=\"wp-block-paragraph\">The dataset consists of these variables:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Clicked<\/strong>: 1 if the listing was clicked, 0 otherwise \u2013 binary<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Position<\/strong>: the rank of the listing \u2013 numeric<\/li>\n<li class=\"wp-block-list-item\">\n<strong>D<\/strong>: treatment indicator, 1 if position &gt; 30, 0 otherwise \u2013 binary<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Category<\/strong>: product category of the listing \u2013 nominal<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Organic<\/strong>: 1 if organic, 0 if from a professional seller \u2013 binary<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Boosted<\/strong>: 1 if was paid to be at the top, 0 otherwise \u2013\u00a0binary<\/li>\n<\/ul>\n<figure class=\"wp-block-table aligncenter\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td>click<\/td>\n<td>rel_position<\/td>\n<td>D<\/td>\n<td>category<\/td>\n<td>organic<\/td>\n<td>boosted<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>-3<\/td>\n<td>0<\/td>\n<td>A<\/td>\n<td>1<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>-14<\/td>\n<td>0<\/td>\n<td>A<\/td>\n<td>1<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>0<\/td>\n<td>3<\/td>\n<td>1<\/td>\n<td>C<\/td>\n<td>1<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>0<\/td>\n<td>10<\/td>\n<td>1<\/td>\n<td>D<\/td>\n<td>0<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>-1<\/td>\n<td>0<\/td>\n<td>K<\/td>\n<td>1<\/td>\n<td>1<\/td>\n<\/tr>\n<\/tbody>\n<\/table><figcaption class=\"wp-element-caption\">A sample of the dataset we are working with.<\/figcaption><\/figure>\n<h3 class=\"wp-block-heading\" id=\"covariates\">Covariates: how to include them to increase accuracy?<\/h3>\n<p class=\"wp-block-paragraph\">The running variable, the cutoff, and the continuity assumption, give you <em>all<\/em> you need to identify the causal effect. But including covariates can sharpen the estimator by reducing variance \u2014 if done right. And, oh is it easy to do it wrong. <\/p>\n<p class=\"wp-block-paragraph\">The easiest thing to \u201cbreak\u201d about the RDD design, is the continuity assumption. Simultaneously, that\u2019s the <em>last<\/em> thing we want to break (I already rambled long enough about this). <\/p>\n<p class=\"wp-block-paragraph\">Therefore, the main quest in adding covariates is to it in such way that we reduce variance, while keeping the continuity assumption intact. One way to formulate that, is to assume continuity without covariates <em>and<\/em> <em>with<\/em> covariates:<\/p>\n<p class=\"wp-block-paragraph\">begin{equation}<br \/>lim_{x to c^-} mathbb{E}[Y_i(0) mid X_i = x] = lim_{x to c^+} mathbb{E}[Y_i(0) mid X_i = x] text{(no covariates)}<br \/>end{equation}<\/p>\n<p class=\"wp-block-paragraph\">begin{equation}<br \/>lim_{x to c^-} mathbb{E}[Y_i(0) mid X_i = x, Z_i] = lim_{x to c^+} mathbb{E}[Y_i(0) mid X_i = x, Z_i]  text{(covariates)}<br \/>end{equation}<\/p>\n<p class=\"wp-block-paragraph\">Where (Z_i) is a vector of covariates, for subject i. Less mathy, two things should remain unchanged after adding covariates:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">The functional form of the running variable, and;<\/li>\n<li class=\"wp-block-list-item\">The (absence of the) jump in treatment assignment at the cutoff<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">I did not find out the above myself; Calonico, Cattaneo, Farrell, and Titiunik (2018) did. They developed a formal framework for incorporating covariates into RDD. I\u2019ll leave the details to the paper. For now, some modelling guidelines can keep us going:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Model covariates linearly<\/strong> so that the treatment effect remains the same with and without covariates, thanks to a simple and smooth partial effect of the covariates;<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Keep the model terms additive<\/strong>, so that the treatment effect remains the LATE, and does not become <em>conditional on covariates<\/em> (CATE); and to avoid <em>adding<\/em> a jump at the cutoff.<\/li>\n<li class=\"wp-block-list-item\">The above implies that there be <em>no<\/em> interactions with the treatment indicator, nor with the running variable. Doing any of these may break continuity and invalidate our RDD design.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Our target model may look like this:<\/p>\n<p class=\"wp-block-paragraph\">begin{equation}<br \/>Y_i = alpha + tau D_i + f(X_i \u2013 c) + beta^top Z_i + varepsilon_i<br \/>end{equation}<\/p>\n<p class=\"wp-block-paragraph\">For letting the covariates interact with the treatment indicator, the sort of model we want to <em>avoid<\/em> looks like this:<\/p>\n<p class=\"wp-block-paragraph\">begin{equation}<br \/>Y_i = alpha + tau D_i + f(X_i \u2013 c) + beta^top (Z_i cdot D_i) + varepsilon_i<br \/>end{equation}<\/p>\n<p class=\"wp-block-paragraph\">Now, let\u2019s distinguish between two ways of practically including covariates:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Direct inclusion<\/strong>: Add them directly to the outcome model alongside the treatment and running variable.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Residualisation<\/strong>: First regress the outcome on the covariates, then use the residuals in the RDD.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">We\u2019ll use residualisation in our case. It\u2019s an effective way reduce noise, produces cleaner visualisations, and protects the strategic value of the data.<\/p>\n<p class=\"wp-block-paragraph\">The snippet below defines the outcome de-noising model and computes the residualised outcome, <code>click_res<\/code>. The idea is simple: once we strip out the variance explained by the covariates, what remains is a less noisy version of our outcome variable\u2014at least in theory. Less noise means more accuracy.<\/p>\n<p class=\"wp-block-paragraph\">In practice, though, the residualisation barely moved the needle this time. We can see that by checking the change in standard deviation:<\/p>\n<p class=\"wp-block-paragraph\"><code>SD(click_res) \/ SD(click) - 1<\/code> gives us about -3%, which is small practically speaking. <\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-r\"># denoising clicks\nmod_outcome_model &lt;- lm(click ~ l1 + organic + boosted, \n                        data = df_listing_level)\n\ndf_listing_level$click_res &lt;- residuals(mod_outcome_model)\n\n# the impact on variance is limited: ~ -3%\nsd(df_listing_level$click_res) \/ sd(df_listing_level$click) - 1<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Even though the denoising didn\u2019t have much effect, we\u2019re still in a good spot. The original outcome variable already has low conditional variance, and patterns around the cutoff are visible to the naked eye, as we can see below.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"576\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/visible_jump-1-1024x576.png?resize=1024%2C576&#038;ssl=1\" alt=\"\" class=\"wp-image-602955\"><figcaption class=\"wp-element-caption\">On the x-axis: ranks relative to the page end (30 positions on one page), and on the y-axis: the residualised average click through.<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">We move on to a few other modelling decisions that generally have a bigger impact: choosing between parametric and non-parametric RDD, the polynomial degree and the bandwidth parameter (h).<\/p>\n<h3 class=\"wp-block-heading\" id=\"choices\">Modelling choices in RDD<\/h3>\n<h4 class=\"wp-block-heading\" id=\"parametric\">Parametric vs non-parametric RDD<\/h4>\n<p class=\"wp-block-paragraph\">You might wonder why we even have to choose between parametric and non-parametric RDD. The answer lies in how each approach trades off <strong>bias and variance<\/strong> in estimating the treatment effect.<\/p>\n<p class=\"wp-block-paragraph\">Choosing <strong>parametric RDD<\/strong> is essentially choosing to reduce variance. It assumes a specific functional form for the relationship between the outcome and the running variable, (mathbb{E}[Y mid X]), and fits that model across the entire dataset. The treatment effect is captured as a discrete jump in an otherwise continuous function. The typical form looks like this:<\/p>\n<p class=\"wp-block-paragraph\">$$Y = beta_0 + beta_1 D + beta_2 X + beta_3 D cdot X + varepsilon$$<\/p>\n<p class=\"wp-block-paragraph\"><strong>Non-parametric RDD<\/strong>, on the other hand, is about reducing bias. It avoids strong assumptions about the global relationship between Y and X and instead estimates the outcome function separately on either side of the cutoff. This flexibility allows the model to more accurately capture what\u2019s happening right around the threshold. The non-parametric estimator is:<\/p>\n<p class=\"has-text-align-center wp-block-paragraph\">(tau = lim_{x downarrow c} mathbb{E}[Y mid X = x] \u2013 lim_{x uparrow c} mathbb{E}[Y mid X = x])<\/p>\n<p class=\"wp-block-paragraph\">So, which should you choose? Honestly, it can feel arbitrary. And that\u2019s okay. This is the first in a series of judgment calls that practitioners often call the fun part of RDD. It\u2019s where modelling becomes as much an art as it is a science.<\/p>\n<p class=\"wp-block-paragraph\">I\u2019ll walk through how I approach that choice. But first, let\u2019s look at two key tuning parameters (especially for non-parametric RDD) that will guide our final decision: the polynomial degree and the bandwidth, h.<\/p>\n<h4 class=\"wp-block-heading\" id=\"polydegree\">Polynomial degree<\/h4>\n<p class=\"wp-block-paragraph\">The relationship between outcome and the running variable can take many forms, and capturing its true shape is crucial for estimating the causal effect accurately. If you\u2019re lucky, everything is linear and there is no need to think of polynomials \u2014 If you\u2019re a realist, then you probably want to learn how they can serve you in the process.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">In selecting the right polynomial degree, the goal is to reduce bias, without inflating the variance of the estimator. So we want to allow for flexibility, but we don\u2019t want to do it more than necessary. Take the examples in the image below: with an outcome of low enough variance, the linear form naturally invites the eyes to estimate the outcome at the cutoff. But the estimate becomes biased with only a slightly more complex form, if we enforce a linear shape in the model. Insisting on a linear form in such a complex case is like fitting your feet into a glove: It kind of works, but it\u2019s very ugly.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">Instead, we give the model more degrees of freedom with a higher-degree polynomial, and estimate the expected <mdspan datatext=\"el1745611088934\" class=\"mdspan-comment\">outcome<\/mdspan> (tau = lim_{x downarrow c} mathbb{E}[Y mid X = x] \u2013 lim_{x uparrow c} mathbb{E}[Y mid X = x]), with lower bias.<\/p>\n<figure class=\"wp-block-image size-large\" datatext=\"\"><img data-recalc-dims=\"1\" height=\"538\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/RDD_-_form-2-1024x538.png?resize=1024%2C538&#038;ssl=1\" alt=\"\" class=\"wp-image-602952\"><figcaption class=\"wp-element-caption\"><mdspan datatext=\"el1745611151754\" class=\"mdspan-comment\">Caption: the relationship between the outcome and running variable can be simple and predictable, or take on a more complex shape that\u2019s more erratic inherently. Getting complex forms right may be harder \u2014 it requires high model fidelity<\/mdspan>, and failing to do so may introduce bias.<\/figcaption><\/figure>\n<h4 class=\"wp-block-heading\" id=\"bandwidth\">The bandwidth parameter: h<\/h4>\n<p class=\"wp-block-paragraph\">Working with polynomials in the way that\u2019s described above does not come free of worries. Two things are required and pose a challenge at the same time:\u00a0<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\u00a0we need to get the modelling <em>right<\/em> for entire range, and;<\/li>\n<li class=\"wp-block-list-item\">\u00a0the entire range should be relevant for the task at hand, which is estimating (tau = lim_{x downarrow c} mathbb{E}[Y mid X = x] \u2013 lim_{x uparrow c} mathbb{E}[Y mid X = x])\u00a0<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Only then we reduce bias as intended; If one of these two is not the case, we risk adding more of it.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">The thing is that modelling the entire range properly is more difficult than modelling a smaller range, specially if the form is complex. So, it\u2019s easier to make mistakes. Moreover, the entire range is almost certain not to be relevant to estimate the causal effect \u2014\u00a0the \u201clocal\u201d in LATE gives it away. How do we work around this?<\/p>\n<p class=\"wp-block-paragraph\">Enter the bandwidth parameter, h. The bandwidth parameters aids the model in leveraging data that is closer to the cutoff, dropping the <em>global<\/em> data idea, and bringing it back to the local scope RDD estimates the effect for. It does so by weighting the data by some function (mathbb{w}(X)) so that more weight is given to entries near the cutoff, and less to the entries further away.<\/p>\n<p class=\"wp-block-paragraph\">For example, with h = 10, the model considers the range of total length 20; 10 on each side of the cutoff.<\/p>\n<p class=\"wp-block-paragraph\">The effective weight depends on the function, (mathbb{w}). A bandwidth function that has a hard-boundary behaviour is called a square, or uniform, kernel. Think of it as a function that gives weights 1 when the data is within bandwidth, and 0 otherwise. The gaussian and triangular kernels are two other frequently used kernels by practitioners. The key difference is that these behave less abruptly in weighting of the entries, compared to the square kernel. The image below visualises the behaviour of the three kernels functions.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"538\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/DAG_-_Fork-3-1024x538.png?resize=1024%2C538&#038;ssl=1\" alt=\"\" class=\"wp-image-602956\"><figcaption class=\"wp-element-caption\">Three weighting functions visualised. The y-axis represents the weight. The square kernel acts as a hard-cutoff as to which entries it allows to be seen by the model. The triangular and gaussian functions behave more smoothly with respect to this.<\/figcaption><\/figure>\n<h4 class=\"wp-block-heading\">Everything put together: non- vs. parametric RDD, polynomial degree and bandwidth<\/h4>\n<p class=\"wp-block-paragraph\">To me, choosing the final model boils down to the question: what is the simplest model that does the good job?<em> <\/em>Indeed \u2014\u00a0the principle of Occam\u2019s razor never goes out of fashion. In practise, this means:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Non- vs. Parametric:<\/strong> is the functional form simple on both sides of the cutoff? Then a single fit, pooling data from both sides will do. Otherwise, nonparametric RDD adds the flexibility that is needed to embrace two different dynamics on either side of the cutoff.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Polynomial degree:<\/strong> when the function is complex, I opt-in for higher degrees to follow the trend better flexibly.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Bandwidth:<\/strong> if just picked a high polynomial degree, then I will let h be larger too. Otherwise, lower values for h often go well with lower degrees of polynomials in my experience*, **.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">* This brings us to the generally accepted recommendation in the literature: keep the polynomial degree lower than 3. In most use cases 2 works well enough. Just make sure you pick mindfully.<\/p>\n<p class=\"wp-block-paragraph\">** Also, note that h fits especially well in the non-parametric mentality; I see these two choices as co-dependent.<\/p>\n<p class=\"wp-block-paragraph\">Back to the listing position scenario. This is the final model to me:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-r\"># modelling the residuals of the outcome (de-noised)\nmod_rdd &lt;- lm(click_res ~ D + ad_position_idx,\n              weight = triangular_kernel(x = ad_position_idx, c = 0, h = 10),\u00a0 # this is h\n              data = df_listing_level)<\/code><\/pre>\n<h3 class=\"wp-block-heading\" id=\"interpretation\">Interpreting RDD results<\/h3>\n<p class=\"wp-block-paragraph\">Let\u2019s look at the model output. The image below shows us the model summary. If you\u2019re familiar with that, it all will come down to interpreting the parameters.<\/p>\n<p class=\"wp-block-paragraph\">The first thing to look at is that treated listings have ~1% point higher probability of being clicked, than untreated listings. To put that in perspective, that\u2019s a +20% change if the click rate of the control is 5%, and ~ +1% increase if the control is 80%. When it comes to <em>practical significance<\/em> of this causal effect, these two uplifts are day and night. I\u2019ll leave this open-ended with a few questions to take home: when would you and your team label this impact as an opportunity to jump on? What other data\/answers do we need to declare this track worthy of following?<\/p>\n<p class=\"wp-block-paragraph\">The remainder of the parameters don\u2019t really add much to the interpretation of the causal effect. But let\u2019s go over them quickly, nonetheless. The second estimate (x) is that of the slope below cutoff slope; the third one (D x (mathbb(x))) is the additional [negative] points added to the previous slope to reflect the slope <em>above<\/em> the cutoff; Finally, the intercept is the average for the units right below the cutoff. Because our outcome variable is residualised, the value -0.012 is the demeaned outcome; it no longer is on the scale of the original outcome.<\/p>\n<figure class=\"wp-block-image aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/Screenshot-2025-04-22-at-09.49.18.png?ssl=1\" alt=\"\" class=\"wp-image-602088\"><\/figure>\n<h3 class=\"wp-block-heading\">Different choices, different models<\/h3>\n<p class=\"wp-block-paragraph\">I\u2019ve put this image together to show a collection of other possible models, had we made different choices in bandwidth, polynomial degree, and parametric-versus-not. Although hardly any of these models would have put the decision maker on a totally wrong path in this particular dataset, each model comes with its bias and variance properties. This <em>does<\/em> colour our <em>confidence<\/em> of the estimate.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"576\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/rdd_models-1024x576.png?resize=1024%2C576&#038;ssl=1\" alt=\"\" class=\"wp-image-602089\"><\/figure>\n<h3 class=\"wp-block-heading\" id=\"placebo\">Placebo testing<\/h3>\n<p class=\"wp-block-paragraph\">In any causal inference method, the identification assumption is everything. One thing is off, and the entire analysis crumbles. We can pretend everything is alright, or we put our methods to the test ourselves (believe me, it\u2019s better when you break your own analysis before it goes out there)<\/p>\n<p class=\"wp-block-paragraph\">Placebo testing is one way to corroborate the results. Placebo testing checks the validity of results by using a setup identical to the real one, minus the actual treatment. If we still see an effect, it signals a flawed design \u2014 continuity can\u2019t be assumed, and causal effects can\u2019t be identified.<\/p>\n<p class=\"wp-block-paragraph\">Good for us, we have a placebo group. The 30-listing page cut only exists on the desktop version of the platform. On mobile, infinite scroll makes it one long page; no pagination, no page jump. So the effect of \u201cgoing to the next page\u201d shouldn\u2019t appear, and it doesn\u2019t.<\/p>\n<p class=\"wp-block-paragraph\">I don\u2019t think we need to do much inference. The graph below already tells us the entire story: without pages, going from the 30th position to the 31st is not different from going from any other position to the next. More importantly, the function is smooth at the cutoff. This finding adds a great deal of credibility to our analysis by showcasing that continuity holds in this placebo group.<\/p>\n<figure class=\"wp-block-image\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/AD_4nXe-rSOTvzK0PzlKdvoSvAlirM8tA2MAJd4qlkrGNrskuQJgaQEO9I-35tY2GzmGrMxS9i_bB4_Qajl15vWYue52e1I5eLbokeJ4G1cTsE8JYwtAMZgDnpIc4LcTC2caJGvWgCO6eg.png?ssl=1\" alt=\"\" class=\"wp-image-602174\"><\/figure>\n<p class=\"wp-block-paragraph\">The placebo test is one of the strongest checks in an RDD. It tests the continuity assumption almost <em>directly<\/em>, by treating the placebo group as a stand-in for the counterfactual. <\/p>\n<p class=\"wp-block-paragraph\">Of course, this relies on a new assumption: that the placebo group is valid; that it is a sufficiently good counterfactual. So the test is powerful only if that assumption is more credible than assuming continuity without evidence.<\/p>\n<p class=\"wp-block-paragraph\">Which means that we need to be open to the possibility that there is no proper placebo group. How do we stress-test our design then?<\/p>\n<h3 class=\"wp-block-heading\" id=\"no-manipulation\">No-manipulation and the density continuity test<\/h3>\n<p class=\"wp-block-paragraph\">Quick recap. There are two related sources of confounding and hence to violating the continuity assumption: <\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">direct confounding from a third variable at the cutoff, and<\/li>\n<li class=\"wp-block-list-item\">manipulation of the running variable.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">The first can\u2019t be tested directly (except with a placebo test). The second can.<\/p>\n<p class=\"wp-block-paragraph\">If units can shift their running variable, they self-select into treatment. The comparison stops being fair: we\u2019re now comparing manipulators to those who couldn\u2019t or didn\u2019t. That self-selection becomes a confounder, if it also affects the outcome.<\/p>\n<p class=\"wp-block-paragraph\">For instance, students who did not make the cut for a scholarship, but go on to effectively smooth-talk their institution into letting them pass with a higher score. That silver tongue can also help them getting better salaries, and act as confounder when we study the effect of scholarships on future income.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"332\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/DAG_-_chain-1-1024x332.png?resize=1024%2C332&#038;ssl=1\" alt=\"\" class=\"wp-image-602099\"><figcaption class=\"wp-element-caption\">In DAG form, running variable manipulation causes selection bias, which in turn makes that the continuity assumption doesn\u2019t longer hold. If we know that continuity holds, then there is no need to test for selection bias by manipulation. But when we cannot (because there is no good placebo group), then at least we can try to test if there is manipulation. <\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">So, what are the signs that we\u2019re in such scenario? An unexpectedly high number of units just above the cutoff, and a dip just below (or vice versa). We can see this as another continuity question, but this time in terms of the density of the samples. <\/p>\n<p class=\"wp-block-paragraph\">While we can\u2019t test the continuity of the potential outcomes directly, we can test the continuity of the density of the running variable at the cutoff. The <em>McCrary<\/em> test is the standard tool for this, exactly testing:<\/p>\n<p class=\"wp-block-paragraph\">(H_0: lim_{x to c^-} f(x) = lim_{x to c^+} f(x) quad text{(No manipulation)})<\/p>\n<p class=\"wp-block-paragraph\">(H_A: lim_{x to c^-} f(x) neq lim_{x to c^+} f(x) quad text{(Manipulation)})<\/p>\n<p class=\"wp-block-paragraph\">where (f(x)) is the density function of the running variable. If (f(x)) jumps at x = c, it suggests that units have sorted themselves just above or below the cutoff \u2014 violating the assumption that the running variable was not manipulable at that margin.<\/p>\n<p class=\"wp-block-paragraph\">The internals of this test is something for a different post, because luckily we can rely <code>rdrobust::rddensity<\/code> to run this test, off-the-shelf.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-r\">require(rddensity)\ndensity_check_obj &lt;- rddensity(X = df_listing_level$ad_position_idx, \n                               c = 0)\nsummary(density_check_obj)\n\n# for the plot below\nrdplotdensity(density_check_obj, X = df_listing_level$ad_position_idx)<\/code><\/pre>\n<figure class=\"wp-block-image aligncenter size-large\"><img data-recalc-dims=\"1\" height=\"576\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/rdd_density-1024x576.png?resize=1024%2C576&#038;ssl=1\" alt=\"\" class=\"wp-image-602092\"><figcaption class=\"wp-element-caption\">A visual representation of the McCrary test.<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">The test shows marginal evidence of a discontinuity in the density of the running variable (T = 1.77, p = 0.077). Binomial counts are unbalanced across the cutoff, suggesting fewer observations just below the threshold.<\/p>\n<p class=\"wp-block-paragraph\">Usually, this is a red flag as it may pose a thread to the continuity assumption. This time however, we know that continuity <em>actually<\/em> holds (see placebo test). <\/p>\n<p class=\"wp-block-paragraph\">Moreover, ranking is done by the algorithm: sellers have no means to manipulate the rank of their listings at all. That\u2019s something we know  by design.<\/p>\n<p class=\"wp-block-paragraph\">Hence, a more plausible explanation is that the discontinuity in the density is driven by platform-side impression logging (not ranking), or my own filtering in the SQL query (which is elaborate, and missing values on the filter variables are not uncommon).<\/p>\n<h3 class=\"wp-block-heading\" id=\"inference\">Inference<\/h3>\n<p class=\"wp-block-paragraph\">The results will do this time around. But Calonico, Cattaneo, and Titiunik (2014) highlight a few issues with OLS RDD estimates like ours. Specifically, about 1) the bias in estimating the expected outcome at the cutoff, that no longer is really<em> at<\/em> the cutoff when we take samples further away from it, and 2) the bandwidth-induced uncertainty that is left out of the model (as h is treated as a hyperparameter, not a model parameter). <\/p>\n<p class=\"wp-block-paragraph\">Their methods are implemented in <code>rdrobust<\/code>, an R and Stata package. I recommend using that software in analyses that are about driving real-life decisions.<\/p>\n<h3 class=\"wp-block-heading\" id=\"analysis-recap\">Analysis recap<\/h3>\n<p class=\"wp-block-paragraph\">We looked at how a listing\u2019s spot in the search results affects how often it gets clicked. By focusing on the cutoff between the first and second page, we found a clear (though modest) causal effect: listings at the top of page two got more clicks than those stuck at the bottom of page one. A placebo test backed this up\u2014on mobile, where there\u2019s infinite scroll and no real \u201cpages,\u201d the effect disappears. That gives us more confidence in the result. Bottom line: where a listing shows up matters, and prioritising top positions could boost engagement and create new commercial possibilities.<\/p>\n<p class=\"wp-block-paragraph\">But before we run with it, a couple of important caveats.<\/p>\n<p class=\"wp-block-paragraph\">First, our result is local\u2014it only tells us what happens near the page-two cutoff. We don\u2019t know if the same effect holds at the top of page one, which probably signals even more value to users. So this might be a lower-bound estimate.<\/p>\n<p class=\"wp-block-paragraph\">Second, volume matters. The first page gets a lot more eyeballs. So even if a top slot on page two gets more clicks per view, a lower spot on page one might still win overall.<\/p>\n<h2 class=\"wp-block-heading\" id=\"conclusions\">Conclusion<\/h2>\n<p class=\"wp-block-paragraph\">Regression Discontinuity Design is not your everyday causal inference method \u2014 it\u2019s a nuanced approach best saved for when the stars align, and randomisation isn\u2019t doable. Make sure that you have a good grip on the design, and be thorough about the core assumptions: try to break them, and then try harder. When you have what you need, it\u2019s an incredibly satisfying design. I hope this reading serves you well the next time you get an opportunity to apply this method.\u00a0<\/p>\n<p class=\"wp-block-paragraph\">It\u2019s great seeing that you got this far into this post. If you want to read more, it\u2019s possible; just not here. So, I compiled a small list of resources for you:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/www.google.com\/search?gs_ssp=eJzj4tVP1zc0TCtJrkoqNDcwYPSSSU4sLU7MUcjMS0stSs1LTlUoyUhVyM2sKEksSAUAUFsPiA&amp;q=causal+inference+the+mixtape&amp;rlz=1C5GCEA_enNL1150NL1150&amp;oq=causal+inference&amp;gs_lcrp=EgZjaHJvbWUqBwgCEC4YgAQyCQgAEEUYORiABDIHCAEQABiABDIHCAIQLhiABDIHCAMQLhiABDIHCAQQLhiABDIGCAUQRRhBMgYIBhBFGEEyBggHEEUYQdIBCDI3NDBqMGo3qAIIsAIB&amp;sourceid=chrome&amp;ie=UTF-8\" data-type=\"link\" data-id=\"https:\/\/www.google.com\/search?gs_ssp=eJzj4tVP1zc0TCtJrkoqNDcwYPSSSU4sLU7MUcjMS0stSs1LTlUoyUhVyM2sKEksSAUAUFsPiA&amp;q=causal+inference+the+mixtape&amp;rlz=1C5GCEA_enNL1150NL1150&amp;oq=causal+inference&amp;gs_lcrp=EgZjaHJvbWUqBwgCEC4YgAQyCQgAEEUYORiABDIHCAEQABiABDIHCAIQLhiABDIHCAMQLhiABDIHCAQQLhiABDIGCAUQRRhBMgYIBhBFGEEyBggHEEUYQdIBCDI3NDBqMGo3qAIIsAIB&amp;sourceid=chrome&amp;ie=UTF-8\">Causal Inference Mixtape<\/a>: for an extensive read on RDD and more<\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/web.stanford.edu\/~swager\/causal_inf_book.pdf\" data-type=\"link\" data-id=\"https:\/\/web.stanford.edu\/~swager\/causal_inf_book.pdf\">Causal Inference: A Statistical Learning Approach<\/a>: formal and technically to the point<\/li>\n<li class=\"wp-block-list-item\">and of course our trusty Wikipedia (actually great to get started)<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Also check out the reference section below for some deep-reads.<\/p>\n<p class=\"wp-block-paragraph\">Happy to connect on <a href=\"https:\/\/www.linkedin.com\/in\/alejandro-alvarez-p%C3%A9rez-a21912b0\/\">LinkedIn<\/a>, where I discuss more topics like the one here. Also, feel free to bookmark my personal <a href=\"https:\/\/aalvarezperez.github.io\/\">website<\/a> that is much cosier than here.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<p class=\"wp-block-paragraph\"><em>All images in this post are my own<\/em>. <em>The dataset that I used is real, and it is not publicly available.<\/em> <em>Moreover, the values extracted from it are anonymised<\/em>; <em>modified or omitted<\/em>, <em>to avoid revealing strategic insights about the company.<\/em><\/p>\n<p class=\"wp-block-paragraph\"><strong><em>References<\/em><\/strong><\/p>\n<p class=\"wp-block-paragraph\"><strong><em>Calonico, S., Cattaneo, M. D., Farrell, M. H., &amp; Titiunik, R. (2018).<\/em><\/strong><em> Regression Discontinuity Designs Using Covariates. Retrieved from <\/em><a href=\"http:\/\/arxiv.org\/abs\/1809.03904v1\"><em>http:\/\/arxiv.org\/abs\/1809.03904v1<\/em><\/a><\/p>\n<p class=\"wp-block-paragraph\"><strong><em>Calonico, S., Cattaneo, M. D., &amp; Titiunik, R. (2014).<\/em><\/strong><em> Robust nonparametric confidence intervals for regression-discontinuity designs. Econometrica, 82(6), 2295\u20132326. https:\/\/doi.org\/10.3982\/ECTA11757<\/em><\/p>\n<p class=\"wp-block-paragraph\"><code><\/code><\/p>\n<p>The post <a href=\"https:\/\/towardsdatascience.com\/regression-discontinuity-design-how-it-works-and-when-to-use-it\/\">Regression Discontinuity Design: How It Works and When to Use It<\/a> appeared first on <a href=\"https:\/\/towardsdatascience.com\/\">Towards Data Science<\/a>.<\/p>\n<\/div>\n<p> \t<BR><br \/>\n <BR><\/BR><br \/>\n    Alejandro Alvarez Perez<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n<a href=\"https:\/\/towardsdatascience.com\/regression-discontinuity-design-how-it-works-and-when-to-use-it\/\">Go to original source<\/a><br \/>\n \t<BR><br \/>\n <BR><\/BR><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Regression Discontinuity Design: How It Works and When to Use It Regression Discontinuity Design: How It Works and When to Use It You\u2019re an avid data scientist and experimenter. You know that randomisation is the summit of Mount Evidence Credibility, and you also know that when you can\u2019t randomise, you resort to observational data and [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[62,2585,210,83,2586,240,2587],"tags":[331,193,334],"class_list":["post-3630","post","type-post","status-publish","format-standard","hentry","category-aimldsaimlds","category-causal-data-science","category-causal-inference","category-data-science","category-econometrics","category-editors-pick","category-regression-discontinuity","tag-causal","tag-inference","tag-when"],"_links":{"self":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/3630"}],"collection":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/comments?post=3630"}],"version-history":[{"count":0,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/3630\/revisions"}],"wp:attachment":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/media?parent=3630"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/categories?post=3630"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/tags?post=3630"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}