1<!DOCTYPE html> 2<html lang="en"><head>
3<script src="index_files/libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 3
3
4<script src="index_files/libs/quarto-html/tabby.min.js"></script>
vendor: 1 bytes, line 4
4
5<script src="index_files/libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 5
5
6<script src="index_files/libs/quarto-html/tippy.umd.min.js"></script>
6 7<link href="index_files/libs/quarto-html/tippy.css" rel="stylesheet"> 8<link href="index_files/libs/quarto-html/light-border.css" rel="stylesheet"> 9<link href="index_files/libs/quarto-html/quarto-syntax-highlighting-bb62276ab45c84d9efd712f51ff97b5a.css" rel="stylesheet" id="quarto-text-highlighting-styles"> 10<link href="index_files/libs/quarto-contrib/fontawesome6-6.7.2/all.min.css" rel="stylesheet"> 11<link href="index_files/libs/quarto-contrib/fontawesome6-6.7.2/latex-fontsize.css" rel="stylesheet">
12<script src="index_files/libs/quarto-contrib/glightbox/glightbox.min.js"></script>
12 13<link href="index_files/libs/quarto-contrib/glightbox/glightbox.min.css" rel="stylesheet"> 14<link href="index_files/libs/quarto-contrib/glightbox/lightbox.css" rel="stylesheet"><meta charset="utf-8"> 15 <meta name="generator" content="quarto-1.10.18"> 16 17 <meta name="author" content="Sakeef M. Karim Amherst College SOCIAL RESEARCH"> 18 <title>Lecture 4 for SOCIâ316 at Amherst College</title> 19 <meta name="apple-mobile-web-app-capable" content="yes"> 20 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> 21 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui"> 22 <link rel="stylesheet" href="index_files/libs/revealjs/dist/reset.css"> 23 <link rel="stylesheet" href="index_files/libs/revealjs/dist/reveal.css"> 24 <style> 25 /* Default styles provided by pandoc. 26 ** See https://pandoc.org/MANUAL.html#variables-for-html for config info. 27 */ 28 span.smallcaps{font-variant: small-caps;} 29 div.columns{display: flex; gap: 1.5em;} 30 div.column{flex: auto;} 31 @media screen { 32 div.columns{gap: min(4vw, 1.5em);} 33 div.column{overflow-x: auto;} 34 } 35 div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 36 ul.task-list{list-style: none;} 37 ul.task-list li input[type="checkbox"] { 38 width: 0.8em; 39 margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 40 vertical-align: middle; 41 } 42 /* CSS for citations */ 43 div.csl-bib-body { } 44 div.csl-entry { 45 clear: both; 46 } 47 .hanging-indent div.csl-entry { 48 margin-left:2em; 49 text-indent:-2em; 50 } 51 div.csl-left-margin { 52 min-width:2em; 53 float:left; 54 } 55 div.csl-right-inline { 56 margin-left:2em; 57 padding-left:1em; 58 } 59 div.csl-indent { 60 margin-left: 2em; 61 } </style> 62 <link rel="stylesheet" href="index_files/libs/revealjs/dist/theme/quarto-b7529e71c7438fb8fba5788ff2fe02e1.css"> 63 <link rel="stylesheet" href="logo.css"> 64
64<script>window.backupDefine = window.define; window.define = undefined;</script>
64<script src="https://cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.js"></script>
64 65
65<script>document.addEventListener("DOMContentLoaded", function () { 66 var mathElements = document.getElementsByClassName("math"); 67 var macros = []; 68 for (var i = 0; i < mathElements.length; i++) { 69 var texText = mathElements[i].firstChild; 70 if (mathElements[i].tagName == "SPAN") { 71 katex.render(texText.data, mathElements[i], { 72 displayMode: mathElements[i].classList.contains('display'), 73 throwOnError: false, 74 macros: macros, 75 fleqn: false 76 }); 77}}}); 78 </script>
78 79
79<script>window.define = window.backupDefine; window.backupDefine = undefined;</script>
79<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css"> 80 <link href="index_files/libs/revealjs/plugin/quarto-line-highlight/line-highlight.css" rel="stylesheet"> 81 <link href="index_files/libs/revealjs/plugin/reveal-menu/menu.css" rel="stylesheet"> 82 <link href="index_files/libs/revealjs/plugin/reveal-menu/quarto-menu.css" rel="stylesheet"> 83 <link href="index_files/libs/revealjs/plugin/reveal-chalkboard/font-awesome/css/all.css" rel="stylesheet"> 84 <link href="index_files/libs/revealjs/plugin/reveal-chalkboard/style.css" rel="stylesheet"> 85 <link href="index_files/libs/revealjs/plugin/quarto-support/footer.css" rel="stylesheet"> 86 <style type="text/css"> 87 .reveal div.sourceCode { 88 margin: 0; 89 overflow: auto; 90 } 91 .reveal div.hanging-indent { 92 margin-left: 1em; 93 text-indent: -1em; 94 } 95 .reveal .slide:not(.center) { 96 height: 100%; 97 overflow-y: auto; 98 } 99 .reveal .slide.scrollable { 100 overflow-y: auto; 101 } 102 .reveal .footnotes { 103 height: 100%; 104 overflow-y: auto; 105 } 106 .reveal .slide .absolute { 107 position: absolute; 108 display: block; 109 } 110 .reveal .footnotes ol { 111 counter-reset: ol; 112 list-style-type: none; 113 margin-left: 0; 114 } 115 .reveal .footnotes ol li:before { 116 counter-increment: ol; 117 content: counter(ol) ". "; 118 } 119 .reveal .footnotes ol li > p:first-child { 120 display: inline-block; 121 } 122 .reveal .slide ul, 123 .reveal .slide ol { 124 margin-bottom: 0.5em; 125 } 126 .reveal .slide ul li, 127 .reveal .slide ol li { 128 margin-top: 0.4em; 129 margin-bottom: 0.2em; 130 } 131 .reveal .slide ul[role="tablist"] li { 132 margin-bottom: 0; 133 } 134 .reveal .slide ul li > *:first-child, 135 .reveal .slide ol li > *:first-child { 136 margin-block-start: 0; 137 } 138 .reveal .slide ul li > *:last-child, 139 .reveal .slide ol li > *:last-child { 140 margin-block-end: 0; 141 } 142 .reveal .slide .columns:nth-child(3) { 143 margin-block-start: 0.8em; 144 } 145 .reveal blockquote { 146 box-shadow: none; 147 } 148 .reveal .tippy-content>* { 149 margin-top: 0.2em; 150 margin-bottom: 0.7em; 151 } 152 .reveal .tippy-content>*:last-child { 153 margin-bottom: 0.2em; 154 } 155 .reveal .slide > img.stretch.quarto-figure-center, 156 .reveal .slide > img.r-stretch.quarto-figure-center { 157 display: block; 158 margin-left: auto; 159 margin-right: auto; 160 } 161 .reveal .slide > img.stretch.quarto-figure-left, 162 .reveal .slide > img.r-stretch.quarto-figure-left { 163 display: block; 164 margin-left: 0; 165 margin-right: auto; 166 } 167 .reveal .slide > img.stretch.quarto-figure-right, 168 .reveal .slide > img.r-stretch.quarto-figure-right { 169 display: block; 170 margin-left: auto; 171 margin-right: 0; 172 } 173 </style> 174 <link rel="icon" type="media/png" href="media/network.png"> 175 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> 176 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css"> 177 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"> 178 179 <style type="text/css"> 180 .monaco-editor pre { 181 background-color: unset !important; 182 } 183 184 .qwebr-editor-toolbar { 185 width: 100%; 186 display: flex; 187 justify-content: space-between; 188 box-sizing: border-box; 189 } 190 191 .qwebr-editor-toolbar-left-buttons, .qwebr-editor-toolbar-right-buttons { 192 display: flex; 193 } 194 195 .qwebr-non-interactive-loading-container.qwebr-cell-needs-evaluation, .qwebr-non-interactive-loading-container.qwebr-cell-evaluated { 196 justify-content: center; 197 display: flex; 198 background-color: rgba(250, 250, 250, 0.65); 199 border: 1px solid rgba(233, 236, 239, 0.65); 200 border-radius: 0.5rem; 201 margin-top: 15px; 202 margin-bottom: 15px; 203 } 204 205 .qwebr-r-project-logo { 206 color: #2767B0; /* R Project's blue color */ 207 } 208 209 .qwebr-icon-status-spinner { 210 color: #7894c4; 211 } 212 213 .qwebr-icon-run-code { 214 color: #0d9c29 215 } 216 217 body.quarto-light .qwebr-output-code-stdout { 218 color: #111; 219 } 220 221 body.quarto-dark .qwebr-output-code-stdout { 222 color: #EEE; 223 } 224 225 .qwebr-output-code-stderr { 226 color: #db4133; 227 } 228 229 body.quarto-light .qwebr-editor { 230 border: 1px solid #EEEEEE; 231 } 232 233 body.quarto-light .qwebr-editor-toolbar { 234 background-color: #EEEEEE; 235 padding: 0.2rem 0.5rem; 236 } 237 238 body.quarto-dark .qwebr-editor { 239 border: 1px solid #111; 240 } 241 242 body.quarto-dark .qwebr-editor-toolbar { 243 background-color: #111; 244 padding: 0.2rem 0.5rem; 245 } 246 247 .qwebr-button { 248 display: inline-block; 249 font-weight: 400; 250 line-height: 1; 251 text-decoration: none; 252 text-align: center; 253 padding: 0.375rem 0.75rem; 254 font-size: .9rem; 255 border-radius: 0.25rem; 256 transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; 257 } 258 259 body.quarto-light .qwebr-button { 260 background-color: #EEEEEE; 261 color: #000; 262 border-color: #dee2e6; 263 border: 1px solid rgba(0,0,0,0); 264 } 265 266 body.quarto-dark .qwebr-button { 267 background-color: #111; 268 color: #EEE; 269 border-color: #dee2e6; 270 border: 1px solid rgba(0,0,0,0); 271 } 272 273 body.quarto-light .qwebr-button:hover { 274 color: #000; 275 background-color: #d9dce0; 276 border-color: #c8ccd0; 277 } 278 279 body.quarto-dark .qwebr-button:hover { 280 color: #d9dce0; 281 background-color: #323232; 282 border-color: #d9dce0; 283 } 284
285 .qwebr-button:disabled,.qwebr-button.disabled,fieldset:disabled .qwebr-button { 286 pointer-events: none; 287 opacity: .65 288 } 289 290 .qwebr-button-reset { 291 color: #696969; /*#4682b4;*/ 292 } 293 294 .qwebr-button-copy { 295 color: #696969; 296 } 297 298 /* Style the code highlight lines */ 299 body.quarto-light .qwebr-editor-highlight-line { 300 background-color: lightblue; 301 } 302 303 body.quarto-dark .qwebr-editor-highlight-line { 304 background-color: darkblue; 305 } 306 307 /* Style the modal pop-up */ 308 309 /* The Modal (background) */ 310 .qwebr-modal { 311 display: none; /* Hidden by default */ 312 position: fixed; /* Stay in place */ 313 z-index: 1; /* Sit on top */ 314 left: 0; 315 top: 0; 316 width: 100%; /* Full width */ 317 height: 100%; /* Full height */ 318 overflow: auto; /* Enable scroll if needed */ 319 background-color: rgb(0,0,0); /* Fallback color */ 320 background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 321 padding-top: 60px; 322 } 323 324 /* Modal Content */ 325 .qwebr-modal-content { 326 background-color: #fefefe; 327 margin: 5% auto; /* 15% from the top and centered */ 328 padding: 20px; 329 border: 1px solid #888; 330 width: 80%; /* Could be more or less, depending on screen size */ 331 } 332 333 .qwebr-modal-content-code { 334 max-height: 50vh; 335 min-height: 5vh; 336 overflow: scroll; 337 border: 1px solid #888; 338 } 339 340 /* The Close Button */ 341 .qwebr-modal-close { 342 color: #aaa; 343 float: right; 344 font-size: 28px; 345 font-weight: bold; 346 } 347 348 .qwebr-modal-close:hover, 349 .qwebr-modal-close:focus { 350 color: black; 351 text-decoration: none; 352 cursor: pointer; 353 } 354 355 .qwebr-download-btn { 356 margin-top: 10px; 357 text-decoration: none !important; 358 } 359 360 /* Styling to download image that is created */ 361 362 .qwebr-canvas-image { 363 position: relative; 364 display: inline-block; 365 margin: 10px; 366 } 367 368 .qwebr-canvas-image-download-btn { 369 position: absolute; 370 top: 10px; 371 right: 10px; 372 padding: 5px 10px; 373 background-color: #007BFF; 374 color: white; 375 border: none; 376 cursor: pointer; 377 display: none; 378 } 379 380 figure:hover .qwebr-canvas-image-download-btn { 381 display: block; 382 } 383 384 /* Custom styling for RevealJS Presentations*/ 385 386 /* Reset the style of the interactive area */ 387 .reveal div.qwebr-interactive-area { 388 display: block; 389 box-shadow: none; 390 max-width: 100%; 391 max-height: 100%; 392 margin: 0; 393 padding: 0; 394 } 395 396 /* Provide space to entries */ 397 .reveal div.qwebr-output-code-area pre div { 398 margin: 1px 2px 1px 10px; 399 } 400 401 /* Collapse the inside code tags to avoid extra space between line outputs */ 402 .reveal pre div code.qwebr-output-code-stdout, .reveal pre div code.qwebr-output-code-stderr { 403 padding: 0; 404 display: contents; 405 } 406 407 body.reveal.quarto-light pre div code.qwebr-output-code-stdout { 408 color: #111; 409 } 410 411 body.reveal.quarto-dark pre div code.qwebr-output-code-stdout { 412 color: #EEEEEE; 413 } 414 415 .reveal pre div code.qwebr-output-code-stderr { 416 color: #db4133; 417 } 418 419 420 /* Create a border around console and output (does not effect graphs) */ 421 body.reveal.quarto-light div.qwebr-console-area { 422 border: 1px solid #EEEEEE; 423 box-shadow: 2px 2px 10px #EEEEEE; 424 } 425 426 body.reveal.quarto-dark div.qwebr-console-area { 427 border: 1px solid #111; 428 box-shadow: 2px 2px 10px #111; 429 } 430 431 432 /* Cap output height and allow text to scroll */ 433 /* TODO: Is there a better way to fit contents/max it parallel to the monaco editor size? */ 434 .reveal div.qwebr-output-code-area pre { 435 max-height: 400px; 436 overflow: scroll; 437 } 438 439 iframe.qwebr-output-code-browse { 440 width: 100%; 441 442 /* 443 TODO: How to make the height automatic according to the widget size, 444 or respect the quarto code block options? 445 */ 446 min-height: 500px; 447 } 448 449 </style> 450
450<script type="module"> 451 // Document level settings ---- 452 453 // Determine if we need to install R packages 454 globalThis.qwebrInstallRPackagesList = ['tidyverse', 'nycflights13', 'gapminder', 'palmerpenguins']; 455 456 // Specify possible locations to search for the repository 457 globalThis.qwebrPackageRepoURLS = ['https://repo.r-wasm.org/']; 458 459 // Check to see if we have an empty array, if we do set to skip the installation. 460 globalThis.qwebrSetupRPackages = !(qwebrInstallRPackagesList.indexOf("") !== -1); 461 globalThis.qwebrAutoloadRPackages = true; 462 463 // Display a startup message? 464 globalThis.qwebrShowStartupMessage = false; 465 globalThis.qwebrShowHeaderMessage = false; 466 467 // Describe the webR settings that should be used 468 globalThis.qwebrCustomizedWebROptions = { 469 "baseURL": "https://webr.r-wasm.org/v0.6.0/", 470 "serviceWorkerUrl": "", 471 "homedir": "/home/web_user", 472 "channelType": "ChannelType.Automatic" 473 }; 474 475 // Store cell data 476 globalThis.qwebrCellDetails = [{"code":"soci316 <- tibble(initials = c(\"SA\", \"CB\", \"AB\", \"AC\", \"SCL\", \"RK\", \"AL\", \"NM\", \"SO\", \"NT\", \"PV\", \"TW\", \"SX\"))\n\nset.seed(2026)\n\ntreatment <- soci316 |> slice_sample(prop = 0.5) |> pull(1)\n\nsoci316 |> \nmutate(condition = if_else(initials %in% treatment, \"Treatment\", \"Control\") |> fct_rev()) |> \narrange(condition, initials)","id":1,"options":{"autorun":"false","classes":"styled-output","comment":"","context":"interactive","dpi":72,"editor-font-scale":"0.5","editor-max-height":"","editor-quick-suggestions":"false","editor-word-wrap":"true","fig-cap":"","fig-height":5,"fig-width":7,"label":"unnamed-chunk-1","message":"true","out-height":"","out-width":"700px","output":"true","read-only":"false","results":"markup","warning":"true"}}]; 477 478 </script>
478 479
479<script type="module"> 480 // Declare startupMessageQWebR globally 481 globalThis.qwebrStartupMessage = document.createElement("p"); 482 483 // Verify if OffScreenCanvas is supported 484 globalThis.qwebrOffScreenCanvasSupport = function() { 485 return typeof OffscreenCanvas !== 'undefined' 486 } 487 488 // Function to set the button text 489 globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) { 490 document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 491 btn.innerHTML = buttonText; 492 btn.disabled = !enableCodeButton; 493 }); 494 } 495 496 // Function to update the status message in non-interactive cells 497 globalThis.qwebrUpdateStatusMessage = function(message) { 498 document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => { 499 elem.innerText = message; 500 }); 501 } 502 503 // Function to update the status message 504 globalThis.qwebrUpdateStatusHeader = function(message) { 505 qwebrStartupMessage.innerHTML = ` 506 <i class="fa-solid fa-spinner fa-spin qwebr-icon-status-spinner"></i> 507 <span>${message}</span>`; 508 } 509 510 // Function to return true if element is found, false if not 511 globalThis.qwebrCheckHTMLElementExists = function(selector) { 512 const element = document.querySelector(selector); 513 return !!element; 514 } 515 516 // Function that detects whether reveal.js slides are present 517 globalThis.qwebrIsRevealJS = function() { 518 // If the '.reveal .slides' selector exists, RevealJS is likely present 519 return qwebrCheckHTMLElementExists('.reveal .slides'); 520 } 521 522 // Initialize the Quarto sidebar element 523 function qwebrSetupQuartoSidebar() { 524 var newSideBarDiv = document.createElement('div'); 525 newSideBarDiv.id = 'quarto-margin-sidebar'; 526 newSideBarDiv.className = 'sidebar margin-sidebar'; 527 newSideBarDiv.style.top = '0px'; 528 newSideBarDiv.style.maxHeight = 'calc(0px + 100vh)'; 529 530 return newSideBarDiv; 531 } 532 533 // Position the sidebar in the document 534 function qwebrPlaceQuartoSidebar() { 535 // Get the reference to the element with id 'quarto-document-content' 536 var referenceNode = document.getElementById('quarto-document-content'); 537 538 // Create the new div element 539 var newSideBarDiv = qwebrSetupQuartoSidebar(); 540 541 // Insert the new div before the 'quarto-document-content' element 542 referenceNode.parentNode.insertBefore(newSideBarDiv, referenceNode); 543 } 544 545 function qwebrPlaceMessageContents(content, html_location = "title-block-header", revealjs_location = "title-slide") { 546 547 // Get references to header elements 548 const headerHTML = document.getElementById(html_location); 549 const headerRevealJS = document.getElementById(revealjs_location); 550 551 // Determine where to insert the quartoTitleMeta element 552 if (headerHTML || headerRevealJS) { 553 // Append to the existing "title-block-header" element or "title-slide" div 554 (headerHTML || headerRevealJS).appendChild(content); 555 } else { 556 // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script 557 const monacoScript = document.getElementById("qwebr-monaco-editor-init"); 558 const header = document.createElement("header"); 559 header.setAttribute("id", "title-block-header"); 560 header.appendChild(content); 561 monacoScript.after(header); 562 } 563 } 564 565 566 567 function qwebrOffScreenCanvasSupportWarningMessage() { 568 569 // Verify canvas is supported. 570 if(qwebrOffScreenCanvasSupport()) return; 571 572 // Create the main container div 573 var calloutContainer = document.createElement('div'); 574 calloutContainer.classList.add('callout', 'callout-style-default', 'callout-warning', 'callout-titled'); 575 576 // Create the header div 577 var headerDiv = document.createElement('div'); 578 headerDiv.classList.add('callout-header', 'd-flex', 'align-content-center'); 579 580 // Create the icon container div 581 var iconContainer = document.createElement('div'); 582 iconContainer.classList.add('callout-icon-container'); 583 584 // Create the icon element 585 var iconElement = document.createElement('i'); 586 iconElement.classList.add('callout-icon'); 587 588 // Append the icon element to the icon container 589 iconContainer.appendChild(iconElement); 590 591 // Create the title container div 592 var titleContainer = document.createElement('div'); 593 titleContainer.classList.add('callout-title-container', 'flex-fill'); 594 titleContainer.innerText = 'Warning: Web Browser Does Not Support Graphing!'; 595 596 // Append the icon container and title container to the header div 597 headerDiv.appendChild(iconContainer); 598 headerDiv.appendChild(titleContainer); 599 600 // Create the body container div 601 var bodyContainer = document.createElement('div'); 602 bodyContainer.classList.add('callout-body-container', 'callout-body'); 603 604 // Create the paragraph element for the body content 605 var paragraphElement = document.createElement('p'); 606 paragraphElement.innerHTML = 'This web browser does not have support for displaying graphs through the <code>quarto-webr</code> extension since it lacks an <code>OffScreenCanvas</code>. Please upgrade your web browser to one that supports <code>OffScreenCanvas</code>.'; 607 608 // Append the paragraph element to the body container 609 bodyContainer.appendChild(paragraphElement); 610 611 // Append the header div and body container to the main container div 612 calloutContainer.appendChild(headerDiv); 613 calloutContainer.appendChild(bodyContainer); 614 615 // Append the main container div to the document depending on format 616 qwebrPlaceMessageContents(calloutContainer, "title-block-header"); 617 618 } 619 620 621 // Function that attaches the document status message and diagnostics 622 function displayStartupMessage(showStartupMessage, showHeaderMessage) { 623 if (!showStartupMessage) { 624 return; 625 } 626 627 // Create the outermost div element for metadata 628 const quartoTitleMeta = document.createElement("div"); 629 quartoTitleMeta.classList.add("quarto-title-meta"); 630 631 // Create the first inner div element 632 const firstInnerDiv = document.createElement("div"); 633 firstInnerDiv.setAttribute("id", "qwebr-status-message-area"); 634 635 // Create the second inner div element for "WebR Status" heading and contents 636 const secondInnerDiv = document.createElement("div"); 637 secondInnerDiv.setAttribute("id", "qwebr-status-message-title"); 638 secondInnerDiv.classList.add("quarto-title-meta-heading"); 639 secondInnerDiv.innerText = "WebR Status"; 640 641 // Create another inner div for contents 642 const secondInnerDivContents = document.createElement("div"); 643 secondInnerDivContents.setAttribute("id", "qwebr-status-message-body"); 644 secondInnerDivContents.classList.add("quarto-title-meta-contents"); 645 646 // Describe the WebR state 647 qwebrStartupMessage.innerText = "ð¡ Loading..."; 648 qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text"); 649 // Add `aria-live` to auto-announce the startup status to screen readers 650 qwebrStartupMessage.setAttribute("aria-live", "assertive"); 651 652 // Append the startup message to the contents 653 secondInnerDivContents.appendChild(qwebrStartupMessage); 654 655 // Add a status indicator for COOP and COEP Headers if needed 656 if (showHeaderMessage) { 657 const crossOriginMessage = document.createElement("p"); 658 crossOriginMessage.innerText = `${crossOriginIsolated ? 'ð¢' : 'ð¡'} COOP & COEP Headers`; 659 crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header"); 660 secondInnerDivContents.appendChild(crossOriginMessage); 661 } 662 663 // Combine the inner divs and contents 664 firstInnerDiv.appendChild(secondInnerDiv); 665 firstInnerDiv.appendChild(secondInnerDivContents); 666 quartoTitleMeta.appendChild(firstInnerDiv); 667 668 // Place message on webpage
669 qwebrPlaceMessageContents(quartoTitleMeta); 670 } 671 672 function qwebrAddCommandHistoryModal() { 673 // Create the modal div 674 var modalDiv = document.createElement('div'); 675 modalDiv.id = 'qwebr-history-modal'; 676 modalDiv.className = 'qwebr-modal'; 677 678 // Create the modal content div 679 var modalContentDiv = document.createElement('div'); 680 modalContentDiv.className = 'qwebr-modal-content'; 681 682 // Create the span for closing the modal 683 var closeSpan = document.createElement('span'); 684 closeSpan.id = 'qwebr-command-history-close-btn'; 685 closeSpan.className = 'qwebr-modal-close'; 686 closeSpan.innerHTML = '×'; 687 688 // Create the h1 element for the modal 689 var modalH1 = document.createElement('h1'); 690 modalH1.textContent = 'R History Command Contents'; 691 692 // Create an anchor element for downloading the Rhistory file 693 var downloadLink = document.createElement('a'); 694 downloadLink.href = '#'; 695 downloadLink.id = 'qwebr-download-history-btn'; 696 downloadLink.className = 'qwebr-download-btn'; 697 698 // Create an 'i' element for the icon 699 var icon = document.createElement('i'); 700 icon.className = 'bi bi-file-code'; 701 702 // Append the icon to the anchor element 703 downloadLink.appendChild(icon); 704 705 // Add the text 'Download R History' to the anchor element 706 downloadLink.appendChild(document.createTextNode(' Download R History File')); 707 708 // Create the pre for command history contents 709 var commandContentsPre = document.createElement('pre'); 710 commandContentsPre.id = 'qwebr-command-history-contents'; 711 commandContentsPre.className = 'qwebr-modal-content-code'; 712 713 // Append the close span, h1, and history contents pre to the modal content div 714 modalContentDiv.appendChild(closeSpan); 715 modalContentDiv.appendChild(modalH1); 716 modalContentDiv.appendChild(downloadLink); 717 modalContentDiv.appendChild(commandContentsPre); 718 719 // Append the modal content div to the modal div 720 modalDiv.appendChild(modalContentDiv); 721 722 // Append the modal div to the body 723 document.body.appendChild(modalDiv); 724 } 725 726 function qwebrRegisterRevealJSCommandHistoryModal() { 727 // Select the <ul> element inside the <div> with data-panel="Custom0" 728 let ulElement = document.querySelector('div[data-panel="Custom0"] > ul.slide-menu-items'); 729 730 // Find the last <li> element with class slide-tool-item 731 let lastItem = ulElement.querySelector('li.slide-tool-item:last-child'); 732 733 // Calculate the next data-item value 734 let nextItemValue = 0; 735 if (lastItem) { 736 nextItemValue = parseInt(lastItem.dataset.item) + 1; 737 } 738 739 // Create a new <li> element 740 let newListItem = document.createElement('li'); 741 newListItem.className = 'slide-tool-item'; 742 newListItem.dataset.item = nextItemValue.toString(); // Set the next available data-item value 743 744 // Create the <a> element inside the <li> 745 let newLink = document.createElement('a'); 746 newLink.href = '#'; 747 newLink.id = 'qwebrRHistoryButton'; // Set the ID for the new link 748 749 // Create the <kbd> element inside the <a> 750 let newKbd = document.createElement('kbd'); 751 newKbd.textContent = ' '; // Set to empty as we are not registering a keyboard shortcut 752 753 // Create text node for the link text 754 let newText = document.createTextNode(' View R History'); 755 756 // Append <kbd> and text node to <a> 757 newLink.appendChild(newKbd); 758 newLink.appendChild(newText); 759 760 // Append <a> to <li> 761 newListItem.appendChild(newLink); 762 763 // Append <li> to <ul> 764 ulElement.appendChild(newListItem); 765 } 766 767 // Handle setting up the R history modal 768 function qwebrCodeLinks() { 769 770 if (qwebrIsRevealJS()) { 771 qwebrRegisterRevealJSCommandHistoryModal(); 772 return; 773 } 774 775 // Create the container div 776 var containerDiv = document.createElement('div'); 777 containerDiv.className = 'quarto-code-links'; 778 779 // Create the h2 element 780 var h2 = document.createElement('h2'); 781 h2.textContent = 'webR Code Links'; 782 783 // Create the ul element 784 var ul = document.createElement('ul'); 785 786 // Create the li element 787 var li = document.createElement('li'); 788 789 // Create the a_history_btn element 790 var a_history_btn = document.createElement('a'); 791 a_history_btn.href = 'javascript:void(0)'; 792 a_history_btn.setAttribute('id', 'qwebrRHistoryButton'); 793 794 // Create the i_history_btn element 795 var i_history_btn = document.createElement('i'); 796 i_history_btn.className = 'bi bi-file-code'; 797 798 // Create the text node for the link text 799 var text_history_btn = document.createTextNode('View R History'); 800 801 // Append the icon element and link text to the a element 802 a_history_btn.appendChild(i_history_btn); 803 a_history_btn.appendChild(text_history_btn); 804 805 // Append the a element to the li element 806 li.appendChild(a_history_btn); 807 808 // Append the li element to the ul element 809 ul.appendChild(li); 810 811 // Append the h2 and ul elements to the container div 812 containerDiv.appendChild(h2); 813 containerDiv.appendChild(ul); 814 815 // Append the container div to the element with the ID 'quarto-margin-sidebar' 816 var sidebar = document.getElementById('quarto-margin-sidebar'); 817 818 // If the sidebar element is not found, create it 819 if(!sidebar) { 820 qwebrPlaceQuartoSidebar(); 821 } 822 823 // Re-select the sidebar element (if it was just created) 824 sidebar = document.getElementById('quarto-margin-sidebar'); 825 826 827 // If the sidebar element exists, append the container div to it 828 if(sidebar) { 829 // Append the container div to the sidebar 830 sidebar.appendChild(containerDiv); 831 // Force the sidebar to be clickable by removing the 'zindex-bottom' class 832 // added in pre-release: https://github.com/quarto-dev/quarto-cli
832/commit/f0c53a1ffcaa1de4eccbf07803b096898248adcc 833 sidebar.className = 'sidebar margin-sidebar'; 834 } else { 835 // Get a debugger ... 836 console.warn('Element with ID "quarto-margin-sidebar" not found.'); 837 } 838 } 839 840 // Call the function to append the code links for qwebR into the right sidebar 841 qwebrCodeLinks(); 842 843 // Add the command history modal 844 qwebrAddCommandHistoryModal(); 845 846 displayStartupMessage(qwebrShowStartupMessage, qwebrShowHeaderMessage); 847 qwebrOffScreenCanvasSupportWarningMessage(); 848 </script>
848 849
849<script type="module"> 850 // Define a global storage and retrieval solution ---- 851 852 // Store commands executed in R 853 globalThis.qwebrRCommandHistory = []; 854 855 // Function to retrieve the command history 856 globalThis.qwebrFormatRHistory = function() { 857 return qwebrRCommandHistory.join("\n\n"); 858 } 859 860 // Retrieve HTML Elements ---- 861 862 // Get the command modal 863 const command_history_modal = document.getElementById("qwebr-history-modal"); 864 865 // Get the button that opens the command modal 866 const command_history_btn = document.getElementById("qwebrRHistoryButton"); 867 868 // Get the <span> element that closes the command modal 869 const command_history_close_span = document.getElementById("qwebr-command-history-close-btn"); 870 871 // Get the download button for r history information 872 const command_history_download_btn = document.getElementById("qwebr-download-history-btn"); 873 874 // Plug in command history into modal/download button ---- 875 876 // Function to populate the modal with command history 877 function populateCommandHistoryModal() { 878 document.getElementById("qwebr-command-history-contents").innerHTML = qwebrFormatRHistory() || "No commands have been executed yet."; 879 } 880 881 // Function to format the current date and time to 882 // a string with the format YYYY-MM-DD-HH-MM-SS 883 function formatDateTime() { 884 const now = new Date(); 885 886 const year = now.getFullYear(); 887 const day = String(now.getDate()).padStart(2, '0');
888 const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based 889 const hours = String(now.getHours()).padStart(2, '0'); 890 const minutes = String(now.getMinutes()).padStart(2, '0'); 891 const seconds = String(now.getSeconds()).padStart(2, '0'); 892 893 return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`; 894 } 895 896 897 // Function to convert document title with datetime to a safe filename 898 function safeFileName() { 899 // Get the current page title 900 let pageTitle = document.title; 901 902 // Combine the current page title with the current date and time 903 let pageNameWithDateTime = `Rhistory-${pageTitle}-${formatDateTime()}`; 904 905 // Replace unsafe characters with safe alternatives 906 let safeFilename = pageNameWithDateTime.replace(/[\\/:\*\?! "<>\|]/g, '-'); 907 908 return safeFilename; 909 } 910 911 912 // Function to download list contents as text file 913 function downloadRHistory() { 914 // Get the current page title + datetime and use it as the filename 915 const filename = `${safeFileName()}.R`; 916 917 // Get the text contents of the R History list 918 const text = qwebrFormatRHistory(); 919 920 // Create a new Blob object with the text contents 921 const blob = new Blob([text], { type: 'text/plain' }); 922 923 // Create a new anchor element for the download 924 const a = document.createElement('a'); 925 a.style.display = 'none'; 926 a.href = URL.createObjectURL(blob); 927 a.download = filename; 928 929 // Append the anchor to the body, click it, and remove it 930 document.body.appendChild(a); 931 a.click(); 932 document.body.removeChild(a); 933 } 934 935 // Register event handlers ---- 936 937 // When the user clicks the View R History button, open the command modal 938 command_history_btn.onclick = function() { 939 populateCommandHistoryModal(); 940 command_history_modal.style.display = "block"; 941 } 942 943 // When the user clicks on <span> (x), close the command modal 944 command_history_close_span.onclick = function() { 945 command_history_modal.style.display = "none"; 946 } 947 948 // When the user clicks anywhere outside of the command modal, close it 949 window.onclick = function(event) { 950 if (event.target == command_history_modal) { 951 command_history_modal.style.display = "none"; 952 } 953 } 954 955 // Add an onclick event listener to the download button so that 956 // the user can download the R history as a text file 957 command_history_download_btn.onclick = function() { 958 downloadRHistory(); 959 }; 960 </script>
960 961
961<script type="module"> 962 // Supported Evaluation Types for Context 963 globalThis.EvalTypes = Object.freeze({ 964 Interactive: 'interactive', 965 Setup: 'setup', 966 Output: 'output', 967 }); 968 969 // Function that obtains the font size for a given element 970 globalThis.qwebrCurrentFontSizeOnElement = function(element, cssProperty = 'font-size') { 971 972 const currentFontSize = parseFloat( 973 window 974 .getComputedStyle(element) 975 .getPropertyValue(cssProperty) 976 ); 977 978 return currentFontSize; 979 } 980 981 // Function to determine font scaling 982 globalThis.qwebrScaledFontSize = function(div, qwebrOptions) { 983 // Determine if we should compute font-size using RevealJS's `--r-main-font-size` 984 // or if we can directly use the document's `font-size`. 985 const cssProperty = document.body.classList.contains('reveal') ? 986 "--r-main-font-size" : "font-size"; 987 988 // Get the current font size on the div element 989 const elementFontSize = qwebrCurrentFontSizeOnElement(div, cssProperty); 990 991 // Determine the scaled font size value 992 const scaledFontSize = ((qwebrOptions['editor-font-scale'] ?? 1) * elementFontSize) ?? 17.5; 993 994 return scaledFontSize; 995 } 996 997 998 // Function that dispatches the creation request 999 globalThis.qwebrCreateHTMLElement = function ( 1000 cellData 1001 ) { 1002 1003 // Extract key components 1004 const evalType = cellData.options.context; 1005 const qwebrCounter = cellData.id; 1006 1007 // We make an assumption that insertion points are defined by the Lua filter as: 1008 // qwebr-insertion-location-{qwebrCounter} 1009 const elementLocator = document.getElementById(`qwebr-insertion-location-${qwebrCounter}`); 1010 1011 // Figure out the routine to use to insert the element. 1012 let qwebrElement; 1013 switch ( evalType ) { 1014 case EvalTypes.Interactive: 1015 qwebrElement = qwebrCreateInteractiveElement(qwebrCounter, cellData.options); 1016 break; 1017 case EvalTypes.Output: 1018 qwebrElement = qwebrCreateNonInteractiveOutputElement(qwebrCounter, cellData.options); 1019 break; 1020 case EvalTypes.Setup: 1021 qwebrElement = qwebrCreateNonInteractiveSetupElement(qwebrCounter, cellData.options); 1022 break; 1023 default: 1024 qwebrElement = document.createElement('div'); 1025 qwebrElement.textContent = 'Error creating `quarto-webr` element'; 1026 } 1027 1028 // Insert the dynamically generated object at the document location. 1029 elementLocator.appendChild(qwebrElement); 1030 }; 1031 1032 // Function that setups the interactive element creation 1033 globalThis.qwebrCreateInteractiveElement = function (qwebrCounter, qwebrOptions) { 1034 1035 // Create main div element 1036 var mainDiv = document.createElement('div'); 1037 mainDiv.id = 'qwebr-interactive-area-' + qwebrCounter; 1038 mainDiv.className = `qwebr-interactive-area`; 1039 if (qwebrOptions.classes) { 1040 mainDiv.className += " " + qwebrOptions.classes 1041 } 1042 1043 // Add a unique cell identifier that users can customize 1044 if (qwebrOptions.label) { 1045 mainDiv.setAttribute('data-id', qwebrOptions.label); 1046 } 1047 1048 // Create toolbar div 1049 var toolbarDiv = document.createElement('div'); 1050 toolbarDiv.className = 'qwebr-editor-toolbar'; 1051 toolbarDiv.id = 'qwebr-editor-toolbar-' + qwebrCounter; 1052 1053 // Create a div to hold the left buttons 1054 var leftButtonsDiv = document.createElement('div'); 1055 leftButtonsDiv.className = 'qwebr-editor-toolbar-left-buttons'; 1056 1057 // Create a div to hold the right buttons 1058 var rightButtonsDiv = document.createElement('div'); 1059 rightButtonsDiv.className = 'qwebr-editor-toolbar-right-buttons'; 1060 1061 // Create Run Code button 1062 var runCodeButton = document.createElement('button'); 1063 runCodeButton.className = 'btn btn-default qwebr-button qwebr-button-run'; 1064 runCodeButton.disabled = true; 1065 runCodeButton.type = 'button'; 1066 runCodeButton.id = 'qwebr-button-run-' + qwebrCounter; 1067 runCodeButton.textContent = 'ð¡ Loading webR...'; 1068 runCodeButton.title = `Run code (Shift + Enter)`; 1069 1070 // Append buttons to the leftButtonsDiv 1071 leftButtonsDiv.appendChild(runCodeButton); 1072 1073 // Create Reset button 1074 var resetButton = document.createElement('button'); 1075 resetButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-reset'; 1076 resetButton.type = 'button'; 1077 resetButton.id = 'qwebr-button-reset-' + qwebrCounter; 1078 resetButton.title = 'Start over'; 1079 resetButton.innerHTML = '<i class="fa-solid fa-arrows-rotate"></i>'; 1080 1081 // Create Copy button 1082 var copyButton = document.createElement('button'); 1083 copyButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-copy';
1084 copyButton.type = 'button'; 1085 copyButton.id = 'qwebr-button-copy-' + qwebrCounter; 1086 copyButton.title = 'Copy code'; 1087 copyButton.innerHTML = '<i class="fa-regular fa-copy"></i>'; 1088 1089 // Append buttons to the rightButtonsDiv 1090 rightButtonsDiv.appendChild(resetButton); 1091 rightButtonsDiv.appendChild(copyButton); 1092 1093 // Create console area div 1094 var consoleAreaDiv = document.createElement('div'); 1095 consoleAreaDiv.id = 'qwebr-console-area-' + qwebrCounter; 1096 consoleAreaDiv.className = 'qwebr-console-area'; 1097 1098 // Create editor div 1099 var editorDiv = document.createElement('div'); 1100 editorDiv.id = 'qwebr-editor-' + qwebrCounter; 1101 editorDiv.className = 'qwebr-editor'; 1102 1103 // Create output code area div 1104 var outputCodeAreaDiv = document.createElement('div'); 1105 outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; 1106 outputCodeAreaDiv.className = 'qwebr-output-code-area'; 1107 outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); 1108 1109 // Create pre element inside output code area 1110 var preElement = document.createElement('pre'); 1111 preElement.style.visibility = 'hidden'; 1112 outputCodeAreaDiv.appendChild(preElement); 1113 1114 // Create output graph area div 1115 var outputGraphAreaDiv = document.createElement('div'); 1116 outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; 1117 outputGraphAreaDiv.className = 'qwebr-output-graph-area'; 1118 1119 // Append buttons to the toolbar 1120 toolbarDiv.appendChild(leftButtonsDiv); 1121 toolbarDiv.appendChild(rightButtonsDiv); 1122 1123 // Append all elements to the main div 1124 mainDiv.appendChild(toolbarDiv); 1125 consoleAreaDiv.appendChild(editorDiv); 1126 consoleAreaDiv.appendChild(outputCodeAreaDiv); 1127 mainDiv.appendChild(consoleAreaDiv); 1128 mainDiv.appendChild(outputGraphAreaDiv); 1129 1130 return mainDiv; 1131 } 1132 1133 // Function that adds output structure for non-interactive output 1134 globalThis.qwebrCreateNonInteractiveOutputElement = function(qwebrCounter, qwebrOptions) { 1135 // Create main div element 1136 var mainDiv = document.createElement('div'); 1137 mainDiv.id = 'qwebr-noninteractive-area-' + qwebrCounter; 1138 mainDiv.className = `qwebr-noninteractive-area`; 1139 if (qwebrOptions.classes) { 1140 mainDiv.className += " " + qwebrOptions.classes 1141 } 1142 1143 // Add a unique cell identifier that users can customize 1144 if (qwebrOptions.label) { 1145 mainDiv.setAttribute('data-id', qwebrOptions.label); 1146 } 1147 1148 // Create a status container div 1149 var statusContainer = createLoadingContainer(qwebrCounter); 1150 1151 // Create output code area div 1152 var outputCodeAreaDiv = document.createElement('div'); 1153 outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; 1154 outputCodeAreaDiv.className = 'qwebr-output-code-area'; 1155 outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); 1156 1157 // Create pre element inside output code area 1158 var preElement = document.createElement('pre'); 1159 preElement.style.visibility = 'hidden'; 1160 outputCodeAreaDiv.appendChild(preElement); 1161 1162 // Create output graph area div 1163 var outputGraphAreaDiv = document.createElement('div'); 1164 outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; 1165 outputGraphAreaDiv.className = 'qwebr-output-graph-area'; 1166 1167 // Append all elements to the main div 1168 mainDiv.appendChild(statusContainer); 1169 mainDiv.appendChild(outputCodeAreaDiv); 1170 mainDiv.appendChild(outputGraphAreaDiv); 1171 1172 return mainDiv; 1173 }; 1174 1175 // Function that adds a stub in the page to indicate a setup cell was used. 1176 globalThis.qwebrCreateNonInteractiveSetupElement = function(qwebrCounter, qwebrOptions) { 1177 // Create main div element 1178 var mainDiv = document.createElement('div'); 1179 mainDiv.id = `qwebr-noninteractive-setup-area-${qwebrCounter}`; 1180 mainDiv.className = `qwebr-noninteractive-setup-area`; 1181 if (qwebrOptions.classes) { 1182 mainDiv.className += " " + qwebrOptions.classes 1183 } 1184 1185 1186 // Add a unique cell identifier that users can customize 1187 if (qwebrOptions.label) { 1188 mainDiv.setAttribute('data-id', qwebrOptions.label); 1189 } 1190 1191 // Create a status container div 1192 var statusContainer = createLoadingContainer(qwebrCounter); 1193 1194 // Append status onto the main div 1195 mainDiv.appendChild(statusContainer); 1196 1197 return mainDiv; 1198 } 1199 1200 1201 // Function to create loading container with specified ID 1202 globalThis.createLoadingContainer = function(qwebrCounter) { 1203 1204 // Create a status container 1205 const container = document.createElement('div'); 1206 container.id = `qwebr-non-interactive-loading-container-${qwebrCounter}`; 1207 container.className = 'qwebr-non-interactive-loading-container qwebr-cell-needs-evaluation'; 1208 1209 // Create an R project logo to indicate its a code space 1210 const rProjectIcon = document.createElement('i'); 1211 rProjectIcon.className = 'fa-brands fa-r-project fa-3x qwebr-r-project-logo'; 1212 1213 // Setup a loading icon from font awesome 1214 const spinnerIcon = document.createElement('i'); 1215 spinnerIcon.className = 'fa-solid fa-spinner fa-spin fa-1x qwebr-icon-status-spinner'; 1216 1217 // Add a section for status text 1218 const statusText = document.createElement('p'); 1219 statusText.id = `qwebr-status-text-${qwebrCounter}`; 1220 statusText.className = `qwebr-status-text qwebr-cell-needs-evaluation`; 1221 statusText.innerText = 'Loading webR...'; 1222 1223 // Incorporate an inner container 1224 const innerContainer = document.createElement('div'); 1225 1226 // Append elements to the inner container 1227 innerContainer.appendChild(spinnerIcon); 1228 innerContainer.appendChild(statusText); 1229 1230 // Append elements to the main container 1231 container.appendChild(rProjectIcon); 1232 container.appendChild(innerContainer); 1233 1234 return container; 1235 } 1236 </script>
1236 1237
1237<script type="module"> 1238 // Function to install a single package 1239 async function qwebrInstallRPackage(packageName) { 1240 await mainWebR.evalRVoid(`webr::install('${packageName}');`); 1241 } 1242 1243 // Function to load a single package 1244 async function qwebrLoadRPackage(packageName) { 1245 await mainWebR.evalRVoid(`require('${packageName}', quietly = TRUE)`); 1246 } 1247 1248 // Generic function to process R packages 1249 async function qwebrProcessRPackagesWithStatus(packages, processType, displayStatusMessageUpdate = true) { 1250 // Switch between contexts 1251 const messagePrefix = processType === 'install' ? 'Installing' : 'Loading'; 1252 1253 // Modify button state 1254 qwebrSetInteractiveButtonState(`ð¡ ${messagePrefix} package ...`, false); 1255 1256 // Iterate over packages 1257 for (let i = 0; i < packages.length; i++) { 1258 const activePackage = packages[i]; 1259 const formattedMessage = `${messagePrefix} package ${i + 1} out of ${packages.length}: ${activePackage}`; 1260 1261 // Display the update in header 1262 if (displayStatusMessageUpdate) { 1263 qwebrUpdateStatusHeader(formattedMessage); 1264 } 1265 1266 // Display the update in non-active areas 1267 qwebrUpdateStatusMessage(formattedMessage); 1268 1269 // Run package installation 1270 if (processType === 'install') { 1271 await qwebrInstallRPackage(activePackage); 1272 } else { 1273 await qwebrLoadRPackage(activePackage); 1274 } 1275 } 1276 1277 // Clean slate 1278 if (processType === 'load') { 1279 await mainWebR.flush(); 1280 } 1281 } 1282 1283 // Start a timer 1284 const initializeWebRTimerStart = performance.now(); 1285 1286 // Encase with a dynamic import statement 1287 globalThis.qwebrInstance = import(qwebrCustomizedWebROptions.baseURL + "webr.mjs").then( 1288 async ({ WebR, ChannelType }) => { 1289 // Populate WebR options with defaults or new values based on `webr` meta 1290 globalThis.mainWebR = new WebR(qwebrCustomizedWebROptions); 1291 1292 // Initialization WebR 1293 await mainWebR.init(); 1294 1295 // Setup a shelter 1296 globalThis.mainWebRCodeShelter = await new mainWebR.Shelter(); 1297 1298 // Setup a pager to allow processing help documentation 1299 await mainWebR.evalRVoid('webr::pager_install()'); 1300 1301 // Setup a viewer to allow processing htmlwidgets. 1302 // This might not be available in old webr version 1303 await mainWebR.evalRVoid('try({ webr::viewer_install() })'); 1304 1305 // Override the existing install.packages() to use webr::install() 1306 await mainWebR.evalRVoid('webr::shim_install()'); 1307 1308 // Specify the repositories to pull from 1309 // Note: webR does not use the `repos` option, but instead uses `webr_pkg_repos` 1310 // inside of `install()`. However, other R functions still pull from `repos`. 1311 await mainWebR.evalRVoid(` 1312 options( 1313 webr_pkg_repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}), 1314 repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}) 1315 ) 1316 `); 1317 1318 // Check to see if any packages need to be installed 1319 if (qwebrSetupRPackages) { 1320 // Obtain only a unique list of packages 1321 const uniqueRPackageList = Array.from(new Set(qwebrInstallRPackagesList)); 1322 1323 // Install R packages one at a time (either silently or with a status update) 1324 await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', qwebrShowStartupMessage); 1325 1326 if (qwebrAutoloadRPackages) { 1327 // Load R packages one at a time (either silently or with a status update) 1328 await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', qwebrShowStartupMessage); 1329 } 1330 } 1331 } 1332 ); 1333 1334 // Stop timer 1335 const initializeWebRTimerEnd = performance.now(); 1336 1337 </script>
1337 1338
1338<script type="module"> 1339 // Function to verify a given JavaScript Object is empty 1340 globalThis.qwebrIsObjectEmpty = function (arr) { 1341 return Object.keys(arr).length === 0; 1342 } 1343 1344 // Global version of the Escape HTML function that converts HTML 1345 // characters to their HTML entities. 1346 globalThis.qwebrEscapeHTMLCharacters = function(unsafe) { 1347 return unsafe 1348 .replace(/&/g, "&") 1349 .replace(/</g, "<") 1350 .replace(/>/g, ">") 1351 .replace(/"/g, """) 1352 .replace(/'/g, "'"); 1353 }; 1354 1355 // Passthrough results 1356 globalThis.qwebrIdentity = function(x) { 1357 return x; 1358 }; 1359 1360 // Append a comment 1361 globalThis.qwebrPrefixComment = function(x, comment) { 1362 return `${comment}${x}`; 1363 }; 1364 1365 // Function to store the code in the history 1366 globalThis.qwebrLogCodeToHistory = function(codeToRun, options) { 1367 qwebrRCommandHistory.push( 1368 `# Ran code in ${options.label} at ${new Date().toLocaleString()} ----\n${codeToRun}` 1369 ); 1370 }; 1371 1372 // Function to attach a download button onto the canvas 1373 // allowing the user to download the image. 1374 function qwebrImageCanvasDownloadButton(canvas, canvasContainer) { 1375 1376 // Create the download button 1377 const downloadButton = document.createElement('button'); 1378 downloadButton.className = 'qwebr-canvas-image-download-btn'; 1379 downloadButton.textContent = 'Download Image'; 1380 canvasContainer.appendChild(downloadButton); 1381 1382 // Trigger a download of the image when the button is clicked 1383 downloadButton.addEventListener('click', function() { 1384 const image = canvas.toDataURL('image/png'); 1385 const link = document.createElement('a'); 1386 link.href = image; 1387 link.download = 'qwebr-canvas-image.png'; 1388 link.click(); 1389 }); 1390 } 1391 1392 1393 // Function to parse the pager results 1394 globalThis.qwebrParseTypePager = async function (msg) { 1395 1396 // Split out the event data 1397 const { path, title, deleteFile } = msg.data; 1398 1399 // Process the pager data by reading the information from disk 1400 const paged_data = await mainWebR.FS.readFile(path).then((data) => { 1401 // Obtain the file content 1402 let content = new TextDecoder().decode(data); 1403 1404 // Remove excessive backspace characters until none remain 1405 while(content.match(/.[\b]/)){ 1406 content = content.replace(/.[\b]/g, ''); 1407 } 1408 1409 // Returned cleaned data 1410 return content; 1411 }); 1412 1413 // Unlink file if needed 1414 if (deleteFile) { 1415 await mainWebR.FS.unlink(path); 1416 } 1417 1418 // Return extracted data with spaces 1419 return paged_data; 1420 }; 1421 1422 1423 // Function to parse the browse results 1424 globalThis.qwebrParseTypeBrowse = async function (msg) { 1425 1426 // msg.type === "browse" 1427 const path = msg.data.url; 1428 1429 // Process the browse data by reading the information from disk 1430 const browse_data = await mainWebR.FS.readFile(path).then((data) => { 1431 // Obtain the file content 1432 let content = new TextDecoder().decode(data); 1433 1434 return content; 1435 }); 1436 1437 // Return extracted data as-is 1438 return browse_data; 1439 }; 1440 1441 // Function to run the code using webR and parse the output 1442 globalThis.qwebrComputeEngine = async function( 1443 codeToRun, 1444 elements, 1445 options) { 1446 1447 // Call into the R compute engine that persists within the document scope. 1448 // To be prepared for all scenarios, the following happens: 1449 // 1. We setup a canvas device to write to by making a namespace call into the {webr} package 1450 // 2. We use values inside of the options array to set the figure size. 1451 // 3. We capture the output stream information (STDOUT and STERR) 1452 // 4. We disable the current device's image creation. 1453 // 5. Piece-wise parse the results into the different output areas 1454 1455 // Create a pager variable for help/file contents 1456 let pager = []; 1457 1458 // Handle how output is processed 1459 let showMarkup = options.results === "markup" && options.output !== "asis"; 1460 let processOutput; 1461 1462 if (showMarkup) { 1463 processOutput = qwebrEscapeHTMLCharacters; 1464 } else { 1465 processOutput = qwebrIdentity; 1466 } 1467 1468 // ---- 1469 // Convert from Inches to Pixels by using DPI (dots per inch) 1470 // for bitmap devices (dpi * inches = pixels) 1471 let fig_width = options["fig-width"] * options["dpi"]; 1472 let fig_height = options["fig-height"] * options["dpi"]; 1473 1474 // Initialize webR 1475 await mainWebR.init(); 1476 1477 // Configure capture output 1478 let captureOutputOptions = { 1479 withAutoprint: true, 1480 captureStreams: true, 1481 captureConditions: false, 1482 // env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0 1483 }; 1484 1485 // Determine if the browser supports OffScreen 1486 if (qwebrOffScreenCanvasSupport()) { 1487 // Mirror default options of webr::canvas() 1488 // with changes to figure height and width. 1489 captureOutputOptions.captureGraphics = { 1490 width: fig_width, 1491 height: fig_height, 1492 bg: "white", // default: transparent 1493 pointsize: 12, 1494 capture: true 1495 }; 1496 } else { 1497 // Disable generating graphics 1498 captureOutputOptions.captureGraphics = false;
1499 } 1500 1501 // Store the code to run in history 1502 qwebrLogCodeToHistory(codeToRun, options); 1503 1504 // Setup a webR canvas by making a namespace call into the {webr} package 1505 // Evaluate the R code 1506 // Remove the active canvas silently 1507 const result = await mainWebRCodeShelter.captureR( 1508 `${codeToRun}`, 1509 captureOutputOptions 1510 ); 1511 1512 // ----- 1513 1514 // Start attempting to parse the result data 1515 processResultOutput:try { 1516 1517 // Avoid running through output processing 1518 if (options.results === "hide" || options.output === "false") { 1519 break processResultOutput; 1520 } 1521 1522 // Merge output streams of STDOUT and STDErr (messages and errors are combined.) 1523 // Require both `warning` and `message` to be true to display `STDErr`. 1524 const out = result.output 1525 .filter( 1526 evt => evt.type === "stdout" || 1527 ( evt.type === "stderr" && (options.warning === "true" && options.message === "true")) 1528 ) 1529 .map((evt, index) => { 1530 const className = `qwebr-output-code-${evt.type}`; 1531 const outputResult = qwebrPrefixComment(processOutput(evt.data), options.comment); 1532 return `<code id="${className}-editor-${elements.id}-result-${index + 1}" class="${className}">${outputResult}</code>`; 1533 }) 1534 .join("\n"); 1535 1536 1537 // Clean the state 1538 // We're now able to process pager events. 1539 // As a result, we cannot maintain a true 1-to-1 output order 1540 // without individually feeding each line 1541 const msgs = await mainWebR.flush(); 1542 1543 // Use `map` to process the filtered "pager" events asynchronously 1544 const pager = []; 1545 const browse = []; 1546 1547 await Promise.all( 1548 msgs.map( 1549 async (msg) => { 1550 1551 const msgType = msg.type || "unknown"; 1552 1553 switch(msgType) { 1554 case 'pager': 1555 const pager_data = await qwebrParseTypePager(msg); 1556 pager.push(pager_data); 1557 break; 1558 case 'browse': 1559 const browse_data = await qwebrParseTypeBrowse(msg); 1560 browse.push(browse_data); 1561 break; 1562 } 1563 return; 1564 } 1565 ) 1566 ); 1567 1568 // Nullify the output area of content 1569 elements.outputCodeDiv.innerHTML = ""; 1570 elements.outputGraphDiv.innerHTML = ""; 1571 1572 // Design an output object for messages 1573 const pre = document.createElement("pre"); 1574 if (/\S/.test(out)) { 1575 // Display results as HTML elements to retain output styling 1576 const div = document.createElement("div"); 1577 div.innerHTML = out; 1578 1579 // Calculate a scaled font-size value 1580 const scaledFontSize = qwebrScaledFontSize( 1581 elements.outputCodeDiv, options); 1582 1583 // Override output code cell size 1584 pre.style.fontSize = `${scaledFontSize}px`; 1585 pre.appendChild(div); 1586 } else { 1587 // If nothing is present, hide the element. 1588 pre.style.visibility = "hidden"; 1589 } 1590 1591 elements.outputCodeDiv.appendChild(pre); 1592 1593 // Determine if we have graphs to display 1594 if (result.images.length > 0) { 1595 1596 // Create figure element 1597 const figureElement = document.createElement("figure"); 1598 figureElement.className = "qwebr-canvas-image"; 1599 1600 // Place each rendered graphic onto a canvas element 1601 result.images.forEach((img) => { 1602 1603 // Construct canvas for object 1604 const canvas = document.createElement("canvas"); 1605 1606 // Add an image download button 1607 qwebrImageCanvasDownloadButton(canvas, figureElement); 1608 1609 // Set canvas size to image 1610 canvas.width = img.width;
1611 canvas.height = img.height; 1612 1613 // Apply output truncations 1614 canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`; 1615 if (options["out-height"]) { 1616 canvas.style.height = options["out-height"]; 1617 } 1618 1619 // Apply styling 1620 canvas.style.display = "block"; 1621 canvas.style.margin = "auto"; 1622 1623 // Draw image onto Canvas 1624 const ctx = canvas.getContext("2d"); 1625 ctx.drawImage(img, 0, 0, img.width, img.height); 1626 1627 // Append canvas to figure output area 1628 figureElement.appendChild(canvas); 1629 1630 }); 1631 1632 if (options['fig-cap']) { 1633 // Create figcaption element 1634 const figcaptionElement = document.createElement('figcaption'); 1635 figcaptionElement.innerText = options['fig-cap']; 1636 // Append figcaption to figure 1637 figureElement.appendChild(figcaptionElement); 1638 } 1639 1640 elements.outputGraphDiv.appendChild(figureElement); 1641 1642 } 1643 1644 // Display the pager data 1645 if (pager.length > 0) { 1646 // Use the `pre` element to preserve whitespace. 1647 pager.forEach((paged_data, index) => { 1648 const pre_pager = document.createElement("pre"); 1649 pre_pager.innerText = paged_data; 1650 pre_pager.classList.add("qwebr-output-code-pager"); 1651 pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`); 1652 elements.outputCodeDiv.appendChild(pre_pager); 1653 }); 1654 } 1655 1656 // Display the browse data 1657 if (browse.length > 0) { 1658 // Use the `pre` element to preserve whitespace. 1659 browse.forEach((browse_data, index) => { 1660 const iframe_browse = document.createElement('iframe'); 1661 iframe_browse.classList.add("qwebr-output-code-browse"); 1662 iframe_browse.setAttribute("id", `qwebr-output-code-browse-editor-${elements.id}-result-${index + 1}`); 1663 iframe_browse.style.width = "100%"; 1664 iframe_browse.style.minHeight = "500px"; 1665 elements.outputCodeDiv.appendChild(iframe_browse); 1666 1667 iframe_browse.contentWindow.document.open(); 1668 iframe_browse.contentWindow.document.write(browse_data); 1669 iframe_browse.contentWindow.document.close(); 1670 }); 1671 } 1672 } finally { 1673 // Clean up the remaining code 1674 mainWebRCodeShelter.purge(); 1675 } 1676 }; 1677 1678 // Function to execute the code (accepts code as an argument) 1679 globalThis.qwebrExecuteCode = async function ( 1680 codeToRun, 1681 id, 1682 options = {}) { 1683 1684 // If options are not passed, we fall back on the bare minimum to handle the computation 1685 if (qwebrIsObjectEmpty(options)) { 1686 options = { 1687 "context": "interactive", 1688 "fig-width": 7, "fig-height": 5, 1689 "out-width": "700px", "out-height": "", 1690 "dpi": 72, 1691 "results": "markup", 1692 "warning": "true", "message": "true", 1693 }; 1694 } 1695 1696 // Next, we access the compute areas values 1697 const elements = { 1698 runButton: document.getElementById(`qwebr-button-run-${id}`), 1699 outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`), 1700 outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`), 1701 id: id, 1702 } 1703 1704 // Disallowing execution of other code cells 1705 document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 1706 btn.disabled = true; 1707 }); 1708 1709 if (options.context == EvalTypes.Interactive) { 1710 // Emphasize the active code cell 1711 elements.runButton.innerHTML = '<i class="fa-solid fa-spinner fa-spin qwebr-icon-status-spinner"></i> <span>
1711Run Code</span>'; 1712 } 1713 1714 // Evaluate the code and parse the output into the document 1715 await qwebrComputeEngine(codeToRun, elements, options); 1716 1717 // Switch to allowing execution of code 1718 document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 1719 btn.disabled = false; 1720 }); 1721 1722 if (options.context == EvalTypes.Interactive) { 1723 // Revert to the initial code cell state 1724 elements.runButton.innerHTML = '<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>Run Code</span>'; 1725 } 1726 } 1727 1728 </script>
1728 1729</head> 1730<body class="quarto-light">
1731<script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
vendor: 1 bytes, line 1731
1731
1732<script type="module" id="qwebr-monaco-editor-init"> 1733 1734 // Configure the Monaco Editor's loader 1735 require.config({ 1736 paths: { 1737 'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' 1738 } 1739 }); 1740</script>
vendor: 1 bytes, line 1740
1740
1741<script type="module"> 1742// Function to update Monaco Editors when body class changes 1743function updateMonacoEditorsOnBodyClassChange() { 1744 // Select the body element 1745 const body = document.querySelector('body'); 1746 1747 // Options for the observer (which mutations to observe) 1748 const observerOptions = { 1749 attributes: true, // Observe changes to attributes 1750 attributeFilter: ['class'] // Only observe changes to the 'class' attribute 1751 }; 1752 1753 // Callback function to execute when mutations are observed 1754 const bodyClassChangeCallback = function(mutationsList, observer) { 1755 for(let mutation of mutationsList) { 1756 if (mutation.type === 'attributes' && mutation.attributeName === 'class') { 1757 // Class attribute has changed 1758 // Update all Monaco Editors on the page 1759 updateMonacoEditorTheme(); 1760 } 1761 } 1762 }; 1763 1764 // Create an observer instance linked to the callback function 1765 const observer = new MutationObserver(bodyClassChangeCallback); 1766 1767 // Start observing the target node for configured mutations 1768 observer.observe(body, observerOptions); 1769} 1770 1771// Function to update all instances of Monaco Editors on the page 1772function updateMonacoEditorTheme() { 1773 // Determine what VS Theme to use 1774 const vsThemeToUse = document.body.classList.contains("quarto-dark") ? 'vs-dark' : 'vs' ; 1775 1776 // Iterate through all initialized Monaco Editors 1777 qwebrEditorInstances.forEach( function(editorInstance) { 1778 editorInstance.updateOptions({ theme: vsThemeToUse }); 1779 }); 1780} 1781 1782// Call the function to start observing changes to body class 1783updateMonacoEditorsOnBodyClassChange(); 1784</script>
vendor: 1 bytes, line 1784
1784
1785<script type="module"> 1786// Global array to store Monaco Editor instances 1787globalThis.qwebrEditorInstances = []; 1788 1789function isValidCodeLineNumbers(stringCodeLineNumbers) { 1790 // Regular expression to match valid input strings 1791 const regex = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/; 1792 return regex.test(stringCodeLineNumbers); 1793} 1794 1795// Function that builds and registers a Monaco Editor instance 1796globalThis.qwebrCreateMonacoEditorInstance = function (cellData) { 1797 1798 const initialCode = cellData.code; 1799 const qwebrCounter = cellData.id; 1800 const qwebrOptions = cellData.options; 1801 1802 // Retrieve the previously created document elements 1803 let runButton = document.getElementById(`qwebr-button-run-${qwebrCounter}`); 1804 let resetButton = document.getElementById(`qwebr-button-reset-${qwebrCounter}`); 1805 let copyButton = document.getElementById(`qwebr-button-copy-${qwebrCounter}`); 1806 let editorDiv = document.getElementById(`qwebr-editor-${qwebrCounter}`); 1807 1808 // Load the Monaco Editor and create an instance 1809 let editor; 1810 require(['vs/editor/editor.main'], function () { 1811 editor = monaco.editor.create(editorDiv, { 1812 value: initialCode, 1813 language: 'r', 1814 theme: 'vs-light', 1815 automaticLayout: true, // Works wonderfully with RevealJS 1816 scrollBeyondLastLine: false, 1817 minimap: { 1818 enabled: false 1819 }, 1820 fontSize: qwebrScaledFontSize(editorDiv, qwebrOptions), 1821 renderLineHighlight: "none", // Disable current line highlighting 1822 hideCursorInOverviewRuler: true, // Remove cursor indictor in right hand side scroll bar 1823 readOnly: qwebrOptions['read-only'] ?? false, 1824 quickSuggestions: qwebrOptions['editor-quick-suggestions'] ?? false, 1825 wordWrap: (qwebrOptions['editor-word-wrap'] == 'true' ? "on" : "off") 1826 }); 1827 1828 // Store the official counter ID to be used in keyboard shortcuts 1829 editor.__qwebrCounter = qwebrCounter; 1830 1831 // Store the official div container ID 1832 editor.__qwebrEditorId = `qwebr-editor-${qwebrCounter}`; 1833 1834 // Store the initial code value and options 1835 editor.__qwebrinitialCode = initialCode; 1836 editor.__qwebrOptions = qwebrOptions; 1837 1838 // Set at the model level the preferred end of line (EOL) character to LF. 1839 // This prevent `\r\n` from being given to the webR engine if the user is on Windows. 1840 // See details in: https://github.com/coatless/quarto-webr/issues/94 1841 // Associated error text: 1842 // Error: <text>:1:7 unexpected input 1843 1844 // Retrieve the underlying model 1845 const model = editor.getModel(); 1846 // Set EOL for the model 1847 model.setEOL(monaco.editor.EndOfLineSequence.LF); 1848 1849 // Dynamically modify the height of the editor window if new lines are added. 1850 let ignoreEvent = false; 1851 const updateHeight = () => { 1852 // Increment editor height by 2 to prevent vertical scroll bar from appearing 1853 const contentHeight = editor.getContentHeight() + 2; 1854 1855 // Retrieve editor-max-height option 1856 const maxEditorHeight = qwebrOptions['editor-max-height']; 1857 1858 // If editor-max-height is missing, allow infinite growth. Otherwise, threshold. 1859 const editorHeight = !maxEditorHeight ? contentHeight : Math.min(contentHeight, maxEditorHeight); 1860 1861 // We're avoiding a width change 1862 //editorDiv.style.width = `${width}px`; 1863 editorDiv.style.height = `${editorHeight}px`; 1864 try { 1865 ignoreEvent = true; 1866 1867 // The key to resizing is this call 1868 editor.layout(); 1869 } finally { 1870 ignoreEvent = false; 1871 } 1872 }; 1873 1874 // Function to generate decorations to highlight lines 1875 // in the editor based on input string. 1876 function decoratorHighlightLines(codeLineNumbers) { 1877 // Store the lines to be lighlight 1878 let linesToHighlight = []; 1879 1880 // Parse the codeLineNumbers string to get the line numbers to highlight 1881 // First, split the string by commas 1882 codeLineNumbers.split(',').forEach(part => { 1883 // Check if we have a range of lines 1884 if (part.includes('-')) { 1885 // Handle range of lines (e.g., "6-8") 1886 const [start, end] = part.split('-').map(Number); 1887 for (let i = start; i <= end; i++) { 1888 linesToHighlight.push(i); 1889 } 1890 } else { 1891 // Handle single line (e.g., "7") 1892 linesToHighlight.push(Number(part)); 1893 } 1894 }); 1895 1896 // Create monaco decorations for the lines to highlight 1897 const decorations = linesToHighlight.map(lineNumber => ({ 1898 range: new monaco.Range(lineNumber, 1, lineNumber, 1), 1899 options: { 1900 isWholeLine: true, 1901 className: 'qwebr-editor-highlight-line' 1902 } 1903 })); 1904 1905 // Return decorations to be applied to the editor
1906 return decorations; 1907 } 1908 1909 // Ensure that the editor-code-line-numbers option is set and valid 1910 // then apply styling 1911 if (qwebrOptions['editor-code-line-numbers']) { 1912 // Remove all whitespace from the string 1913 const codeLineNumbers = qwebrOptions['editor-code-line-numbers'].replace(/\s/g,''); 1914 // Check if the string is valid for line numbers, e.g., "1,3-5,7" 1915 if (isValidCodeLineNumbers(codeLineNumbers)) { 1916 // Apply the decorations to the editor 1917 editor.createDecorationsCollection(decoratorHighlightLines(codeLineNumbers)); 1918 } else { 1919 // Warn the user that the input is invalid 1920 console.warn(`Invalid "editor-code-line-numbers" value in code cell ${qwebrOptions['label']}: ${codeLineNumbers}`); 1921 } 1922 } 1923 1924 // Helper function to check if selected text is empty 1925 function isEmptyCodeText(selectedCodeText) { 1926 return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === ""); 1927 } 1928 1929 // Registry of keyboard shortcuts that should be re-added to each editor window 1930 // when focus changes. 1931 const addWebRKeyboardShortCutCommands = () => { 1932 // Add a keydown event listener for Shift+Enter to run all code in cell 1933 editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => { 1934 1935 // Retrieve all text inside the editor 1936 qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions); 1937 }); 1938 1939 // Add a keydown event listener for CMD/Ctrl+Enter to run selected code 1940 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { 1941 1942 // Get the selected text from the editor 1943 const selectedText = editor.getModel().getValueInRange(editor.getSelection()); 1944 // Check if no code is selected 1945 if (isEmptyCodeText(selectedText)) { 1946 // Obtain the current cursor position 1947 let currentPosition = editor.getPosition(); 1948 // Retrieve the current line content 1949 let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber); 1950 1951 // Propose a new position to move the cursor to 1952 let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1); 1953 1954 // Check if the new position is beyond the last line of the editor 1955 if (newPosition.lineNumber > editor.getModel().getLineCount()) { 1956 // Add a new line at the end of the editor 1957 editor.executeEdits("addNewLine", [{ 1958 range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1), 1959 text: "\n", 1960 forceMoveMarkers: true, 1961 }]); 1962 } 1963 1964 // Run the entire line of code. 1965 qwebrExecuteCode(currentLine, editor.__qwebrCounter, editor.__qwebrOptions); 1966 1967 // Move cursor to new position 1968 editor.setPosition(newPosition); 1969 } else { 1970 // Code to run when Ctrl+Enter is pressed with selected code 1971 qwebrExecuteCode(selectedText, editor.__qwebrCounter, editor.__qwebrOptions); 1972 } 1973 }); 1974 } 1975 1976 // Register an on focus event handler for when a code cell is selected to update 1977 // what keyboard shortcut commands should work. 1978 // This is a workaround to fix a regression that happened with multiple 1979 // editor windows since Monaco 0.32.0 1980 // https://github.com/microsoft/monaco-editor/issues/2947 1981 editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands); 1982 1983 // Register an on change event for when new code is added to the editor window 1984 editor.onDidContentSizeChange(updateHeight); 1985 1986 // Manually re-update height to account for the content we inserted into the call 1987 updateHeight(); 1988 1989 // Store the editor instance in the global dictionary 1990 qwebrEditorInstances[editor.__qwebrCounter] = editor; 1991 1992 }); 1993 1994 // Add a click event listener to the run button 1995 runButton.onclick = function () { 1996 qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions); 1997 }; 1998 1999 // Add a click event listener to the reset button 2000 copyButton.onclick = function () { 2001 // Retrieve current code data 2002 const data = editor.getValue(); 2003 2004 // Write code data onto the clipboard. 2005 navigator.clipboard.writeText(data || ""); 2006 }; 2007 2008 // Add a click event listener to the copy button 2009 resetButton.onclick = function () { 2010 editor.setValue(editor.__qwebrinitialCode); 2011 }; 2012 2013} 2014</script>
2014 2015 <div class="reveal"> 2016 <div class="slides"> 2017 2018<section id="title-slide" class="quarto-title-block center"> 2019 <h1 class="title"><p><span style="color:#a9a9a9;">Week 4</span><br> 2020Causal Inference</p></h1> 2021 <p class="subtitle"><span style="color:#9c9c9c; font-family: Dynalight; font-size: 130%;">Sociâ316</span></p> 2022 2023<div class="quarto-title-authors"> 2024<div class="quarto-title-author"> 2025<div class="quarto-title-author-name"> 2026Sakeef M. Karim <br><span style="font-size: 80%; color:#9c9c9c;"><em>Amherst College</em></span><br><a href="https://www.sakeefkarim.com" class="external" target="_blank"><svg aria-hidden="true" role="img" viewbox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:#856cb0;overflow:visible;position:relative;"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"></path></svg></a> <a href="mailto:[email protected]"><svg aria-hidden="true" role="img" viewbox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:#856cb0;overflow:visible;position:relative;"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"></path></svg></a> <br> <span style="font-weight: 300; font-size: 60%; color: #9c9c9c;">SOCIAL RESEARCH</span> 2027</div> 2028</div> 2029</div> 2030 2031</section> 2032<section> 2033<section id="does-x-to-y-september-22nd" class="title-slide slide level1 center" data-background-color="#43464B"> 2034<h1>Does <span style="font-size:90%"><span class="math inline">X</span></span> <span class="math inline">\to</span> <span style="font-size:90%"><span class="math inline">Y</span></span>?<br>September 22<span style="font-size: 70%;"><sup>nd</sup></span></h1> 2035 2036</section> 2037<section id="an-update" class="slide level2"> 2038<h2>An Update</h2> 2039<h4 id="qualtrics-survey-assignment"><span style="color:#a9a9a9;">Qualtrics Survey Assignment</span></h4> 2040<div style="text-align: center; margin-top: 25px;"> 2041<p><span style="font-size: 85%;">Assignment instructions are live.</span></p> 2042<p align="center"> 2043<iframe class="iframe" src="https://soci-316-qualtrics.netlify.app/" height="420" width="1000" style="border: 1px solid #2e3846;"> 2044</iframe> 2045</p> 2046<div style="text-align: center; font-size: 70%; margin-top:-40px"> 2047<p><a href="https://soci-316-qualtrics.netlify.app/" data-preview-link="true">Full Page</a></p> 2048</div> 2049</div> 2050</section></section> 2051<section> 2052<section id="advancing-causal-claims" class="title-slide slide level1 center" data-background-color="#43464B"> 2053<h1>Advancing Causal Claims</h1> 2054 2055</section> 2056<section id="cause-and-effect" class="slide level2"> 2057<h2>Cause and Effect</h2> 2058<div style="text-align: center; margin-top: 80px;"> 2059<div class="res-block"> 2060<p>Social scientists are often interested in drawing <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> inferences.</p> 2061<p><span class="math inline">X \to Y</span></p> 2062</div> 2063</div> 2064</section> 2065<section id="cause-and-effect-1" class="slide level2 scrollable"> 2066<h2>Cause and Effect</h2> 2067<div style="text-align: center; margin-top: 50px;"> 2068<p>About what, exactly?</p> 2069<div class="fragment"> 2070<p>There are a dizzying array of examples.</p> 2071</div> 2072<div class="fragment"> 2073<p>Here are three questions that invite causal claims:</p> 2074</div> 2075<div class="fragment"> 2076<details class="reveal-detail"> 2077<summary> 2078Three Causal Questions 2079</summary> 2080<div class="detail-content" style="font-size: 75%; text-align: center;"> 2081<ol type="1"> 2082<li class="fragment"><p>
2082Do smaller class sizes improve pedagogical outcomes?</p></li> 2083<li class="fragment"><p>Will investments in family planning programs improve economic and health outcomes for women in low-income settings?</p></li> 2084<li class="fragment"><p>Did the cultural grievances of the âmiddle classâ trigger the rise of fascist politics in interwar Europe?</p></li> 2085</ol> 2086</div> 2087</details> 2088</div> 2089</div> 2090</section> 2091<section id="cause-and-effect-2" class="slide level2"> 2092<h2>Cause and Effect</h2> 2093<div style="text-align: center; margin-top: 60px; font-size: 95%;"> 2094<p>Letâs pause for a second.</p> 2095<div class="fragment"> 2096<p>Try to frame your research question in <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> terms.</p> 2097</div> 2098<div style="margin-top: 30px;"> 2099 2100</div> 2101<div class="fragment"> 2102<p>Rearrange the text in the box below to arrive at your question.</p> 2103<div style="margin-top: 30px;"> 2104 2105</div> 2106 2107<!-- 2108 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2109 QUESTION PAD · Quarto RevealJS drop-in 2110 Space Grotesk / JetBrains Mono · purple ramp (#856cb0) 2111 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2112 Notepad idiom, with the card's border as the clock: the stroke 2113 drains clockwise from top centre, turns red in the warning window 2114 (10% of duration, capped at 60s), and the card pulses at 0:00. 2115 2116 Per-instance attributes on .oe-pad: 2117 data-minutes duration (fractions OK) default 10 2118 data-file export filename default research_question.txt 2119 Master size control: --oe-size. 2120 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2121--> 2122 2123<style> 2124.oe-pad { 2125 --oe-accent: #856cb0; 2126 --oe-deep: #6f5a96; 2127 --oe-pale: #e9e4ff; 2128 --oe-warn: #c75b5b; 2129 --oe-ink: #2a2632; 2130 --oe-mute: #857f93; 2131 --oe-sans: "Space Grotesk", -apple-system, sans-serif; 2132 --oe-mono: "JetBrains Mono", ui-monospace, monospace; 2133 --oe-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 2134 2135 /* â¸â¸ MASTER SIZE CONTROL ââ 17px compact · 20px default · 24px large */ 2136 --oe-size: 20px; 2137 2138 position: relative; 2139 box-sizing: border-box; 2140 max-width: 1000px; 2141 margin: 0 auto; 2142 padding: 1em 1.15em 1.05em; 2143 border-radius: 14px; 2144 background: #fff; 2145 box-shadow: var(--oe-shadow); 2146 font-family: var(--oe-sans); 2147 font-size: var(--oe-size); 2148 color: var(--oe-ink); 2149 text-align: left; 2150} 2151.oe-pad *, .oe-pad *::before, .oe-pad *::after { box-sizing: border-box; } 2152 2153/* ââ perimeter clock ââââââââââââââââââââââââââââââââââââââââââââââ */ 2154.oe-ring { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; } 2155.oe-track { fill: none; stroke: rgba(133, 108, 176, .14); stroke-width: 2.5; } 2156.oe-prog { 2157 fill: none; 2158 stroke: var(--oe-accent); 2159 stroke-width: 2.5; 2160 stroke-linecap: round; 2161 stroke-dasharray: 1 2; 2162 opacity: .55; 2163 transition: stroke .3s ease, opacity .3s ease, filter .3s ease; 2164} 2165.oe-pad[data-state="running"] .oe-prog { opacity: 1; filter: drop-shadow(0 0 4px rgba(133, 108, 176, .55)); } 2166.oe-pad[data-state="paused"] .oe-prog { opacity: .75; } 2167.oe-pad.is-warn .oe-prog { stroke: var(--oe-warn); filter: drop-shadow(0 0 4px rgba(199, 91, 91, .45)); } 2168.oe-pad[data-state="done"] .oe-prog { opacity: 0; } 2169 2170@keyframes oe-ring-pulse { 2171 0% { box-shadow: var(--oe-shadow), 0 0 0 0 rgba(199, 91, 91, .4); } 2172 100% { box-shadow: var(--oe-shadow), 0 0 0 14px rgba(199, 91, 91, 0); } 2173} 2174.oe-pad.is-pulse { animation: oe-ring-pulse 1.4s ease-out 3; } 2175 2176/* ââ header âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2177.oe-head { 2178 display: flex; 2179 align-items: center; 2180 justify-content: space-between; 2181 gap: 1em; 2182 margin-bottom: .6em; 2183} 2184.oe-title { 2185 font-size: 1.15em; 2186 font-weight: 600; 2187 letter-spacing: -.015em; 2188 line-height: 1.2; 2189} 2190 2191.oe-clock { display: flex; align-items: center; gap: .35em; } 2192.oe-time { 2193 font-family: var(--oe-mono); 2194 font-size: 1.05em; 2195 font-weight: 500; 2196 color: var(--oe-accent); 2197 min-width: 3.2em; 2198 text-align: right; 2199 letter-spacing: .02em; 2200 font-variant-numeric: tabular-nums; 2201 transition: color .3s ease; 2202} 2203.oe-pad.is-warn .oe-time { color: var(--oe-warn); } 2204.oe-pad[data-state="done"] .oe-time { color: var(--oe-warn); animation: oe-blink 1.4s ease-in-out infinite; } 2205@keyframes oe-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } } 2206 2207.oe-tbtn { 2208 width: 1.5em; height: 1.5em; 2209 display: grid; place-items: center; 2210 margin: 0; padding: 0; 2211 border: 0; border-radius: 50%; 2212 background: rgba(133, 108, 176, .08); 2213 color: var(--oe-accent); 2214 cursor: pointer; 2215 transition: background-color .2s ease, color .2s ease, transform .15s ease; 2216} 2217.oe-tbtn:hover { background: rgba(133, 108, 176, .16); } 2218.oe-tbtn:active { transform: scale(.94); } 2219.oe-tbtn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; } 2220.oe-tbtn.is-on { background: var(--oe-accent); color: #fff; } 2221.oe-tbtn svg { width: .65em; height: .65em; } 2222 2223/* ââ editor âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2224.oe-editor { 2225 display: block; 2226 width: 100%; 2227 min-height: 6.5em; 2228 padding: .8em 1em; 2229 font-family: var(--oe-sans); 2230 font-size: .95em; 2231 font-weight: 300; 2232 line-height: 1.7; 2233 color: var(--oe-ink); 2234 background: #fff; 2235 border: 1px solid rgba(133, 108, 176, .18); 2236 border-radius: 8px; 2237 resize: vertical; 2238 outline: none; 2239 transition: border-color .2s ease, box-shadow .2s ease; 2240} 2241.oe-editor::placeholder { color: var(--oe-mute); font-style: italic; } 2242.oe-editor:focus { 2243 border-color: rgba(133, 108, 176, .45); 2244 box-shadow: 0 0 0 3px rgba(133, 108, 176, .1); 2245} 2246 2247/* ââ actions ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 2248.oe-actions { 2249 display: flex; 2250 align-items: center; 2251 gap: .35em; 2252 margin-top: .5em; 2253} 2254.oe-btn { 2255 display: inline-flex; 2256 align-items: center; 2257 gap: .4em; 2258 margin: 0; 2259 border: 0; 2260 border-radius: 6px; 2261 padding: .35em 1em; 2262 font-family: var(--oe-sans); 2263 font-size: .78em; 2264 font-weight: 500; 2265 cursor: pointer; 2266 transition: background-color .2s ease, color .2s ease, transform .15s ease; 2267} 2268.oe-btn:active { transform: scale(.98); } 2269.oe-btn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; } 2270.oe-btn svg { width: .85em; height: .85em; } 2271 2272.oe-primary { background: var(--oe-accent); color: #fff; } 2273.oe-primary:hover { background: var(--oe-deep); } 2274 2275.oe-secondary { background: rgba(133, 108, 176, .09); color: rgba(133, 108, 176, .75); } 2276.oe-secondary:hover { background: rgba(133, 108, 176, .16); color: var(--oe-accent); } 2277.oe-secondary.is-done { background: var(--oe-pale); color: var(--oe-deep); } 2278 2279.oe-count { 2280 margin-left: auto; 2281 font-family: var(--oe-mono); 2282 font-size: .62em; 2283 letter-spacing: .06em; 2284 text-transform: uppercase; 2285 color: var(--oe-mute); 2286 font-variant-numeric: tabular-nums; 2287} 2288 2289.oe-clear { 2290 background: rgba(199, 91, 91, .09); 2291 color: var(--oe-warn); 2292 margin-left: .35em; 2293} 2294.oe-clear:hover { background: rgba(199, 91, 91, .16); } 2295.oe-clear.is-arming { background: var(--oe-warn); color: #fff; } 2296 2297@media (prefers-reduced-motion: reduce) { 2298 .oe-pad.is-pulse, .oe-pad[data-state="done"] .oe-time { animation: none; } 2299 .oe-prog, .oe-btn, .oe-tbtn { transition: none; } 2300} 2301</style> 2302 2303<div class="oe-pad" data-minutes="10" data-file="research_question.txt"> 2304 2305 <svg class="oe-ring" aria-hidden="true"> 2306 <path class="oe-track"></path> 2307 <path class="oe-prog" pathlength="1"></path> 2308 </svg> 2309 2310 <div class="oe-head"> 2311 <span class="oe-title">Your Research Question</span> 2312 <div class="oe-clock">
2313 <span class="oe-time" role="timer">10:00</span> 2314 <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer"> 2315 <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg> 2316 <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg> 2317 </button> 2318 <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer"> 2319 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg> 2320 </button> 2321 </div> 2322 </div> 2323 2324 <textarea class="oe-editor" rows="4" placeholder="How does X shape variation in Y?"></textarea> 2325 2326 <div class="oe-actions"> 2327 <button class="oe-btn oe-primary oe-export" type="button"> 2328 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> 2329 Export .txt 2330 </button> 2331 <button class="oe-btn oe-secondary oe-copy" type="button"> 2332 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> 2333 <span class="oe-copy-label">Copy</span> 2334 </button> 2335 <span class="oe-count">0 words</span> 2336 <button class="oe-btn oe-clear" type="button"> 2337 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg> 2338 <span class="oe-clear-label">Clear</span> 2339 </button> 2340 </div> 2341</div> 2342
2343<script> 2344(function () { 2345 if (window.__oePadReady) { window.__oePadScan(); return; } 2346 window.__oePadReady = true; 2347 2348 window.__oePadScan = function () { 2349 document.querySelectorAll('.oe-pad').forEach(function (pad) { 2350 if (pad.dataset.oeBound) return; 2351 pad.dataset.oeBound = '1'; 2352 2353 var TOTAL = (parseFloat(pad.dataset.minutes) || 10) * 60000, 2354 WARN_AT = Math.min(60000, TOTAL * 0.1); 2355 2356 var q = function (s) { return pad.querySelector(s); }; 2357 var elRing = q('.oe-ring'), elTrack = q('.oe-track'), elProg = q('.oe-prog'), 2358 elTime = q('.oe-time'), elToggle = q('.oe-toggle'), elPlay = q('.oe-i-play'), 2359 elPause = q('.oe-i-pause'), elReset = q('.oe-reset'), elText = q('.oe-editor'), 2360 elCount = q('.oe-count'), elCopy = q('.oe-copy'), elCopyL = q('.oe-copy-label'), 2361 elClear = q('.oe-clear'), elClearL = q('.oe-clear-label'); 2362 2363 var left = TOTAL, deadline = 0, raf = 0, shown = -1, armed = null, flash = null; 2364 2365 /* ââ perimeter path, starting at top centre ââ */ 2366 function draw() { 2367 var w = pad.offsetWidth, h = pad.offsetHeight; 2368 if (!w || !h) return; 2369 var s = 1.25, 2370 r = Math.max(0, Math.min(parseFloat(getComputedStyle(pad).borderTopLeftRadius) || 0, w / 2, h / 2) - s), 2371 x0 = s, y0 = s, x1 = w - s, y1 = h - s; 2372 var d = 'M' + w / 2 + ' ' + y0 + 'H' + (x1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + (y0 + r) + 2373 'V' + (y1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x1 - r) + ' ' + y1 + 2374 'H' + (x0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x0 + ' ' + (y1 - r) + 2375 'V' + (y0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x0 + r) + ' ' + y0 + 'Z'; 2376 elRing.setAttribute('viewBox', '0 0 ' + w + ' ' + h); 2377 elTrack.setAttribute('d', d); 2378 elProg.setAttribute('d', d); 2379 } 2380 if ('ResizeObserver' in window) new ResizeObserver(draw).observe(pad); 2381 else window.addEventListener('resize', draw); 2382 2383 /* ââ timer ââ */ 2384 function fmt(s) { return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); } 2385 2386 function setState(st) { 2387 pad.dataset.state = st; 2388 var on = st === 'running'; 2389 elToggle.classList.toggle('is-on', on); 2390 elPlay.style.display = on ? 'none' : ''; 2391 elPause.style.display = on ? '' : 'none'; 2392 } 2393 2394 function paint() { 2395 elProg.style.strokeDashoffset = -(1 - left / TOTAL); 2396 var secs = Math.ceil(left / 1000); 2397 if (secs !== shown) { shown = secs; elTime.textContent = fmt(secs); } 2398 pad.classList.toggle('is-warn', left <= WARN_AT); 2399 } 2400 2401 function tick(now) { 2402 left = Math.max(0, deadline - now); 2403 paint(); 2404 if (left === 0) { finish(); return; } 2405 raf = requestAnimationFrame(tick); 2406 } 2407 2408 function finish() { 2409 cancelAnimationFrame(raf); 2410 setState('done'); 2411 pad.classList.remove('is-pulse'); 2412 void pad.offsetWidth; 2413 pad.classList.add('is-pulse'); 2414 } 2415 2416 elToggle.addEventListener('click', function () { 2417 if (pad.dataset.state === 'running') { 2418 cancelAnimationFrame(raf); 2419 left = Math.max(0, deadline - performance.now()); 2420 setState('paused'); 2421 paint(); 2422 return; 2423 } 2424 if (left <= 0) { left = TOTAL; shown = -1; pad.classList.remove('is-pulse'); } 2425 deadline = performance.now() + left; 2426 setState('running'); 2427 raf = requestAnimationFrame(tick); 2428 elText.focus(); 2429 }); 2430 2431 elReset.addEventListener('click', function () { 2432 cancelAnimationFrame(raf); 2433 left = TOTAL; shown = -1; 2434 pad.classList.remove('is-pulse'); 2435 setState('idle'); 2436 paint(); 2437 }); 2438 2439 /* ââ word count ââ */ 2440 function count() { 2441 var w = elText.value.trim(); 2442 var n = w ? w.split(/\s+/).length : 0; 2443 elCount.textContent = n + (n === 1 ? ' word' : ' words'); 2444 } 2445 elText.addEventListener('input', count); 2446 2447 /* Reveal listens on document â keep every keystroke out of its hands */
2448 ['keydown', 'keypress', 'keyup'].forEach(function (ev) { 2449 elText.addEventListener(ev, function (e) { e.stopPropagation(); }); 2450 }); 2451 pad.addEventListener('keydown', function (e) { 2452 if (e.key === ' ' || e.key === 'Spacebar') e.stopPropagation(); 2453 }); 2454 2455 /* ââ export / copy / clear ââ */ 2456 q('.oe-export').addEventListener('click', function () { 2457 var t = elText.value; 2458 if (!t.trim()) return; 2459 var a = document.createElement('a'); 2460 a.href = URL.createObjectURL(new Blob([t], { type: 'text/plain;charset=utf-8' })); 2461 a.download = pad.dataset.file || 'research_question.txt'; 2462 a.click(); 2463 setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000); 2464 }); 2465 2466 elCopy.addEventListener('click', function () { 2467 var t = elText.value; 2468 if (!t.trim()) return; 2469 function done() { 2470 clearTimeout(flash); 2471 elCopy.classList.add('is-done'); 2472 elCopyL.textContent = 'Copied'; 2473 flash = setTimeout(function () { 2474 elCopy.classList.remove('is-done'); 2475 elCopyL.textContent = 'Copy'; 2476 }, 1600); 2477 } 2478 if (navigator.clipboard && navigator.clipboard.writeText) { 2479 navigator.clipboard.writeText(t).then(done, function () { elText.select(); }); 2480 } else { elText.select(); document.execCommand('copy'); done(); } 2481 }); 2482 2483 elClear.addEventListener('click', function () { 2484 if (!elText.value.trim()) return; 2485 if (armed) { 2486 clearTimeout(armed); armed = null; 2487 elClear.classList.remove('is-arming'); 2488 elClearL.textContent = 'Clear'; 2489 elText.value = ''; 2490 count(); 2491 elText.focus(); 2492 return; 2493 } 2494 elClear.classList.add('is-arming'); 2495 elClearL.textContent = 'Confirm'; 2496 armed = setTimeout(function () { 2497 armed = null; 2498 elClear.classList.remove('is-arming'); 2499 elClearL.textContent = 'Clear'; 2500 }, 3000); 2501 }); 2502 2503 setState('idle'); 2504 paint(); 2505 count(); 2506 draw(); 2507 }); 2508 }; 2509 2510 if (document.readyState === 'loading') { 2511 document.addEventListener('DOMContentLoaded', window.__oePadScan); 2512 } else { window.__oePadScan(); } 2513 if (window.Reveal && Reveal.on) Reveal.on('ready', window.__oePadScan); 2514})(); 2515</script>
2515 2516 2517</div> 2518</div> 2519</section> 2520<section id="cause-and-effect-3" class="slide level2"> 2521<h2>Cause and Effect</h2> 2522<div style="text-align: center; margin-top: 80px;"> 2523<div class="res-block"> 2524<p>We can, of course, deploy quantitative tools (e.g., estimators, weighting, causal diagrams, experiments) to âresolveâ causal questionsâor at least adduce evidence grounded in statistical reasoning.</p> 2525</div> 2526<div style="text-align: center;"> 2527<div class="scrollable-note"> 2528<p><span style="font-size: 85%;">For an overview of how causality âworksâ in ethnographic research, see Small <span class="citation" data-cites="small_causal_2013">(<a href="#/references" role="doc-biblioref" onclick="">2013</a>)</span>.</span></p> 2529</div> 2530</div> 2531</div> 2532</section> 2533<section id="cause-and-effect-4" class="slide level2"> 2534<h2>Cause and Effect</h2> 2535<div style="text-align: center; margin-top: 100px; margin-bottom: 30px;"> 2536<p>Letâs assume that our outcomes of substantive interest are approximately linear. Can we estimate linear regression models to âresolveâ our <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> questions?</p> 2537<p><span class="math inline">y = \beta_0 + \beta_1 x + \epsilon</span></p> 2538</div> 2539<div class="fragment"> 2540<details class="reveal-detail"> 2541<summary> 2542The Answer 2543</summary> 2544<div class="detail-content" style="font-size: 75%; text-align: center;"> 2545<p>It <span style="text-decoration: underline; text-decoration-color: #856cb0;"><em>depends</em></span>.</p> 2546</div> 2547</details> 2548</div> 2549</section> 2550<section id="cause-and-effect-5" class="slide level2"> 2551<h2>Cause and Effect</h2> 2552<h4 id="the-class-size-example"><span style="color:#a9a9a9;">The Class Size Example</span></h4> 2553 2554<!-- 2555 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2556 DAG · two-step confounder reveal · Quarto RevealJS drop-in 2557 Space Grotesk · purple ramp (#856cb0) 2558 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2559 Step 1: "Confounder?" on a hollow, dashed node. 2560 Step 2: label crossfades to "Field of Study"; node fills. 2561 Advance with the normal Reveal key (it's a fragment) or click the 2562 figure. Click again, or step back, to return. Size: --dag-size. 2563 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2564--> 2565 2566<style> 2567.dag2 { 2568 --dag-ink: #2a2632; 2569 --dag-accent: #856cb0; 2570 --dag-deep: #6f5a96; 2571 --dag-pale: #f3effa; 2572 --dag-lilac: #c9bce2; 2573 --dag-size: 22px; 2574 2575 position: relative; 2576 max-width: 960px; 2577 margin: 0 auto; 2578 font-family: "Space Grotesk", -apple-system, sans-serif; 2579 font-size: var(--dag-size); 2580 line-height: 1; 2581 cursor: pointer; 2582 -webkit-tap-highlight-color: transparent; 2583} 2584.dag2 svg { display: block; width: 100%; height: auto; overflow: visible; } 2585 2586/* edges */ 2587.dag2 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; } 2588.dag2 .e-dash { stroke-dasharray: 9 6 2 6; } 2589 2590/* nodes */ 2591.dag2 .ring { fill: none; stroke-width: 1.5; } 2592.dag2 .n-x .core { fill: var(--dag-ink); } .dag2 .n-x .ring { stroke: var(--dag-ink); } 2593.dag2 .n-y .core { fill: var(--dag-lilac); } .dag2 .n-y .ring { stroke: var(--dag-lilac); } 2594 2595.dag2 .n-z .core { fill: var(--dag-pale); transition: fill .5s ease; } 2596.dag2 .n-z .ring { 2597 stroke: var(--dag-accent); 2598 stroke-dasharray: 4 4; 2599 transition: stroke-dasharray .5s ease; 2600} 2601.dag2.is-on .n-z .core { fill: var(--dag-accent); } 2602.dag2.is-on .n-z .ring { stroke-dasharray: 0 0; } 2603 2604/* labels */ 2605.dag2 .lab { 2606 position: absolute; 2607 transform: translate(-50%, -50%); 2608 display: grid; 2609 padding: .22em .45em .26em; 2610 border-radius: 5px; 2611 font-weight: 500; 2612 letter-spacing: -.005em; 2613 white-space: nowrap; 2614} 2615.dag2 .lab-x { left: 9.06%; top: 96.3%; background: var(--dag-ink); color: #fff; } 2616.dag2 .lab-y { left: 90.94%; top: 96.3%; background: var(--dag-lilac); color: var(--dag-ink); } 2617.dag2 .lab-z { 2618 left: 50%; top: 18.9%; 2619 background: var(--dag-accent); color: #fff; 2620 transition: background-color .5s ease; 2621} 2622.dag2.is-on .lab-z { background: var(--dag-deep); } 2623 2624/* crossfade: both states share one grid cell, so the box never jumps */ 2625.dag2 .lab-z >
2625 span { 2626 grid-area: 1 / 1; 2627 justify-self: center; 2628 transition: opacity .35s ease, transform .35s ease, filter .35s ease; 2629} 2630.dag2 .lab-z .s2 { opacity: 0; transform: translateY(.3em); filter: blur(3px); } 2631.dag2.is-on .lab-z .s1 { opacity: 0; transform: translateY(-.3em); filter: blur(3px); } 2632.dag2.is-on .lab-z .s2 { opacity: 1; transform: none; filter: none; transition-delay: .1s; } 2633 2634.dag2 .dag2-frag { position: absolute; width: 0; height: 0; overflow: hidden; } 2635 2636@media (prefers-reduced-motion: reduce) { 2637 .dag2 *, .dag2 .lab-z > span { transition: none !important; filter: none !important; } 2638} 2639</style> 2640 2641<div class="dag2" role="img" aria-label="DAG: a confounder, later identified as field of study, causes both Amherst class size and income at 30; class size to income is the path of interest."> 2642 <svg viewbox="0 0 960 470" aria-hidden="true"> 2643 <defs> 2644 <marker id="dag2-arr" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"> 2645 <path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path> 2646 </marker> 2647 </defs> 2648 2649 <path class="e" marker-end="url(#dag2-arr)" d="M470 106 C440 175 300 210 250 236 S100 322 90 382"></path> 2650 <path class="e" marker-end="url(#dag2-arr)" d="M490 106 C520 175 660 210 710 236 S860 322 870 382"></path> 2651 <path class="e e-dash" marker-end="url(#dag2-arr)" d="M117 413 H838"></path> 2652 2653 <g class="n-z"><circle class="core" cx="480" cy="47" r="20"></circle><circle class="ring" cx="480" cy="47" r="23"></circle></g> 2654 <g class="n-x"><circle class="core" cx="87" cy="413" r="20"></circle><circle class="ring" cx="87" cy="413" r="23"></circle></g> 2655 <g class="n-y"><circle class="core" cx="873" cy="413" r="20"></circle><circle class="ring" cx="873" cy="413" r="23"></circle></g> 2656 </svg> 2657 2658 <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Field of Study</span></span> 2659 <span class="lab lab-x">Amherst Class Size</span> 2660 <span class="lab lab-y">Income at 30</span> 2661 2662 <span class="fragment dag2-frag"></span> 2663</div> 2664
2665<script> 2666(function () { 2667 document.querySelectorAll('.dag2').forEach(function (d) { 2668 if (d.dataset.bound) return; 2669 d.dataset.bound = '1'; 2670 2671 var f = d.querySelector('.dag2-frag'); 2672 var R = window.Reveal && Reveal.on ? Reveal : null; 2673 function set(on) { d.classList.toggle('is-on', on); } 2674 function sync() { set(f.classList.contains('visible')); } 2675 2676 if (R) { 2677 R.on('fragmentshown', function (e) { if (e.fragment === f) set(true); }); 2678 R.on('fragmenthidden', function (e) { if (e.fragment === f) set(false); }); 2679 R.on('slidechanged', sync); 2680 R.on('ready', sync); 2681 } 2682 2683 /* click toggles; with Reveal, it moves the fragment state itself so 2684 arrow keys and clicks never disagree */ 2685 d.addEventListener('click', function () { 2686 var on = !d.classList.contains('is-on'); 2687 var i = parseInt(f.getAttribute('data-fragment-index'), 10); 2688 if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1); 2689 else set(on); 2690 }); 2691 }); 2692})(); 2693</script>
2693 2694 2695</section> 2696<section id="cause-and-effect-6" class="slide level2"> 2697<h2>Cause and Effect</h2> 2698<h4 id="the-class-size-example-1"><span style="color:#a9a9a9;">The Class Size Example</span></h4> 2699<div style="margin-top: 100px; text-align: center;"> 2700<p>How can we deal with <em>confounding</em>âi.e., to estimate the <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal effect</strong></span> of class size at Amherst<br>on post-graduate outcomes?</p> 2701<div style="margin-top: 60px;"> 2702 2703</div> 2704<div class="fragment"> 2705<div class="rq-block"> 2706<p><span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><em>Randomization</em></span> is the gold standard.</p> 2707<p>We will return to this point in a second.</p> 2708</div> 2709</div> 2710</div> 2711</section></section> 2712<section> 2713<section id="some-technical-details" class="title-slide slide level1 center" data-background-color="#43464B"> 2714<h1>Some Technical Details</h1> 2715 2716</section> 2717<section id="treatments-and-outcomes" class="slide level2"> 2718<h2>Treatments and Outcomes</h2> 2719<div style="margin-top: 40px;"> 2720 2721</div> 2722<div class="elegant-quote"> 2723<div class="shrinker"> 2724<p><span style="color: #856cb0;"><strong>Causal relationships</strong></span> refer to <span style="color: white; background-color: #856cb0; padding: 1px 5px;">the cause-and-effect connection between two variables</span> ⦠We refer to the variable where the change originates as the <span style="color: #856cb0;"><strong>treatment variable</strong></span>. We refer to the variable that may change in response to the change in the treatment variable as the <span style="color: #856cb0;"><strong>outcome variable</strong></span>.</p> 2725<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:28</a>
2725, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 2726</div> 2727</div> 2728</section> 2729<section id="treatments-and-outcomes-1" class="slide level2"> 2730<h2>Treatments and Outcomes</h2> 2731<h4 id="under-the-assumption-of-a-binary-treatment"><span style="color:#a9a9a9;">Under the Assumption of a Binary Treatment</span></h4> 2732<div style="margin-top: 110px; font-size: 80%;"> 2733<span class="math display"> 2734X_i= 2735\begin{cases} 27361 & \text{if individual } i \text{ receives the treatment}\\ 27370 & \text{if individual } i \text{ does not receive the treatment} 2738\end{cases} 2739</span> 2740<div class="fragment" style="margin-top: 50px;"> 2741<p><span class="math display"> 2742small_i= 2743\begin{cases} 27441 & \text{if individual } i \text{ assigned to small classes at Amherst}\\ 27450 & \text{if individual } i \text{ was not assigned to small classes at Amherst} 2746\end{cases} 2747</span></p> 2748</div> 2749</div> 2750</section> 2751<section id="treatments-and-outcomes-2" class="slide level2"> 2752<h2>Treatments and Outcomes</h2> 2753<div style="margin-top: 40px;"> 2754 2755</div> 2756<div class="elegant-quote"> 2757<div class="shrinker-plus"> 2758<p>When estimating the <span style="color: #856cb0;"><strong>causal effect of <span class="math inline">X</span> on <span class="math inline">Y</span></strong></span>, <span style="color: white; background-color: #856cb0; padding: 1px 5px;">we attempt to quantify the change in the outcome variable <span class="math inline">Y</span> that is caused by a change in the treatment variable <span class="math inline">X</span></span> ⦠In mathematical notation, we represent change with <span class="math inline">\Delta</span> (the Greek letter Delta), and thus, we represent a change in the outcome as <span class="math inline">\Delta Y</span>. To measure this change in the outcome <span class="math inline">Y</span>, ideally we would compare two potential outcomes: <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">the outcome when the treatment is present and the outcome when the treatment is absent</span>.</p> 2759<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:29â30</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 2760</div> 2761</div> 2762</section> 2763<section id="treatments-and-outcomes-3" class="slide level2"> 2764<h2>Treatments and Outcomes</h2> 2765<div style="margin-top: 55px;"> 2766 2767</div> 2768<div class="elegant-quote"> 2769<div class="shrinker"> 2770<ul> 2771<li><p><span style="color: #856cb0;"><span class="math inline">Y_{i}(X_{i}=1)</span></span> is the <span style="color: #856cb0;"><strong>potential outcome under the <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">treatment</span> condition</strong></span> for individual <span class="math inline">i</span> (the value of <span class="math inline">Y_{i}</span> if <span class="math inline">X_{i}=1</span>).</p></li> 2772<li><p><span style="color: #856cb0;"><span class="math inline">Y_{i}(X_{i}=0)</span></span> is the <span style="color: #856cb0;"><strong>potential outcome under the <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">control</span> condition</strong></span> for individual <span class="math inline">i</span> (the value of <span class="math inline">Y_{i}</span> if <span class="math inline">X_{i}=0</span>).</p></li> 2773</ul> 2774<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:30</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 2775</div> 2776</div> 2777<div class="fragment" style="margin-top: 10px; font-size: 80%;"> 2778<p><span class="math display"> 2779\Delta income_{i} 2780= 2781\underbrace{income_{i}\!\left(small_{i}=1\right)}_{\text{Treatment}} 2782- 2783\underbrace{income_{i}\!\left(small_{i}=0\right)}_{\text{Control}} 2784</span></p> 2785</div> 2786</section> 2787<section id="treatments-and-outcomes-4" class="slide level2"> 2788<h2>Treatments and Outcomes</h2> 2789<div style="margin-top: 60px;"> 2790 2791</div> 2792<div class="po-card"> 2793<p><span class="po-title">If we could observe both potential outcomes</span></p> 2794<div class="po-eq"> 2795<p><span class="math display"> 2796\textit{Individual effect}_i \;=\; \Delta Y_i \;=\; {\color{#856cb0}
2796{Y_i(X_i{=}1)}} \;-\; {\color{#8a8494}{Y_i(X_i{=}0)}} 2797</span></p> 2798</div> 2799<div class="po-defs"> 2800<p><span class="po-where">where</span></p> 2801<p><span class="po-term"><span class="math inline">\Delta Y_i</span></span></p> 2802<p><span class="po-desc">The change in the outcome individual <span class="math inline">i</span> would have experienced by receiving the treatment, compared to not receiving it.</span></p> 2803<p><span class="po-term"><span class="math inline">{\color{#856cb0}{Y_i(X_i{=}1)}}</span><br><span class="math inline">{\color{#8a8494}{Y_i(X_i{=}0)}}</span></span></p> 2804<p><span class="po-desc">The two potential outcomes for the same individual <span class="math inline">i</span>, under the <span class="po-t">treatment</span> and <span class="po-c">control</span> conditions, respectively.</span></p> 2805</div> 2806</div> 2807<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:30</a>)</span>.</span></p> 2808<style> 2809.po-card { 2810 --po-accent: #856cb0; 2811 --po-ink: #2a2632; 2812 --po-mute: #8a8494; 2813 --po-tint: #f7f4fb; 2814 2815 max-width: 900px; 2816 margin: 1.3em auto 0; 2817 padding: 1em 1.5em 1.15em; 2818 border-radius: 14px; 2819 background: #fff; 2820 border: 1px solid rgba(133, 108, 176, .16); 2821 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 2822 color: var(--po-ink); 2823 text-align: left; 2824} 2825.po-card p { margin: 0; } 2826 2827.po-title { 2828 display: block; 2829 text-align: center; 2830 font-size: .78em; 2831 font-weight: 600; 2832 letter-spacing: -.01em; 2833 color: var(--po-accent); 2834} 2835 2836.po-eq { 2837 margin: .5em 0 .8em; 2838 padding: .1em 1em; 2839 border-radius: 10px; 2840 background: var(--po-tint); 2841 font-size: .7em; 2842 text-align: center; 2843 overflow: hidden; 2844} 2845.po-eq p { margin: 0; } 2846.po-eq mjx-container[display="true"] { margin: .45em 0 !important; } 2847 2848.po-defs { 2849 display: grid; 2850 grid-template-columns: max-content 1fr; 2851 align-items: baseline; 2852 column-gap: 1.2em; 2853 row-gap: .7em; 2854 font-size: .6em; 2855 line-height: 1.5; 2856} 2857.po-defs > p:first-child { grid-column: 1 / -1; } 2858.po-where { font-style: italic; color: var(--po-mute); } 2859.po-term { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; } 2860.po-desc { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; } 2861.po-t { color: var(--po-accent); font-weight: 500; } 2862.po-c { color: var(--po-mute); font-weight: 500; } 2863 2864.po-source { 2865 display: block; 2866 margin-top: 1em; 2867 text-align: center; 2868 font-size: .5em; 2869 color: #a9a9a9; 2870} 2871</style> 2872</section> 2873<section id="alternate-worlds" class="slide level2"> 2874<h2>Alternate Worlds</h2> 2875<div style="margin-top: 40px;"> 2876 2877</div> 2878<div class="elegant-quote"> 2879<div class="shrinker"> 2880<p><span style="color: white; background-color: #856cb0; padding: 1px 5px;">Unfortunately, this kind of analysis is not possible.</span> In the real world, we never observe both potential outcomes for the same individual. Instead, we observe only the <span style="color: #856cb0;"><strong>factual outcome</strong></span>, which is the potential outcome under whichever condition (treatment or control) was received in reality. We can never observe the <span style="color: #856cb0;"><strong>counterfactual outcome</strong></span>, which is the potential outcome that would have occurred under whichever condition (treatment or control) was not received in reality. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">As a result, we cannot compute causal effects at the individual level</span>.</p> 2881<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:32</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 2882</div> 2883</div> 2884</section> 2885<section id="alternate-worlds-1" class="slide level2"> 2886<h2>Alternate Worlds</h2> 2887<div style="margin-top: 100px;"> 2888 2889</div> 2890<div class="fpci-card"> 2891 <div class="fpci-title">The fundamental problem of causal inference</div> 2892 2893 <div class="fpci-body"> 2894 <p>To measure causal effects, we need to compare the factual outcome with the counterfactual outcome,</p> 2895 <p class="fpci-key"><span>but we can never observe the counterfactual outcome.</span></p> 2896 </div> 2897</div> 2898 2899<style> 2900.fpci-card { 2901 --fp-accent: #856cb0; 2902 --fp-deep: #5e4a86; 2903 --fp-ink: #2a2632; 2904 --fp-tint: #f5f1fb; 2905 2906 display: grid; 2907 grid-template-columns: 30% 1fr; 2908 max-width: 960px; 2909 margin: 1.3em auto 0; 2910 border-radius: 14px; 2911 overflow: hidden; 2912 background: #fff; 2913 border: 1px solid rgba(133, 108, 176, .16); 2914 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 2915 color: var(--fp-ink); 2916 text-align: left; 2917} 2918 2919/* title panel */ 2920.fpci-title { 2921 display: flex; 2922 align-items: center; 2923 padding: 1em 1.1em 1em 1.2em; 2924 background: var(--fp-tint); 2925 border-right: 1px solid rgba(133, 108, 176, .14); 2926 font-size: 1em; 2927 font-weight: 600; 2928 line-height: 1.15; 2929 letter-spacing: -.025em; 2930 color: var(--fp-deep); 2931 text-wrap: balance; 2932} 2933 2934/* statement */ 2935.fpci-body { 2936 align-self: center; 2937 padding: 1em 1.3em 1em 1.35em; 2938 font-size: .8em; 2939 line-height: 1.6; 2940} 2941.fpci-body p { margin: 0 !important; } 2942.fpci-body p:first-child { 2943 font-weight: 400; 2944 color: rgba(42, 38, 50, .78); 2945 text-wrap: pretty; 2946} 2947 2948/* the punchline: its own line, one emphasis. 2949 inline + clone, so if it ever wraps each line gets its own pill */ 2950.fpci-key { margin-top: .55em !important; line-height: 1.95; } 2951.fpci-key span { 2952 padding: .14em .55em .18em; 2953 -webkit-box-decoration-break: clone; 2954 box-decoration-break: clone; 2955 border-radius: 7px; 2956 background: var(--fp-accent); 2957 line-height:1.5; 2958 color: #fff; 2959 font-weight: 500; 2960 box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7); 2961} 2962</style> 2963<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:33</a>)</span>.</span></p> 2964<style> 2965.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; } 2966</style> 2967</section> 2968<section id="alternate-worlds-2" class="slide level2"> 2969<h2>Alternate Worlds</h2> 2970<div style="text-align: center; margin-top: 30px; font-size: 60%; color: grey; margin-bottom: -10px;"> 2971<p><em>Alas, this canât happen in real life for person <span class="math inline">i</span></em></p> 2972</div> 2973<div class="quarto-figure quarto-figure-center"> 2974<figure> 2975<p><a href="./media/spiderman.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img data-src="./media/spiderman.jpg" class="quarto-figure quarto-figure-center" style="width:63.0%"></a></p> 2976</figure> 2977</div> 2978<div style="font-size: 50%; text-align: center; margin-top: -30px;"> 2979<p>Image can be retrieved <a href="https://i.redd.it/bekphnqftcb41.jpg" class="external" target="_blank">here</a>.</p> 2980</div> 2981</section></section> 2982<section> 2983<section id="approximating-counterfactuals" class="title-slide slide level1 center" data-background-color="#43464B"> 2984<h1>Approximating Counterfactuals</h1> 2985 2986</section> 2987<section id="average-treatment-effects-ates" class="slide level2"> 2988<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2> 2989<div style="margin-top: 40px; font-size: 95.25%;"> 2990<div class="elegant-quote"> 2991<div class="shrinker-plus"> 2992<p>To get around the fundamental problem of causal inference, we must find good approximations for the counterfactual outcomes. To accomplish this, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we move away from individual-level effects and focus on the <em>average</em> causal effect across a <em>group of individuals</em>
2992.</span></p> 2993<p>The <span style="color: #856cb0;"><strong>average causal effect</strong></span> of the treatment <span class="math inline">X</span> on the outcome <span class="math inline">Y</span>, also known as the <span style="color: #856cb0;"><strong>average treatment effect</strong></span>, is the average of all the individual causal effects of <span class="math inline">X</span> on <span class="math inline">Y</span> within a group. Since each individual causal effect is the change in <span class="math inline">Y</span> caused by a change in <span class="math inline">X</span> for a particular individual, <span style="color: white; background-color: #856cb0; padding: 1px 5px;">the average causal effect of <span class="math inline">X</span> on <span class="math inline">Y</span> is the <em>average</em> change in <span class="math inline">Y</span> caused by a change in <span class="math inline">X</span> for a <em>group of individuals</em></span>.</p> 2994<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:33</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 2995</div> 2996</div> 2997</div> 2998</section> 2999<section id="average-treatment-effects-ates-1" class="slide level2"> 3000<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2> 3001<div class="po-card"> 3002<p><span class="po-title">If we could observe both potential outcomes</span></p> 3003<div class="po-eq"> 3004<p><span class="math display"> 3005{\color{#856cb0}{\overline{\textit{Individual effects}}}} \;=\; \frac{\sum_{i=1}^{n} \textit{Individual effects}_{i}}{n} 3006</span></p> 3007</div> 3008<div class="po-defs"> 3009<p><span class="po-where">where</span></p> 3010<p><span class="po-term"><span class="math inline">{\color{#856cb0}{\overline{\textit{Individual effects}}}}</span></span></p> 3011<p><span class="po-desc">The <span class="po-t">average treatment effect</span> for the observations in the study.</span></p> 3012<p><span class="po-term"><span class="math inline">\textit{Individual effects}_{i}</span></span></p> 3013<p><span class="po-desc">The individual treatment effect for observation <span class="math inline">i</span>.</span></p> 3014<p><span class="po-term"><span class="math inline">\sum_{i=1}^{n} \textit{Individual effects}_{i}</span></span></p> 3015<p><span class="po-desc">The sum of all <span class="math inline">\textit{Individual effects}_{i}</span>, from <span class="math inline">i=1</span> to <span class="math inline">i=n</span>.</span></p> 3016<p><span class="po-term"><span class="math inline">n</span></span></p> 3017<p><span class="po-desc">The number of observations in the study.</span></p> 3018</div> 3019</div> 3020<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:34</a>)</span>.</span></p> 3021<style> 3022.po-card { 3023 --po-accent: #856cb0; 3024 --po-ink: #2a2632; 3025 --po-mute: #8a8494; 3026 --po-tint: #f7f4fb; 3027 3028 max-width: 980px; 3029 margin: 1.3em auto 0; 3030 padding: 1em 1.5em 1.15em; 3031 border-radius: 14px; 3032 background: #fff; 3033 border: 1px solid rgba(133, 108, 176, .16); 3034 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 3035 color: var(--po-ink); 3036 text-align: left; 3037} 3038.po-card p { margin: 0; } 3039 3040.po-title { 3041 display: block; 3042 text-align: center; 3043 font-size: .78em; 3044 font-weight: 600; 3045 letter-spacing: -.01em; 3046 color: var(--po-accent); 3047} 3048 3049.po-eq { 3050 margin: .5em 0 .8em; 3051 padding: .1em 1em; 3052 border-radius: 10px; 3053 background: var(--po-tint); 3054 font-size: .7em; 3055 text-align: center; 3056 overflow: hidden; 3057} 3058.po-eq p { margin: 0; } 3059.po-eq mjx-container[display="true"] { margin: .45em 0 !important; } 3060 3061.po-defs { 3062 display: grid; 3063 grid-template-columns: max-content 1fr; 3064 align-items: baseline; 3065 column-gap: 1.2em; 3066 row-gap: .7em; 3067 font-size: .6em; 3068 line-height: 1.5; 3069} 3070.po-defs > p:first-child { grid-column: 1 / -1; } 3071.po-where { font-style: italic; color: var(--po-mute); } 3072.po-term { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; } 3073.po-desc { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; } 3074.po-t { color: var(--po-accent); font-weight: 500; } 3075.po-c { color: var(--po-mute); font-weight: 500; } 3076 3077.po-source { 3078 display: block; 3079 margin-top: 1em; 3080 text-align: center; 3081 font-size: .5em; 3082 color: #a9a9a9; 3083} 3084</style> 3085</section> 3086<section id="average-treatment-effects-ates-2" class="slide level2"> 3087<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2> 3088<div style="margin-top: 40px;"> 3089 3090</div> 3091<div class="elegant-quote"> 3092<div class="shrinker"> 3093<p>How can we obtain good approximations for the counterfactual outcomes, which by definition cannot be observed? As we will see in detail soon, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we must find or create a situation in which the treated observations and the untreated observations are similar with respect to all the variables that might affect the outcome other than the treatment variable itself</span>. The best way to accomplish this is by conducting a <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">randomized experiment</span>.</p> 3094<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:34</a>
3094, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 3095</div> 3096</div> 3097</section> 3098<section id="random-assignment" class="slide level2"> 3099<h2>Random Assignment</h2> 3100<div style="margin-top: 40px;"> 3101 3102</div> 3103<div class="elegant-quote"> 3104<div class="shrinker-super-plus"> 3105<p>In a <span style="color: #856cb0;"><strong>randomized experiment</strong></span>, also known as a randomized controlled trial (RCT), researchers decide who receives the treatment based on <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">a random process</span> â¦</p> 3106<ul> 3107<li>the <span style="color: #856cb0;"><strong>treatment group</strong></span> consists of the individuals who <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">received the treatment</span> (the group of observations for which <span class="math inline">X_{i}=1</span>)</li> 3108<li>the <span style="color: #856cb0;"><strong>control group</strong></span> consists of the individuals who <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">did not receive the treatment</span> (the group of observations for which <span class="math inline">X_{i}=0</span>)</li> 3109</ul> 3110<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:34</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 3111</div> 3112</div> 3113</section> 3114<section id="random-assignment-1" class="slide level2"> 3115<h2>Random Assignment</h2> 3116<h4 id="in-soci316"><span style="font-family: Dynalight; font-size: 130%; color:#a9a9a9;">In Sociâ316</span></h4> 3117<div style="font-size: 82%; margin-top: 10px;"> 3118<div id="qwebr-insertion-location-1"></div> 3119<noscript>Please enable JavaScript to experience the dynamic code cell content on this page.</noscript> 3120</div> 3121</section> 3122<section id="random-assignment-2" class="slide level2"> 3123<h2>Random Assignment</h2> 3124<h4 id="the-class-size-example-2"><span style="color:#a9a9a9;">The Class Size Example</span></h4> 3125<div style="margin-top: 40px;"> 3126 3127</div> 3128 3129<!-- 3130 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3131 RANDOMIZATION · Quarto RevealJS drop-in 3132 Space Grotesk · purple ramp (#856cb0) 3133 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3134 400 students as a dot grid, grouped by major. Randomize sends each 3135 dot to Treatment or Control (complete randomization, 200 per arm) 3136 and re-sorts each arm by major, so composition reads as stripes. 3137 Balance rows show each arm's share, the difference in percentage 3138 points on a ±10 pp bar, and the mean difference across all draws. 3139 "Run 100 draws" makes the point that differences average to ~0. 3140 3141 Edit GROUPS / N at the top of the script. Size: --rz-size. 3142 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3143--> 3144 3145<style> 3146.rz { 3147 --rz-accent: #856cb0; 3148 --rz-deep: #6f5a96; 3149 --rz-ink: #2a2632; 3150 --rz-mute: #857f93; 3151 --rz-tint: #f7f4fb; 3152 --rz-size: 20px; 3153 3154 box-sizing: border-box; 3155 max-width: 1000px; 3156 margin: .4em auto 0; 3157 padding: .9em 1.15em 1em; 3158 border-radius: 14px; 3159 background: #fff; 3160 border: 1px solid rgba(133, 108, 176, .16); 3161 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 3162 font-family: "Space Grotesk", -apple-system, sans-serif; 3163 font-size: var(--rz-size); 3164 line-height: 1.3; 3165 color: var(--rz-ink); 3166 text-align: left; 3167} 3168.rz *, .rz *::before, .rz *::after { box-sizing: border-box; } 3169 3170/* ââ header: legend + controls âââââââââââââââââââââââââââââââââââ */ 3171.rz-head { display: flex; align-items: center; gap: 1em; margin-bottom: .8em; } 3172 3173.rz-legend { display: flex; flex-wrap: wrap; gap: .3em 1em; font-size: .68em; color: var(--rz-mute); } 3174.rz-leg { display: inline-flex; align-items: center; gap: .4em; white-space: nowrap; } 3175.rz-leg b { font-weight: 500; color: var(--rz-ink); } 3176.rz-sw { width: .72em; height: .72em; border-radius: 50%; flex: none; } 3177 3178.rz-ctrls { margin-left: auto; display: flex; align-items: center; gap: .35em; } 3179.rz-btn { 3180 display: inline-flex; align-items: center; gap: .45em; 3181 margin: 0; border: 0; border-radius: 6px; 3182 padding: .42em 1em; 3183 font: inherit; font-size: .74em; font-weight: 500; white-space: nowrap; 3184 cursor: pointer; 3185 transition: background-color .2s ease, color .2s ease, transform .15s ease, box-shadow .2s ease; 3186} 3187.rz-btn:active { transform: scale(.97); } 3188.rz-btn:focus-visible, .rz-icon:focus-visible { outline: 2px solid var(--rz-accent); outline-offset: 2px; } 3189.rz-btn svg { width: .95em; height: .95em; } 3190.rz-primary { background: var(--rz-accent); color: #fff; box-shadow: 0 8px 18px -10px rgba(94, 74, 134, .9); } 3191.rz-primary:hover { background: var(--rz-deep); } 3192.rz-secondary { background: rgba(133, 108, 176, .09); color: var(--rz-deep); } 3193.rz-secondary:hover { background: rgba(133, 108, 176, .16); } 3194.rz-icon { 3195 width: 1.75em; height: 1.75em; display: grid; place-items: center; 3196 margin: 0; padding: 0; border: 0; border-radius: 50%; 3197 background: transparent; color: rgba(111, 90, 150, .7); cursor: pointer; 3198 transition: background-color .2s ease, color .2s ease; 3199} 3200.rz-icon:hover { background: var(--rz-tint); color: var(--rz-deep); } 3201.rz-icon svg { width: .8em; height: .8em; } 3202.rz-btn:disabled { opacity: .55; cursor: default; } 3203 3204/* ââ stage âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 3205.rz-labs { 3206 display: grid; 3207 grid-template-columns: 20fr 2fr 20fr; /* matches the dot grid exactly */ 3208 font-size: .68em; 3209 margin-bottom: .45em; 3210} 3211.rz-lab { grid-row: 1; transition: opacity .45s ease; color: var(--rz-mute); } 3212.rz-lab b { font-weight: 600; color: var(--rz-ink); margin-right: .35em; } 3213.rz-lab-all { grid-column: 1 / -1; text-align: center; } 3214.rz-lab-t { grid-column: 1; } 3215.rz-lab-c { grid-column: 3; } 3216.rz[data-state="pop"] .rz-lab-t, 3217.rz[data-state="pop"] .rz-lab-c { opacity: 0; } 3218.rz[data-state="split"] .rz-lab-all { opacity: 0; } 3219 3220.rz-canvas { position: relative; width: 100%; aspect-ratio: 42 / 10; } 3221.rz-dot { 3222 position: absolute; left: 0; top: 0; 3223 border-radius: 50%; 3224 transition: transform .8s cubic-bezier(.2, .75, .25, 1); 3225 will-change: transform; 3226}
3227.rz.no-anim .rz-dot { transition: none !important; } 3228.rz-divide { 3229 position: absolute; left: 50%; top: -.2em; bottom: -.2em; 3230 border-left: 1px dashed rgba(133, 108, 176, .45); 3231 opacity: 0; transition: opacity .5s ease .35s; 3232} 3233.rz[data-state="split"] .rz-divide { opacity: 1; } 3234 3235/* ââ balance âââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 3236.rz-bal { 3237 display: grid; 3238 grid-template-columns: minmax(7em, 1.2fr) .8fr .8fr 2.2fr 1fr; 3239 align-items: center; 3240 column-gap: 1.1em; 3241 row-gap: .42em; 3242 margin-top: 1em; 3243 padding-top: .7em; 3244 border-top: 1px solid rgba(133, 108, 176, .14); 3245 font-size: .66em; 3246} 3247.rz-th { font-size: .88em; font-weight: 500; color: var(--rz-mute); padding-bottom: .15em; } 3248.rz-num { text-align: right; font-variant-numeric: tabular-nums; } 3249.rz-major { display: flex; align-items: center; gap: .5em; font-weight: 500; } 3250.rz-major span { font-weight: 400; color: var(--rz-mute); } 3251.rz-pct small { color: var(--rz-mute); font-size: .85em; margin-left: .25em; } 3252 3253.rz-diff { display: flex; align-items: center; gap: .7em; } 3254.rz-dval { min-width: 3.4em; text-align: right; font-variant-numeric: tabular-nums; font-weight: 500; } 3255.rz-dbar { position: relative; flex: 1; height: .5em; border-radius: 99px; background: rgba(133, 108, 176, .09); } 3256.rz-dbar::after { 3257 content: ""; position: absolute; left: 50%; top: -.25em; bottom: -.25em; 3258 width: 1px; background: rgba(42, 38, 50, .35); 3259} 3260.rz-dfill { 3261 position: absolute; top: 0; bottom: 0; left: 50%; width: 0; 3262 border-radius: 99px; background: var(--rz-accent); 3263 transition: left .6s cubic-bezier(.2, .75, .25, 1), width .6s cubic-bezier(.2, .75, .25, 1); 3264} 3265.rz-mean { color: var(--rz-deep); font-weight: 500; } 3266.rz-empty { color: rgba(133, 127, 147, .6); } 3267 3268@media (prefers-reduced-motion: reduce) { 3269 .rz-dot, .rz-dfill, .rz-lab, .rz-divide, .rz-btn { transition: none !important; } 3270} 3271</style> 3272 3273<div class="rz" data-state="pop"> 3274 <div class="rz-head"> 3275 <div class="rz-legend"></div> 3276 <div class="rz-ctrls"> 3277 <button class="rz-icon rz-reset" type="button" aria-label="Reset"> 3278 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg> 3279 </button> 3280 <button class="rz-btn rz-secondary rz-many" type="button"> 3281 <svg viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M4 5.5v13l8-6.5zM12 5.5v13l8-6.5z"></path></svg> 3282 Run 100 draws 3283 </button> 3284 <button class="rz-btn rz-primary rz-go" type="button"> 3285 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line><polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line><line x1="4" y1="4" x2="9" y2="9"></line></svg> 3286 Randomize 3287 </button> 3288 </div> 3289 </div> 3290 3291 <div class="rz-labs"> 3292 <div class="rz-lab rz-lab-all"><b>All students</b><span class="rz-n-all"></span></div> 3293 <div class="rz-lab rz-lab-t"><b>Treatment</b>small classes, <span class="rz-n-t"></span></div> 3294 <div class="rz-lab rz-lab-c"><b>Control</b>regular classes, <span class="rz-n-c"></span></div> 3295 </div> 3296 3297 <div class="rz-canvas"><div class="rz-divide"></div></div> 3298 3299 <div class="rz-bal"> 3300 <div class="rz-th">Major</div> 3301 <div class="rz-th rz-num">Treatment</div> 3302 <div class="rz-th rz-num">Control</div> 3303 <div class="rz-th">Difference, pp <span style="opacity:.7">(bar spans ±10)</span></div> 3304 <div class="rz-th rz-num rz-mean-h">Mean difference</div> 3305 </div> 3306</div> 3307
3308<script> 3309(function () { 3310 var GROUPS = [ 3311 { name: 'Major A', color: '#4b3a78', share: .50 }, 3312 { name: 'Major B', color: '#3d8fb8', share: .30 }, 3313 { name: 'Major C', color: '#5a9a6e', share: .15 }, 3314 { name: 'Major D', color: '#d9824b', share: .05 } 3315 ]; 3316 var N = 400, ROWS = 10, COLS = 20, GAP = 2, SPAN = COLS * 2 + GAP, BAR_MAX = 10; 3317 3318 function init(root) { 3319 if (root.dataset.rzBound) return; 3320 root.dataset.rzBound = '1'; 3321 3322 var q = function (s) { return root.querySelector(s); }; 3323 var canvas = q('.rz-canvas'), bal = q('.rz-bal'); 3324 var T = Math.floor(N / 2), C = N - T; 3325 q('.rz-n-all').textContent = N; 3326 q('.rz-n-t').textContent = 'n = ' + T; 3327 q('.rz-n-c').textContent = 'n = ' + C; 3328 3329 /* population, sorted by major; rounding drift goes to the largest group */ 3330 var counts = GROUPS.map(function (g) { return Math.round(g.share * N); }); 3331 counts[0] += N - counts.reduce(function (a, b) { return a + b; }, 0); 3332 var pop = []; 3333 counts.forEach(function (n, g) { for (var i = 0; i < n; i++) pop.push(g); }); 3334 3335 /* legend */ 3336 q('.rz-legend').innerHTML = GROUPS.map(function (g, i) { 3337 return '<span class="rz-leg"><span class="rz-sw" style="background:' + g.color + '"></span><b>' + 3338 g.name + '</b>' + Math.round(counts[i] / N * 100) + '%</span>'; 3339 }).join(''); 3340 3341 /* dots */ 3342 var dots = pop.map(function (g) { 3343 var d = document.createElement('div'); 3344 d.className = 'rz-dot'; 3345 d.style.background = GROUPS[g].color; 3346 canvas.appendChild(d); 3347 return { el: d, g: g, col: 0, row: 0 }; 3348 }); 3349 3350 /* balance rows */ 3351 var rows = GROUPS.map(function (g, i) { 3352 var cells = [ 3353 '<div class="rz-major"><span class="rz-sw" style="background:' + g.color + '"></span>' + g.name + '</div>', 3354 '<div class="rz-num rz-pct rz-t"></div>', 3355 '<div class="rz-num rz-pct rz-c"></div>', 3356 '<div class="rz-diff"><span class="rz-dval"></span><span class="rz-dbar"><span class="rz-dfill"></span></span></div>', 3357 '<div class="rz-num rz-mean"></div>' 3358 ]; 3359 var tmp = document.createElement('div'); 3360 tmp.innerHTML = cells.join(''); 3361 var els = Array.prototype.slice.call(tmp.children); 3362 els.forEach(function (e) { bal.appendChild(e); }); 3363 return { t: els[1], c: els[2], dval: els[3].querySelector('.rz-dval'), 3364 fill: els[3].querySelector('.rz-dfill'), mean: els[4] }; 3365 }); 3366 var meanHead = q('.rz-mean-h'); 3367 3368 /* ââ layout ââ */ 3369 var cell = 0; 3370 function place(d, delay) { 3371 var size = cell * .74, off = (cell - size) / 2; 3372 d.el.style.width = d.el.style.height = size + 'px'; 3373 d.el.style.transitionDelay = delay ? delay + 'ms' : '0ms'; 3374 d.el.style.transform = 'translate(' + (d.col * cell + off) + 'px,' + (d.row * cell + off) + 'px)'; 3375 } 3376 function relayout() { 3377 var w = canvas.offsetWidth; 3378 if (!w) return; 3379 cell = w / SPAN; 3380 root.classList.add('no-anim'); 3381 dots.forEach(function (d) { place(d, 0); }); 3382 void canvas.offsetWidth; 3383 root.classList.remove('no-anim'); 3384 } 3385 if ('ResizeObserver' in window) new ResizeObserver(relayout).observe(canvas); 3386 else window.addEventListener('resize', relayout); 3387 3388 function slotsPop() { 3389 dots.forEach(function (d, i) { 3390 d.col = Math.floor(i / ROWS) + GAP / 2; 3391 d.row = i % ROWS; 3392 }); 3393 } 3394 function slotsSplit(arm) { // arm[i] = 1 treated, 0 control 3395 var t = [], c = []; 3396 dots.forEach(function (d, i) { (arm[i] ? t : c).push(d); }); 3397 var byMajor = function (a, b) { return a.g - b.g || Math.random() - .5; }; 3398 t.sort(byMajor); c.sort(byMajor); 3399 t.forEach(function (d, j) { d.col = Math.floor(j / ROWS); d.row = j % ROWS; }); 3400 c.forEach(function (d, j) { d.col = COLS + GAP + Math.floor(j / ROWS); d.row = j % ROWS; }); 3401 } 3402 function animate() { 3403 dots.forEach(function (d) { place(d, Math.random() * 420); }); 3404 } 3405 3406 /* ââ randomization ââ */ 3407 function assign() { // complete randomization: exactly T treated 3408 var idx = dots.map(function (_, i) { return i; }); 3409 for (var i = idx.length - 1; i > 0; i--) { 3410 var j = Math.floor(Math.random() * (i + 1)), k = idx[i]; idx[i] = idx[j]; idx[j] = k; 3411 } 3412 var arm = new Array(N).fill(0); 3413 for (var m = 0; m < T; m++) arm[idx[m]] = 1; 3414 return arm; 3415 } 3416 function shares(arm) { 3417 var t = GROUPS.map(function () { return 0; }), c = t.slice();
3418 dots.forEach(function (d, i) { (arm[i] ? t : c)[d.g]++; }); 3419 return { t: t.map(function (x) { return x / T * 100; }), c: c.map(function (x) { return x / C * 100; }) }; 3420 } 3421 3422 var draws = 0, sums = GROUPS.map(function () { return 0; }); 3423 var sgn = function (x) { return (x > 0.049 ? '+' : x < -0.049 ? '\u2212' : '') + Math.abs(x).toFixed(1); }; 3424 3425 function record(s) { 3426 draws++; 3427 s.t.forEach(function (_, g) { sums[g] += s.t[g] - s.c[g]; }); 3428 } 3429 function paint(s) { 3430 rows.forEach(function (r, g) { 3431 if (!s) { 3432 r.t.innerHTML = r.c.innerHTML = r.dval.innerHTML = r.mean.innerHTML = '<span class="rz-empty">\u2014</span>'; 3433 r.fill.style.left = '50%'; r.fill.style.width = '0'; 3434 return; 3435 } 3436 var d = s.t[g] - s.c[g], w = Math.min(Math.abs(d), BAR_MAX) / BAR_MAX * 50; 3437 r.t.textContent = s.t[g].toFixed(1) + '%'; 3438 r.c.textContent = s.c[g].toFixed(1) + '%'; 3439 r.dval.textContent = sgn(d); 3440 r.fill.style.left = (d >= 0 ? 50 : 50 - w) + '%'; 3441 r.fill.style.width = w + '%'; 3442 r.mean.textContent = sgn(sums[g] / draws); 3443 }); 3444 meanHead.textContent = draws 3445 ? 'Mean difference, ' + draws + (draws === 1 ? ' draw' : ' draws') 3446 : 'Mean difference'; 3447 } 3448 3449 function drawOnce() { 3450 var arm = assign(), s = shares(arm); 3451 record(s); 3452 slotsSplit(arm); 3453 root.dataset.state = 'split'; 3454 animate(); 3455 paint(s); 3456 } 3457 3458 q('.rz-go').addEventListener('click', drawOnce); 3459 3460 q('.rz-many').addEventListener('click', function () { 3461 for (var i = 0; i < 99; i++) record(shares(assign())); // silent draws 3462 drawOnce(); // the 100th is shown 3463 }); 3464 3465 q('.rz-reset').addEventListener('click', function () { 3466 draws = 0; sums = sums.map(function () { return 0; }); 3467 slotsPop(); 3468 root.dataset.state = 'pop'; 3469 animate(); 3470 paint(null); 3471 }); 3472 3473 /* space/enter on a button shouldn't advance the slide */ 3474 root.addEventListener('keydown', function (e) { 3475 if (e.key === ' ' || e.key === 'Enter' || e.key === 'Spacebar') e.stopPropagation(); 3476 }); 3477 3478 slotsPop(); 3479 relayout(); 3480 paint(null); 3481 } 3482 3483 function scan() { document.querySelectorAll('.rz').forEach(init); } 3484 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', scan); 3485 else scan(); 3486 if (window.Reveal && Reveal.on) Reveal.on('ready', scan); 3487})(); 3488</script>
3488 3489 3490</section> 3491<section id="random-assignment-3" class="slide level2"> 3492<h2>Random Assignment</h2> 3493<div style="margin-top: 40px;"> 3494 3495</div> 3496<div class="elegant-quote"> 3497<div class="shrinker-super-plus"> 3498<p>When treatment assignment is randomized, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">the only thing that distinguishes the treatment group from the control group, besides the reception of the treatment, is chance</span>. This means that although the treatment and control groups consist of different individuals, the two groups are comparable to each other, <em>on average</em>, in all respects other than whether or not they received the treatment.</p> 3499<p>Random treatment assignment makes the treatment and control groups <span style="color: white; line-height: 1.4; background-color: #856cb0; padding: 1px 5px;"><em>on average</em> identical to each other in all observed and unobserved pre-treatment characteristics</span>. <span style="color: #856cb0;"><strong>Pre-treatment characteristics</strong></span> are the characteristics of the individuals in a study before the treatment is administered.</p> 3500<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:36</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 3501</div> 3502</div> 3503</section> 3504<section id="random-assignment-4" class="slide level2"> 3505<h2>Random Assignment</h2> 3506<div style="text-align: center; margin-top: 150px;"> 3507<div class="fpci-card"> 3508 <div class="fpci-title">Randomization of treatment assignment</div> 3509 3510 <div class="fpci-body"> 3511 <p>By randomly assigning treatment, we ensure that treatment and control groups are, on average,</p> 3512 <p class="fpci-key"><span>identical to each other in all observed and unobserved <span class="fpci-nb">pre-treatment</span> characteristics.</span></p> 3513 </div> 3514</div> 3515 3516<style> 3517.fpci-card { 3518 --fp-accent: #856cb0; 3519 --fp-deep: #5e4a86; 3520 --fp-ink: #2a2632; 3521 --fp-tint: #f5f1fb; 3522 3523 display: grid; 3524 grid-template-columns: 30% 1fr; 3525 max-width: 960px; 3526 margin: 1.3em auto 0; 3527 border-radius: 14px; 3528 overflow: hidden; 3529 background: #fff; 3530 border: 1px solid rgba(133, 108, 176, .16); 3531 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 3532 color: var(--fp-ink); 3533 text-align: left; 3534} 3535 3536/* title panel */ 3537.fpci-title { 3538 display: flex; 3539 align-items: center; 3540 padding: 1em 1.1em 1em 1.2em; 3541 background: var(--fp-tint); 3542 border-right: 1px solid rgba(133, 108, 176, .14); 3543 font-size: .86em; 3544 font-weight: 600; 3545 line-height: 1.15; 3546 letter-spacing: -.025em; 3547 color: var(--fp-deep); 3548 text-wrap: balance; 3549} 3550 3551/* statement */ 3552.fpci-body { 3553 align-self: center; 3554 padding: 1em 1.3em 1em 1.35em; 3555 font-size: .7em; 3556 line-height: 1.6; 3557} 3558.fpci-body p { margin: 0 !important; } 3559.fpci-body p:first-child { 3560 font-weight: 400; 3561 color: rgba(42, 38, 50, .78); 3562 text-wrap: pretty; 3563} 3564 3565/* the punchline: its own line, one emphasis. 3566 inline + clone, so if it ever wraps each line gets its own pill */ 3567.fpci-key { margin-top: .55em !important; line-height: 1.95; text-wrap: balance; } 3568.fpci-key .fpci-nb { all: unset; white-space: nowrap; } 3569.fpci-key span { 3570 padding: .14em .55em .18em; 3571 -webkit-box-decoration-break: clone; 3572 box-decoration-break: clone; 3573 border-radius: 7px; 3574 background: var(--fp-accent); 3575 color: #fff; 3576 font-weight: 500; 3577 box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7); 3578} 3579</style> 3580<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:36</a>)</span>.</span></p> 3581<style> 3582.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; } 3583</style> 3584</div> 3585</section> 3586<section id="random-assignment-5" class="slide level2"> 3587<h2>Random Assignment</h2> 3588<div style="text-align: center; margin-top: 30px; font-size: 60%; color: grey; margin-bottom: -10px;"> 3589<p><em>With randomization, this holds for two <strong>populations</strong></em>, <span class="math inline">C</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=0</span>)</span> <em>and</em> <span class="math inline">T</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=1</span>)</span></p> 3590</div> 3591<div class="quarto-figure quarto-figure-center"> 3592<figure> 3593<p><a href="./media/spiderman.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img data-src="./media/spiderman.jpg" class="quarto-figure quarto-figure-center" style="width:63.0%"></a></p> 3594</figure> 3595</div> 3596<div style="font-size: 50%; text-align: center; margin-top: -30px;"> 3597<p>Image can be retrieved <a href="https://i.redd.it/bekphnqftcb41.jpg" class="external" target="_blank">here</a>.</p> 3598</div> 3599</section> 3600<section id="random-assignment-and-ates" class="slide level2"> 3601<h2>Random Assignment and ATEs</h2> 3602<div style="margin-top: 40px;"> 3603 3604</div> 3605<div class="elegant-quote"> 3606<div class="shrinker-super-plus"> 3607<p>If the treatment and control groups were comparable before the treatment was administered ⦠<span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we can use the factual outcome of one group as an approximation for the counterfactual outcome of the other</span>. In other words, we can assume that the average outcome of the treatment group is a good estimate of the average outcome of the control group, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">had the control group received the treatment</span>. Similarly, we can assume that the average outcome of the control group is a good estimate of the average outcome of the treatment group, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">had the treatment group not received the treatment</span>. As a result, we can <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">approximate the average treatment effect by computing the difference in the average outcomes between the treatment and control groups</span>. Since both of these average outcomes <em>are</em> observed, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">this is an analysis we <em>are</em> able to perform</span>.</p> 3608<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:37</a>
3608, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 3609</div> 3610</div> 3611</section> 3612<section id="random-assignment-and-ates-1" class="slide level2"> 3613<h2>Random Assignment and ATEs</h2> 3614<div class="po-card"> 3615<p><span class="po-title">If groups were comparable before the treatment was administered</span></p> 3616<div class="po-eq"> 3617<p><span class="math display"> 3618\widehat{\textit{ATE}} \;=\; {\color{#856cb0}{\overline{Y}_{\text{Treatment group},\,T}}} \;-\; {\color{#8a8494}{\overline{Y}_{\text{Control group},\,C}}} 3619</span></p> 3620</div> 3621<div class="po-defs"> 3622<p><span class="po-where">where</span></p> 3623<p><span class="po-term"><span class="math inline">\widehat{\textit{ATE}}</span></span></p> 3624<p><span class="po-desc">The estimated average treatment effect. <span class="po-aside">The hat marks it as an estimate.</span></span></p> 3625<p><span class="po-term"><span class="math inline">{\color{#856cb0}{\overline{Y}_{\text{Treatment group},\,T}}}</span></span></p> 3626<p><span class="po-desc">The observed average outcome for the <span class="po-t">treatment group</span>.</span></p> 3627<p><span class="po-term"><span class="math inline">{\color{#8a8494}{\overline{Y}_{\text{Control group},\,C}}}</span></span></p> 3628<p><span class="po-desc">The observed average outcome for the <span class="po-c">control group</span>.</span></p> 3629</div> 3630</div> 3631<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:37</a>)</span>.</span></p> 3632<style> 3633.po-card { 3634 --po-accent: #856cb0; 3635 --po-ink: #2a2632; 3636 --po-mute: #8a8494; 3637 --po-tint: #f7f4fb; 3638 3639 max-width: 900px; 3640 margin: 1.3em auto 0; 3641 padding: 1em 1.5em 1.15em; 3642 border-radius: 14px; 3643 background: #fff; 3644 border: 1px solid rgba(133, 108, 176, .16); 3645 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 3646 color: var(--po-ink); 3647 text-align: left; 3648} 3649.po-card p { margin: 0; } 3650 3651.po-title { 3652 display: block; 3653 text-align: center; 3654 font-size: .78em; 3655 font-weight: 600; 3656 letter-spacing: -.01em; 3657 color: var(--po-accent); 3658} 3659 3660.po-eq { 3661 margin: .5em 0 .8em; 3662 padding: .1em 1em; 3663 border-radius: 10px; 3664 background: var(--po-tint); 3665 font-size: .7em; 3666 text-align: center; 3667 overflow: hidden; 3668} 3669.po-eq p { margin: 0; } 3670.po-eq mjx-container[display="true"] { margin: .45em 0 !important; } 3671 3672.po-defs { 3673 display: grid; 3674 grid-template-columns: max-content 1fr; 3675 align-items: baseline; 3676 column-gap: 1.2em; 3677 row-gap: .7em; 3678 font-size: .6em; 3679 line-height: 1.5; 3680} 3681.po-defs > p:first-child { grid-column: 1 / -1; } 3682.po-where { font-style: italic; color: var(--po-mute); } 3683.po-term { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; } 3684.po-desc { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; } 3685.po-aside { color: var(--po-mute); font-size: .88em; margin-left: .3em; } 3686.po-t { color: var(--po-accent); font-weight: 500; } 3687.po-c { color: var(--po-mute); font-weight: 500; } 3688 3689.po-source { 3690 display: block; 3691 margin-top: 1em; 3692 text-align: center; 3693 font-size: .5em; 3694 color: #a9a9a9; 3695} 3696</style> 3697</section> 3698<section id="random-assignment-and-ates-2" class="slide level2"> 3699<h2>Random Assignment and ATEs</h2> 3700<div style="margin-top: 80px;"> 3701 3702</div> 3703<div class="fpci-card fpci-long"> 3704 <div class="fpci-title"><span>Estimating average causal effects using randomized experiments and the <span class="fpci-nb">difference-in-means</span> estimator</span></div> 3705 3706 <div class="fpci-body"> 3707 <p>By using random treatment assignment, we can assume that the treatment and control groups were comparable before the administration of the treatment. As a result,</p> 3708 <p class="fpci-key"><span>we can rely on the <span class="fpci-nb">difference-in-means</span> estimator to provide a valid estimate of the average treatment effect.</span></p> 3709 </div> 3710</div> 3711 3712<style> 3713.fpci-card { 3714 --fp-accent: #856cb0; 3715 --fp-deep: #5e4a86; 3716 --fp-ink: #2a2632; 3717 --fp-tint: #f5f1fb; 3718 3719 display: grid; 3720 grid-template-columns: 30% 1fr; 3721 max-width: 960px; 3722 margin: 1.3em auto 0; 3723 border-radius: 14px; 3724 overflow: hidden; 3725 background: #fff; 3726 border: 1px solid rgba(133, 108, 176, .16); 3727 box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 3728 color: var(--fp-ink); 3729 text-align: left; 3730} 3731 3732.fpci-title { 3733 display: flex; 3734 align-items: center; 3735 padding: 1em 1.1em 1em 1.2em; 3736 background: var(--fp-tint); 3737 border-right: 1px solid rgba(133, 108, 176, .14); 3738 font-size: .86em; 3739 font-weight: 600; 3740 line-height: 1.15; 3741 letter-spacing: -.025em; 3742 color: var(--fp-deep); 3743 text-wrap: balance; 3744} 3745 3746.fpci-body { 3747 align-self: center; 3748 padding: 1em 1.3em 1em 1.35em; 3749 font-size: .7em; 3750 line-height: 1.6; 3751} 3752.fpci-body p { margin: 0 !important; } 3753.fpci-body p:first-child { 3754 font-weight: 400; 3755 color: rgba(42, 38, 50, .78); 3756 text-wrap: pretty; 3757} 3758 3759.fpci-key { margin-top: .55em !important; line-height: 1.95; text-wrap: balance; } 3760.fpci-key > span { 3761 padding: .14em .55em .18em; 3762 -webkit-box-decoration-break: clone; 3763 box-decoration-break: clone; 3764 border-radius: 7px; 3765 background: var(--fp-accent); 3766 color: #fff; 3767 font-weight: 500; 3768 box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7); 3769} 3770.fpci-nb { white-space: nowrap; } 3771 3772/* long titles: a wider title panel, set a touch smaller */ 3773.fpci-long { grid-template-columns: 36% 1fr; } 3774.fpci-long .fpci-title { font-size: .76em; line-height: 1.18; } 3775</style> 3776<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:38</a>)</span>.</span></p> 3777<style> 3778.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; } 3779</style> 3780</section></section> 3781<section> 3782<section id="a-class-conversation" class="title-slide slide level1 center" data-background-color="#43464B"> 3783<h1>A Class Conversation</h1> 3784 3785</section> 3786<section id="the-challenges-of-causal-inference" class="slide level2"> 3787<h2>The Challenges of Causal Inference</h2> 3788<div style="margin-top: 40px; text-align: center; font-size: 90%;"> 3789<p>As Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:38</a>)</span> note, social scientists often run into <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">ethical</span>, <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">logistical</span>, and <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">financial</span> obstacles that ensure that fielding a randomized experiment is challengingâif not <em>impossible</em>. With this in mind, think aboutâand refineâyour own <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> question.</p> 3790<div class="fragment"> 3791<details class="reveal-detail-above"> 3792<summary> 3793Orienting Questions 3794</summary> 3795<div class="detail-content" style="font-size: 75%; text-align: center;"> 3796<div class="rq-block"> 3797<p>
3797Can you randomly sort respondents into <span class="math inline">C</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=0</span>)</span> <em>and</em> <span class="math inline">T</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=1</span>)</span>? How might you go about doing so? What are some challenges you may run into?</p> 3798</div> 3799</div> 3800</details> 3801<div class="oe-pad" data-minutes="10" data-file="research_question_updated.txt"> 3802 3803 <svg class="oe-ring" aria-hidden="true"> 3804 <path class="oe-track"></path> 3805 <path class="oe-prog" pathlength="1"></path> 3806 </svg> 3807 3808 <div class="oe-head"> 3809 <span class="oe-title">Your Updated Research Question</span> 3810 <div class="oe-clock"> 3811 <span class="oe-time" role="timer">10:00</span> 3812 <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer"> 3813 <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg> 3814 <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg> 3815 </button> 3816 <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer"> 3817 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg> 3818 </button> 3819 </div> 3820 </div> 3821 3822 <textarea class="oe-editor" rows="4" placeholder="How does X shape variation in Y?"></textarea> 3823 3824 <div class="oe-actions"> 3825 <button class="oe-btn oe-primary oe-export" type="button"> 3826 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> 3827 Export .txt 3828 </button> 3829 <button class="oe-btn oe-secondary oe-copy" type="button"> 3830 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> 3831 <span class="oe-copy-label">Copy</span> 3832 </button> 3833 <span class="oe-count">0 words</span> 3834 <button class="oe-btn oe-clear" type="button"> 3835 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg> 3836 <span class="oe-clear-label">Clear</span> 3837 </button> 3838 </div> 3839</div> 3840 3841 3842</div> 3843</div> 3844</section></section> 3845<section> 3846<section id="experiments-dags-september-24th" class="title-slide slide level1 center" data-background-color="#43464B"> 3847<h1>Experiments, DAGsâ<br>September 24<span style="font-size: 70%;"><sup>th</sup></span></h1> 3848 3849</section> 3850<section id="your-next-assignment" class="slide level2"> 3851<h2>Your Next Assignment</h2> 3852<div style="text-align: center; margin-top: 140px;"> 3853<p><span class="frame-expand fx-pill" data-src="https://soci-316.netlify.app/#qualtrics">Qualtrics Survey Assessment</span></p> 3854</div> 3855</section> 3856<section id="an-invitation-from-ljst" class="slide level2"> 3857<h2>An Invitation from LJST</h2> 3858<div style="text-align: center; font-size: 50%; color: grey; margin-bottom: -10px; margin-top: 25px;"> 3859<p><em>Click to Expand Invitation</em></p> 3860</div> 3861<div class="quarto-figure quarto-figure-center"> 3862<figure> 3863<p><a href="./media/Maggie%20Blackhawk%20Poster%20UPDATED.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img data-src="./media/Maggie%20Blackhawk%20Poster%20UPDATED.png" class="quarto-figure quarto-figure-center" style="width:33.0%"></a></p> 3864</figure> 3865</div> 3866</section></section> 3867<section> 3868<section id="high-level-overview-experiments" class="title-slide slide level1 center" data-background-color="#43464B"> 3869<h1><span class="title-ill">High-Level Overview</span> Experiments</h1> 3870 3871</section> 3872<section id="experiments" class="slide level2"> 3873<h2>Experiments</h2> 3874<div class="elegant-quote" style="margin-top: 40px;"> 3875<p>An <span style="color: #856cb0;"><strong>experiment</strong></span> is a research method where the researcher manipulates one or more <span style="color: #856cb0;"><strong>independent variables</strong></span> to determine the effect(s) on a <span style="color: #856cb0;"><strong>dependent variable</strong></span>. Experiments share three key features: <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">(1) manipulation of the independent variable, (2) random assignment of participants to experimental and control conditions, and (3) experimental control of other factors that could influence the outcome of the experiment</span>.</p> 3876<p><span class="citation" data-cites="carr_art_2020">(<a href="#/references" role="doc-biblioref" onclick="">Carr et al. 2020:230</a>
3876, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 3877</div> 3878</section> 3879<section id="experiments-1" class="slide level2 scrollable"> 3880<h2>Experiments</h2> 3881<div style="margin-top: 100px; text-align: center;"> 3882<p>Experiments provide leverage in three important ways.</p> 3883<div class="fragment"> 3884<details class="reveal-detail" name="experiments"> 3885<summary> 3886Three Key Affordances 3887</summary> 3888<div class="detail-content" style="font-size: 75%; text-align: center;"> 3889<ol type="1"> 3890<li class="fragment"><p>Experiments are ideal for adjudicating causal claims.</p></li> 3891<li class="fragment"><p>Experiments compel researchers to test specific posited mechanisms, i.e. beyond reporting descriptive associations.</p></li> 3892<li class="fragment"><p>Experiments can be used to test highly abstract theoriesâgenerally through fine control of the experimental setting.</p></li> 3893</ol> 3894</div> 3895</details> 3896</div> 3897<div class="fragment"> 3898<details class="reveal-detail" name="experiments"> 3899<summary> 3900A Question 3901</summary> 3902<div class="detail-content" style="font-size: 75%; text-align: center;"> 3903<p>What are some of the <em>weaknesses</em> of experiments?</p> 3904</div> 3905</details> 3906</div> 3907</div> 3908</section> 3909<section id="experiments-2" class="slide level2"> 3910<h2>Experiments</h2> 3911<div style="margin-top: 60px;"> 3912 3913</div> 3914<!-- 3915 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3916 EXPERIMENTAL METHODS · Carr et al. (2020), Table 8.1 3917 Quarto RevealJS drop-in (exm- namespace) 3918 Same visual system as the sv- / md- tables · Space Grotesk · purple 3919 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3920 Include with {{< include _carr-table-8-1.qmd >}} or paste as is. 3921 The source line sits OUTSIDE the fence as a ::: {.exm-source} div, 3922 because raw HTML is not passed through citeproc. 3923 3924 Cell text is verbatim; each point in the original is its own item, 3925 marked + (strength) or â (weakness). 3926 3927 Pop-up cells 3928 · Click any strengths/weaknesses cell (or Tab + Enter/Space) to 3929 open it full-screen. â / â step through all eight cells in 3930 reading order (lab strengths, lab weaknesses, field strengthsâ¦). 3931 · Ã, Esc, or a click on the backdrop closes. The cell you last 3932 viewed stays marked (shaded, purple rule); Escape again or 3933 leaving the slide clears it. The lightbox's source line is 3934 copied from the .exm-source div. 3935 · Reveal-safe: the lightbox lives on <body>; Esc and arrows are 3936 caught before Reveal while it is open; Reveal's keyboard is 3937 suspended and restored; a mouse-opened lightbox hands the keys 3938 back to the deck on close. 3939 3940 Size knob: --exm-size on .exm-table (default .35em). 3941 Row icons: delete the <svg class="exm-ico"> elements to drop them. 3942 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3943--> 3944 3945<style> 3946.reveal .exm-table { 3947 --exm-accent: #856cb0; 3948 --exm-deep: #54407a; 3949 --exm-soft: #b7a5d3; 3950 --exm-ink: #241c31; 3951 --exm-mute: #6d6480; 3952 --exm-line: #e3dbee; 3953 --exm-wash: #f8f5fc; 3954 --exm-edge: #efe8f8; 3955 3956 /* â¸â¸ SIZE ââ */ 3957 --exm-size: .35em; 3958 3959 width: 100%; 3960 margin: .3em auto 0; 3961 font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif; 3962 font-size: var(--exm-size); 3963 line-height: 1.3; 3964 color: var(--exm-ink); 3965 text-align: left; 3966} 3967 3968.reveal .exm-table .exm-head, 3969.reveal .exm-table .exm-row { 3970 display: grid; 3971 grid-template-columns: 17fr 41.5fr 41.5fr; 3972} 3973 3974/* ââ header âââââââââââââââââââââââââââââââââââââââââââ */ 3975.reveal .exm-table .exm-head { border-bottom: 2px solid var(--exm-
3975accent); } 3976.reveal .exm-table .exm-corner { 3977 align-self: end; 3978 padding: 0 .8em .55em 0; 3979 font-size: .95em; 3980 font-weight: 500; 3981 color: var(--exm-mute); 3982} 3983.reveal .exm-table .exm-col { 3984 display: flex; 3985 align-items: center; 3986 gap: .5em; 3987 padding: 0 1em .55em; 3988} 3989.reveal .exm-table .exm-col-title { 3990 font-size: 1.4em; 3991 font-weight: 600; 3992 letter-spacing: -.01em; 3993 color: var(--exm-deep); 3994} 3995.exm-sign { 3996 flex: none; 3997 width: 1.15em; 3998 height: 1.15em; 3999 fill: none; 4000 stroke: var(--exm-accent); 4001 stroke-width: 1.8; 4002 stroke-linecap: round; 4003} 4004 4005/* ââ rows âââââââââââââââââââââââââââââââââââââââââââââ */ 4006.reveal .exm-table .exm-row { border-bottom: 1px solid var(--exm-line); } 4007 4008.reveal .exm-table .exm-label { 4009 display: flex; 4010 align-items: center; 4011 gap: .55em; 4012 padding: .6em .8em .6em 0; 4013 font-size: 1.08em; 4014 font-weight: 600; 4015 line-height: 1.2; 4016 color: var(--exm-ink); 4017} 4018.exm-ico { 4019 flex: none; 4020 width: 1.3em; 4021 height: 1.3em; 4022 fill: none; 4023 stroke: var(--exm-accent); 4024 stroke-width: 1.6; 4025 stroke-linecap: round; 4026 stroke-linejoin: round; 4027} 4028 4029.reveal .exm-table .exm-name { max-width: 6.6em; } /* "X / experiments" on two lines throughout */ 4030 4031/* clickable cells: vertically centred, full-height hover */ 4032.reveal .exm-table .exm-cell { 4033 position: relative; 4034 display: flex; 4035 flex-direction: column; 4036 justify-content: center; 4037 gap: .3em; 4038 padding: .55em 1.6em .55em 1em; 4039 cursor: zoom-in; 4040 outline: none; 4041 transition: background-color .15s ease; 4042} 4043.reveal .exm-table .exm-cell:hover { background: var(--exm-wash); } 4044.reveal .exm-table .exm-cell:focus-visible { box-shadow: inset 0 0 0 1.5px var(--exm-accent); } 4045.reveal .exm-table .exm-cell.is-last { 4046 background: var(--exm-edge); 4047 box-shadow: inset 3px 0 0 var(--exm-accent); 4048} 4049 4050/* left rule grows on hover, as in the sv- tables' edge marker */ 4051.reveal .exm-table .exm-cell::before { 4052 content: ""; 4053 position: absolute; 4054 left: 0; top: 0; bottom: 0; 4055 width: 3px; 4056 background: var(--exm-accent); 4057 transform: scaleY(0); 4058 transition: transform .22s cubic-bezier(.2, .7, .3, 1); 4059} 4060.reveal .exm-table .exm-cell:hover::before { transform: scaleY(1); } 4061 4062/* expand glyph, top-right on hover/focus */ 4063.reveal .exm-table .exm-zoom { 4064 position: absolute; 4065 top: .7em; right: .7em; 4066 width: .7em; height: .7em; 4067 fill: none; 4068 stroke: var(--exm-accent); 4069 stroke-width: 2.4; 4070 stroke-linecap: round; 4071 stroke-linejoin: round; 4072 opacity: 0; 4073 transition: opacity .2s ease, transform .2s ease; 4074 pointer-events: none; 4075} 4076.reveal .exm-table .exm-cell:hover .exm-zoom, 4077.reveal .exm-table .exm-cell:focus-visible .exm-zoom { opacity: .9; transform: translate(1px, -1px); } 4078 4079/* items: hanging + / â marks (shared by table and lightbox, hence unscoped) */ 4080.exm-pt { 4081 display: grid; 4082 grid-template-columns: 1.05em 1fr; 4083 align-items: baseline; 4084} 4085.exm-pt::before { 4086 font-weight: 600; 4087 line-height: 1; 4088 color: var(--exm-accent); 4089} 4090.is-pro > .exm-pt::before { content: "+"; } 4091.is-con > .exm-pt::before { content: "\2212"; color: var(--exm-mute); } 4092 4093/* ââ source line (Markdown div outside the fence) âââââ */ 4094.reveal .exm-source { 4095 margin-top: .6em; 4096 font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif; 4097 font-size: .5em; 4098 text-align: center; 4099 color: #6d6480; 4100} 4101.reveal .exm-source p { margin: 0; } 4102 4103/* ââ one entrance per visit (backwards: releases opacity on end) ââ */ 4104@keyframes exm-in { 4105 from { opacity: 0; transform: translateY(5px); } 4106 to { opacity: 1; transform: none; } 4107} 4108.reveal section.present .exm-table .exm-row { 4109 animation: exm-in .4s ease-out backwards; 4110 animation-delay: calc(var(--i, 0) * 50ms + 40ms); 4111} 4112 4113/* ââ lightbox (on <body>, outside Reveal's scaled slides: px units) ââ */ 4114.exm-box { 4115 --exm-accent: #856cb0; 4116 --exm-deep: #54407a; 4117 --exm-soft: #b7a5d3; 4118 --exm-ink: #241c31; 4119 --exm-mute: #6d6480; 4120 --exm-line: #e3dbee; 4121 --exm-tint: #f7f4fc; 4122 --exm-mono: "JetBrains Mono", ui-monospace, monospace; 4123 4124 position: fixed; 4125 inset: 0; 4126 z-index: 10000; 4127 display: flex; 4128 align-items: center; 4129 justify-content: center; 4130 padding: 64px 104px 52px; 4131 background: rgba(28, 22, 38, .72); 4132 -webkit-backdrop-filter: blur(8px); 4133 backdrop-filter: blur(8px); 4134 font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif; 4135 text-align: left; 4136 cursor: zoom-out; 4137 opacity: 0; 4138 visibility: hidden; 4139 transition: opacity .22s ease, visibility .22s; 4140} 4141.exm-box.is-open { opacity: 1; visibility: visible; } 4142 4143.exm-card { 4144 position: relative; 4145 width: min(820px, 100%); 4146 max-height: 100%; 4147 overflow: auto; 4148 background: #fff; 4149 border-radius: 12px; 4150 box-shadow: 0 24px 70px rgba(28, 22, 38, .4); 4151 cursor: default; 4152 transform: translateY(10px) scale(.985); 4153 transition: transform .26s cubic-bezier(.22, .61, .36, 1); 4154} 4155.exm-box.is-open .exm-card { transform: none; } 4156 4157.exm-content { transition: opacity .14s ease; } 4158.exm-content.is-swap { opacity: 0; } 4159 4160.exm-bar { 4161 display: flex; 4162 align-items: center; 4163 gap: 14px; 4164 padding: 18px 26px 16px; 4165 background: var(--exm-tint);
4166 border-bottom: 2px solid var(--exm-accent); 4167} 4168.exm-bar .exm-ico { width: 28px; height: 28px; } 4169.exm-bar-name { 4170 font-size: 26px; 4171 font-weight: 600; 4172 letter-spacing: -.01em; 4173 line-height: 1.15; 4174 color: var(--exm-deep); 4175} 4176.exm-tag { 4177 display: inline-flex; 4178 align-items: center; 4179 gap: 6px; 4180 padding: 4px 11px 4px 8px; 4181 border-radius: 999px; 4182 font-size: 14px; 4183 font-weight: 500; 4184 color: var(--exm-deep); 4185 background: #fff; 4186 box-shadow: inset 0 0 0 1px var(--exm-line); 4187} 4188.exm-tag .exm-sign { width: 15px; height: 15px; } 4189.exm-count { 4190 margin-left: auto; 4191 font-family: var(--exm-mono); 4192 font-size: 12px; 4193 letter-spacing: .08em; 4194 color: var(--exm-accent); 4195 font-variant-numeric: tabular-nums; 4196} 4197 4198.exm-body { 4199 display: flex; 4200 flex-direction: column; 4201 gap: 16px; 4202 padding: 26px 30px 8px 26px; 4203 font-size: clamp(19px, 2vw, 24px); 4204 line-height: 1.5; 4205 color: var(--exm-ink); 4206} 4207.exm-body .exm-pt { grid-template-columns: 1.3em 1fr; } 4208 4209.exm-foot { 4210 padding: 12px 30px 20px; 4211 font-size: 13px; 4212 text-align: right; 4213 color: var(--exm-mute); 4214} 4215.exm-foot:empty { display: none; } 4216.exm-foot code { font-family: var(--exm-mono); font-size: .92em; color: var(--exm-deep); } 4217 4218/* controls */ 4219.exm-btn { 4220 position: absolute; 4221 display: grid; 4222 place-items: center; 4223 margin: 0; 4224 padding: 0; 4225 border: 1px solid rgba(255, 255, 255, .22); 4226 border-radius: 50%; 4227 background: rgba(255, 255, 255, .1); 4228 color: #fff; 4229 cursor: pointer; 4230 transition: background .2s ease, border-color .2s ease, transform .2s ease, opacity .2s ease; 4231} 4232.exm-btn svg { 4233 width: 16px; height: 16px; 4234 fill: none; stroke: currentColor; stroke-width: 2.2; 4235 stroke-linecap: round; stroke-linejoin: round; 4236} 4237.exm-btn:hover:not(:disabled) { background: var(--exm-accent); border-color: var(--exm-accent); } 4238.exm-btn:focus-visible { outline: 2px solid var(--exm-soft); outline-offset: 2px; } 4239.exm-btn:disabled { opacity: .25; cursor: default; } 4240 4241.exm-close { top: 16px; right: 20px; width: 38px; height: 38px; } 4242.exm-close:hover { transform: rotate(90deg); } 4243.exm-prev, .exm-next { top: 50%; width: 46px; height: 46px; margin-top: -23px; } 4244.exm-prev { left: 28px; } 4245.exm-next { right: 28px; } 4246 4247.exm-keys { 4248 position: absolute; 4249 bottom: 16px; left: 22px; 4250 font-family: var(--exm-mono); 4251 font-size: 10px; 4252 letter-spacing: .04em; 4253 color: rgba(255, 255, 255, .45); 4254} 4255.exm-keys b { 4256 font-weight: 500; 4257 color: rgba(255, 255, 255, .75); 4258 border: 1px solid rgba(255, 255, 255, .25); 4259 border-radius: 3px; 4260 padding: 1px 5px; 4261 margin-right: 5px; 4262} 4263.exm-keys b + b { margin-left: -2px; } 4264.exm-keys span { margin-right: 14px; } 4265 4266@media (max-width: 760px) { 4267 .exm-box { padding: 60px 12px 76px; } 4268 .exm-bar { flex-wrap: wrap; padding: 16px 18px 14px; } 4269 .exm-bar-name { font-size: 21px; } 4270 .exm-body { padding: 20px 20px 6px 18px; } 4271 .exm-prev, .exm-next { top: auto; bottom: 16px; margin-top: 0; } 4272 .exm-prev { left: calc(50% - 56px); } 4273 .exm-next { right: calc(50% - 56px); } 4274 .exm-keys { display: none; } 4275} 4276 4277@media (prefers-reduced-motion: reduce) { 4278 .reveal section.present .exm-table .exm-row { animation: none; } 4279 .reveal .exm-table .exm-cell, .reveal .exm-table .exm-cell::before, .reveal .exm-table .exm-zoom, 4280 .exm-box, .exm-card, .exm-content, .exm-btn { transition: none !important; } 4281 .exm-card { transform: none; } 4282} 4283@media print { .exm-box, .reveal .exm-table .exm-zoom { display: none !important; } } 4284</style> 4285 4286<div class="exm-table" role="table" aria-label="Strengths and weaknesses of four experimental methods, adapted from Carr et al. (2020), Table 8.1"> 4287 4288 <div class="exm-head" role="row"> 4289 <div class="exm-corner" role="columnheader">Experimental Method</div> 4290 <div class="exm-col" role="columnheader"> 4291 <svg class="exm-sign" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"></circle><path d="M12 7.5v9M7.5 12h9"></path></svg> 4292 <span class="exm-col-title">Strengths</span> 4293 </div> 4294 <div class="exm-col" role="columnheader"> 4295 <svg class="exm-sign" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"></circle><path d="M7.5 12h9"></path></svg>
4296 <span class="exm-col-title">Weaknesses</span> 4297 </div> 4298 </div> 4299 4300 <div class="exm-body-rows" role="rowgroup"> 4301 4302 <div class="exm-row" role="row" style="--i:0"> 4303 <div class="exm-label" role="rowheader"> 4304 <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><path d="M9 3h6M10 3v6.2L4.6 18.4A1.7 1.7 0 0 0 6.1 21h11.8a1.7 1.7 0 0 0 1.5-2.6L14 9.2V3"></path><path d="M7.2 15h9.6"></path></svg> 4305 <span class="exm-name">Laboratory experiments</span> 4306 </div> 4307 <div class="exm-cell is-pro" role="cell"> 4308 <div class="exm-pt">Researcher has maximum control over the experimental setting and can ensure that the manipulation of the independent variable was salient to all participants.</div> 4309 <div class="exm-pt">The artificial setting allows researchers to test more abstract theories and to disentangle complex social phenomena.</div> 4310 </div> 4311 <div class="exm-cell is-con" role="cell"> 4312 <div class="exm-pt">Most laboratory experiments rely on nonrandom samples, so results are less generalizable.</div> 4313 <div class="exm-pt">Because the laboratory environment is highly artificial, lab experiments are less effective for exploring more applied, âreal worldâ questions.</div> 4314 </div> 4315 </div> 4316 4317 <div class="exm-row" role="row" style="--i:1"> 4318 <div class="exm-label" role="rowheader"> 4319 <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"></path><circle cx="12" cy="10" r="2.4"></circle></svg> 4320 <span class="exm-name">Field experiments</span> 4321 </div> 4322 <div class="exm-cell is-pro" role="cell"> 4323 <div class="exm-pt">Because field experiments take place in a real-world setting, they are good at evaluating more applied questions and for assessing whether an outcome, such as discrimination, occurs.</div> 4324 </div> 4325 <div class="exm-cell is-con" role="cell"> 4326 <div class="exm-pt">Researchers have less control over the experimental setting and frequently rely on nonrandom or regional samples.</div> 4327 <div class="exm-pt">Less suited for evaluating the processes that produce an outcome, and less useful for testing abstract theories.</div> 4328 </div> 4329 </div> 4330 4331 <div class="exm-row" role="row" style="--i:2"> 4332 <div class="exm-label" role="rowheader"> 4333 <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"></rect><path d="M9 4V3h6v1"></path><path d="M8.5 10l1.3 1.3L12 9M8.5 15.5l1.3 1.3 2.2-2.3M14 10.3h2M14 15.8h2"></path></svg> 4334 <span class="exm-name">Survey experiments</span> 4335 </div> 4336 <div class="exm-cell is-pro" role="cell"> 4337 <div class="exm-pt">Survey experiments often use representative samples, so results are generalizable.</div> 4338 </div> 4339 <div class="exm-cell is-con" role="cell"> 4340 <div class="exm-pt">Researchers have less control over the experimental setting.</div> 4341 <div class="exm-pt">Often rely on self-reports, which do not always correspond to actual behavior.</div> 4342 </div> 4343 </div> 4344 4345 <div class="exm-row" role="row" style="--i:3"> 4346 <div class="exm-label" role="rowheader"> 4347 <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M3 12h18M12 3c2.6 2.6 3.8 5.6 3.8 9s-1.2 6.4-3.8 9c-2.6-2.6-3.8-5.6-3.8-9S9.4 5.6 12 3z"></path></svg> 4348 <span class="exm-name">Natural experiments</span> 4349 </div> 4350 <div class="exm-cell is-pro" role="cell"> 4351 <div class="exm-pt">Realistic setting allows researchers to draw strong conclusions about specific settings.</div> 4352 <div class="exm-pt">Good for assessing whether an outcome occurs.</div> 4353 </div> 4354 <div class="exm-cell is-con" role="cell"> 4355 <div class="exm-pt">Researchers cannot randomly assign participants to conditions.</div> 4356 <div class="exm-pt">Settings are often so realistic that the results are tied to the setting in which the study was conducted and are not generalizable.</div> 4357 <div class="exm-pt">Less suited for evaluating the processes that produce an outcome.</div> 4358 </div> 4359 </div> 4360 4361 </div> 4362</div> 4363
4364<script> 4365(function () { 4366 if (window.__exmReady) { window.__exmScan(); return; } 4367 window.__exmReady = true; 4368 4369 var ZOOM = '<svg class="exm-zoom" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>'; 4370 var CLOSE = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>'; 4371 var PREV = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg>'; 4372 var NEXT = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>'; 4373 var PLUS = '<svg class="exm-sign" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"/><path d="M12 7.5v9M7.5 12h9"/></svg>'; 4374 var MINUS = '<svg class="exm-sign" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"/><path d="M7.5 12h9"/></svg>'; 4375 4376 function cells(tbl) { return tbl.querySelectorAll('.exm-cell'); } 4377 function mark(tbl, idx) { 4378 cells(tbl).forEach(function (c, k) { c.classList.toggle('is-last', k === idx); }); 4379 } 4380 4381 function deck() { 4382 var R = window.Reveal; 4383 return (R && typeof R.configure === 'function' && typeof R.getConfig === 'function' && 4384 (!R.isReady || R.isReady())) ? R : null; 4385 } 4386 4387 /* ââ lightbox âââââââââââââââââââââââââââââââââââââââââââââââââââ */ 4388 var box, card, content, elIco, elName, elTag, elCount, elBody, elFoot, btnX, btnPrev, btnNext; 4389 var lb = { tbl: null, idx: -1, byKey: false, kbd: null, timer: 0 }; 4390 4391 function build() { 4392 if (box) return; 4393 box = document.createElement('div'); 4394 box.className = 'exm-box'; 4395 box.setAttribute('role', 'dialog'); 4396 box.setAttribute('aria-modal', 'true'); 4397 box.setAttribute('aria-hidden', 'true'); 4398 box.innerHTML = 4399 '<button class="exm-btn exm-close" type="button" aria-label="Close (Esc)">' + CLOSE + '</button>' + 4400 '<button class="exm-btn exm-prev" type="button" aria-label="Previous cell">' + PREV + '</button>' + 4401 '<button class="exm-btn exm-next" type="button" aria-label="Next cell">' + NEXT + '</button>' + 4402 '<article class="exm-card"><div class="exm-content">' + 4403 '<header class="exm-bar"><span class="exm-bar-ico"></span><span class="exm-bar-name"></span>' + 4404 '<span class="exm-tag"></span><span class="exm-count"></span></header>' + 4405 '<div class="exm-body"></div>' + 4406 '<div class="exm-foot"></div>' + 4407 '</div></article>' + 4408 '<div class="exm-keys"><b>â</b><b>â</b><span>Browse</span><b>Esc</b>Close</div>'; 4409 /* on <body>: a fixed box inside Reveal's transformed .slides 4410 would be positioned against the slide, not the viewport */ 4411 document.body.appendChild(box); 4412 4413 card = box.querySelector('.exm-card'); 4414 content = box.querySelector('.exm-content'); 4415 elIco = box.querySelector('.exm-bar-ico'); 4416 elName = box.querySelector('.exm-bar-name'); 4417 elTag = box.querySelector('.exm-tag'); 4418 elCount = box.querySelector('.exm-count'); 4419 elBody = box.querySelector('.exm-body'); 4420 elFoot = box.querySelector('.exm-foot'); 4421 btnX = box.querySelector('.exm-close'); 4422 btnPrev = box.querySelector('.exm-prev'); 4423 btnNext = box.querySelector('.exm-next'); 4424 4425 btnX.addEventListener('click', close); 4426 btnPrev.addEventListener('click', function () { step(-1); }); 4427 btnNext.addEventListener('click', function () { step(1); }); 4428 box.addEventListener('click', function (e) { 4429 if (!e.target.closest('.exm-card, .exm-btn')) close(); 4430 }); 4431 } 4432 4433 function isOpen() { return !!box && box.classList.contains('is-open'); } 4434 4435 function render(idx, swap) { 4436 var all = cells(lb.tbl), c = all[idx]; 4437 if (!c) return; 4438 lb.idx = idx; 4439 mark(lb.tbl, idx); 4440 btnPrev.disabled = idx === 0; 4441 btnNext.disabled = idx === all.length - 1; 4442 4443 function fill() { 4444 var row = c.closest('.exm-row'); 4445 var name = row.querySelector('.exm-name'); 4446 var ico = row.querySelector('.exm-ico'); 4447 var pro = c.classList.contains('is-pro'); 4448 elIco.innerHTML = ico ? ico.outerHTML : ''; 4449 elName.textContent = name ? name.textContent.trim() : ''; 4450 elTag.innerHTML = (pro ? PLUS : MINUS) + (pro ? 'Strengths' : 'Weaknesses'); 4451 elCount.textContent = (idx + 1) + ' / ' + all.length; 4452 elBody.className = 'exm-body ' + (pro ? 'is-pro' : 'is-con'); 4453 elBody.innerHTML = ''; 4454 c.querySelectorAll('.exm-pt').forEach(function (p) { elBody.appendChild(p.cloneNode(true)); }); 4455 box.setAttribute('aria-label', elName.textContent + ': ' + (pro ? 'strengths' : 'weaknesses')); 4456 card.scrollTop = 0; 4457 } 4458 clearTimeout(lb.timer); 4459 if (swap) { 4460 content.classList.add('is-swap'); 4461 lb.timer = setTimeout(function () { fill(); content.classList.remove('is-swap'); }, 140); 4462 } else { content.classList.remove('is-swap'); fill(); } 4463 } 4464 4465 function step(d) { 4466 if (!isOpen()) return; 4467 var to = lb.idx + d; 4468 if (to >= 0 && to < cells(lb.tbl).length) render(to, true); 4469 } 4470 4471 function open(tbl, idx, byKey) { 4472 build(); 4473 lb.tbl = tbl; 4474 lb.byKey = !!byKey; 4475 var scope = tbl.closest('section') || tbl.parentNode || document; 4476 var src = scope.querySelector('.exm-source'); 4477 elFoot.innerHTML = ''; 4478 if (src) { 4479 var copy = src.cloneNode(true); 4480 elFoot.innerHTML = (copy.querySelector('p') || copy).innerHTML;
4480 /* keeps the `Table 8.1` code span */ 4481 elFoot.querySelectorAll('a').forEach(function (a) { a.replaceWith(document.createTextNode(a.textContent)); }); 4482 } 4483 render(idx, false); 4484 4485 var R = deck(); 4486 if (R) { lb.kbd = R.getConfig().keyboard; R.configure({ keyboard: false }); } 4487 4488 box.setAttribute('aria-hidden', 'false'); 4489 box.classList.add('is-open'); 4490 btnX.focus({ preventScroll: true }); 4491 } 4492 4493 function close() { 4494 if (!isOpen()) return; 4495 box.classList.remove('is-open'); 4496 box.setAttribute('aria-hidden', 'true'); 4497 4498 var R = deck(); 4499 if (R && lb.kbd !== null) { R.configure({ keyboard: lb.kbd }); lb.kbd = null; } 4500 4501 var c = cells(lb.tbl)[lb.idx]; 4502 if (R && !lb.byKey) { 4503 /* mouse-opened in a deck: release focus so arrows/Space drive the deck */ 4504 if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); 4505 } else if (c) { 4506 c.focus({ preventScroll: true }); 4507 } 4508 } 4509 4510 /* ââ binding ââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 4511 window.__exmScan = function () { 4512 document.querySelectorAll('.exm-table').forEach(function (tbl) { 4513 if (tbl.dataset.exmBound) return; 4514 tbl.dataset.exmBound = '1'; 4515 cells(tbl).forEach(function (c, k) { 4516 c.setAttribute('tabindex', '0'); 4517 c.setAttribute('aria-haspopup', 'dialog'); 4518 c.insertAdjacentHTML('beforeend', ZOOM); 4519 c.addEventListener('click', function () { open(tbl, k, false); }); 4520 c.addEventListener('keydown', function (e) { 4521 if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') { 4522 e.preventDefault(); 4523 e.stopPropagation(); /* Space would otherwise advance */ 4524 open(tbl, k, true); 4525 } 4526 }); 4527 }); 4528 }); 4529 }; 4530 4531 /* capture phase: ahead of Reveal's document-level handler */ 4532 window.addEventListener('keydown', function (e) { 4533 if (isOpen()) { 4534 var k = e.key; 4535 if (k === 'Escape') close(); 4536 else if (k === 'ArrowRight' || k === 'ArrowDown' || k === 'PageDown') step(1); 4537 else if (k === 'ArrowLeft' || k === 'ArrowUp' || k === 'PageUp') step(-1); 4538 else return; 4539 e.preventDefault(); 4540 e.stopPropagation(); 4541 return; 4542 } 4543 if (e.key !== 'Escape') return; 4544 var on = document.querySelector('.exm-table .exm-cell.is-last'); 4545 if (!on) return; 4546 e.stopPropagation(); /* clear the last-viewed mark instead of opening the overview */ 4547 mark(on.closest('.exm-table'), -1); 4548 }, true); 4549 4550 /* keep focus inside the lightbox while it is open */ 4551 document.addEventListener('focusin', function (e) { 4552 if (isOpen() && !box.contains(e.target)) btnX.focus({ preventScroll: true }); 4553 }); 4554 4555 function hook() { 4556 window.__exmScan(); 4557 var R = window.Reveal; 4558 if (!R || typeof R.on !== 'function') return; 4559 R.on('ready', window.__exmScan); 4560 R.on('slidechanged', function (ev) { 4561 if (ev.previousSlide) ev.previousSlide.querySelectorAll('.exm-table').forEach(function (t) { mark(t, -1); }); 4562 }); 4563 } 4564 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', hook); 4565 else hook(); 4566})(); 4567</script>
4567 4568<div class="exm-source"> 4569<p>Adapted from <code>Table 8.1</code> in Carr et al. <span class="citation" data-cites="carr_art_2020">(<a href="#/references" role="doc-biblioref" onclick="">2020</a>)</span></p> 4570</div> 4571</section> 4572<section id="experiments-3" class="slide level2 scrollable"> 4573<h2>Experiments</h2> 4574<h4 id="an-example"><span style="color:#a9a9a9;">An Example</span></h4> 4575<div style="margin-top: 40px;"> 4576 4577</div> 4578<!-- 4579 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4580 VIGNETTE EXPERIMENT · "John" · Quarto RevealJS drop-in (jv-) 4581 Space Grotesk / JetBrains Mono · purple ramp (#856cb0) 4582 Same system as the Gabriel vignette (vg-) and rating conjoint (ar-) 4583 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4584 Include with {{< include _vignette-john.qmd >}} or paste as is. 4585 4586 Flow 4587 01 Vignette â the party slot in the vignette is a button; click 4588 it to draw Democrat or Republican (p = .5 each). 4589 02 Outcomes â two 7-point items, enabled once John is drawn. 4590 The arm is named only after both are answered. 4591 Then â "Show the counterfactual" swaps in the counterfactual 4592 vignette (ratings locked while it is shown). 4593 Reset â available once John is drawn; clears the draw and 4594 the ratings for the next pass. Nothing is recorded. 4595 4596 The vignette text is verbatim. The treatment is appended as its own 4597 final sentence ("John is a ___.") so the fixed text is untouched. 4598 4599 Master size: --jv-size near the top of the <style> block. 4600 Arm labels: CONFIG block in the script. Item wording: markup below. 4601 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4602--> 4603 4604<style> 4605.jv-scope { 4606 --jv-accent: #856cb0; 4607 --jv-deep: #6f5a96; 4608 --jv-mid: #a08bc4; 4609 --jv-soft: #b7a5d3; 4610 --jv-pale: #e9e4ff; 4611 --jv-line: rgba(133, 108, 176, .16); 4612 --jv-ink: #2a2632; 4613 --jv-mute: #8d879b; 4614 --jv-sans: "Space Grotesk", -apple-system, sans-serif; 4615 --jv-mono: "JetBrains Mono", ui-monospace, monospace; 4616 4617 /* â¸â¸ MASTER SIZE CONTROL ââ 19px compact · 22px default · 24px large */ 4618 --jv-size: 22px; 4619 4620 max-width: 900px; 4621 margin: 0 auto; 4622 font-family: var(--jv-sans); 4623 font-size: var(--jv-size); 4624 line-height: 1.4; 4625 color: var(--jv-ink); 4626 text-align: left; 4627} 4628.jv-scope *, .jv-scope *::before, .jv-scope *::after { box-sizing: border-box; } 4629.jv-scope button { font-family: inherit; margin: 0; } 4630 4631/* ââ top bar: stepper · arm · results âââââââââââââââââââââââââââââ */ 4632.jv-bar { 4633 display: flex; 4634 align-items: center; 4635 gap: .9em; 4636 margin-bottom: .75em; 4637} 4638.jv-scope .jv-steps { 4639 display: flex; 4640 align-items: center; 4641 gap: .55em; 4642 margin: 0; 4643 padding: 0; 4644 list-style: none; 4645 font-family: var(--jv-mono); 4646 font-size: .56em; 4647 letter-spacing: .09em; 4648 text-transform: uppercase; 4649 color: var(--jv-mute); 4650} 4651.jv-scope .jv-steps li { display: inline-flex; align-items: center; gap: .5em; margin: 0; transition: color .25s ease; } 4652.jv-scope .jv-steps li + li::before { 4653 content: ""; 4654 width: 2.2em; 4655 height: 1px; 4656 margin-right: .1em; 4657 background: var(--jv-line); 4658} 4659.jv-num { 4660 display: inline-grid; 4661 place-items: center; 4662 width: 2.1em; height: 2.1em; 4663 border: 1px solid var(--jv-line); 4664 border-radius: 50%; 4665 font-weight: 600; 4666 letter-spacing: 0; 4667 transition: background-color .25s ease, border-color .25s ease, color .25s ease; 4668} 4669.jv-steps li.is-here { color: var(--jv-deep); } 4670.jv-steps li.is-here .jv-num { border-color: var(--jv-accent); background: var(--jv-accent); color: #fff; } 4671.jv-steps li.is-done { color: var(--jv-mid); } 4672.jv-steps li.is-done .jv-num { border-color: var(--jv-soft); color: var(--jv-accent); } 4673 4674.jv-arm { 4675 margin-left: auto; 4676 font-family: var(--jv-mono); 4677 font-size: .56em; 4678 letter-spacing: .1em; 4679 text-transform: uppercase; 4680 color: var(--jv-mute); 4681 white-space: nowrap; 4682} 4683.jv-arm b { font-weight: 600; color: var(--jv-deep); } 4684 4685/* ââ vignette card ââââââââââââââââââââââââââââââââââââââââââââââââ */ 4686.jv-card { 4687 padding: 1.1em 1.5em 1.15em; 4688 border: 1px solid var(--jv-line); 4689 border-left: 3px solid var(--jv-accent); 4690 border-radius: 0 10px 10px 0; 4691 background: rgba(133, 108, 176, .035); 4692 transition: border-left-color .3s ease, background-color .3s ease; 4693} 4694.jv-card.is-counterfactual { 4695 border-left-style: dashed; 4696 border-left-color: var(--jv-soft); 4697 background: rgba(133, 108, 176, .02); 4698} 4699.jv-scope .jv-text { 4700 margin: 0; 4701 font-size: .9em; 4702 font-weight: 300; 4703 line-height: 1.72; 4704} 4705.jv-treat { font-weight: 400; } 4706 4707/* the slot: a Randomize button until drawn, then the treatment token */ 4708.jv-t { 4709 display: inline-flex; 4710 align-items: center; 4711 gap: .35em; 4712 padding: 0 .38em; 4713 border: 1px solid var(--jv-accent); 4714 border-radius: 5px; 4715 background: var(--jv-accent); 4716 color: #fff; 4717 font-size: 1em; 4718 font-weight: 500; 4719 line-height: 1.45; 4720 vertical-align: baseline; 4721 cursor: default; 4722} 4723.jv-t:focus-visible { outline: 3px solid var(--jv-pale); outline-offset: 2px; } 4724.jv-t svg { width: .85em; height: .85em; } 4725.jv-t.is-empty { 4726 padding: .1em .6em; 4727 background: #fff; 4728 color: var(--jv-deep); 4729 border-style: dashed; 4730 font-family: var(--jv-mono); 4731 font-size: .72em; 4732 letter-spacing: .08em; 4733 text-transform: uppercase; 4734 line-height: 1.5; 4735 cursor: pointer; 4736 animation: jv-breathe 2.2s ease-in-out infinite; 4737 transition: background-color .2s ease, color .2s ease; 4738} 4739.jv-t.is-empty:hover { background: var(--jv-accent); color: #fff; border-style: solid; animation: none; } 4740@keyframes jv-breathe { 4741 0%, 100% { box-shadow: 0 0 0 0 rgba(133,108,176,.0); } 4742 50% { box-shadow: 0 0 0 .28em rgba(133,108,176,.16); } 4743} 4744.jv-t.is-swapping { animation: jv-swap .42s cubic-bezier(.22,.61,.36,1); } 4745@keyframes jv-swap { 4746 0% { opacity: 1; transform: translateY(0); } 4747 45% { opacity: 0; transform: translateY(-.35em); } 4748 55% { opacity: 0; transform: translateY(.35em); } 4749 100% { opacity: 1; transform: translateY(0); } 4750} 4751 4752.jv-flag { 4753 display: none; 4754 margin-top: .7em; 4755 font-family: var(--jv-mono); 4756 font-size: .52em; 4757 letter-spacing: .1em; 4758 text-transform: uppercase; 4759 color: var(--jv-mid); 4760} 4761.jv-card.is-counterfactual .jv-flag { display: block; } 4762.jv-card.is-counterfactual .jv-t { background: var(--jv-soft); border-color: var(--jv-soft); } 4763 4764/* ââ outcomes: survey-battery rows ââââââââââââââââââââââââââââââââ */ 4765.jv-items { 4766 margin-top: .8em; 4767 border-top: 1px solid var(--jv-line); 4768 transition: opacity .25s ease; 4769} 4770.jv-items.is-locked { opacity: .38; pointer-events: none; } 4771 4772.jv-item { 4773 display: grid; 4774 grid-template-columns: 1fr auto; 4775 align-items: center; 4776 gap: 1em; 4777 padding: .6em 0; 4778 border-bottom: 1px solid var(--jv-line); 4779} 4780.jv-scope .jv-q { 4781 display: flex; 4782 align-items: baseline; 4783 gap: .6em; 4784 margin: 0; 4785 font-size: .8em; 4786 font-weight: 500; 4787 line-height: 1.35; 4788 letter-spacing: -.005em; 4789}
4790.jv-qn { 4791 flex: none; 4792 font-family: var(--jv-mono); 4793 font-size: .72em; 4794 font-weight: 600; 4795 letter-spacing: .06em; 4796 color: var(--jv-accent); 4797} 4798 4799.jv-scale-row { display: flex; align-items: center; gap: .55em; } 4800.jv-anchor { 4801 flex: 0 0 8.2em; 4802 font-family: var(--jv-mono); 4803 font-size: .5em; 4804 line-height: 1.35; 4805 letter-spacing: .07em; 4806 text-transform: uppercase; 4807 text-align: center; 4808 text-wrap: balance; 4809 color: var(--jv-mute); 4810} 4811.jv-scale { display: inline-flex; gap: .26em; } 4812.jv-pt { 4813 width: 1.75em; 4814 height: 1.75em; 4815 padding: 0; 4816 border: 1px solid rgba(133, 108, 176, .32); 4817 border-radius: 6px; 4818 background: #fff; 4819 color: var(--jv-deep); 4820 font-size: .78em; 4821 font-weight: 500; 4822 font-variant-numeric: tabular-nums; 4823 cursor: pointer; 4824 transition: background-color .15s ease, color .15s ease, border-color .15s ease, transform .12s ease; 4825} 4826.jv-pt:focus-visible { outline: 2px solid var(--jv-soft); outline-offset: 2px; } 4827/* ordinal fill: every point up to the value is tinted, deeper as it climbs */ 4828.jv-pt.is-fill { 4829 background: rgba(133, 108, 176, calc(.05 + var(--k) * .045)); 4830 border-color: rgba(133, 108, 176, .45); 4831} 4832.jv-pt.is-peek { background: rgba(133, 108, 176, .28); border-color: var(--jv-accent); } 4833.jv-pt.is-on { 4834 background: var(--jv-accent); 4835 border-color: var(--jv-accent); 4836 color: #fff; 4837 transform: translateY(-1px); 4838 box-shadow: 0 4px 10px rgba(133, 108, 176, .32); 4839} 4840 4841/* ââ after-answer row âââââââââââââââââââââââââââââââââââââââââââââ */ 4842.jv-after { 4843 display: flex; 4844 align-items: center; 4845 gap: .6em; 4846 min-height: 2.1em; 4847 margin-top: .7em; 4848} 4849.jv-swap, .jv-reset { visibility: hidden; opacity: 0; transition: opacity .25s ease; } 4850.jv-scope.is-answered .jv-swap, 4851.jv-scope.is-drawn .jv-reset { visibility: visible; opacity: 1; } 4852 4853.jv-ghost { 4854 display: inline-flex; 4855 align-items: center; 4856 gap: .45em; 4857 padding: .32em .9em; 4858 border: 1px solid var(--jv-line); 4859 border-radius: 999px; 4860 background: transparent; 4861 color: var(--jv-mute); 4862 font-family: var(--jv-mono); 4863 font-size: .54em; 4864 letter-spacing: .09em; 4865 text-transform: uppercase; 4866 cursor: pointer; 4867 transition: color .2s ease, border-color .2s ease, background-color .2s ease; 4868} 4869.jv-ghost:hover { color: var(--jv-accent); border-color: var(--jv-accent); background: rgba(133,108,176,.06); } 4870.jv-ghost:focus-visible { outline: 2px solid var(--jv-pale); outline-offset: 2px; } 4871.jv-ghost svg { width: 1em; height: 1em; } 4872.jv-ghost.is-on { color: var(--jv-deep); border-color: var(--jv-accent); background: rgba(133,108,176,.1); } 4873 4874.jv-spacer { flex: 1; } 4875 4876@media (prefers-reduced-motion: reduce) { 4877 .jv-scope * { animation: none !important; transition: none !important; } 4878} 4879</style> 4880 4881<div class="jv-scope"> 4882 4883 <div class="jv-bar"> 4884 <ol class="jv-steps" aria-label="Progress"> 4885 <li class="jv-step is-here" data-step="1"><span class="jv-num">01</span>Vignette</li> 4886 <li class="jv-step" data-step="2"><span class="jv-num">02</span>Outcomes</li> 4887 </ol> 4888 <span class="jv-arm" aria-live="polite"></span> 4889 </div> 4890 4891 <div class="jv-main"> 4892 4893 <div class="jv-card"> 4894 <p class="jv-text"> 4895 John is a 44-year-old white man living in a suburb of Grand Rapids, Michigan. He lives with his wife and their two teenage children. He works as a facilities manager for a regi
4895onal healthcare network. He coaches youth soccer on weekends and attends church a few times a year. He owns his home and follows local and national news when he can. He voted in the last two general elections but tends to skip the primaries. 4896 <span class="jv-treat" aria-live="polite">John is a <button class="jv-t is-empty" type="button"></button>.</span> 4897 </p> 4898 <span class="jv-flag">Counterfactual arm · not the one this respondent saw</span> 4899 </div> 4900 4901 <div class="jv-items is-locked"> 4902 <div class="jv-item" data-q="0"> 4903 <p class="jv-q"><span class="jv-qn">Q1</span>How likely is it that John has a college degree?</p> 4904 <div class="jv-scale-row"> 4905 <span class="jv-anchor">Very unlikely</span> 4906 <div class="jv-scale" role="radiogroup" aria-label="How likely is it that John has a college degree? 1 = very unlikely, 7 = very likely"></div> 4907 <span class="jv-anchor">Very likely</span> 4908 </div> 4909 </div> 4910 <div class="jv-item" data-q="1"> 4911 <p class="jv-q"><span class="jv-qn">Q2</span>How similar is John to people like you?</p> 4912 <div class="jv-scale-row"> 4913 <span class="jv-anchor">Not at all similar</span> 4914 <div class="jv-scale" role="radiogroup" aria-label="How similar is John to people like you? 1 = not at all similar, 7 = very similar"></div> 4915 <span class="jv-anchor">Very similar</span> 4916 </div> 4917 </div> 4918 </div> 4919 4920 <div class="jv-after"> 4921 <button class="jv-ghost jv-swap" type="button" aria-pressed="false"> 4922 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 16H3l4-4"></path><path d="M3 16h13a5 5 0 0 0 0-10h-3"></path></svg> 4923 <span class="jv-swap-label">Show the counterfactual</span> 4924 </button> 4925 <span class="jv-spacer"></span> 4926 <button class="jv-ghost jv-reset" type="button"> 4927 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"></path><path d="M3 3v5h5"></path></svg> 4928 Reset 4929 </button> 4930 </div> 4931 </div> 4932 4933</div> 4934
4935<script> 4936(function () { 4937 if (window.__jvReady) { window.__jvScan(); return; } 4938 window.__jvReady = true; 4939 4940 /* ââ CONFIG âââââââââââââââââââââââââââââââââââââââââââââââââââââ 4941 The two arms. The token reads "John is a <label>." */ 4942 var ARMS = [ 4943 { label: 'Democrat' }, 4944 { label: 'Republican' } 4945 ]; 4946 var POINTS = 7; 4947 /* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 4948 4949 var DICE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>'; 4950 4951 window.__jvScan = function () { 4952 document.querySelectorAll('.jv-scope').forEach(function (scope) { 4953 if (scope.dataset.jvBound) return; 4954 scope.dataset.jvBound = '1'; 4955 4956 var $ = function (s) { return scope.querySelector(s); }; 4957 var steps = scope.querySelectorAll('.jv-step'), 4958 elArm = $('.jv-arm'), 4959 elCard = $('.jv-card'), 4960 elT = $('.jv-t'), 4961 elItems = $('.jv-items'), 4962 elSwap = $('.jv-swap'), 4963 elSwapL = $('.jv-swap-label'), 4964 elReset = $('.jv-reset'), 4965 scales = scope.querySelectorAll('.jv-scale'); 4966 4967 var arm = null, ans = [null, null], other = false; 4968 4969 /* build the two 7-point scales */ 4970 scales.forEach(function (sc, q) { 4971 for (var v = 1; v <= POINTS; v++) { 4972 var b = document.createElement('button'); 4973 b.type = 'button'; 4974 b.className = 'jv-pt'; 4975 b.textContent = v; 4976 b.dataset.v = v; 4977 b.style.setProperty('--k', v); 4978 b.setAttribute('role', 'radio'); 4979 b.setAttribute('aria-checked', 'false'); 4980 sc.appendChild(b); 4981 } 4982 sc.addEventListener('click', function (e) { 4983 var b = e.target.closest('.jv-pt'); if (b) rate(q, +b.dataset.v); 4984 }); 4985 sc.addEventListener('mouseover', function (e) { 4986 var b = e.target.closest('.jv-pt'); if (b) peek(q, +b.dataset.v); 4987 }); 4988 sc.addEventListener('mouseleave', function () { peek(q, 0); }); 4989 }); 4990 4991 /* ââ state painting âââââââââââââââââââââââââââââââââââââââââ */ 4992 function setStep() { 4993 var done1 = !!arm, done2 = ans[0] !== null && ans[1] !== null; 4994 steps[0].className = 'jv-step ' + (done1 ? 'is-done' : 'is-here'); 4995 steps[1].className = 'jv-step ' + (done2 ? 'is-done' : done1 ? 'is-here' : ''); 4996 } 4997 4998 function paintScale(q) { 4999 var val = ans[q]; 5000 scales[q].querySelectorAll('.jv-pt').forEach(function (b) { 5001 var v = +b.dataset.v; 5002 b.classList.toggle('is-fill', val !== null && v < val); 5003 b.classList.toggle('is-on', v === val); 5004 b.setAttribute('aria-checked', v === val ? 'true' : 'false'); 5005 }); 5006 } 5007 function peek(q, upto) { 5008 scales[q].querySelectorAll('.jv-pt').forEach(function (b) { 5009 b.classList.toggle('is-peek', upto > 0 && +b.dataset.v <= upto && !b.classList.contains('is-on')); 5010 }); 5011 } 5012 5013 function paintToken(label) { 5014 elT.classList.remove('is-swapping'); 5015 void elT.offsetWidth; 5016 elT.classList.add('is-swapping'); 5017 setTimeout(function () { elT.textContent = label; }, 180); 5018 } 5019 5020 function emptyToken() { 5021 elT.classList.remove('is-swapping'); 5022 elT.classList.add('is-empty'); 5023 elT.disabled = false; 5024 elT.innerHTML = DICE + '<span>Randomize</span>'; 5025 elT.setAttribute('aria-label', 'Randomize John\u2019s party'); 5026 } 5027 5028 /* ââ actions ââââââââââââââââââââââââââââââââââââââââââââââââ */ 5029 function draw() { 5030 if (arm) return; 5031 arm = ARMS[Math.floor(Math.random() * ARMS.length)]; 5032 elT.classList.remove('is-empty'); 5033 elT.disabled = true; 5034 elT.removeAttribute('aria-label'); 5035 paintToken(arm.label); 5036 elItems.classList.remove('is-locked'); 5037 scope.classList.add('is-drawn'); 5038 setStep(); 5039 } 5040 5041 function rate(q, v) { 5042 if (!arm || other) return; 5043 ans[q] = v; 5044 paintScale(q); 5045 peek(q, 0); 5046 setStep(); 5047 if (ans[0] !== null && ans[1] !== null) { 5048 scope.classList.add('is-answered'); 5049 elArm.innerHTML = 'Arm · <b>' + arm.label + '</b>'; 5050 } 5051 } 5052 5053 function toggleOther() { 5054 other = !other; 5055 var alt = ARMS.filter(function (a) { return a !== arm; })[0]; 5056 paintToken(other ? alt.label : arm.label); 5057 elCard.classList.toggle('is-counterfactual', other); 5058 elItems.classList.toggle('is-locked', other); 5059 elSwap.classList.toggle('is-on', other); 5060 elSwap.setAttribute('aria-pressed', other ? 'true' : 'false'); 5061 elSwapL.textContent = other ? 'Back to original' : 'Show the counterfactual'; 5062 } 5063 5064 function reset() { 5065 arm = null;
5065 ans = [null, null]; other = false; 5066 emptyToken(); 5067 elCard.classList.remove('is-counterfactual'); 5068 elItems.classList.add('is-locked'); 5069 elSwap.classList.remove('is-on'); 5070 elSwap.setAttribute('aria-pressed', 'false'); 5071 elSwapL.textContent = 'Show the counterfactual'; 5072 scope.classList.remove('is-answered', 'is-drawn'); 5073 elArm.innerHTML = ''; 5074 paintScale(0); paintScale(1); 5075 setStep(); 5076 } 5077 5078 /* ââ wiring âââââââââââââââââââââââââââââââââââââââââââââââââ */ 5079 elT.addEventListener('click', draw); 5080 elSwap.addEventListener('click', toggleOther); 5081 elReset.addEventListener('click', reset); 5082 5083 /* Space/Enter on any control must not advance the Reveal slide */ 5084 scope.addEventListener('keydown', function (e) { 5085 if ((e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') && e.target.closest('button')) { 5086 e.stopPropagation(); 5087 } 5088 }); 5089 5090 reset(); 5091 }); 5092 }; 5093 5094 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', window.__jvScan); 5095 else window.__jvScan(); 5096 if (window.Reveal && Reveal.on) Reveal.on('ready', window.__jvScan); 5097})(); 5098</script>
5098 5099</section> 5100<section id="experiments-4" class="slide level2"> 5101<h2>Experiments</h2> 5102<div style="text-align: center; margin-top: 60px; font-size: 95%;"> 5103<p>Letâs pause for a second.</p> 5104<div class="fragment"> 5105<p>Design a <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>survey experiment</strong></span> linked to your research question.</p> 5106</div> 5107<div style="margin-top: 30px;"> 5108 5109</div> 5110<div class="fragment"> 5111<p>Rearrange the text in the box below to arrive at your pitch.</p> 5112<div style="margin-top: 30px;"> 5113 5114</div> 5115<!-- 5116 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5117 EXPERIMENTAL PITCH PAD · Quarto RevealJS drop-in (oe- namespace) 5118 Space Grotesk / JetBrains Mono · purple ramp (#856cb0) 5119 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5120 Same component as the "Your Research Question" pad. The card's 5121 border is the clock: the stroke drains clockwise from top centre, 5122 turns red in the warning window (10% of duration, capped at 60s), 5123 and the card pulses at 0:00. 5124 5125 Per-instance attributes on .oe-pad: 5126 data-minutes duration (fractions OK) default 10 5127 data-file export filename default research_question.txt 5128 Master size control: --oe-size. 5129 5130 If the research-question pad is already in this deck, only the 5131 <div class="oe-pad"> block is needed; the style and script here are 5132 guarded and harmless if repeated. 5133 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5134--> 5135 5136<style> 5137.oe-pad { 5138 --oe-accent: #856cb0; 5139 --oe-deep: #6f5a96; 5140 --oe-pale: #e9e4ff; 5141 --oe-warn: #c75b5b; 5142 --oe-ink: #2a2632; 5143 --oe-mute: #857f93; 5144 --oe-sans: "Space Grotesk", -apple-system, sans-serif; 5145 --oe-mono: "JetBrains Mono", ui-monospace, monospace; 5146 --oe-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38); 5147 5148 /* â¸â¸ MASTER SIZE CONTROL ââ 17px compact · 20px default · 24px large */ 5149 --oe-size: 20px; 5150 5151 position: relative; 5152 box-sizing: border-box; 5153 max-width: 1000px; 5154 margin: 0 auto; 5155 padding: 1em 1.15em 1.05em; 5156 border-radius: 14px; 5157 background: #fff; 5158 box-shadow: var(--oe-shadow); 5159 font-family: var(--oe-sans); 5160 font-size: var(--oe-size); 5161 color: var(--oe-ink); 5162 text-align: left; 5163} 5164.oe-pad *, .oe-pad *::before, .oe-pad *::after { box-sizing: border-box; } 5165 5166/* ââ perimeter clock ââââââââââââââââââââââââââââââââââââââââââââââ */ 5167.oe-ring { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; } 5168.oe-track { fill: none; stroke: rgba(133, 108, 176, .14); stroke-width: 2.5; } 5169.oe-prog { 5170 fill: none; 5171 stroke: var(--oe-accent); 5172 stroke-width: 2.5; 5173 stroke-linecap: round; 5174 stroke-dasharray: 1 2; 5175 opacity: .55; 5176 transition: stroke .3s ease, opacity .3s ease, filter .3s ease; 5177} 5178.oe-pad[data-state="running"] .oe-prog { opacity: 1; filter: drop-shadow(0 0 4px rgba(133, 108, 176, .55)); } 5179.oe-pad[data-state="paused"] .oe-prog { opacity: .75; } 5180.oe-pad.is-warn .oe-prog { stroke: var(--oe-warn); filter: drop-shadow(0 0 4px rgba(199, 91, 91, .45)); } 5181.oe-pad[data-state="done"] .oe-prog { opacity: 0; } 5182 5183@keyframes oe-ring-pulse { 5184 0% { box-shadow: var(--oe-shadow), 0 0 0 0 rgba(199, 91, 91, .4); } 5185 100% { box-shadow: var(--oe-shadow), 0 0 0 14px rgba(199, 91, 91, 0); } 5186} 5187.oe-pad.is-pulse { animation: oe-ring-pulse 1.4s ease-out 3; } 5188 5189/* ââ header âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 5190.oe-head { 5191 display: flex; 5192 align-items: center; 5193 justify-content: space-between; 5194 gap: 1em; 5195 margin-bottom: .6em; 5196} 5197.oe-title { 5198 font-size: 1.15em; 5199 font-weight: 600; 5200 letter-spacing: -.015em; 5201 line-height: 1.2; 5202} 5203 5204.oe-clock { display: flex; align-items: center; gap: .35em; } 5205.oe-time { 5206 font-family: var(--oe-mono); 5207 font-size: 1.05em; 5208 font-weight: 500; 5209 color: var(--oe-accent); 5210 min-width: 3.2em; 5211 text-align: right; 5212 letter-spacing: .02em; 5213 font-variant-numeric: tabular-nums; 5214 transition: color .3s ease; 5215} 5216.oe-pad.is-warn .oe-time { color: var(--oe-warn); } 5217.oe-pad[data-state="done"] .oe-time { color: var(--oe-warn); animation: oe-blink 1.4s ease-in-out infinite; } 5218@keyframes oe-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } } 5219 5220.oe-tbtn { 5221 width: 1.5em; height: 1.5em; 5222 display: grid; place-items: center; 5223 margin: 0; padding: 0; 5224 border: 0; border-radius: 50%; 5225 background: rgba(133, 108, 176, .08); 5226 color: var(--oe-accent); 5227 cursor: pointer; 5228 transition: background-color .2s ease, color .2s ease, transform .15s ease; 5229} 5230.oe-tbtn:hover { background: rgba(133, 108, 176, .16); } 5231.oe-tbtn:active { transform: scale(.94); } 5232.oe-tbtn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; } 5233.oe-tbtn.is-on { background: var(--oe-accent); color: #fff; } 5234.oe-tbtn svg { width: .65em; height: .65em; } 5235 5236/* ââ editor âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 5237.oe-editor { 5238 display: block; 5239 width: 100%; 5240 min-height: 6.5em; 5241 padding: .8em 1em; 5242 font-family: var(--oe-sans); 5243 font-size: .95em; 5244 font-weight: 300; 5245 line-height: 1.7; 5246 color: var(--oe-ink); 5247 background: #fff; 5248 border: 1px solid rgba(133, 108, 176, .18); 5249 border-radius: 8px; 5250 resize: vertical; 5251 outline: none; 5252 transition: border-color .2s ease, box-shadow .2s ease; 5253} 5254.oe-editor::placeholder { color: var(--oe-mute); font-style: italic; } 5255.oe-editor:focus { 5256 border-color: rgba(133, 108, 176, .45); 5257 box-shadow: 0 0 0 3px rgba(133, 108, 176, .1); 5258} 5259 5260/* ââ actions ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 5261.oe-actions { 5262 display: flex; 5263 align-items: center; 5264 gap: .35em; 5265 margin-top: .5em; 5266} 5267.oe-btn { 5268 display: inline-flex; 5269 align-items: center; 5270 gap: .4em; 5271 margin: 0; 5272 border: 0; 5273 border-radius: 6px; 5274 padding: .35em 1em; 5275 font-family: var(--oe-sans); 5276 font-size: .78em; 5277 font-weight: 500; 5278 cursor: pointer; 5279 transition: background-color .2s ease, color .2s ease, transform .15s ease; 5280} 5281.oe-btn:active { transform: scale(.98); } 5282.oe-btn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; } 5283.oe-btn svg { width: .85em; height: .85em; } 5284 5285.oe-primary { background: var(--oe-accent); color: #fff; } 5286.oe-primary:hover { background: var(--oe-deep); } 5287 5288.oe-secondary { background: rgba(133, 108, 176, .09); color: rgba(133, 108, 176, .75); } 5289.oe-secondary:hover { background: rgba(133, 108, 176, .16); color: var(--oe-accent); } 5290.oe-secondary.is-done { background: var(--oe-pale); color: var(--oe-deep); } 5291 5292.oe-count { 5293 margin-left: auto; 5294 font-family: var(--oe-mono); 5295 font-size: .62em; 5296 letter-spacing: .06em; 5297 text-transform: uppercase; 5298 color: var(--oe-mute); 5299 font-variant-numeric: tabular-nums; 5300} 5301 5302.oe-clear { 5303 background: rgba(199, 91, 91, .09); 5304 color: var(--oe-warn); 5305 margin-left: .35em; 5306} 5307.oe-clear:hover { background: rgba(199, 91, 91, .16); } 5308.oe-clear.is-arming { background: var(--oe-warn); color: #fff; } 5309 5310@media (prefers-reduced-motion: reduce) { 5311 .oe-pad.is-pulse, .oe-pad[data-state="done"] .oe-time { animation: none; } 5312 .oe-prog, .oe-btn, .oe-tbtn { transition: none; } 5313} 5314</style> 5315 5316<div class="oe-pad" data-minutes="10" data-file="experiment.txt"> 5317 5318 <svg class="oe-ring" aria-hidden="true"> 5319 <path class="oe-track"></path> 5320 <path class="oe-prog" pathlength="1"></path> 5321 </svg> 5322 5323 <div class="oe-head"> 5324 <span class="oe-title">An Experimental Pitch</span> 5325 <div class="oe-clock">
5326 <span class="oe-time" role="timer">10:00</span> 5327 <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer"> 5328 <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg> 5329 <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg> 5330 </button> 5331 <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer"> 5332 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg> 5333 </button> 5334 </div> 5335 </div> 5336 5337 <textarea class="oe-editor" rows="4" placeholder="My experiment will explore ..."></textarea> 5338 5339 <div class="oe-actions"> 5340 <button class="oe-btn oe-primary oe-export" type="button"> 5341 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> 5342 Export .txt 5343 </button> 5344 <button class="oe-btn oe-secondary oe-copy" type="button"> 5345 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> 5346 <span class="oe-copy-label">Copy</span> 5347 </button> 5348 <span class="oe-count">0 words</span> 5349 <button class="oe-btn oe-clear" type="button"> 5350 <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg> 5351 <span class="oe-clear-label">Clear</span> 5352 </button> 5353 </div> 5354</div> 5355
5356<script> 5357(function () { 5358 if (window.__oePadReady) { window.__oePadScan(); return; } 5359 window.__oePadReady = true; 5360 5361 window.__oePadScan = function () { 5362 document.querySelectorAll('.oe-pad').forEach(function (pad) { 5363 if (pad.dataset.oeBound) return; 5364 pad.dataset.oeBound = '1'; 5365 5366 var TOTAL = (parseFloat(pad.dataset.minutes) || 10) * 60000, 5367 WARN_AT = Math.min(60000, TOTAL * 0.1); 5368 5369 var q = function (s) { return pad.querySelector(s); }; 5370 var elRing = q('.oe-ring'), elTrack = q('.oe-track'), elProg = q('.oe-prog'), 5371 elTime = q('.oe-time'), elToggle = q('.oe-toggle'), elPlay = q('.oe-i-play'), 5372 elPause = q('.oe-i-pause'), elReset = q('.oe-reset'), elText = q('.oe-editor'), 5373 elCount = q('.oe-count'), elCopy = q('.oe-copy'), elCopyL = q('.oe-copy-label'), 5374 elClear = q('.oe-clear'), elClearL = q('.oe-clear-label'); 5375 5376 var left = TOTAL, deadline = 0, raf = 0, shown = -1, armed = null, flash = null; 5377 5378 /* ââ perimeter path, starting at top centre ââ */ 5379 function draw() { 5380 var w = pad.offsetWidth, h = pad.offsetHeight; 5381 if (!w || !h) return; 5382 var s = 1.25, 5383 r = Math.max(0, Math.min(parseFloat(getComputedStyle(pad).borderTopLeftRadius) || 0, w / 2, h / 2) - s), 5384 x0 = s, y0 = s, x1 = w - s, y1 = h - s; 5385 var d = 'M' + w / 2 + ' ' + y0 + 'H' + (x1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + (y0 + r) + 5386 'V' + (y1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x1 - r) + ' ' + y1 + 5387 'H' + (x0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x0 + ' ' + (y1 - r) + 5388 'V' + (y0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x0 + r) + ' ' + y0 + 'Z'; 5389 elRing.setAttribute('viewBox', '0 0 ' + w + ' ' + h); 5390 elTrack.setAttribute('d', d); 5391 elProg.setAttribute('d', d); 5392 } 5393 if ('ResizeObserver' in window) new ResizeObserver(draw).observe(pad); 5394 else window.addEventListener('resize', draw); 5395 5396 /* ââ timer ââ */ 5397 function fmt(s) { return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); } 5398 5399 function setState(st) { 5400 pad.dataset.state = st; 5401 var on = st === 'running'; 5402 elToggle.classList.toggle('is-on', on); 5403 elPlay.style.display = on ? 'none' : ''; 5404 elPause.style.display = on ? '' : 'none'; 5405 } 5406 5407 function paint() { 5408 elProg.style.strokeDashoffset = -(1 - left / TOTAL); 5409 var secs = Math.ceil(left / 1000); 5410 if (secs !== shown) { shown = secs; elTime.textContent = fmt(secs); } 5411 pad.classList.toggle('is-warn', left <= WARN_AT); 5412 } 5413 5414 function tick(now) { 5415 left = Math.max(0, deadline - now); 5416 paint(); 5417 if (left === 0) { finish(); return; } 5418 raf = requestAnimationFrame(tick); 5419 } 5420 5421 function finish() { 5422 cancelAnimationFrame(raf); 5423 setState('done'); 5424 pad.classList.remove('is-pulse'); 5425 void pad.offsetWidth; 5426 pad.classList.add('is-pulse'); 5427 } 5428 5429 elToggle.addEventListener('click', function () { 5430 if (pad.dataset.state === 'running') { 5431 cancelAnimationFrame(raf); 5432 left = Math.max(0, deadline - performance.now()); 5433 setState('paused'); 5434 paint(); 5435 return; 5436 } 5437 if (left <= 0) { left = TOTAL; shown = -1; pad.classList.remove('is-pulse'); } 5438 deadline = performance.now() + left; 5439 setState('running'); 5440 raf = requestAnimationFrame(tick); 5441 elText.focus(); 5442 }); 5443 5444 elReset.addEventListener('click', function () { 5445 cancelAnimationFrame(raf); 5446 left = TOTAL; shown = -1; 5447 pad.classList.remove('is-pulse'); 5448 setState('idle'); 5449 paint(); 5450 }); 5451 5452 /* ââ word count ââ */ 5453 function count() { 5454 var w = elText.value.trim(); 5455 var n = w ? w.split(/\s+/).length : 0; 5456 elCount.textContent = n + (n === 1 ? ' word' : ' words'); 5457 } 5458 elText.addEventListener('input', count); 5459 5460 /* Reveal listens on document â keep every keystroke out of its hands */
5461 ['keydown', 'keypress', 'keyup'].forEach(function (ev) { 5462 elText.addEventListener(ev, function (e) { e.stopPropagation(); }); 5463 }); 5464 pad.addEventListener('keydown', function (e) { 5465 if (e.key === ' ' || e.key === 'Spacebar') e.stopPropagation(); 5466 }); 5467 5468 /* ââ export / copy / clear ââ */ 5469 q('.oe-export').addEventListener('click', function () { 5470 var t = elText.value; 5471 if (!t.trim()) return; 5472 var a = document.createElement('a'); 5473 a.href = URL.createObjectURL(new Blob([t], { type: 'text/plain;charset=utf-8' })); 5474 a.download = pad.dataset.file || 'research_question.txt'; 5475 a.click(); 5476 setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000); 5477 }); 5478 5479 elCopy.addEventListener('click', function () { 5480 var t = elText.value; 5481 if (!t.trim()) return; 5482 function done() { 5483 clearTimeout(flash); 5484 elCopy.classList.add('is-done'); 5485 elCopyL.textContent = 'Copied'; 5486 flash = setTimeout(function () { 5487 elCopy.classList.remove('is-done'); 5488 elCopyL.textContent = 'Copy'; 5489 }, 1600); 5490 } 5491 if (navigator.clipboard && navigator.clipboard.writeText) { 5492 navigator.clipboard.writeText(t).then(done, function () { elText.select(); }); 5493 } else { elText.select(); document.execCommand('copy'); done(); } 5494 }); 5495 5496 elClear.addEventListener('click', function () { 5497 if (!elText.value.trim()) return; 5498 if (armed) { 5499 clearTimeout(armed); armed = null; 5500 elClear.classList.remove('is-arming'); 5501 elClearL.textContent = 'Clear'; 5502 elText.value = ''; 5503 count(); 5504 elText.focus(); 5505 return; 5506 } 5507 elClear.classList.add('is-arming'); 5508 elClearL.textContent = 'Confirm'; 5509 armed = setTimeout(function () { 5510 armed = null; 5511 elClear.classList.remove('is-arming'); 5512 elClearL.textContent = 'Clear'; 5513 }, 3000); 5514 }); 5515 5516 setState('idle'); 5517 paint(); 5518 count(); 5519 draw(); 5520 }); 5521 }; 5522 5523 if (document.readyState === 'loading') { 5524 document.addEventListener('DOMContentLoaded', window.__oePadScan); 5525 } else { window.__oePadScan(); } 5526 if (window.Reveal && Reveal.on) Reveal.on('ready', window.__oePadScan); 5527})(); 5528</script>
5528 5529</div> 5530</div> 5531</section></section> 5532<section> 5533<section id="high-level-overview-directed-acyclic-graphs-dags" class="title-slide slide level1 center" data-background-color="#43464B"> 5534<h1><span class="title-ill">High-Level Overview</span> Directed Acyclic Graphs <span style="font-size: 60%;">(DAGs)</span></h1> 5535 5536</section> 5537<section id="dags" class="slide level2"> 5538<h2>DAGs</h2> 5539<div class="elegant-quote" style="margin-top: 40px; font-size: 83%;"> 5540<p>Directed acyclic graphs (DAGs) offer a powerful visual framework for representing and reasoning about causal relationships. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">At their core, DAGs capture the directional flow of causalityâeach arrow represents an immediate cause-and-effect relationship between variables, and the absence of cycles reflects the unidirectional nature of time and causation</span>. While social science theories often allow for reverse causality or feedback loops, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">DAGs require breaking these cycles into sequential events, making causality more explicit and analyzable</span>.</p> 5541<p><span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">Cunningham 2026</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 5542</div> 5543</section> 5544<section id="dags-1" class="slide level2"> 5545<h2>DAGs</h2> 5546<div class="elegant-quote" style="margin-top: 40px; font-size: 83%;"> 5547<p>Despite their simplicity, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">DAGs are grounded in the same counterfactual framework that underpins potential outcomes</span>. A causal effect is still defined as a comparison between two states of the world: the observed outcome under one intervention and the counterfactual outcome under another. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">This connection makes DAGs a natural complement to potential outcomes by offering a graphical representation of various assumptions we must make when identifying effects</span>. In fact, this is precisely one of the strengths of this approach â <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">it forces the researcher to think long and hard about the treatment assignment mechanisms</span>, which are one of the main factors that dictate whether one research design is more suitable than another.</p> 5548<p><span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">Cunningham 2026</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p> 5549</div> 5550</section> 5551<section id="dags-2" class="slide level2"> 5552<h2>DAGs</h2> 5553<h4 id="example-from-cunningham--cunningham2026remix"><span style="color:#a9a9a9;">Example from Cunningham <span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">2026</a>)</span></span></h4> 5554<div style="margin-top: 60px;"> 5555 5556</div> 5557 5558<!-- 5559 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5560 DAG · controlling for family income · Quarto RevealJS drop-in 5561 Space Grotesk · purple ramp (#856cb0) · same idiom as dag-confounder 5562 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5563 Default: Family Income is left alone (round node). 5564 Step 2: it is controlled for â the node squares off into a box, 5565 fills, and a "controlled" tag fades in. 5566 Advance with the normal Reveal key (it's a fragment) or click the 5567 figure. Click again, or step back, to return. Size: --dag-size. 5568 Dashed edges run from the unobserved node. 5569 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5570--> 5571 5572<style> 5573.dag3 { 5574 --dag-ink: #2a2632; 5575 --dag-accent: #856cb0; 5576 --dag-deep: #6f5a96; 5577 --dag-pale: #f3effa; 5578 --dag-lilac: #c9bce2; 5579 --dag-mute: #8a8494; 5580 --dag-size: 22px; 5581 5582 position: relative; 5583 max-width: 960px; 5584 margin: 0 auto; 5585 font-family: "Space Grotesk", -apple-system, sans-serif; 5586 font-size: var(--dag-size); 5587 line-height: 1; 5588 cursor: pointer; 5589 -webkit-tap-highlight-color: transparent; 5590} 5591.dag3 svg { display: block; width: 100%; height: auto; overflow: visible; } 5592 5593/* edges */ 5594.dag3 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; } 5595.dag3 .e-u { stroke: var(--dag-mute); stroke-dasharray: 7 6; } 5596 5597/* nodes */ 5598.dag3 .core, .dag3 .ring { transition: rx .45s cubic-bezier(.22,.61,.36,1), ry .45s cubic-bezier(.22,.61,.36,1), fill .45s ease, stroke .45s ease; } 5599.dag3 .ring { fill: none; stroke-width: 1.5; } 5600.dag3 .n-pe .core { fill: #fff; } .dag3 .n-pe .ring { stroke: var(--dag-ink); } 5601.dag3 .n-d .core { fill: var(--dag-ink); } .dag3 .n-d .ring { stroke: var(--dag-ink); } 5602.dag3 .n-y .core { fill: var(--dag-lilac); } .dag3 .n-y .ring { stroke: var(--dag-lilac); } 5603.dag3 .n-b .core { fill: #fff; } .dag3 .n-b .ring { stroke: var(--dag-mute); stroke-dasharray: 4 4; } 5604 5605/* the node that changes: circle (left alone) â box (controlled) */ 5606.dag3 .n-i .core { fill: var(--dag-pale);
5606 rx: 20px; ry: 20px; } 5607.dag3 .n-i .ring { stroke: var(--dag-accent); rx: 23px; ry: 23px; } 5608.dag3.is-on .n-i .core { fill: var(--dag-accent); rx: 4px; ry: 4px; } 5609.dag3.is-on .n-i .ring { stroke: var(--dag-deep); rx: 6px; ry: 6px; } 5610 5611/* labels */ 5612.dag3 .lab { 5613 position: absolute; 5614 transform: translate(-50%, -50%); 5615 padding: .22em .45em .26em; 5616 border-radius: 5px; 5617 font-weight: 500; 5618 letter-spacing: -.005em; 5619 white-space: nowrap; 5620} 5621.dag3 .lab-pe { left: 12.5%; top: 13.2%; background: var(--dag-pale); color: var(--dag-ink); } 5622.dag3 .lab-i { left: 45.83%; top: 13.2%; background: var(--dag-accent); color: #fff; transition: background-color .45s ease; } 5623.dag3 .lab-d { left: 62.5%; top: 86.8%; background: var(--dag-ink); color: #fff; } 5624.dag3 .lab-y { left: 89.58%; top: 86.8%; background: var(--dag-lilac); color: var(--dag-ink); } 5625.dag3 .lab-b { left: 12.5%; top: 86.8%; color: var(--dag-mute); font-style: italic; font-weight: 400; } 5626.dag3.is-on .lab-i { background: var(--dag-deep); } 5627 5628/* "controlled" tag, right of the node */ 5629.dag3 .tag { 5630 position: absolute; 5631 left: 49.9%; top: 25.5%; 5632 transform: translate(0, -50%) translateX(-.4em); 5633 padding: .28em .55em; 5634 border: 1px solid rgba(133, 108, 176, .45); 5635 border-radius: 999px; 5636 background: #fff; 5637 font-family: "JetBrains Mono", ui-monospace, monospace; 5638 font-size: .58em; 5639 letter-spacing: .1em; 5640 text-transform: uppercase; 5641 color: var(--dag-deep); 5642 white-space: nowrap; 5643 opacity: 0; 5644 transition: opacity .35s ease, transform .35s ease; 5645} 5646.dag3.is-on .tag { opacity: 1; transform: translate(0, -50%); transition-delay: .15s; } 5647 5648.dag3 .dag3-frag { position: absolute; width: 0; height: 0; overflow: hidden; } 5649 5650@media (prefers-reduced-motion: reduce) { 5651 .dag3 *, .dag3 .tag { transition: none !important; } 5652} 5653</style> 5654 5655<div class="dag3" role="img" aria-label="DAG: parental education affects family income and college degree; family income affects college degree and income; college degree affects income; unobserved factors affect parental education and college degree. In the second state, family income is controlled for."> 5656 <svg viewbox="0 0 960 440" aria-hidden="true"> 5657 <defs> 5658 <marker id="dag3-arr" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"> 5659 <path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path> 5660 </marker> 5661 <marker id="dag3-arr-u" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"> 5662 <path d="M0 0 L10 5 L0 10 z" fill="#8a8494"></path> 5663 </marker> 5664 </defs> 5665 5666 <!-- observed paths --> 5667 <path class="e" marker-end="url(#dag3-arr)" d="M146 110 H412"></path> <!-- PE â I --> 5668 <path class="e" marker-end="url(#dag3-arr)" d="M143 121 L574 318"></path> <!-- PE â D --> 5669 <path class="e" marker-end="url(#dag3-arr)" d="M455 131 L583 307"></path> <!-- I â D --> 5670 <path class="e" marker-end="url(#dag3-arr)" d="M465 102 C640 10 870 110 862 302"></path> <!-- I â Y --> 5671 <path class="e" marker-end="url(#dag3-arr)" d="M627 330 H832"></path> <!-- D â Y --> 5672 5673 <!-- unobserved paths --> 5674 <path class="e e-u" marker-end="url(#dag3-arr-u)" d="M120 305 V138"></path> <!-- B â PE --> 5675 <path class="e e-u" marker-end="url(#dag3-arr-u)" d="M146 330 H570"></path> <!-- B â D --> 5676 5677 <g class="n-pe"><circle class="core" cx="120" cy="110" r="20"></circle><circle class="ring" cx="120" cy="110" r="23"></circle></g> 5678 <g class="n-i"><rect class="core" x="420" y="90" width="40" height="40" rx="20" ry="20"></rect><rect class="ring" x="417" y="87" width="46" height="46" rx="23" ry="23"></rect></g> 5679 <g class="n-d"><circle class="core" cx="600" cy="330" r="20"></circle><circle class="ring" cx="600" cy="330" r="23"></circle></g> 5680 <g class="n-y"><circle class="core" cx="860" cy="330" r="20"></circle><circle class="ring" cx="860" cy="330" r="23"></circle></g> 5681 <g class="n-b"><circle class="core" cx="120" cy="330" r="20"></circle><circle class="ring" cx="120" cy="330" r="23"></circle></g> 5682 </svg> 5683 5684 <span class="lab lab-pe">Parental Education</span> 5685 <span class="lab lab-i">
5685Family Income</span> 5686 <span class="lab lab-d">College Degree</span> 5687 <span class="lab lab-y">Earnings</span> 5688 <span class="lab lab-b">Unobserved</span> 5689 <span class="tag">Adjusted</span> 5690 5691 <span class="fragment dag3-frag"></span> 5692</div> 5693
5694<script> 5695(function () { 5696 /* bind after the page has loaded, so Reveal exists whether its script 5697 sits before or after this block */ 5698 function bind() { 5699 var R = window.Reveal && Reveal.on ? Reveal : null; 5700 document.querySelectorAll('.dag3').forEach(function (d) { 5701 if (d.dataset.bound) return; 5702 d.dataset.bound = '1'; 5703 5704 var f = d.querySelector('.dag3-frag'); 5705 function set(on) { d.classList.toggle('is-on', on); } 5706 function sync() { set(f.classList.contains('visible')); } 5707 5708 if (R) { 5709 R.on('fragmentshown', function (e) { if (e.fragment === f) set(true); }); 5710 R.on('fragmenthidden', function (e) { if (e.fragment === f) set(false); }); 5711 R.on('slidechanged', sync); 5712 R.on('ready', sync); 5713 sync(); 5714 } 5715 5716 /* click toggles; with Reveal, it moves the fragment state itself so 5717 arrow keys and clicks never disagree */ 5718 d.addEventListener('click', function () { 5719 var on = !d.classList.contains('is-on'); 5720 var i = parseInt(f.getAttribute('data-fragment-index'), 10); 5721 if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1); 5722 else set(on); 5723 }); 5724 }); 5725 } 5726 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind); 5727 else bind(); 5728})(); 5729</script>
5729 5730 5731</section> 5732<section id="dags-3" class="slide level2"> 5733<h2>DAGs</h2> 5734<h4 id="correlation-neq-causation"><span style="color:#a9a9a9;">Correlation <span class="math inline">\neq</span> Causation</span></h4> 5735<div style="margin-top: 60px;"> 5736 5737</div> 5738 5739 5740<!-- 5741 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5742 DAG · three confounder triangles · Quarto RevealJS drop-in 5743 Space Grotesk · purple ramp (#856cb0) · same idiom as dag-confounder 5744 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5745 Each triangle opens as "Confounder?" on a hollow, dashed node; its 5746 step 2 crossfades in the confounder and fills the node. 5747 Each reveal is a Reveal fragment, in left-to-right order: advance 5748 as usual, or click a triangle. Clicking moves the deck's fragment 5749 position, so clicking the third reveals all three and clicking the 5750 first again hides all three, exactly as the arrow keys would. 5751 Size: --dag-size. Each .dag4 also works on its own slide. 5752 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5753--> 5754 5755<style> 5756.dag4-row { 5757 display: grid; 5758 grid-template-columns: repeat(3, 1fr); 5759 gap: 1.4em; 5760 max-width: 1020px; 5761 margin: 1.6em auto 0; 5762 font-size: 17px; 5763} 5764.dag4 { 5765 --dag-ink: #2a2632; 5766 --dag-accent: #856cb0; 5767 --dag-deep: #6f5a96; 5768 --dag-pale: #f3effa; 5769 --dag-lilac: #c9bce2; 5770 --dag-size: 19px; 5771 5772 position: relative; 5773 font-family: "Space Grotesk", -apple-system, sans-serif; 5774 font-size: var(--dag-size); 5775 line-height: 1; 5776 cursor: pointer; 5777 -webkit-tap-highlight-color: transparent; 5778} 5779.dag4 svg { display: block; width: 100%; height: auto; overflow: visible; } 5780 5781/* edges */ 5782.dag4 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; } 5783.dag4 .e-dash { stroke-dasharray: 9 6 2 6; } 5784 5785/* nodes */ 5786.dag4 .ring { fill: none; stroke-width: 1.5; } 5787.dag4 .n-x .core { fill: var(--dag-ink); } .dag4 .n-x .ring { stroke: var(--dag-ink); } 5788.dag4 .n-y .core { fill: var(--dag-lilac); } .dag4 .n-y .ring { stroke: var(--dag-lilac); } 5789 5790.dag4 .n-z .core { fill: var(--dag-pale); transition: fill .5s ease; } 5791.dag4 .n-z .ring { 5792 stroke: var(--dag-accent); 5793 stroke-dasharray: 4 4; 5794 transition: stroke-dasharray .5s ease; 5795} 5796.dag4.is-on .n-z .core { fill: var(--dag-accent); } 5797.dag4.is-on .n-z .ring { stroke-dasharray: 0 0; } 5798 5799/* labels */ 5800.dag4 .lab { 5801 position: absolute; 5802 transform: translate(-50%, -50%); 5803 display: grid; 5804 padding: .22em .45em .26em; 5805 border-radius: 5px; 5806 font-weight: 500; 5807 letter-spacing: -.005em; 5808 white-space: nowrap; 5809} 5810.dag4 .lab-x { left: 21.9%; top: 92.7%; background: var(--dag-ink); color: #fff; } 5811.dag4 .lab-y { left: 78.1%; top: 92.7%; background: var(--dag-lilac); color: var(--dag-ink); } 5812.dag4 .lab-z { 5813 left: 50%; top: 26.7%; 5814 background: var(--dag-accent); color: #fff; 5815 transition: background-color .5s ease; 5816} 5817.dag4.is-on .lab-z { background: var(--dag-deep); } 5818 5819/* crossfade: both states share one grid cell, so the box never jumps */ 5820.dag4 .lab-z > span { 5821 grid-area: 1 / 1; 5822 justify-self: center; 5823 transition: opacity .35s ease, transform .35s ease, filter .35s ease; 5824} 5825.dag4 .lab-z .s2 { opacity: 0; transform: translateY(.3em); filter: blur(3px); } 5826.dag4.is-on .lab-z .s1 { opacity: 0; transform: translateY(-.3em); filter: blur(3px); } 5827.dag4.is-on .lab-z .s2 { opacity: 1; transform: none; filter: none; transition-delay: .1s; } 5828 5829.dag4 .dag4-frag { position: absolute; width: 0; height: 0; overflow: hidden; } 5830 5831@media (prefers-reduced-motion: reduce) { 5832 .dag4 *, .dag4 .lab-z > span { transition: none !important; filter: none !important; } 5833} 5834</style> 5835 5836<div class="dag4-row"> 5837 5838 <!-- 1 · Ice cream â crime, confounded by season --> 5839 <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as season, causes both ice cream sales and crime."> 5840 <svg viewbox="0 0 320 300" aria-hidden="true"> 5841 <defs><marker id="dag4-arr-1" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
5841<path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs> 5842 <path class="e" marker-end="url(#dag4-arr-1)" d="M150 100 C128 152 88 168 76 210"></path> 5843 <path class="e" marker-end="url(#dag4-arr-1)" d="M170 100 C192 152 232 168 244 210"></path> 5844 <path class="e e-dash" marker-end="url(#dag4-arr-1)" d="M96 236 H221"></path> 5845 <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g> 5846 <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g> 5847 <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g> 5848 </svg> 5849 <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Season</span></span> 5850 <span class="lab lab-x">Ice Cream</span> 5851 <span class="lab lab-y">Crime</span> 5852 <span class="fragment dag4-frag"></span> 5853 </div> 5854 5855 <!-- 2 · Private school â SAT scores, confounded by parental SES --> 5856 <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as parental SES, causes both private schooling and SAT scores."> 5857 <svg viewbox="0 0 320 300" aria-hidden="true"> 5858 <defs><marker id="dag4-arr-2" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs> 5859 <path class="e" marker-end="url(#dag4-arr-2)" d="M150 100 C128 152 88 168 76 210"></path> 5860 <path class="e" marker-end="url(#dag4-arr-2)" d="M170 100 C192 152 232 168 244 210"></path> 5861 <path class="e e-dash" marker-end="url(#dag4-arr-2)" d="M96 236 H221"></path> 5862 <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g> 5863 <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g> 5864 <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g> 5865 </svg> 5866 <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Parental SES</span></span> 5867 <span class="lab lab-x">Private School</span> 5868 <span class="lab lab-y">SAT Scores</span> 5869 <span class="fragment dag4-frag"></span> 5870 </div> 5871 5872 <!-- 3 · Shoe size â reading ability, confounded by age --> 5873 <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as age, causes both shoe size and reading ability."> 5874 <svg viewbox="0 0 320 300" aria-hidden="true"> 5875 <defs><marker id="dag4-arr-3" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs> 5876 <path class="e" marker-end="url(#dag4-arr-3)" d="M150 100 C128 152 88 168 76 210"></path> 5877 <path class="e" marker-end="url(#dag4-arr-3)" d="M170 100 C192 152 232 168 244 210"></path> 5878 <path class="e e-dash" marker-end="url(#dag4-arr-3)" d="M96 236 H221"></path> 5879 <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g> 5880 <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g> 5881 <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g> 5882 </svg> 5883 <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Age</span></span> 5884 <span class="lab lab-x">Shoe Size</span> 5885 <span class="lab lab-y">Reading Ability</span> 5886 <span class="fragment dag4-frag"></span> 5887 </div> 5888 5889</div> 5890
5891<script> 5892(function () { 5893 /* bind after load, so Reveal exists whether its script sits before or 5894 after this block */ 5895 function bind() { 5896 var R = window.Reveal && Reveal.on ? Reveal : null; 5897 document.querySelectorAll('.dag4').forEach(function (d) { 5898 if (d.dataset.bound) return; 5899 d.dataset.bound = '1'; 5900 5901 var f = d.querySelector('.dag4-frag'); 5902 function set(on) { d.classList.toggle('is-on', on); } 5903 function sync() { set(f.classList.contains('visible')); } 5904 5905 if (R) { 5906 /* a single advance can show several fragments at once (e.g. a 5907 click on the third triangle), so check every triangle */ 5908 R.on('fragmentshown', sync); 5909 R.on('fragmenthidden', sync); 5910 R.on('slidechanged', sync); 5911 R.on('ready', sync); 5912 sync(); 5913 } 5914 5915 /* click toggles by moving the deck's fragment position, so arrow 5916 keys and clicks never disagree */ 5917 d.addEventListener('click', function () { 5918 var on = !d.classList.contains('is-on'); 5919 var i = parseInt(f.getAttribute('data-fragment-index'), 10); 5920 if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1); 5921 else set(on); 5922 }); 5923 }); 5924 } 5925 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind); 5926 else bind(); 5927})(); 5928</script>
5928 5929 5930</section> 5931<section id="dags-4" class="slide level2"> 5932<h2>DAGs</h2> 5933<h4 id="do-not-condition-on-a-collider"><span style="color:#a9a9a9;">Do Not Condition on a Collider</span></h4> 5934<!-- 5935 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5936 DAG · conditioning on a collider (two examples) · Quarto RevealJS 5937 Space Grotesk · purple ramp (#856cb0) · same idiom as the income DAG 5938 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5939 Base: two unconnected causes point into a round collider. 5940 Adjusted: the collider squares off into a box and a "Conditioned" 5941 tag fades in; a dashed purple curve draws in between the 5942 two causes (an association induced by the conditioning, 5943 not a causal arrow). 5944 Each example's Adjusted state is a Reveal fragment, left to right: 5945 advance as usual, or click a DAG. Clicks move the deck's fragment 5946 position, so they follow the same order as the arrow keys. 5947 Size: --dag-size. 5948 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5949--> 5950 5951<style> 5952.dag5-row { 5953 display: grid; 5954 grid-template-columns: repeat(2, 1fr); 5955 gap: 2.4em; 5956 max-width: 1000px; 5957 margin: 1.4em auto 0; 5958 font-size: 17px; 5959} 5960.dag5 { 5961 --dag-ink: #2a2632; 5962 --dag-accent: #856cb0; 5963 --dag-deep: #6f5a96; 5964 --dag-pale: #f3effa; 5965 --dag-size: 19px; 5966 5967 position: relative; 5968 font-family: "Space Grotesk", -apple-system, sans-serif; 5969 font-size: var(--dag-size); 5970 line-height: 1; 5971 cursor: pointer; 5972 -webkit-tap-highlight-color: transparent; 5973} 5974.dag5 svg { display: block; width: 100%; height: auto; overflow: visible; } 5975 5976/* causal edges */ 5977.dag5 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; } 5978 5979/* induced association: dashed curve, drawn in through a mask */ 5980.dag5 .assoc { fill: none; stroke: var(--dag-accent); stroke-width: 2.4; stroke-dasharray: 7 6; stroke-linecap: round; } 5981.dag5 .assoc-mask { 5982 fill: none; stroke: #fff; stroke-width: 12; 5983 stroke-dasharray: 1 1; stroke-dashoffset: 1; 5984 transition: stroke-dashoffset .8s cubic-bezier(.22,.61,.36,1); 5985} 5986.dag5.is-on .assoc-mask { stroke-dashoffset: 0; transition-delay: .2s; } 5987.dag5 .assoc-end { fill: var(--dag-accent); opacity: 0; transition: opacity .3s ease; } 5988.dag5.is-on .assoc-end { opacity: 1; transition-delay: .2s; } 5989 5990/* nodes */ 5991.dag5 .core, .dag5 .ring { transition: rx .45s cubic-bezier(.22,.61,.36,1), ry .45s cubic-bezier(.22,.61,.36,1), fill .45s ease, stroke .45s ease; } 5992.dag5 .ring { fill: none; stroke-width: 1.5; } 5993.dag5 .n-a .core { fill: var(--dag-ink); } .dag5 .n-a .ring { stroke: var(--dag-ink); } 5994 5995/* the collider: circle (base) â box (conditioned) */ 5996.dag5 .n-c .core { fill: var(--dag-pale); rx: 18px; ry: 18px; } 5997.dag5 .n-c .ring { stroke: var(--dag-accent); rx: 21px; ry: 21px; } 5998.dag5.is-on .n-c .core { fill: var(--dag-accent); rx: 4px; ry: 4px; } 5999.dag5.is-on .n-c .ring { stroke: var(--dag-deep); rx: 6px; ry: 6px; } 6000 6001/* labels */ 6002.dag5 .lab { 6003 position: absolute; 6004 transform: translate(-50%, -50%); 6005 padding: .22em .45em .26em; 6006 border-radius: 5px; 6007 font-weight: 500; 6008 letter-spacing: -.005em; 6009 white-space: nowrap; 6010} 6011.dag5 .lab em { font-style: italic; } 6012.dag5 .lab-a1 { left: 21.6%; top: 20.6%; background: var(--dag-ink); color: #fff; } 6013.dag5 .lab-a2 { left: 78.4%; top: 20.6%; background: var(--dag-ink); color: #fff; } 6014.dag5 .lab-c { left: 50%; top: 94.2%; background: var(--dag-accent); color: #fff; transition: background-color .45s ease; } 6015.dag5.is-on .lab-c { background: var(--dag-deep); } 6016 6017/* "Conditioned" tag, right of the collider */ 6018.dag5 .tag { 6019 position: absolute; 6020 left: 55.9%; top: 79%; 6021 transform: translate(0, -50%) translateX(-.4em);
6022 padding: .28em .55em; 6023 border: 1px solid rgba(133, 108, 176, .45); 6024 border-radius: 999px; 6025 background: #fff; 6026 font-family: "JetBrains Mono", ui-monospace, monospace; 6027 font-size: .58em; 6028 letter-spacing: .1em; 6029 text-transform: uppercase; 6030 color: var(--dag-deep); 6031 white-space: nowrap; 6032 opacity: 0; 6033 transition: opacity .35s ease, transform .35s ease; 6034} 6035.dag5.is-on .tag { opacity: 1; transform: translate(0, -50%); transition-delay: .15s; } 6036 6037.dag5 .dag5-frag { position: absolute; width: 0; height: 0; overflow: hidden; } 6038 6039@media (prefers-reduced-motion: reduce) { 6040 .dag5 *, .dag5 .tag { transition: none !important; } 6041} 6042</style> 6043 6044<div class="dag5-row"> 6045 6046 <!-- 1 · Talent â Hollywood Success â Beauty --> 6047 <div class="dag5" role="img" aria-label="Collider DAG: talent and beauty both cause Hollywood success and are otherwise unrelated. Conditioning on Hollywood success induces an association between talent and beauty."> 6048 <svg viewbox="0 0 440 310" aria-hidden="true"> 6049 <defs> 6050 <marker id="dag5-arr-1" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker> 6051 <mask id="dag5-m-1" maskunits="userSpaceOnUse" x="0" y="0" width="440" height="310"> 6052 <path class="assoc-mask" pathlength="1" d="M119 124 Q220 196 321 124"></path> 6053 </mask> 6054 </defs> 6055 <path class="e" marker-end="url(#dag5-arr-1)" d="M111.3 127.6 L203 226.7"></path> 6056 <path class="e" marker-end="url(#dag5-arr-1)" d="M328.7 127.6 L237 226.7"></path> 6057 <path class="assoc" mask="url(#dag5-m-1)" d="M119 124 Q220 196 321 124"></path> 6058 <circle class="assoc-end" cx="119" cy="124" r="3"></circle><circle class="assoc-end" cx="321" cy="124" r="3"></circle> 6059 <g class="n-a"><circle class="core" cx="95" cy="110" r="18"></circle><circle class="ring" cx="95" cy="110" r="21"></circle></g> 6060 <g class="n-a"><circle class="core" cx="345" cy="110" r="18"></circle><circle class="ring" cx="345" cy="110" r="21"></circle></g> 6061 <g class="n-c"><rect class="core" x="202" y="227" width="36" height="36" rx="18" ry="18"></rect><rect class="ring" x="199" y="224" width="42" height="42" rx="21" ry="21"></rect></g> 6062 </svg> 6063 <span class="lab lab-a1">Talent</span> 6064 <span class="lab lab-a2">Beauty</span> 6065 <span class="lab lab-c">Hollywood Success</span> 6066 <span class="tag">Conditioned</span> 6067 <span class="fragment dag5-frag"></span> 6068 </div> 6069 6070 <!-- 2 · Rolling Stone 500 â Sample Selection â Chart Topper --> 6071 <div class="dag5" role="img" aria-label="Collider DAG: Rolling Stone 500 inclusion and chart-topping both cause sample selection and are otherwise unrelated. Conditioning on sample selection induces an association between them."> 6072 <svg viewbox="0 0 440 310" aria-hidden="true"> 6073 <defs> 6074 <marker id="dag5-arr-2" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker> 6075 <mask id="dag5-m-2" maskunits="userSpaceOnUse" x="0" y="0" width="440" height="310"> 6076 <path class="assoc-mask" pathlength="1" d="M119 124 Q220 196 321 124"></path> 6077 </mask> 6078 </defs> 6079 <path class="e" marker-end="url(#dag5-arr-2)" d="M111.3 127.6 L203 226.7"></path> 6080 <path class="e" marker-end="url(#dag5-arr-2)" d="M328.7 127.6 L237 226.7"></path> 6081 <path class="assoc" mask="url(#dag5-m-2)" d="M119 124 Q220 196 321 124"></path> 6082 <circle class="assoc-end" cx="119" cy="124" r="3"></circle><circle class="assoc-end" cx="321" cy="124" r="3"></circle> 6083 <g class="n-a"><circle class="core" cx="95" cy="110" r="18"></circle><circle class="ring" cx="95" cy="110" r="21"></circle></g> 6084 <g class="n-a"><circle class="core" cx="345" cy="110" r="18"></circle><circle class="ring" cx="345" cy="110" r="21"></circle></g> 6085 <g class="n-c"><rect class="core" x="202" y="227" width="36" height="36" rx="18" ry="18"></rect><rect class="ring" x="199" y="224" width="42" height="42" rx="21" ry="21"></rect></g> 6086 </svg> 6087 <span class="lab lab-a1"><em>Rolling Stone</em> 500</span> 6088 <span class="lab lab-a2">Chart Topper</span> 6089 <span class="lab lab-c">Sample Selection</span> 6090 <span class="tag">Conditioned</span> 6091 <span class="fragment dag5-frag"></span> 6092 </div> 6093 6094</div> 6095
6096<script> 6097(function () { 6098 /* bind after load, so Reveal exists whether its script sits before or 6099 after this block */ 6100 function bind() { 6101 var R = window.Reveal && Reveal.on ? Reveal : null; 6102 document.querySelectorAll('.dag5').forEach(function (d) { 6103 if (d.dataset.bound) return; 6104 d.dataset.bound = '1'; 6105 6106 var f = d.querySelector('.dag5-frag'); 6107 function set(on) { d.classList.toggle('is-on', on); } 6108 function sync() { set(f.classList.contains('visible')); } 6109 6110 if (R) { 6111 R.on('fragmentshown', sync); 6112 R.on('fragmenthidden', sync); 6113 R.on('slidechanged', sync); 6114 R.on('ready', sync); 6115 sync(); 6116 } 6117 6118 /* click toggles by moving the deck's fragment position, so arrow 6119 keys and clicks never disagree */ 6120 d.addEventListener('click', function () { 6121 var on = !d.classList.contains('is-on'); 6122 var i = parseInt(f.getAttribute('data-fragment-index'), 10); 6123 if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1); 6124 else set(on); 6125 }); 6126 }); 6127 } 6128 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind); 6129 else bind(); 6130})(); 6131</script>
6131 6132 6133</section> 6134<section id="dags-5" class="slide level2"> 6135<h2>DAGs</h2> 6136<div class="rq-block" style="margin-top: 100px; text-align: center;"> 6137<p>In the next <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">5-10 minutes</span>, come up with a DAG related to your research project!</p> 6138</div> 6139</section></section> 6140<section> 6141<section id="enjoy-the-weekend" class="title-slide slide level1 center" data-background-color="#43464B"> 6142<h1>Enjoy the Weekend</h1> 6143 6144</section> 6145<section id="references" class="slide level2 smaller scrollable"> 6146<h2>References</h2> 6147<!-- <div style="text-align: center; margin-bottom: 15px"> --> 6148<!-- [**Note**: Scroll []{style="color: #856cb0;"} to access the entire bibliography]{style="color: #9c9c9c; font-size:95%;"} --> 6149<!-- </div> --> 6150 6151<div id="refs" class="references csl-bib-body hanging-indent" role="list"> 6152<div id="ref-carr_art_2020" class="csl-entry" role="listitem"> 6153Carr, Deborah S., Elizabeth Heger Boyle, Benjamin Cornwell, Shelley J. Correll, Robert Crosnoe, et al. 2020. <em>The Art and Science of Social Research</em>. Second Edition. New York: W. W. Norton & Company, Inc. 6154</div> 6155<div id="ref-cunningham2026remix" class="csl-entry" role="listitem"> 6156Cunningham, Scott. 2026. <em><a href="https://mixtape.scunning.com/">Causal Inference: The Remix</a></em>. 2nd ed. New Haven, CT: Yale University Press. 6157</div> 6158<div id="ref-llaudet_data_2023" class="csl-entry" role="listitem"> 6159Llaudet, Elena, and KÅsuke Imai. 2023. <em>Data Analysis for Social Science: A Friendly and Practical Introduction</em>. Princeton (N.J.) Oxford: Princeton University press. 6160</div> 6161<div id="ref-small_causal_2013" class="csl-entry" role="listitem"> 6162Small, Mario Luis. 2013. <span>âCausal <span>Thinking</span> and <span>Ethnographic</span> <span>Research</span>.â</span> <em>American Journal of Sociology</em> 119(3):597â601. doi: <a href="https://doi.org/10.1086/675893">10.1086/675893</a>. 6163</div> 6164</div> 6165</section></section> 6166 </div> 6167 <div class="quarto-auto-generated-content" style="display: none;"> 6168<p><img src="media/amherst_seal_light.png" class="slide-logo"></p> 6169<div class="footer footer-default"> 6170 6171</div> 6172</div></div> 6173 6174
6174<script>window.backupDefine = window.define; window.define = undefined;</script>
6174 6175
6175<script src="index_files/libs/revealjs/dist/reveal.js"></script>
6175 6176 <!-- reveal.js plugins --> 6177
6177<script src="index_files/libs/revealjs/plugin/quarto-line-highlight/line-highlight.js"></script>
6177 6178
6178<script src="index_files/libs/revealjs/plugin/pdf-export/pdfexport.js"></script>
6178 6179
6179<script src="index_files/libs/revealjs/plugin/reveal-menu/menu.js"></script>
6179 6180
6180<script src="index_files/libs/revealjs/plugin/reveal-menu/quarto-menu.js"></script>
6180 6181
6181<script src="index_files/libs/revealjs/plugin/reveal-chalkboard/plugin.js"></script>
6181 6182
6182<script src="index_files/libs/revealjs/plugin/quarto-support/support.js"></script>
6182 6183 6184 6185
6185<script src="index_files/libs/revealjs/plugin/notes/notes.js"></script>
6185 6186
6186<script src="index_files/libs/revealjs/plugin/search/search.js"></script>
6186 6187
6187<script src="index_files/libs/revealjs/plugin/zoom/zoom.js"></script>
6187 6188
6188<script>window.define = window.backupDefine; window.backupDefine = undefined;</script>
6188 6189 6190
6190<script> 6191 6192 // Full list of configuration options available at: 6193 // https://revealjs.com/config/ 6194 Reveal.initialize({ 6195'controlsAuto': true, 6196'previewLinksAuto': true, 6197'pdfSeparateFragments': false, 6198'autoAnimateEasing': "ease", 6199'autoAnimateDuration': 1, 6200'autoAnimateUnmatched': true, 6201'jumpToSlide': true, 6202'menu': {"side":"left","useTextContentForMissingTitles":true,"markers":false,"loadIcons":false,"custom":[{"title":"Tools","icon":"<i class=\"fas fa-gear\"></i>","content":"<ul class=\"slide-menu-items\">\n<li class=\"slide-tool-item active\" data-item=\"0\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.fullscreen(event)\"><kbd>f</kbd> Fullscreen</a></li>\n<li class=\"slide-tool-item\" data-item=\"1\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.speakerMode(event)\"><kbd>s</kbd> Speaker View</a></li>\n<li class=\"slide-tool-item\" data-item=\"2\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.overview(event)\"><kbd>o</kbd> Slide Overview</a></li>\n<li class=\"slide-tool-item\" data-item=\"3\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.togglePdfExport(event)\"><kbd>e</kbd> PDF Export Mode</a></li>\n<li class=\"slide-tool-item\" data-item=\"4\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleScrollView(event)\"><kbd>r</kbd> Scroll View Mode</a></li>\n<li class=\"slide-tool-item\" data-item=\"5\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleChalkboard(event)\"><kbd>b</kbd> Toggle Chalkboard</a></li>\n<li class=\"slide-tool-item\" data-item=\"6\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleNotesCanvas(event)\"><kbd>c</kbd> Toggle Notes Canvas</a></li>\n<li class=\"slide-tool-item\" data-item=\"7\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.downloadDrawings(event)\"><kbd>d</kbd> Download Drawings</a></li>\n<li class=\"slide-tool-item\" data-item=\"8\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.keyboardHelp(event)\"><kbd>?</kbd> Keyboard Help</a></li>\n</ul>"}],"openButton":true,"numbers":true,"width":"wide"}, 6203'chalkboard': {"buttons":true}, 6204'smaller': false, 6205 6206 // Display controls in the bottom right corner 6207 controls: false, 6208 6209 // Help the user learn the controls by providing hints, for example by 6210 // bouncing the down arrow when they first encounter a vertical slide 6211 controlsTutorial: false, 6212 6213 // Determines where controls appear, "edges" or "bottom-right" 6214 controlsLayout: 'edges', 6215 6216 // Visibility rule for backwards navigation arrows; "faded", "hidden" 6217 // or "visible" 6218 controlsBackArrows: 'faded', 6219 6220 // Display a presentation progress bar 6221 progress: true, 6222 6223 // Display the page number of the current slide 6224 slideNumber: false, 6225 6226 // 'all', 'print', or 'speaker' 6227 showSlideNumber: 'all', 6228 6229 // Add the current slide number to the URL hash so that reloading the 6230 // page/copying the URL will return you to the same slide 6231 hash: true, 6232 6233 // Start with 1 for the hash rather than 0 6234 hashOneBasedIndex: false, 6235 6236 // Flags if we should monitor the hash and change slides accordingly 6237 respondToHashChanges: true, 6238 6239 // Push each slide change to the browser history 6240 history: true, 6241 6242 // Enable keyboard shortcuts for navigation 6243 keyboard: true, 6244 6245 // Enable the slide overview mode 6246 overview: true, 6247 6248 // Disables the default reveal.js slide layout (scaling and centering) 6249 // so that you can use custom CSS layout 6250 disableLayout: false, 6251 6252 // Vertical centering of slides 6253 center: false, 6254 6255 // Enables touch navigation on devices with touch input 6256 touch: true, 6257 6258 // Loop the presentation 6259 loop: false, 6260 6261 // Change the presentation direction to be RTL 6262 rtl: false, 6263 6264 // see https://revealjs.com/vertical-slides/#navigation-mode 6265 navigationMode: 'linear', 6266 6267 // Randomizes the order of slides each time the presentation loads 6268 shuffle: false, 6269 6270 // Turns fragments on and off globally 6271 fragments: true, 6272 6273 // Flags whether to include the current fragment in the URL, 6274 // so that reloading brings you to the same fragment position 6275 fragmentInURL: false, 6276 6277 // Flags if the presentation is running in an embedded mode, 6278 // i.e. contained within a limited portion of the screen 6279 embedded: false, 6280 6281 // Flags if we should show a help overlay when the questionmark 6282 // key is pressed 6283 help: true, 6284 6285 // Flags if it should be possible to pause the presentation (blackout) 6286 pause: true, 6287 6288 // Flags if speaker notes should be visible to all viewers 6289 showNotes: false, 6290 6291 // Global override for autoplaying embedded media (null/true/false) 6292 autoPlayMedia: null, 6293 6294 // Global override for preloading lazy-loaded iframes (null/true/false) 6295 preloadIframes: null, 6296 6297 // Number of milliseconds between automatically proceeding to the
6298 // next slide, disabled when set to 0, this value can be overwritten 6299 // by using a data-autoslide attribute on your slides 6300 autoSlide: 0, 6301 6302 // Stop auto-sliding after user input 6303 autoSlideStoppable: true, 6304 6305 // Use this method for navigation when auto-sliding 6306 autoSlideMethod: null, 6307 6308 // Specify the average time in seconds that you think you will spend 6309 // presenting each slide. This is used to show a pacing timer in the 6310 // speaker view 6311 defaultTiming: null, 6312 6313 // Enable slide navigation via mouse wheel 6314 mouseWheel: false, 6315 6316 // The display mode that will be used to show slides 6317 display: 'block', 6318 6319 // Hide cursor if inactive 6320 hideInactiveCursor: true, 6321 6322 // Time before the cursor is hidden (in ms) 6323 hideCursorTime: 5000, 6324 6325 // Opens links in an iframe preview overlay 6326 previewLinks: false, 6327 6328 // Transition style (none/fade/slide/convex/concave/zoom) 6329 transition: 'none', 6330 6331 // Transition speed (default/fast/slow) 6332 transitionSpeed: 'default', 6333 6334 // Transition style for full page slide backgrounds 6335 // (none/fade/slide/convex/concave/zoom) 6336 backgroundTransition: 'none', 6337 6338 // Number of slides away from the current that are visible 6339 viewDistance: 3, 6340 6341 // Number of slides away from the current that are visible on mobile 6342 // devices. It is advisable to set this to a lower number than 6343 // viewDistance in order to save resources. 6344 mobileViewDistance: 2, 6345 6346 // The "normal" size of the presentation, aspect ratio will be preserved 6347 // when the presentation is scaled to fit different resolutions. Can be 6348 // specified using percentage units. 6349 width: 1050, 6350 6351 height: 700, 6352 6353 // Factor of the display size that should remain empty around the content 6354 margin: 0.1, 6355 6356 // reveal.js plugins 6357 plugins: [QuartoLineHighlight, PdfExport, RevealMenu, RevealChalkboard, QuartoSupport, 6358 6359 RevealNotes, 6360 RevealSearch, 6361 RevealZoom 6362 ] 6363 }); 6364 </script>
6364 6365
6365<script> 6366 (function () { 6367 function isFootnotesSlide(section) { 6368 if (!section) return false; 6369 6370 // Common pandoc/quarto patterns: 6371 if (section.id === "footnotes") return true; 6372 if (section.classList && section.classList.contains("footnotes")) return true; 6373 6374 // Sometimes the slide *contains* a footnotes container: 6375 if (section.querySelector && section.querySelector("div.footnotes, section.footnotes")) return true; 6376 6377 // Fallback: slide heading literally âFootnotesâ (case-insensitive) 6378 const h = section.querySelector && section.querySelector("h1,h2,h3"); 6379 if (h && h.textContent && h.textContent.trim().toLowerCase() === "footnotes") return true; 6380 6381 return false; 6382 } 6383 6384 function removeFootnotesSlides() { 6385 // Remove both horizontal slides and vertical stacks if needed 6386 const sections = Array.from(document.querySelectorAll(".reveal .slides section")); 6387 sections.forEach(sec => { 6388 if (isFootnotesSlide(sec)) sec.remove(); 6389 }); 6390 6391 // If we removed something after Reveal init, resync 6392 if (window.Reveal && typeof window.Reveal.sync === "function") { 6393 window.Reveal.sync(); 6394 } 6395 } 6396 6397 // Run after load (works whether Reveal has initialized yet or not) 6398 window.addEventListener("load", removeFootnotesSlides); 6399 })(); 6400 </script>
6400 6401 <style> 6402 /* Detail-panel list items: hidden until stepped in by the script */ 6403 details .dx-step { 6404 opacity: 0; 6405 visibility: hidden; 6406 transition: opacity .3s ease, visibility 0s linear .3s; 6407 } 6408 details .dx-step.dx-on { 6409 opacity: 1; 6410 visibility: visible; 6411 transition: opacity .3s ease; 6412 } 6413 </style> 6414 6415
6415<script> 6416 (() => { 6417 const SEL = 'details.reveal-detail, details.reveal-detail-above, details.reveal-detail-push'; 6418 const NEXT = new Set(['ArrowRight', 'ArrowDown', 'PageDown', 'n', 'N', 'l', 'j']); 6419 const PREV = new Set(['ArrowLeft', 'ArrowUp', 'PageUp', 'p', 'P', 'h', 'k']); 6420 6421 /* Take .incremental items inside detail panels out of Reveal's 6422 fragment system permanently; the script steps them instead. */ 6423 const convert = () => document.querySelectorAll(SEL).forEach(d => 6424 d.querySelectorAll('.fragment').forEach(el => { 6425 el.classList.remove('fragment', 'visible', 'current-fragment'); 6426 el.removeAttribute('data-fragment-index'); 6427 el.classList.add('dx-step'); 6428 }) 6429 ); 6430 6431 const steps = d => Array.from(d.querySelectorAll('.dx-step')); 6432 const reset = d => steps(d).forEach(s => s.classList.remove('dx-on')); 6433 6434 /* Closed panels, empty list: the state every visit starts from */ 6435 const fresh = slide => { 6436 if (!slide) return; 6437 slide.querySelectorAll(SEL).forEach(d => { d.open = false; reset(d); }); 6438 }; 6439 6440 /* Returns an action if an open panel on the current slide should 6441 consume this step; null means Reveal navigates as normal. */ 6442 const claim = dir => { 6443 if (!window.Reveal) return null; 6444 if ((Reveal.isOverview && Reveal.isOverview()) || 6445 (Reveal.isPaused && Reveal.isPaused())) return null; 6446 const slide = Reveal.getCurrentSlide(); 6447 if (!slide) return null; 6448 const open = Array.from(slide.querySelectorAll(SEL)).filter(d => d.open); 6449 if (dir > 0) { 6450 for (const d of open) { 6451 const s = steps(d).find(x => !x.classList.contains('dx-on')); 6452 if (s) return () => s.classList.add('dx-on'); 6453 } 6454 } else { 6455 for (const d of open.reverse()) { 6456 const on = steps(d).filter(x => x.classList.contains('dx-on')); 6457 if (on.length) return () => on[on.length - 1].classList.remove('dx-on'); 6458 } 6459 } 6460 return null; 6461 }; 6462 6463 const editable = t => 6464 t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)); 6465 6466 /* Keyboard and presentation clickers (PageDown/PageUp/arrows) */ 6467 window.addEventListener('keydown', e => { 6468 if (e.altKey || e.ctrlKey || e.metaKey || editable(e.target)) return; 6469 const onSummary = e.target.closest && e.target.closest('summary'); 6470 if (onSummary && (e.key === ' ' || e.key === 'Enter')) return; // let these toggle the panel 6471 let dir = 0; 6472 if (e.key === ' ') dir = e.shiftKey ? -1 : 1; 6473 else if (NEXT.has(e.key)) dir = 1; 6474 else if (PREV.has(e.key)) dir = -1; 6475 if (!dir) return; 6476 const act = claim(dir); 6477 if (!act) return; 6478 e.preventDefault(); 6479 e.stopImmediatePropagation(); 6480 act(); 6481 }, true); 6482 6483 /* Reveal's on-screen navigation arrows */ 6484 window.addEventListener('click', e => { 6485 const b = e.target.closest && e.target.closest( 6486 '.navigate-right, .navigate-down, .navigate-next, ' + 6487 '.navigate-left, .navigate-up, .navigate-prev'); 6488 if (!b) return; 6489 const dir = b.matches('.navigate-right, .navigate-down, .navigate-next') ? 1 : -1; 6490 const act = claim(dir); 6491 if (!act) return; 6492 e.preventDefault(); 6493 e.stopImmediatePropagation(); 6494 act(); 6495 }, true); 6496 6497 /* Opening or closing a panel restarts its list; drop focus from the 6498 summary so the next Space advances instead of re-toggling. */ 6499 document.addEventListener('toggle', e => { 6500 const d = e.target; 6501 if (!(d instanceof HTMLDetailsElement) || !d.matches(SEL)) return; 6502 reset(d); 6503 const s = d.querySelector('summary'); 6504 if (s && document.activeElement === s) s.blur(); 6505 }, true); 6506 6507 const init = () => { 6508 convert(); 6509 Reveal.sync(); 6510 6511 /* Reset on the way out and on the way in, so every visit â 6512 forward, backward, or by jump link â starts fresh. */ 6513 Reveal.on('slidechanged', ev => { 6514 fresh(ev.previousSlide); 6515 fresh(ev.currentSlide); 6516 }); 6517 6518 /* Covers loading the deck directly onto a slide with a panel */ 6519 fresh(Reveal.getCurrentSlide()); 6520 }; 6521 6522 convert(); 6523 if (window.Reveal && Reveal.isReady && Reveal.isReady()) init(); 6524 else if (window.Reveal) Reveal.on('ready', init); 6525 })(); 6526 </script>
6526 6527 <!-- 6528 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 6529 FRAME-EXPAND · trigger â near-full-page embedded site 6530 Space Grotesk / JetBrains Mono · purple ramp (#856cb0) 6531 Works unchanged in a standalone Quarto HTML page or a RevealJS deck. 6532 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 6533 Load ONCE per document, either: 6534 ⢠YAML: include-after-body: frame-expand-purple.html (preferred), or 6535 ⢠paste inside a ```{=html} fence (one slide / anywhere in the .qmd) 6536 6537 Inline, in prose: 6538 6539 [Instructions]{.frame-expand data-src="https://soci-316-qualtrics.netlify.app/"} 6540 6541 As a centred button, add .fx-pill: 6542 6543 ::: {style="text-align: center;"} 6544 [Instructions]{.frame-expand .fx-pill data-src="https://soci-316-qualtrics.netlify.app/"} 6545 ::: 6546 6547 Optional: 6548 data-caption="Survey instructions" bar title (defaults to trigger text) 6549 6550 Closes on Ã, Esc, or a click on the backdrop. Each embedded page loads 6551 on first open and stays alive afterwards (across slide changes too), 6552 so progress inside it survives close/reopen until the document reloads. 6553 6554 RevealJS specifics: 6555 ⢠overlay lives on <body>, outside Reveal's transformed .slides 6556 ⢠Esc is caught before Reveal (no overview); Enter/Space on a trigger 6557 opens it without advancing the slide 6558 ⢠Reveal's keyboard is suspended while open, restored on close 6559 ⢠after a mouse-opened panel closes, focus is released so Space / 6560 arrows drive the deck again rather than reopening the panel 6561 6562 Same class names as the Spectral/red file: load one or the other per 6563 document, never both. 6564 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 6565 --> 6566 6567 <style> 6568 /* tokens scoped to the component, not :root, so the deck theme is untouched */ 6569 .frame-expand, .fx-overlay { 6570 --fx-accent: #856cb0; 6571 --fx-deep: #6f5a96; 6572 --fx-soft: #b7a5d3; 6573 --fx-tint: #f7f4fc; 6574 --fx-line: #c8bfff; 6575 --fx-ink: #2a2632; 6576 --fx-scrim: rgba(28, 22, 38, .55); 6577 --fx-sans: "Space Grotesk", system-ui, sans-serif; 6578 --fx-mono: "JetBrains Mono", ui-monospace, monospace; 6579 } 6580 6581 /* ââ inline trigger âââââââââââââââââââââââââââââââââââââââââââââââââ */ 6582 .frame-expand { 6583 color: var(--fx-accent); 6584 border-bottom: 1.5px dotted var(--fx-accent); 6585 padding-bottom: 1px; 6586 cursor: pointer; 6587 white-space: nowrap; 6588 transition: color .15s ease, border-color .15s ease; 6589 } 6590 .frame-expand:hover { color: var(--fx-deep); border-bottom-style: solid; } 6591 .frame-expand:focus-visible { outline: 2px solid var(--fx-soft); outline-offset: 2px; border-radius: 2px; } 6592 6593 .frame-expand .fx-ico { 6594 display: inline-block; 6595 width: .72em; height: .72em; 6596 margin-left: .28em; 6597 vertical-align: .02em; 6598 opacity: .75; 6599 } 6600 .frame-expand .fx-ico svg { display: block; width: 100%; height: 100%; } 6601 6602 /* ââ pill trigger âââââââââââââââââââââââââââââââââââââââââââââââââââ */ 6603 .frame-expand.fx-pill { 6604 display: inline-flex; 6605 align-items: center; 6606 gap: .45em; 6607 padding: .45em 1.05em; 6608 border: 1px solid rgba(133, 108, 176, .4); 6609 border-radius: 7px; 6610 background: rgba(133, 108, 176, .1); 6611 color: var(--fx-deep); 6612 font-weight: 500; 6613 line-height: 1.3; 6614 transition: background-color .18s ease, color .18s ease, border-color .18s ease, 6615 transform .18s ease, box-shadow .18s ease; 6616 } 6617 .frame-expand.fx-pill .fx-ico { margin-left: 0; } 6618 .frame-expand.fx-pill:hover, 6619 .frame-expand.fx-pill:focus-visible { 6620 background: var(--fx-accent); 6621 border-color: var(--fx-accent); 6622 color: #fff; 6623 transform: translateY(-1px); 6624 box-shadow: 0 4px 12px rgba(133, 108, 176, .35); 6625 } 6626 .frame-expand.fx-pill:focus-visible { outline: none; box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--fx-soft); } 6627 6628 /* ââ overlay (px throughout: it sits outside Reveal's scaled slides) â */ 6629 .fx-overlay { 6630 position: fixed; 6631 inset: 0; 6632 z-index: 10000; /* above Quarto navbar, Reveal controls/menu */ 6633 display: flex; 6634 align-items: center; 6635 justify-content: center; 6636 padding: clamp(8px, 2.5vh, 28px) clamp(8px, 2.5vw, 36px); 6637 background: var(--fx-scrim); 6638 -webkit-backdrop-filter: blur(6px); 6639 backdrop-filter: blur(6px); 6640 font-family: var(--fx-sans); 6641 opacity: 0; 6642 visibility: hidden; 6643 transition: opacity .22s ease, visibility .22s ease; 6644 } 6645 .fx-overlay.is-open { opacity: 1; visibility: visible; } 6646 6647 .fx-panel { 6648 display: flex; 6649 flex-direction: column; 6650 width: 100%; 6651 height: 100%; 6652 max-width: 1400px; 6653 background: #fff; 6654 border-radius: 12px; 6655 overflow: hidden; 6656 box-shadow: 0 24px 70px rgba(28, 22, 38, .35); 6657 transform: translateY(10px) scale(.99); 6658 transition: transform .22s ease; 6659 } 6660 .fx-overlay.is-open .fx-panel { transform: none; } 6661 6662 .fx-bar { 6663 display: flex; 6664 align-items: center; 6665 gap: 10px; 6666 padding: 9px 10px 9px 18px; 6667 background: var(--fx-tint); 6668 border-bottom: 1px solid var(--fx-line); 6669 } 6670 .fx-heading { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 12px; } 6671 .fx-title { 6672 font-family: var(--fx-sans); 6673 font-size: 16px; 6674 font-weight: 500; 6675 line-height: 1.3; 6676 color: var(--fx-ink); 6677 white-space: nowrap; 6678 overflow: hidden; 6679 text-overflow: ellipsis; 6680 } 6681 .fx-host { 6682 font-family: var(--fx-mono); 6683 font-size: 11.5px; 6684 letter-spacing: .03em; 6685 color: rgba(133, 108, 176, .7); 6686 white-space: nowrap; 6687 } 6688 6689 .fx-overlay .fx-btn { 6690 flex: none; 6691 display: inline-grid; 6692 place-items: center; 6693 width: 34px; height: 34px; 6694 margin: 0; 6695 padding: 0; 6696 font: inherit; 6697 line-height: 1; 6698 border: 1px solid rgba(133, 108, 176, .35); 6699 border-radius: 7px; 6700 background: #fff; 6701 color: var(--fx-deep); 6702 text-decoration: none; 6703 cursor: pointer; 6704 transition: background-color .15s ease, color .15s ease, border-color .15s ease; 6705 } 6706 .fx-overlay .fx-btn svg { width: 15px; height: 15px; } 6707 .fx-overlay .fx-btn:hover, 6708 .fx-overlay .fx-btn:focus-visible { 6709 background: var(--fx-accent); 6710 border-color: var(--fx-accent); 6711 color: #fff; 6712 } 6713 .fx-overlay .fx-btn:focus-visible { outline: 2px solid var(--fx-soft); outline-offset: 2px; } 6714 .fx-overlay a.fx-btn::after { display: none !important; } /* Quarto external-link icon */ 6715 6716 .fx-body { position: relative; flex: 1; background: #fff; } 6717 .fx-body iframe { 6718 position: absolute; 6719 inset: 0; 6720 width: 100%; 6721 height: 100%; 6722 max-width: none; 6723 max-height: none; 6724 margin: 0; 6725 border: 0; 6726 display: none; 6727 } 6728 .fx-body iframe.is-active { display: block; } 6729 6730 .fx-loading { 6731 position: absolute; 6732 inset: 0; 6733 display: none; 6734 place-items: center; 6735 font-family: var(--fx-mono); 6736 font-size: 12.5px; 6737 letter-spacing: .04em; 6738 color: rgba(133, 108, 176, .75); 6739 } 6740 .fx-loading.is-on { display: grid; } 6741 6742 @media (max-width: 640px) { 6743 .fx-overlay { padding: 0; } 6744 .fx-panel { border-radius: 0; } 6745 .fx-host { display: none; } 6746 } 6747 @media (prefers-reduced-motion: reduce) { 6748 .frame-expand, .frame-expand.fx-pill, .fx-overlay, .fx-panel, .fx-overlay .fx-btn { transition: none !important; } 6749 .fx-panel { transform: none; } 6750 } 6751 @media print { 6752 .fx-overlay, .frame-expand .fx-ico { display: none !important; } 6753 } 6754 </style> 6755 6756
6756<script> 6757 (function () { 6758 if (window.__fxReady) { if (window.__fxScan) window.__fxScan(); return; } 6759 window.__fxReady = true; 6760 6761 var GLYPH = '<span class="fx-ico"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 2.5h4v4M13.5 2.5 9 7M6.5 13.5h-4v-4M2.5 13.5 7 9"/></svg></span>'; 6762 var ICON_TAB = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 2.5h4v4M13.5 2.5 7.5 8.5M12 9.5v3a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3"/></svg>'; 6763 var ICON_CLOSE = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>'; 6764 6765 var overlay, titleEl, hostEl, tabBtn, closeBtn, body, loading; 6766 var frames = {}; /* src â iframe, kept alive */ 6767 var opener = null, openedByKey = false; 6768 var prevOverflow = '', prevKeyboard = null; 6769 6770 function deck() { 6771 var R = window.Reveal; 6772 return (R && typeof R.configure === 'function' && typeof R.getConfig === 'function' && 6773 (!R.isReady || R.isReady())) ? R : null; 6774 } 6775 function isOpen() { return !!overlay && overlay.classList.contains('is-open'); } 6776 6777 function build() { 6778 if (overlay) return; 6779 overlay = document.createElement('div'); 6780 overlay.className = 'fx-overlay'; 6781 overlay.setAttribute('role', 'dialog'); 6782 overlay.setAttribute('aria-modal', 'true'); 6783 overlay.setAttribute('aria-labelledby', 'fx-title'); 6784 overlay.setAttribute('aria-hidden', 'true'); 6785 overlay.innerHTML = 6786 '<div class="fx-panel">' + 6787 '<div class="fx-bar">' + 6788 '<div class="fx-heading"><span class="fx-title" id="fx-title"></span><span class="fx-host"></span></div>' + 6789 '<a class="fx-btn fx-tab" target="_blank" rel="noopener" aria-label="Open in new tab" title="Open in new tab">' + ICON_TAB + '</a>' + 6790 '<button type="button" class="fx-btn fx-close" aria-label="Close (Esc)" title="Close (Esc)">' + ICON_CLOSE + '</button>' + 6791 '</div>' + 6792 '<div class="fx-body"><div class="fx-loading">Loadingâ¦</div></div>' + 6793 '</div>'; 6794 /* on <body>, never inside .reveal .slides (transformed ancestor 6795 would break position: fixed) */ 6796 document.body.appendChild(overlay); 6797 6798 titleEl = overlay.querySelector('.fx-title'); 6799 hostEl = overlay.querySelector('.fx-host'); 6800 tabBtn = overlay.querySelector('.fx-tab'); 6801 closeBtn = overlay.querySelector('.fx-close'); 6802 body = overlay.querySelector('.fx-body'); 6803 loading = overlay.querySelector('.fx-loading'); 6804 6805 closeBtn.addEventListener('click', close); 6806 overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); }); 6807 } 6808 6809 function open(trigger, byKey) { 6810 var src = trigger.getAttribute('data-src'); 6811 if (!src) return; 6812 build(); 6813 var label = trigger.getAttribute('data-caption') || trigger.textContent.trim(); 6814 6815 titleEl.textContent = label; 6816 try { hostEl.textContent = new URL(src, location.href).hostname; } catch (e) { hostEl.textContent = ''; } 6817 tabBtn.href = src; 6818 6819 Object.keys(frames).forEach(function (k) { frames[k].classList.remove('is-active'); }); 6820 var f = frames[src]; 6821 if (!f) { 6822 f = document.createElement('iframe'); 6823 f.title = label; 6824 f.setAttribute('allow', 'fullscreen; clipboard-write'); 6825 loading.classList.add('is-on'); 6826 f.addEventListener('load', function () { loading.classList.remove('is-on'); }, { once: true }); 6827 f.src = src; 6828 body.appendChild(f); 6829 frames[src] = f; 6830 } else { 6831 loading.classList.remove('is-on'); 6832 } 6833 f.classList.add('is-active'); 6834 6835 opener = trigger; 6836 openedByKey = !!byKey; 6837 prevOverflow = document.documentElement.style.overflow; 6838 document.documentElement.style.overflow = 'hidden'; 6839 6840 var R = deck(); 6841 if (R) { prevKeyboard = R.getConfig().keyboard; R.configure({ keyboard: false }); } 6842 6843 overlay.setAttribute('aria-hidden', 'false'); 6844 overlay.classList.add('is-open'); 6845 closeBtn.focus({ preventScroll: true }); 6846 } 6847 6848 function close() { 6849 if (!isOpen()) return; 6850 overlay.classList.remove('is-open'); 6851 overlay.setAttribute('aria-hidden', 'true'); 6852 document.documentElement.style.overflow = prevOverflow; 6853 6854 var R = deck(); 6855 if (R && prevKeyboard !== null) { R.configure({ keyboard: prevKeyboard }); prevKeyboard = null; } 6856 6857 if (opener) { 6858 if (R && !openedByKey) { 6859 /* mouse-opened in a deck: leave the trigger unfocused so the 6860 next Space/arrow advances the deck instead of reopening */ 6861 if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); 6862 } else {
6863 opener.focus({ preventScroll: true }); 6864 } 6865 } 6866 opener = null; 6867 } 6868 6869 function scan() { 6870 document.querySelectorAll('.frame-expand[data-src]').forEach(function (t) { 6871 if (t.dataset.fxBound) return; 6872 t.dataset.fxBound = '1'; 6873 t.setAttribute('role', 'button'); 6874 t.setAttribute('tabindex', '0'); 6875 t.setAttribute('aria-haspopup', 'dialog'); 6876 t.insertAdjacentHTML('beforeend', GLYPH); 6877 }); 6878 } 6879 window.__fxScan = scan; 6880 6881 document.addEventListener('click', function (e) { 6882 var t = e.target.closest && e.target.closest('.frame-expand[data-src]'); 6883 if (!t) return; 6884 e.preventDefault(); 6885 open(t, e.detail === 0); /* detail 0 = synthetic/keyboard click */ 6886 }); 6887 6888 /* capture phase: runs before Reveal's document-level handler */ 6889 document.addEventListener('keydown', function (e) { 6890 if (isOpen()) { 6891 if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); } 6892 return; 6893 } 6894 var t = e.target.closest && e.target.closest('.frame-expand[data-src]'); 6895 if (t && (e.key === 'Enter' || e.key === ' ')) { 6896 e.preventDefault(); e.stopPropagation(); open(t, true); 6897 } 6898 }, true); 6899 6900 /* keep focus inside the dialog while open */ 6901 document.addEventListener('focusin', function (e) { 6902 if (isOpen() && !overlay.contains(e.target)) closeBtn.focus({ preventScroll: true }); 6903 }); 6904 6905 function init() { build(); scan(); } 6906 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); 6907 else init(); 6908 if (window.Reveal && typeof Reveal.on === 'function') Reveal.on('ready', scan); 6909 })(); 6910 </script>
6910 6911 6912
6912<script> 6913 // htmlwidgets need to know to resize themselves when slides are shown/hidden. 6914 // Fire the "slideenter" event (handled by htmlwidgets.js) when the current 6915 // slide changes (different for each slide format). 6916 (function () { 6917 // dispatch for htmlwidgets 6918 function fireSlideEnter() { 6919 const event = window.document.createEvent("Event"); 6920 event.initEvent("slideenter", true, true); 6921 window.document.dispatchEvent(event); 6922 } 6923 6924 function fireSlideChanged(previousSlide, currentSlide) { 6925 fireSlideEnter(); 6926 6927 // dispatch for shiny 6928 if (window.jQuery) { 6929 if (previousSlide) { 6930 window.jQuery(previousSlide).trigger("hidden"); 6931 } 6932 if (currentSlide) { 6933 window.jQuery(currentSlide).trigger("shown"); 6934 } 6935 } 6936 } 6937 6938 // hookup for slidy 6939 if (window.w3c_slidy) { 6940 window.w3c_slidy.add_observer(function (slide_num) { 6941 // slide_num starts at position 1 6942 fireSlideChanged(null, w3c_slidy.slides[slide_num - 1]); 6943 }); 6944 } 6945 6946 })(); 6947 </script>
6947 6948 6949
6949<script id="quarto-html-after-body" type="application/javascript"> 6950 window.document.addEventListener("DOMContentLoaded", function (event) { 6951 const tabsets = window.document.querySelectorAll(".panel-tabset-tabby") 6952 tabsets.forEach(function(tabset) { 6953 const tabby = new Tabby('#' + tabset.id); 6954 }); 6955 const isCodeAnnotation = (el) => { 6956 for (const clz of el.classList) { 6957 if (clz.startsWith('code-annotation-')) { 6958 return true; 6959 } 6960 } 6961 return false; 6962 } 6963 const onCopySuccess = function(e) { 6964 // button target 6965 const button = e.trigger; 6966 // don't keep focus 6967 button.blur(); 6968 // flash "checked" 6969 button.classList.add('code-copy-button-checked'); 6970 var currentTitle = button.getAttribute("title"); 6971 button.setAttribute("title", "Copied!"); 6972 let tooltip; 6973 if (window.bootstrap) { 6974 button.setAttribute("data-bs-toggle", "tooltip"); 6975 button.setAttribute("data-bs-placement", "left"); 6976 button.setAttribute("data-bs-title", "Copied!"); 6977 tooltip = new bootstrap.Tooltip(button, 6978 { trigger: "manual", 6979 customClass: "code-copy-button-tooltip", 6980 offset: [0, -8]}); 6981 tooltip.show(); 6982 } 6983 setTimeout(function() { 6984 if (tooltip) { 6985 tooltip.hide(); 6986 button.removeAttribute("data-bs-title"); 6987 button.removeAttribute("data-bs-toggle"); 6988 button.removeAttribute("data-bs-placement"); 6989 } 6990 button.setAttribute("title", currentTitle); 6991 button.classList.remove('code-copy-button-checked'); 6992 }, 1000); 6993 // clear code selection 6994 e.clearSelection(); 6995 } 6996 const getTextToCopy = function(trigger) { 6997 const outerScaffold = trigger.parentElement.cloneNode(true); 6998 const codeEl = outerScaffold.querySelector('code'); 6999 for (const childEl of codeEl.children) { 7000 if (isCodeAnnotation(childEl)) { 7001 childEl.remove(); 7002 } 7003 } 7004 return codeEl.innerText; 7005 } 7006 const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { 7007 text: getTextToCopy 7008 }); 7009 clipboard.on('success', onCopySuccess); 7010 if (window.document.getElementById('quarto-embedded-source-code-modal')) { 7011 const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { 7012 text: getTextToCopy, 7013 container: window.document.getElementById('quarto-embedded-source-code-modal') 7014 }); 7015 clipboardModal.on('success', onCopySuccess); 7016 } 7017 var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); 7018 var mailtoRegex = new RegExp(/^mailto:/); 7019 var filterRegex = new RegExp('/' + window.location.host + '/'); 7020 var isInternal = (href) => { 7021 return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); 7022 } 7023 // Inspect non-navigation links and adorn them if external 7024 var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); 7025 for (var i=0; i<links.length; i++) { 7026 const link = links[i]; 7027 if (!isInternal(link.href)) { 7028 // undo the damage that might have been done by quarto-nav.js in the case of 7029 // links that we want to consider external 7030 if (link.dataset.originalHref !== undefined) { 7031 link.href = link.dataset.originalHref; 7032 } 7033 // default icon 7034 link.classList.add("external"); 7035 } 7036 } 7037 function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { 7038 const config = { 7039 allowHTML: true, 7040 maxWidth: 500, 7041 delay: 100,
7042 arrow: false, 7043 appendTo: function(el) { 7044 return el.closest('section.slide') || el.parentElement; 7045 }, 7046 interactive: true, 7047 interactiveBorder: 10, 7048 theme: 'light-border', 7049 placement: 'bottom-start', 7050 }; 7051 if (contentFn) { 7052 config.content = contentFn; 7053 } 7054 if (onTriggerFn) { 7055 config.onTrigger = onTriggerFn; 7056 } 7057 if (onUntriggerFn) { 7058 config.onUntrigger = onUntriggerFn; 7059 } 7060 config['offset'] = [0,0]; 7061 config['maxWidth'] = 700; 7062 window.tippy(el, config); 7063 } 7064 const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); 7065 for (var i=0; i<noterefs.length; i++) { 7066 const ref = noterefs[i]; 7067 tippyHover(ref, function() { 7068 // use id or data attribute instead here 7069 let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); 7070 try { href = new URL(href).hash; } catch {} 7071 const id = href.replace(/^#\/?/, ""); 7072 const note = window.document.getElementById(id); 7073 if (note) { 7074 return note.innerHTML; 7075 } else { 7076 return ""; 7077 } 7078 }); 7079 } 7080 let selectedAnnoteEl; 7081 const selectorForAnnotation = ( cell, annotation) => { 7082 let cellAttr = 'data-code-cell="' + cell + '"'; 7083 let lineAttr = 'data-code-annotation="' + annotation + '"'; 7084 const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; 7085 return selector; 7086 } 7087 const selectCodeLines = (annoteEl) => { 7088 const doc = window.document; 7089 const targetCell = annoteEl.getAttribute("data-target-cell"); 7090 const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); 7091 const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); 7092 const lines = annoteSpan.getAttribute("data-code-lines").split(","); 7093 const lineIds = lines.map((line) => { 7094 return targetCell + "-" + line; 7095 }) 7096 let top = null; 7097 let height = null; 7098 let parent = null; 7099 if (lineIds.length > 0) { 7100 //compute the position of the single el (top and bottom and make a div) 7101 const el = window.document.getElementById(lineIds[0]); 7102 top = el.offsetTop; 7103 height = el.offsetHeight; 7104 parent = el.parentElement.parentElement; 7105 if (lineIds.length > 1) { 7106 const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); 7107 const bottom = lastEl.offsetTop + lastEl.offsetHeight; 7108 height = bottom - top; 7109 } 7110 if (top !== null && height !== null && parent !== null) { 7111 // cook up a div (if necessary) and position it 7112 let div = window.document.getElementById("code-annotation-line-highlight"); 7113 if (div === null) { 7114 div = window.document.createElement("div"); 7115 div.setAttribute("id", "code-annotation-line-highlight"); 7116 div.style.position = 'absolute'; 7117 parent.appendChild(div); 7118 } 7119 div.style.top = top - 2 + "px"; 7120 div.style.height = height + 4 + "px"; 7121 div.style.left = 0; 7122 let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); 7123 if (gutterDiv === null) { 7124 gutterDiv = window.document.createElement("div"); 7125 gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); 7126 gutterDiv.style.position = 'absolute'; 7127 const codeCell = window.document.getElementById(targetCell); 7128 const gutter = codeCell.querySelector('.code-annotation-gutter'); 7129 gutter.appendChild(gutterDiv); 7130 } 7131 gutterDiv.style.top = top - 2 + "px"; 7132 gutterDiv.style.height = height + 4 + "px"; 7133 } 7134 selectedAnnoteEl = annoteEl; 7135 } 7136 }; 7137 const unselectCodeLines = () => { 7138 const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; 7139 elementsIds.forEach((elId) => { 7140 const div = window.document.getElementById(elId); 7141 if (div) { 7142 div.remove(); 7143 } 7144 }); 7145 selectedAnnoteEl = undefined; 7146 }; 7147 // Handle positioning of the toggle 7148 window.addEventListener( 7149 "resize", 7150 throttle(() => { 7151 elRect = undefined; 7152 if (selectedAnnoteEl) { 7153 selectCodeLines(selectedAnnoteEl); 7154 } 7155 }, 10) 7156 ); 7157 function throttle(fn, ms) { 7158 let throttle = false;
7159 let timer; 7160 return (...args) => { 7161 if(!throttle) { // first call gets through 7162 fn.apply(this, args); 7163 throttle = true; 7164 } else { // all the others get throttled 7165 if(timer) clearTimeout(timer); // cancel #2 7166 timer = setTimeout(() => { 7167 fn.apply(this, args); 7168 timer = throttle = false; 7169 }, ms); 7170 } 7171 }; 7172 } 7173 const annoteTargets = window.document.querySelectorAll('.code-annotation-anchor'); 7174 for (let i=0; i<annoteTargets.length; i++) { 7175 const annoteTarget = annoteTargets[i]; 7176 const targetCell = annoteTarget.getAttribute("data-target-cell"); 7177 const targetAnnotation = annoteTarget.getAttribute("data-target-annotation"); 7178 const contentFn = () => { 7179 const content = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); 7180 if (content) { 7181 const tipContent = content.cloneNode(true); 7182 tipContent.classList.add("code-annotation-tip-content"); 7183 return tipContent.outerHTML; 7184 } 7185 } 7186 const config = { 7187 allowHTML: true, 7188 content: contentFn, 7189 onShow: (instance) => { 7190 selectCodeLines(instance.reference); 7191 instance.reference.classList.add('code-annotation-active'); 7192 window.tippy.hideAll(); 7193 }, 7194 onHide: (instance) => { 7195 unselectCodeLines(); 7196 instance.reference.classList.remove('code-annotation-active'); 7197 }, 7198 maxWidth: 300, 7199 delay: [50, 0], 7200 duration: [200, 0], 7201 offset: [5, 10], 7202 arrow: true, 7203 trigger: 'click', 7204 appendTo: function(el) { 7205 return el.parentElement.parentElement.parentElement; 7206 }, 7207 interactive: true, 7208 interactiveBorder: 10, 7209 theme: 'light-border', 7210 placement: 'right', 7211 positionFixed: true, 7212 popperOptions: { 7213 modifiers: [ 7214 { 7215 name: 'flip', 7216 options: { 7217 flipVariations: false, // true by default 7218 allowedAutoPlacements: ['right'], 7219 fallbackPlacements: ['right', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left'], 7220 }, 7221 }, 7222 { 7223 name: 'preventOverflow', 7224 options: { 7225 mainAxis: false, 7226 altAxis: false 7227 } 7228 } 7229 ] 7230 } 7231 }; 7232 window.tippy(annoteTarget, config); 7233 } 7234 const findCites = (el) => { 7235 const parentEl = el.parentElement; 7236 if (parentEl) { 7237 const cites = parentEl.dataset.cites; 7238 if (cites) { 7239 return { 7240 el, 7241 cites: cites.split(' ') 7242 }; 7243 } else { 7244 return findCites(el.parentElement) 7245 } 7246 } else { 7247 return undefined; 7248 } 7249 }; 7250 var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); 7251 for (var i=0; i<bibliorefs.length; i++) { 7252 const ref = bibliorefs[i]; 7253 const citeInfo = findCites(ref); 7254 if (citeInfo) { 7255 tippyHover(citeInfo.el, function() { 7256 var popup = window.document.createElement('div'); 7257 citeInfo.cites.forEach(function(cite) { 7258 var citeDiv = window.document.createElement('div'); 7259 citeDiv.classList.add('hanging-indent'); 7260 citeDiv.classList.add('csl-entry'); 7261 var biblioDiv = window.document.getElementById('ref-' + cite); 7262 if (biblioDiv) { 7263 citeDiv.innerHTML = biblioDiv.innerHTML; 7264 } 7265 popup.appendChild(citeDiv); 7266 }); 7267 return popup.innerHTML; 7268 }); 7269 } 7270 } 7271 }); 7272 </script>
7272 7273
7273<script type="module"> 7274 // Handle cell initialization initialization 7275 qwebrCellDetails.map( 7276 (entry) => { 7277 // Handle the creation of the element 7278 qwebrCreateHTMLElement(entry); 7279 // In the event of interactive, initialize the monaco editor 7280 if (entry.options.context == EvalTypes.Interactive) { 7281 qwebrCreateMonacoEditorInstance(entry); 7282 } 7283 } 7284 ); 7285 7286 // Identify non-interactive cells (in order) 7287 const filteredEntries = qwebrCellDetails.filter(entry => { 7288 const contextOption = entry.options && entry.options.context; 7289 return ['output', 'setup'].includes(contextOption) || (contextOption == "interactive" && entry.options && entry.options.autorun === 'true'); 7290 }); 7291 7292 // Condition non-interactive cells to only be run after webR finishes its initialization. 7293 qwebrInstance.then( 7294 async () => { 7295 const nHiddenCells = filteredEntries.length; 7296 var currentHiddenCell = 0; 7297 7298 7299 // Modify button state 7300 qwebrSetInteractiveButtonState(`ð¡ Running hidden code cells ...`, false); 7301 7302 // Begin processing non-interactive sections 7303 // Due to the iteration policy, we must use a for() loop. 7304 // Otherwise, we would need to switch to using reduce with an empty 7305 // starting promise 7306 for (const entry of filteredEntries) { 7307 7308 // Determine cell being examined 7309 currentHiddenCell = currentHiddenCell + 1; 7310 const formattedMessage = `Evaluating hidden cell ${currentHiddenCell} out of ${nHiddenCells}`; 7311 7312 // Update the document status header 7313 if (qwebrShowStartupMessage) { 7314 qwebrUpdateStatusHeader(formattedMessage); 7315 } 7316 7317 // Display the update in non-active areas 7318 qwebrUpdateStatusMessage(formattedMessage); 7319 7320 // Extract details on the active cell 7321 const evalType = entry.options.context; 7322 const cellCode = entry.code; 7323 const qwebrCounter = entry.id; 7324 7325 if (['output', 'setup'].includes(evalType)) { 7326 // Disable further global status updates 7327 const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); 7328 activeContainer.classList.remove('qwebr-cell-needs-evaluation'); 7329 activeContainer.classList.add('qwebr-cell-evaluated'); 7330 7331 // Update status on the code cell 7332 const activeStatus = document.getElementById(`qwebr-status-text-${qwebrCounter}`); 7333 activeStatus.innerText = " Evaluating hidden code cell..."; 7334 activeStatus.classList.remove('qwebr-cell-needs-evaluation'); 7335 activeStatus.classList.add('qwebr-cell-evaluated'); 7336 } 7337 7338 switch (evalType) { 7339 case 'interactive': 7340 // TODO: Make this more standardized. 7341 // At the moment, we're overriding the interactive status update by pretending its 7342 // output-like. 7343 const tempOptions = entry.options; 7344 tempOptions["context"] = "output" 7345 // Run the code in a non-interactive state that is geared to displaying output 7346 await qwebrExecuteCode(`${cellCode}`, qwebrCounter, tempOptions); 7347 break; 7348 case 'output': 7349 // Run the code in a non-interactive state that is geared to displaying output 7350 await qwebrExecuteCode(`${cellCode}`, qwebrCounter, entry.options); 7351 break; 7352 case 'setup': 7353 const activeDiv = document.getElementById(`qwebr-noninteractive-setup-area-${qwebrCounter}`); 7354 7355 // Store code in history 7356 qwebrLogCodeToHistory(cellCode, entry.options); 7357 7358 // Run the code in a non-interactive state with all output thrown away 7359 await mainWebR.evalRVoid(`${cellCode}`); 7360 break; 7361 default: 7362 break; 7363 } 7364 7365 if (['output', 'setup'].includes(evalType)) { 7366 // Disable further global status updates 7367 const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); 7368 // Disable visibility 7369 activeContainer.style.visibility = 'hidden'; 7370 activeContainer.style.display = 'none'; 7371 } 7372 } 7373 } 7374 ).then( 7375 () => { 7376 // Release document status as ready 7377 7378 if (qwebrShowStartupMessage) { 7379 qwebrStartupMessage.innerText = "ð¢ Ready!" 7380 } 7381 7382 qwebrSetInteractiveButtonState( 7383 `<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>
7383Run Code</span>`, 7384 true 7385 ); 7386 } 7387 ); 7388 </script>
7388 7389
7389<script>var lightboxQuarto = GLightbox({"closeEffect":"zoom","descPosition":"bottom","loop":false,"openEffect":"zoom","selector":".lightbox"}); 7390 (function() { 7391 let previousOnload = window.onload; 7392 window.onload = () => { 7393 if (previousOnload) { 7394 previousOnload(); 7395 } 7396 lightboxQuarto.on('slide_before_load', (data) => { 7397 const { slideIndex, slideNode, slideConfig, player, trigger } = data; 7398 const href = trigger.getAttribute('href'); 7399 if (href !== null) { 7400 const imgEl = window.document.querySelector(`a[href="${href}"] img`); 7401 if (imgEl !== null) { 7402 const srcAttr = imgEl.getAttribute("src"); 7403 if (srcAttr && srcAttr.startsWith("data:")) { 7404 slideConfig.href = srcAttr; 7405 } 7406 } 7407 } 7408 }); 7409 7410 lightboxQuarto.on('slide_after_load', (data) => { 7411 const { slideIndex, slideNode, slideConfig, player, trigger } = data; 7412 if (window.Quarto?.typesetMath) { 7413 window.Quarto.typesetMath(slideNode); 7414 } 7415 }); 7416 7417 }; 7418 7419 })(); 7420 </script>
7420 7421 7422 7423</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.