<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>R on TanHo.ca</title><link>https://tanho.ca/tags/r/</link><description>Recent content in R on TanHo.ca</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright> © 2026 Tan Ho. Unless otherwise specified, content is licensed with [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).</copyright><lastBuildDate>Fri, 02 Oct 2026 14:22:39 -0400</lastBuildDate><atom:link href="https://tanho.ca/tags/r/index.xml" rel="self" type="application/rss+xml"/><item><title>Data Science Hangout</title><link>https://tanho.ca/posts/talks/2025-10_dshangout/</link><pubDate>Thu, 23 Oct 2025 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2025-10_dshangout/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Eqlh3iE1BiU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2025-10_dshangout/featured.png"/></item><item><title>DSLC Slack: Debugging Netlify Deploys</title><link>https://tanho.ca/posts/2024-12-19_deploy-debugging/</link><pubDate>Thu, 19 Dec 2024 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2024-12-19_deploy-debugging/</guid><description>&lt;div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900"&gt;&#10; &lt;span class="text-primary-400 ltr:pr-3 rtl:pl-3 flex items-center"&gt;&#10; &lt;span class="relative block icon"&gt;&#10; &lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 32C114.6 32 .0272 125.1 .0272 240c0 49.63 21.35 94.98 56.97 130.7c-12.5 50.37-54.27 95.27-54.77 95.77c-2.25 2.25-2.875 5.734-1.5 8.734C1.979 478.2 4.75 480 8 480c66.25 0 115.1-31.76 140.6-51.39C181.2 440.9 217.6 448 256 448c141.4 0 255.1-93.13 255.1-208S397.4 32 256 32z"&gt;&lt;/path&gt;&lt;/svg&gt;&#10; &lt;/span&gt;&#10; &lt;/span&gt;&#10; &lt;span class="dark:text-neutral-300"&gt;I greatly enjoy the DSLC Slack Community (&lt;a href="https://dslc.io/join" target="_blank"&gt;&#10; dslc.io/join&lt;/a&gt;),&#10;and spend a fair bit of time answering questions! This post is part of a &lt;a href="https://tanho.ca/tags/dslc-slack"&gt;&#10; series&lt;/a&gt;&#10;where I curate some interesting questions and/or answers that I’ve come across there.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Question&#10; &lt;div id="question" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#question" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;blockquote&gt;&lt;p&gt;Hi all! I&amp;rsquo;m having a hard time understanding why netlify checks on a Pull Request&#10;with only a empty line added in the commit are failing for a blogdown website.&#10;I created &lt;a href="https://github.com/MetaDocencia/SitioWeb/pull/226" target="_blank" rel="noreferrer"&gt;this PR&lt;/a&gt; as a&#10;small example, but basically any kind of PR is failing nevermind its content,&#10;and the site is published-rendered without problem if we commit directly to main.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m super puzzled by this, does anyone have any tips on how can I debug it? Thanks &amp;#x1f64f;&lt;/p&gt;&#10;&lt;p&gt;- MC Nanton&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="relative group"&gt;Answer&#10; &lt;div id="answer" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#answer" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Let&amp;rsquo;s look through some of your Netlify deploy logs. I found these from your GitHub&#10;repo automated checks:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://app.netlify.com/sites/metadocencia/deploys/6758b3ee5881ee0009315995" target="_blank" rel="noreferrer"&gt;failing deploy logs for your PR&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://app.netlify.com/sites/metadocencia/deploys/67587beefabc760008a03d0b" target="_blank" rel="noreferrer"&gt;successful deploy logs for your main branch&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/MetaDocencia/SitioWeb" target="_blank" rel="noreferrer"&gt;repo link&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;editor&amp;rsquo;s note: archived the deploy logs in question to&#10;&lt;a href="https://gist.github.com/tanho63/09ccc1edec99c4b95227cc18b8dc7957" target="_blank" rel="noreferrer"&gt;this gist&lt;/a&gt;&#10;just in case the deploy logs are deleted at some point&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;It seems like your&#10;&lt;a href="https://app.netlify.com/sites/metadocencia/deploys/6758b3ee5881ee0009315995#L100" target="_blank" rel="noreferrer"&gt;PR deploy logs&lt;/a&gt;&#10;show this warning immediately before failing:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WARN 2024/12/10 21:35:19 Module &amp;#34;hugo-academic&amp;#34; is not compatible with this Hugo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;version; run &amp;#34;hugo mod graph&amp;#34; for more information.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;and this definitely doesn&amp;rsquo;t show up on your main deploy logs. Let&amp;rsquo;s try to figure out&#10;where your hugo versions are specified. Here&amp;rsquo;s a relevant section of the deploy logs:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: ❯ Current directory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: /opt/build/repo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: ​&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: ❯ Config file&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: /opt/build/repo/netlify.toml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: ​&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: ❯ Context&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:19 PM: deploy-preview&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;The logs say that the file responsible for specifying hugo version is netlify.toml,&#10;so let&amp;rsquo;s have a look at what that looks like on GitHub&#10;&lt;a href="https://github.com/MetaDocencia/SitioWeb/blob/74e10134f18b5e08a99059d1cb927a14be3afb60/netlify.toml" target="_blank" rel="noreferrer"&gt;right now&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;HUGO_VERSION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.64.0&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;Notice that it specifies &lt;code&gt;context.production&lt;/code&gt; here, and that the context for the&#10;failing logs is &lt;code&gt;deploy-preview&lt;/code&gt;. If we go back into the failing logs, we can confirm&#10;that the hugo-version specified here isn&amp;rsquo;t working properly:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4:35:12 PM: Installing Hugo 0.57.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;So a solution that should work is to update the netlify.toml to specify the&#10;HUGO_VERSION for the deploy-preview context:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;HUGO_VERSION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.64.0&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deploy-preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;HUGO_VERSION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.64.0&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;and this should do the trick. You might need to push it to main instead of to a PR&#10;branch though, Netlify usually uses the config from the main repo branch.&lt;/p&gt;&#10;&lt;p&gt;(Update: this solution worked!)&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2024-12-19_deploy-debugging/featured.jpg"/></item><item><title>The Beginner Data Science Programming Book That Doesn't Exist (Yet?)</title><link>https://tanho.ca/posts/2024-12-10_beginner-book/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2024-12-10_beginner-book/</guid><description>&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"&gt;&lt;path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;Ideas are &lt;a href="https://sive.rs/multiply" target="_blank" rel="noreferrer"&gt;worth nothing unless executed&lt;/a&gt;, and I&amp;rsquo;m&#10;tired of adding ideas to my backlog only to never get to them. This &lt;a href="https://tanho.ca/tags/free-ideas/" &gt;series&lt;/a&gt;&#10;aims to set those ideas free - let me know if you take it on!&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Hot take: the ideal beginner data science programming book doesn&amp;rsquo;t exist (yet)&#10; &lt;div id="hot-take-the-ideal-beginner-data-science-programming-book-doesnt-exist-yet" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hot-take-the-ideal-beginner-data-science-programming-book-doesnt-exist-yet" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;The beloved &lt;a href="https://r4ds.hadley.nz" target="_blank" rel="noreferrer"&gt;R for Data Science&lt;/a&gt; is a fantastic book, and&#10;yet I struggled immensely with following along with it. I think it&amp;rsquo;s because I&#10;can only care so much about flower irises, penguins, and NYC taxis: I quit halfway&#10;through the third chapter of R4DS on my first attempt in 2018ish, &amp;hellip; because I&#10;got bored.&lt;/p&gt;&#10;&lt;p&gt;Since then, I&amp;rsquo;ve gotten treatment for ADHD (which probably explains a lot, tbh),&#10;but I think what really helped the most with learning programming was that I&#10;started working on projects that I actually cared about: fantasy football and NFL.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d work on something for a while, get stuck on something I didn&amp;rsquo;t know, Ctrl-F&#10;the R4DS book + dplyr/tidyr reference manuals, figure out what I needed to know&#10;and how it fit into what I already knew, and then keep going. Reading R4DS cover&#10;to cover was utterly unhelpful until I knew how to fit each concept into a mental&#10;model that I knew how to use.&lt;/p&gt;&#10;&lt;p&gt;My love of all things R and data has since taken off (enough to have made a whole&#10;career of it), and now I often get asked what books I&amp;rsquo;d recommend for learning&#10;NFL/sports analytics, R, data science, programming etc. I supply the easy answer&#10;(&lt;em&gt;&amp;ldquo;you should read R4DS&amp;rdquo;&lt;/em&gt;), but I&amp;rsquo;m a little uncomfortable with it because the book&#10;I wish I could recommend doesn&amp;rsquo;t quite exist.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;the ideal book for learning programming and NFL analytics&#10; &lt;div id="the-ideal-book-for-learning-programming-and-nfl-analytics" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-ideal-book-for-learning-programming-and-nfl-analytics" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;There are a lot of introductory data science and programming books out there, and&#10;even ones that apply the context of NFL: both &lt;a href="https://www.oreilly.com/library/view/football-analytics-with/9781492099611/" target="_blank" rel="noreferrer"&gt;Eric Eager&lt;/a&gt;&#10;and &lt;a href="https://bradcongelio.com/nfl-analytics-with-r-book/" target="_blank" rel="noreferrer"&gt;Brad Congelio&lt;/a&gt; have great&#10;iterations of this (and the latter is free online)!&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m so glad these books exist now and they&amp;rsquo;d have certainly helped a lot when I&#10;first got started, but I&amp;rsquo;m not quite sure these are the ones I&amp;rsquo;ve been wanting:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;how do they stay up to date with a fast-evolving programming landscape?&lt;/li&gt;&#10;&lt;li&gt;how do they compare with the existing literature that is beloved by the community,&#10;like R4DS, and how should beginners proceed if the books disagree on best code&#10;approach?&lt;/li&gt;&#10;&lt;li&gt;what happens if I want to learn another programming language other than R?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I think the book I want to exist would be structured as a &lt;em&gt;companion&lt;/em&gt; to books&#10;like R4DS, but not include &lt;em&gt;any&lt;/em&gt; language-specific code whatsoever. Instead, I&amp;rsquo;d&#10;focus on a few goals:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;create a series of case studies and exercises that challenge learners to write&#10;their own solutions based on domain-specific research questions&#10;&lt;ul&gt;&#10;&lt;li&gt;provide starter data of appropriate complexity/cleanliness in a data repository&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;cover a full breadth of high-level data science / programming topics&#10;&lt;ul&gt;&#10;&lt;li&gt;kind of like R4DS&amp;rsquo;s visualize, transform, import, program, communicate sections&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;maintain up-to-date links to readings for each case study that help learners&#10;find best-practice guidance on a given topic in a supported programming language&#10;&lt;ul&gt;&#10;&lt;li&gt;each case study should provide links to &amp;ldquo;prerequisite reading&amp;rdquo; to solve it,&#10;e.g. links to relevant R4DS chapters for R&lt;/li&gt;&#10;&lt;li&gt;should be easily extended to other languages by linking to equivalent&#10;material, e.g. pivoting data, group by + summarize are relational data&#10;concepts that have parallels in almost every language&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;introduce the essential background knowledge for the domain in question&#10;&lt;ul&gt;&#10;&lt;li&gt;for NFL this should include concepts like EPA, fourth downs, completion&#10;probability, expected rushing yards etc - essentially covering the body of&#10;public research&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;suggest further research questions that can be tackled beyond the controlled&#10;sandbox of the book/repository&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I think something like this would be massively helpful for introducing people to&#10;the relevant public research in a domain, and with putting technical topics into&#10;practical use for learners. It would be robust to existing languages evolving (could&#10;approach the same case study in e.g. tidyverse, data.table etc), easily extended&#10;to new languages (e.g. Python, Julia, Rust), and even to new technologies (probably&#10;could do the whole thing in DuckDB one day). The format also would be easy to adopt&#10;in different sports (soccer, basketball, baseball, hockey?) and to other domains&#10;altogether.&lt;/p&gt;&#10;&lt;p&gt;Of course, I&amp;rsquo;ve been sitting on this idea for a while: I first started a&#10;&lt;a href="https://github.com/nflverse/nflbeginR" target="_blank" rel="noreferrer"&gt;repo&lt;/a&gt; for this idea ~ three years ago&#10;and convinced friends like Sebastian Carl to start thinking about it,&#10;but neither of us have made much progress, which I think is more due to just a lack&#10;of time/energy/expertise in it. I still think it&amp;rsquo;s a worthwhile idea and am writing this&#10;post mostly to set it free and hope someone is inspired to pick it up!&lt;/p&gt;&#10;&lt;p&gt;A similar idea has come up since then: the &lt;a href="https://scorenetwork.org/about.html" target="_blank" rel="noreferrer"&gt;SCORE project&lt;/a&gt;&#10;led by CMU&amp;rsquo;s Statistics and Data Science department aims to provide case studies across&#10;all sports for learning statistics and data science, and has a set of&#10;&lt;a href="https://modules.scorenetwork.org/by-statsds-topic.html" target="_blank" rel="noreferrer"&gt;modules&lt;/a&gt; already.&#10;I&amp;rsquo;m not sure if ~ learning to program in a given language is in the scope&#10;of the project - it seems to be very statistics-focused - but it could otherwise&#10;be an interesting parallel project?&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2024-12-10_beginner-book/featured.jpg"/></item><item><title>Adventures In Scaling Shiny: Lessons Learned Building DynastyProcess</title><link>https://tanho.ca/posts/talks/2024-10_shinyprod2024_scaling/</link><pubDate>Thu, 10 Oct 2024 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2024-10_shinyprod2024_scaling/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/zb3umo3Ka2g?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Materials&#10; &lt;div id="materials" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#materials" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Repo: &lt;a href="https://github.com/tanho63/talk_shinyprod2024_scaling" target="_blank" rel="noreferrer"&gt;https://github.com/tanho63/talk_shinyprod2024_scaling&lt;/a&gt;&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2024-10_shinyprod2024_scaling/featured.png"/></item><item><title>tidyr pivot trick: names_to -&gt; .value</title><link>https://tanho.ca/posts/2023-11-30_pivot-trick/</link><pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2023-11-30_pivot-trick/</guid><description>&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;I originally shared this tip for extracting data from column names as a&#10;&lt;a href="https://twitter.com/_tanho/status/1415100126272577536" target="_blank" rel="noreferrer"&gt;tweet&lt;/a&gt;, and I&amp;rsquo;ve found&#10;myself sharing it over and over again since. Here&amp;rsquo;s a brief transcription now that&#10;I&amp;rsquo;m no longer on Twitter.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;One of my favourite data-wrangling tricks is &lt;a href="https://tidyr.tidyverse.org/reference/pivot_longer.html" target="_blank" rel="noreferrer"&gt;tidyr::pivot_longer&amp;rsquo;s&lt;/a&gt;&#10;&lt;code&gt;names_to&lt;/code&gt; &amp;amp; &lt;code&gt;.value&lt;/code&gt; sentinel - use-cases don&amp;rsquo;t come up often, but it&amp;rsquo;s so deeply&#10;&amp;#x2728; &lt;strong&gt;satisfying&lt;/strong&gt; &amp;#x2728; when it does!&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Scenario&#10; &lt;div id="scenario" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scenario" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Let&amp;rsquo;s say you have this starting dataframe:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# A tibble: 601 × 22&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; season title caught_fred caught_daphnie caught_velma caught_shaggy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 1 What a Nig… FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 1 A Clue for… FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3 1 Hassle in … FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 1 Mine Your … TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 5 1 Decoy for … FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 6 1 What the H… TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 7 1 Never Ape … TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 1 Foul Play … FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9 1 The Backst… FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 1 Bedlam in … FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ 591 more rows&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ 16 more variables: caught_scooby &amp;lt;chr&amp;gt;, captured_fred &amp;lt;chr&amp;gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# captured_daphnie &amp;lt;chr&amp;gt;, captured_velma &amp;lt;chr&amp;gt;, captured_shaggy &amp;lt;chr&amp;gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# captured_scooby &amp;lt;chr&amp;gt;, unmask_fred &amp;lt;chr&amp;gt;, unmask_daphnie &amp;lt;chr&amp;gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# unmask_velma &amp;lt;chr&amp;gt;, unmask_shaggy &amp;lt;chr&amp;gt;, unmask_scooby &amp;lt;chr&amp;gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# snack_fred &amp;lt;chr&amp;gt;, snack_daphnie &amp;lt;chr&amp;gt;, snack_velma &amp;lt;chr&amp;gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# snack_shaggy &amp;lt;chr&amp;gt;, snack_scooby &amp;lt;chr&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ Use `print(n = ...)` to see more rows&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;and you want this ending dataframe:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# A tibble: 3,005 × 7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; season title character caught captured unmask snack&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ 2,995 more rows&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ Use `print(n = ...)` to see more rows&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;How do you extract the characters (Fred, Velma, Daphnie, Shaggy, Scooby) from the&#10;column names into a &lt;code&gt;character&lt;/code&gt; column?&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Past me&#10; &lt;div id="past-me" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#past-me" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Hmm, well, I can pivot_longer, then separate the name, then pivot_wider again?&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;three_lines&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_longer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;separate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;character&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_wider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names_from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values_from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# A tibble: 3,005 × 7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; season title character caught captured unmask snack&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ 2,995 more rows&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ Use `print(n = ...)` to see more rows&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;This seems to work okay!&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;But what if I told you there&amp;rsquo;s a one-liner for this exact situation?&#10; &lt;div id="but-what-if-i-told-you-theres-a-one-liner-for-this-exact-situation" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#but-what-if-i-told-you-theres-a-one-liner-for-this-exact-situation" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;pivot_longer has a &amp;ldquo;names_to&amp;rdquo; argument that takes a special &amp;ldquo;sentinel value&amp;rdquo;&#10;called &lt;code&gt;&amp;quot;.value&amp;quot;&lt;/code&gt; - it immediately re-pivots the columns so that &amp;ldquo;character&amp;rdquo;&#10;(fred, daphne etc) stays as a column and then the &amp;ldquo;.value&amp;rdquo; part becomes colnames&#10;again (caught, captured etc)&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;one_line&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_longer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;names_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;character&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;names_sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# A tibble: 3,005 × 7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; season title character caught captured unmask snack&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ 2,995 more rows&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ℹ Use `print(n = ...)` to see more rows&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;&lt;strong&gt;Why use many line when one line do trick?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;You can read more about this pivot_longer feature in this &lt;a href="https://tidyr.tidyverse.org/articles/pivot.html#multiple-observations-per-row" target="_blank" rel="noreferrer"&gt;tidyr vignette&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Bonus: data.table equivalent&#10; &lt;div id="bonus-datatable-equivalent" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#bonus-datatable-equivalent" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;I learned later (from Hadley quote-tweeting my thread) that this feature was&#10;inspired by a similar feature in &lt;code&gt;data.table&lt;/code&gt;, so I wanted to track down how it&#10;would be done there. As it turns out, kind of difficult because the feature isn&amp;rsquo;t&#10;in the CRAN version: it was in development as of October 2020, merged to main in&#10;May 2021, and still hasn&amp;rsquo;t made it onto CRAN because of some data.table governance&#10;issues (which are finally getting close to being resolved?)&lt;/p&gt;&#10;&lt;p&gt;In any case, &lt;strong&gt;with the main branch of rdatatable/data.table&#10;&lt;a href="https://github.com/Rdatatable/data.table/tree/6b9d559606767562f7f7dd4c7842a9e4a9fb597c" target="_blank" rel="noreferrer"&gt;as of today&lt;/a&gt;&lt;/strong&gt;,&#10;here&amp;rsquo;s how you could do the same pivot trick:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# requires development version of data.table and/or 1.15.0+&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;rlang&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;check_installed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;data.table (&amp;gt;= 1.14.99)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data.table&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;data.table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data.table&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;melt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id.vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# measure() is not actually exported but is silently parsed under the hood by melt()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - uses `sep = &amp;#34;_&amp;#34;` to identify &amp;amp; process the column names (i.e. split with sep &amp;#34;_&amp;#34;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - new column names (i.e. character) are provided unquoted&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - value.name is equivalent to tidyr&amp;#39;s `.value`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;measure.vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; season title character caught captured unmask snack&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt; &amp;lt;char&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1: 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2: 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3: 1 Hassle in the Castle fred FALSE FALSE TRUE TRUE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4: 1 Mine Your Own Business fred TRUE FALSE TRUE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 5: 1 Decoy for a Dognapper fred FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3001: 2 The Dreaded Remake of Jekyll &amp;amp; Hyde! scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3002: Movie Happy Halloween, Scooby-Doo! scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3003: Movie Scooby-Doo! The Sword and the Scoob scooby FALSE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3004: 2 Dark Diner of Route 66! scooby TRUE FALSE FALSE FALSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3005: 2 Total Jeopardy! scooby FALSE FALSE FALSE FALSE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Full Code&#10; &lt;div id="full-code" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#full-code" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tidyverse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;scooby_data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;read.csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-07-13/scoobydoo.csv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;scooby_data&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;caught&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;captured&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unmask&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;snack&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;other&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;not&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Wrestle Maniacs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;three_lines&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_longer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;separate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;character&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_wider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names_from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values_from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;one_line&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;pivot_longer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;names_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;character&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;names_sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;rlang&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;check_installed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;data.table (&amp;gt;= 1.14.9)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data.table&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;data.table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data.table&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;melt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id.vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;season&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# measure() is not actually exported but is silently parsed under the hood by melt()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - uses `sep = &amp;#34;_&amp;#34;` to identify &amp;amp; process the column names (i.e. split with sep &amp;#34;_&amp;#34;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - new column names (i.e. character) are provided unquoted/as bare symbols&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - value.name is equivalent to tidyr&amp;#39;s `.value`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;measure.vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;&lt;a href="https://gist.github.com/tanho63/50d9b323e29165ad3e027bc3cf1c5926" target="_blank" rel="noreferrer"&gt;Original gist&lt;/a&gt; for reference.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2023-11-30_pivot-trick/featured.png"/></item><item><title>Effective Logging Strategies</title><link>https://tanho.ca/posts/talks/2023-10_shinyprod2023_logging/</link><pubDate>Fri, 13 Oct 2023 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2023-10_shinyprod2023_logging/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/wbUXrK_YWfI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Materials&#10; &lt;div id="materials" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#materials" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Repo: &lt;a href="https://github.com/tanho63/talk_shinyprod2023_logging" target="_blank" rel="noreferrer"&gt;https://github.com/tanho63/talk_shinyprod2023_logging&lt;/a&gt;&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2023-10_shinyprod2023_logging/featured.jpg"/></item><item><title>Debugging Shiny Apps</title><link>https://tanho.ca/posts/talks/2023-03_shinyconf2023_debugging/</link><pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2023-03_shinyconf2023_debugging/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/x764Y1qz_iQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Materials&#10; &lt;div id="materials" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#materials" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/tanho63/talk_shinyconf2023_debugging" target="_blank" rel="noreferrer"&gt;https://github.com/tanho63/talk_shinyconf2023_debugging&lt;/a&gt;&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2023-03_shinyconf2023_debugging/featured.png"/></item><item><title>blogdown, distill, and personal websites with R</title><link>https://tanho.ca/posts/2022-02-04_personal-websites/</link><pubDate>Fri, 04 Feb 2022 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2022-02-04_personal-websites/</guid><description>&lt;p&gt;I&amp;rsquo;ve recently been &lt;a href="https://twitter.com/drob/status/928447584712253440" target="_blank" rel="noreferrer"&gt;asked three times&lt;/a&gt;&#10;for my advice on whether to use &lt;strong&gt;blogdown&lt;/strong&gt; or &lt;strong&gt;distill&lt;/strong&gt; when getting started with your&#10;first website with R, so in true DRob fashion: here&amp;rsquo;s a blog post!&lt;/p&gt;&#10;&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;&lt;strong&gt;2023 note&lt;/strong&gt; This post never actually got properly fleshed out beyond a set of&#10;bullet notes, but I figure it&amp;rsquo;s still worth sharing 😅 Have added a couple of 2023 notes&#10;as well.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;TLDR&#10; &lt;div id="tldr" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;They&amp;rsquo;re both great, and incredibly well supported within the R community.&lt;/li&gt;&#10;&lt;li&gt;It doesn&amp;rsquo;t matter what you start with, you can redo your site later. You probably&#10;will want to at some point down the line regardless, it&amp;rsquo;s part of the learning process!&#10;Your content &lt;strong&gt;will&lt;/strong&gt; transfer with minimal effort.&lt;/li&gt;&#10;&lt;li&gt;I recommend &lt;a href="https://hugo-apero-docs.netlify.app/start/" target="_blank" rel="noreferrer"&gt;starting with blogdown and Hugo Apero&lt;/a&gt;&#10;in most cases, EXCEPT&lt;/li&gt;&#10;&lt;li&gt;&amp;hellip;if you know you &lt;strong&gt;dislike&lt;/strong&gt; CSS and styling, in which case&#10;&lt;a href="https://themockup.blog/posts/2020-08-01-building-a-blog-with-distill/" target="_blank" rel="noreferrer"&gt;Distill&lt;/a&gt;&#10;is perfect for you&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="" &gt;Buy a domain name&lt;/a&gt;, serve it with Netlify or GitHub Pages, and go forth and grow your garden!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Distill&#10; &lt;div id="distill" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#distill" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Distill is minimalist in that it &lt;strong&gt;doesn&amp;rsquo;t&lt;/strong&gt; let you do a lot aesthetically -&#10;this means that most distill sites are straightforward and clean and incredibly&#10;easy to maintain/use going forward.&lt;/li&gt;&#10;&lt;li&gt;The most you&amp;rsquo;ll do is pick fonts and colours - everything else is pretty much set out for you.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;&lt;p&gt;&lt;strong&gt;2023 note&lt;/strong&gt; RStudio/Posit folks are promoting Quarto websites for this minimal/simple&#10;aesthetic now.&lt;/p&gt;&#10;&lt;p&gt;Distill sites will still work (Distill simplicity at it&amp;rsquo;s finest!), but if you&#10;haven&amp;rsquo;t started your site yet you might be interested in starting from the &amp;ldquo;latest hotness&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Another compelling reason to use to Quarto is the ability to use Python and Observable JS&#10;alongside with R chunks, so if that&amp;rsquo;s of interest I&amp;rsquo;d look into switching.&lt;/p&gt;&#10;&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Blogdown&#10; &lt;div id="blogdown" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#blogdown" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;blogdown is decidedly not minimal - everything is flexible and can be laid out&#10;in any number of ways&lt;/li&gt;&#10;&lt;li&gt;this means you can pick out some kickass hugo themes from somewhere and plug them in&#10;so that your site doesn&amp;rsquo;t look like other people&amp;rsquo;s sites&#10;&lt;ul&gt;&#10;&lt;li&gt;e.g. &lt;a href="https://tanho.ca/" target="_blank" rel="noreferrer"&gt;https://tanho.ca/&lt;/a&gt; and &lt;a href="https://ffverse.com/" target="_blank" rel="noreferrer"&gt;https://ffverse.com/&lt;/a&gt; are both blogdown sites,&#10;but don&amp;rsquo;t have the same feel at all&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;the flexibility means you&amp;rsquo;re more likely to break your site somehow, and also&#10;increases the temptation that you&amp;rsquo;ll end up nuking your site and rebuilding it&#10;from scratch.&lt;/li&gt;&#10;&lt;li&gt;the latest/safest hotness in blogdown is Alison Hill&amp;rsquo;s &lt;a href="https://hugo-apero-docs.netlify.app/start/" target="_blank" rel="noreferrer"&gt;Hugo Apero theme&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;I spend a bunch of time doing front-end with Shiny so I&amp;rsquo;m not as scared to wade&#10;into the guts of css/html crap - this site is primarily made from a theme called Toha,&#10;and &lt;a href="https://ffverse.com" target="_blank" rel="noreferrer"&gt;ffverse.com&lt;/a&gt; from a theme called Osprey&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;&lt;p&gt;&lt;strong&gt;2023 note&lt;/strong&gt;&#10;I&amp;rsquo;ve since ported tanho.ca to a new theme, &lt;a href="https://blowfish.page" target="_blank" rel="noreferrer"&gt;Blowfish&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I found that I was overriding a lot of the Toha theme components manually and realized&#10;I wanted something that was more flexible.&lt;/p&gt;&#10;&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Which one do I choose?&#10; &lt;div id="which-one-do-i-choose" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#which-one-do-i-choose" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Whichever one appeals to you, if you&amp;rsquo;ve read all the way through to here!&#10;In most cases, I think there&amp;rsquo;s a lot of value in learning to use blogdown, and&#10;would recommend starting there with Apero&amp;hellip;except if:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;you don&amp;rsquo;t care about the aesthetics,&lt;/li&gt;&#10;&lt;li&gt;you know you hate CSS and style decisions, or&lt;/li&gt;&#10;&lt;li&gt;you want to get started quickly&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;in which case Distill is perfect for you!&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2022-02-04_personal-websites/featured.jpg"/></item><item><title>Setting Up Your Own Shiny Server</title><link>https://tanho.ca/posts/talks/2021-10_tantorials_shiny-server/</link><pubDate>Mon, 11 Oct 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2021-10_tantorials_shiny-server/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/JL4T0qfqY7k?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2021-10_tantorials_shiny-server/featured.jpg"/></item><item><title>Wrangling JSON and Lists</title><link>https://tanho.ca/posts/talks/2021-08_tantorials_wrangling-json/</link><pubDate>Mon, 02 Aug 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2021-08_tantorials_wrangling-json/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/fpw4G2-0R-o?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2021-08_tantorials_wrangling-json/featured.jpg"/></item><item><title>Web Scraping with rvest</title><link>https://tanho.ca/posts/talks/2021-08_tantorials_web-scraping/</link><pubDate>Mon, 19 Jul 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/talks/2021-08_tantorials_web-scraping/</guid><description>&#10;&lt;h2 class="relative group"&gt;Recording&#10; &lt;div id="recording" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recording" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/z8yT3E4pz54?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/talks/2021-08_tantorials_web-scraping/featured.jpg"/></item><item><title>Adventures in HTTP Testing for R</title><link>https://tanho.ca/posts/2021-03-08_httptesting/</link><pubDate>Mon, 08 Mar 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2021-03-08_httptesting/</guid><description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Embrace unit testing karma&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Karma says:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Do good things and good things will happen to you.&lt;/li&gt;&#10;&lt;li&gt;Do them the way you know.&lt;/li&gt;&#10;&lt;li&gt;Do them the way you like.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Karma is flexible. Testing needs flexibility.&lt;/p&gt;&#10;&lt;p&gt;Karma thrives on creativity. Testing needs creativity. - &lt;a href="https://www.artima.com/weblogs/viewpost.jsp?thread=203994" target="_blank" rel="noreferrer"&gt;Testivus&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;While developing &lt;a href="https://ffscrapr.dynastyprocess.com" target="_blank" rel="noreferrer"&gt;ffscrapr&lt;/a&gt;, I&amp;rsquo;ve taken a&#10;deeper dive into HTTP testing for R packages. I&amp;rsquo;ve settled on using httptest and&#10;find it very convenient path to building and using mock data, especially since I&amp;rsquo;m&#10;using &lt;a href="https://httr.r-lib.org/" target="_blank" rel="noreferrer"&gt;httr&lt;/a&gt; extensively within the package. I wish&#10;Scott Chamberlain &amp;amp; Maelle Salmon&amp;rsquo;s new book &lt;a href="https://books.ropensci.org/http-testing/" target="_blank" rel="noreferrer"&gt;HTTP testing in R&lt;/a&gt;&#10;had been written before I started - that would have been incredibly helpful as a&#10;guiderail for my experimentations! Here are some thoughts on what I&amp;rsquo;ve discovered&#10;while testing &lt;code&gt;ffscrapr&lt;/code&gt;, mostly trying to capture parts of my experiences that&#10;differ from what&amp;rsquo;s already in the HTTP testing book.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Why test?&#10; &lt;div id="why-test" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-test" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Testing is about making sure that the package (and functions) work as intended,&#10;&lt;strong&gt;in spite of&lt;/strong&gt; external factors. With API packages, sources of errors can include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;internet connectivity (both from the user and from the API)&lt;/li&gt;&#10;&lt;li&gt;API response changes (format changes, variable naming, etc)&lt;/li&gt;&#10;&lt;li&gt;dependency changes (this could include base R versions, operating systems,&#10;as well as the more obvious package dependencies)&lt;/li&gt;&#10;&lt;li&gt;users doing unexpected things with your package (finding corner cases)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A good test setup should test at least the first three of these (user-side corner&#10;cases being difficult to foresee at time of function definition), and ideally should&#10;test each area individually while holding the other two parts constant. For example,&#10;you want to test how the package handles internet connectivity &lt;strong&gt;without&lt;/strong&gt; worrying&#10;about whether the API response changes, or whether the dependencies have changed.&#10;You also want to test the package every time the dependencies change, without&#10;worrying about internet connectivity or API responses changing. And lastly, you&#10;want to test and make sure that the real API response has not changed, because&#10;it might return unexpected results. In addition to these concerns, another really&#10;important element to consider is making sure that it is easy to maintain the testing&#10;setup.&lt;/p&gt;&#10;&lt;p&gt;For &lt;code&gt;ffscrapr&lt;/code&gt;, I think the most important of these are testing the code for&#10;dependency changes and testing the code for API response changes. Internet&#10;connectivity seems fairly straightforward to write code for, and is worth doing&#10;at least once, but especially in a heavier-weight package like ffscrapr (which&#10;imports quite liberally from tidyverse packages like dplyr, purrr, tidyr etc),&#10;dependency testing is crucial to making sure the package continues to clean and&#10;process the data in the intended fashion. Also, fantasy platform APIs do change&#10;on a pretty frequent basis, adding and removing features nearly every year. It&amp;rsquo;s&#10;worth testing against the live APIs to make sure that &lt;code&gt;ffscrapr&lt;/code&gt; continues to&#10;operate as intended and without errors.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Why not skip CRAN testing?&#10; &lt;div id="why-not-skip-cran-testing" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-not-skip-cran-testing" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Many API packages skip all testing on CRAN - it&amp;rsquo;s a hassle to maintain, and the&#10;risk of being booted from CRAN can outweigh the effort of making sure tests don&amp;rsquo;t&#10;fail. I&amp;rsquo;m here to suggest otherwise! I think developers are missing out on one of&#10;CRAN&amp;rsquo;s most important benefits, which is being a massive dependency testing suite.&#10;Packages are tested on many different combinations of operating systems and R&#10;versions on a regular basis, and CRAN will also require your dependency packages&#10;to make sure that changes they make don&amp;rsquo;t break your package (and/or if they do,&#10;to give you advanced notice that it will break your package). This gives you time&#10;to make the appropriate changes to the package to accommodate the change!&lt;/p&gt;&#10;&lt;p&gt;Skipping these tests on CRAN is doing yourself a disservice, in my opinion,&#10;especially with the development of packages like &lt;a href="https://enpiar.com/r/httptest/" target="_blank" rel="noreferrer"&gt;httptest&lt;/a&gt;&#10;(which I use) and alternatives like vcr/webmockr - I think the extra work in making&#10;the package CRAN-testable (at least, to make sure that your R code processes the&#10;responses properly) is worth doing. These packages essentially create local copies&#10;of the data that the API would return from your http query, and redirect your&#10;request to read from the local files instead of letting your request go through&#10;to the API - allowing you to test while holding the API responses constant.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Storing mock data in a separate repository&#10; &lt;div id="storing-mock-data-in-a-separate-repository" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#storing-mock-data-in-a-separate-repository" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;In an ideal world, tests and files are small and can be stored alongside the test&#10;data, such that you don&amp;rsquo;t overrun CRAN&amp;rsquo;s limitations on compressed or installed&#10;package sizes (5MB, generally speaking). With &lt;code&gt;ffscrapr&lt;/code&gt;, I was finding that&#10;responses from certain sites (cough, Fleaflicker, ESPN, cough) were enormous -&#10;they package a whole whack of player data within each call, such that their&#10;responses were coming back at 5-10MB each!&lt;/p&gt;&#10;&lt;p&gt;In consulting the available documentation, I came across &lt;a href="https://github.com/nealrichardson/httptest/issues/11/" target="_blank" rel="noreferrer"&gt;this GitHub issue&lt;/a&gt; in the httptest&#10;repository, in which Neal Richardson describes some of his suggested approaches&#10;to solving this problem:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Creating a function to redact the responses on capture&lt;/li&gt;&#10;&lt;li&gt;Manually pruning the captured responses&lt;/li&gt;&#10;&lt;li&gt;Refactor larger functions so that they can be tested on smaller chunks of data&lt;/li&gt;&#10;&lt;li&gt;Lastly (and least-optimally, in Neal&amp;rsquo;s opinion) - creating a separate data&#10;package that holds the response cache&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I took a stab at each of these approaches, but one by one I had to discard them:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;API responses for fantasy football platforms are subject to change on a pretty&#10;regular basis - I&amp;rsquo;ve had to rebuild the captured responses several times since&#10;I started developing this package in August 2020, and corner cases are unlikely&#10;to appear in the first 5-10 responses of data&lt;/li&gt;&#10;&lt;li&gt;Manually pruning the captured responses is unfeasible for similar reasons as&#10;above, and is also infinitely more time-consuming!&lt;/li&gt;&#10;&lt;li&gt;I did try to refactor some of the larger/longer-running functions into&#10;smaller/more-testable units, especially since APIs frequently have options to&#10;limit and paginate. However, some of these requests were very difficult to&#10;configure in this fashion, and other functions that I wanted to test needed to&#10;aggregate large amounts of data. I wanted the tests to be easily evaluated via&#10;the naked eye.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Thus, I landed on creating a separate package of test data files as my best route&#10;forward. I briefly considered the idea of pushing the data package to CRAN - this&#10;would ensure that it would always be &amp;ldquo;accessible&amp;rdquo; to the CRAN package wherever&#10;accessed - but discarded the prospects of both getting an exemption on package&#10;size AND maintaining regular updates to the test data. Instead, I opted for a&#10;separate GitHub repository of test files, which I considered to be a relatively&#10;reliable place to store the data.&lt;/p&gt;&#10;&lt;p&gt;Of course, a few concerns popped up with this option, which needed handling:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I would need to check that I could download the data at the time of&#10;test/vignette-build, and then skip testing/evaluating if GitHub or the data&#10;were unavailable.&lt;/li&gt;&#10;&lt;li&gt;I would need to find a way to version-control and make sure that I didn&amp;rsquo;t&#10;accidentally delete test data that would cause CRAN testing to break - that&#10;would defeat the whole purpose of going through all the trouble of mocking the data!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I managed the latter concern with &lt;a href="https://github.com/dynastyprocess/ffscrapr-tests/releases/tag/1.3.0" target="_blank" rel="noreferrer"&gt;a GitHub release&lt;/a&gt;,&#10;which allowed me to version-lock the test-data to match the CRAN version. I&amp;rsquo;ll&#10;need to make sure to update the links within the package at every CRAN release,&#10;but I think this should be feasible!&lt;/p&gt;&#10;&#10; &#10; &#10; &#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;&lt;strong&gt;2023 note&lt;/strong&gt; I didn&amp;rsquo;t know of the existence of &lt;a href="https://github.com/ropensci/piggyback" target="_blank" rel="noreferrer"&gt;piggyback&lt;/a&gt;&#10;at the time of this writing but if I did I would have used it instead so as to&#10;not have to track another repository. It&amp;rsquo;s been buried on my project list, but I&amp;rsquo;ve&#10;wanted to make a PR to add a &lt;code&gt;piggyback&lt;/code&gt; option to &lt;code&gt;httptest&lt;/code&gt; for a while now.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Accessing the mock data&#10; &lt;div id="accessing-the-mock-data" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#accessing-the-mock-data" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;This needed some finagling, but I eventually settled on the following code in my &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/tests/testthat/setup.R" target="_blank" rel="noreferrer"&gt;testthat setup file&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;suppressPackageStartupMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testthat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkmate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;(Packages)&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Sys.getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MOCK_BYPASS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;with_mock_api&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;If a system environment variable named MOCK_BYPASS has value of true, override the definition of &amp;ldquo;with_mock_api&amp;rdquo; to be &amp;ldquo;force&amp;rdquo;, which stops the mocking behaviour and replaces it with normal evaluation.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;download_mock&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;identical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Sys.getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MOCK_BYPASS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is.null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;nslookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;If not mock_bypass AND github.com is available (via a curl check), set download_mock to TRUE, otherwise set to FALSE. This checks as to whether the mock data should be downloaded at all.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;FALSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;download_mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;tryCatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;download.file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/dynastyprocess/ffscrapr-tests/archive/1.3.0.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;unzip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;.mockPaths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;ffscrapr-tests-1.3.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ffscrapr-tests-1.3.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;testthat&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;teardown_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;warning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;With &amp;ldquo;skip&amp;rdquo; as default FALSE, try to download the data from the archive link (version controlled) and then unzip it. Then, set the .mockPaths to read this unzipped directory. When done, delete the zipped file and the test data. If anything generates a warning or error, set skip to TRUE.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skippy &amp;lt;- function() NULL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (skip) skippy &amp;lt;- function() testthat::skip(message = &amp;#34;Unable to download test data&amp;#34;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;With skippy defined by default as a function that does nothing, if skip is TRUE (because of the previous step), change the definition of skippy to skip all tests.&lt;/p&gt;&#10;&lt;p&gt;I think this addresses my concerns - if running in a non-live-situation AND mock data can be downloaded, do so, otherwise skip all tests! A similar setup and teardown happens in all the &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/vignettes/espn_basics.Rmd#L21" target="_blank" rel="noreferrer"&gt;vignette RMDs as well&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Capturing the mock data&#10; &lt;div id="capturing-the-mock-data" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#capturing-the-mock-data" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Capturing the mock data turned out to be a fairly straightforward process. I made sure to have the test-data folder in the same top-level directory as the package itself, and then capturing the mock data was comfortably done with:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;start_capturing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;../ffscrapr-tests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;stop_capturing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;I&amp;rsquo;ve had some problems with my PC&amp;rsquo;s locale settings encoding the JSON oddly in the past, but I usually get around that by jumping onto an available RStudio server instance on a Linux server and re-recording the tests there when necessary. (This is probably overkill - if you&amp;rsquo;re reading this and have better suggestions, let me know!)&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Testing the real APIs with GitHub Actions&#10; &lt;div id="testing-the-real-apis-with-github-actions" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-the-real-apis-with-github-actions" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;CRAN obviously cannot test the real APIs, because it cannot handle failures gracefully. Instead, I have &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/.github/workflows/test-apis.yml" target="_blank" rel="noreferrer"&gt;a GitHub Action that does this&lt;/a&gt; by running the package tests with the MOCK_BYPASS environment variable set to &amp;ldquo;true&amp;rdquo;. Paired with a cron schedule of once a week, this successfully catches API changes and issues before users can find them, which helps me anticipate and start patching bugs more effectively.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2021-03-08_httptesting/featured.jpg"/></item><item><title>DSLC Slack: Changing Window Titles in Shiny</title><link>https://tanho.ca/posts/2020-10-22_shinywindowtitles/</link><pubDate>Tue, 20 Oct 2020 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2020-10-22_shinywindowtitles/</guid><description>&lt;div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900"&gt;&#10; &lt;span class="text-primary-400 ltr:pr-3 rtl:pl-3 flex items-center"&gt;&#10; &lt;span class="relative block icon"&gt;&#10; &lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 32C114.6 32 .0272 125.1 .0272 240c0 49.63 21.35 94.98 56.97 130.7c-12.5 50.37-54.27 95.27-54.77 95.77c-2.25 2.25-2.875 5.734-1.5 8.734C1.979 478.2 4.75 480 8 480c66.25 0 115.1-31.76 140.6-51.39C181.2 440.9 217.6 448 256 448c141.4 0 255.1-93.13 255.1-208S397.4 32 256 32z"&gt;&lt;/path&gt;&lt;/svg&gt;&#10; &lt;/span&gt;&#10; &lt;/span&gt;&#10; &lt;span class="dark:text-neutral-300"&gt;I greatly enjoy the DSLC Slack Community (&lt;a href="https://dslc.io/join" target="_blank"&gt;&#10; dslc.io/join&lt;/a&gt;),&#10;and spend a fair bit of time answering questions! This post is part of a &lt;a href="https://tanho.ca/tags/dslc-slack"&gt;&#10; series&lt;/a&gt;&#10;where I curate some interesting questions and/or answers that I’ve come across there.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;&lt;code&gt;Dhiraj&lt;/code&gt; asks:&#10; &lt;div id="dhiraj-asks" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dhiraj-asks" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;blockquote&gt;&lt;p&gt;I have a shiny application which uses the navbarPage() layout. The app has multiple tabPanels with their own titles. Is it possible to have the windowTitle of the navbarPage dynamic to show the titles of the tabPanels ?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="relative group"&gt;Answer&#10; &lt;div id="answer" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#answer" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s a minimal example of the Shiny and JS code you&amp;rsquo;ll need:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shiny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tidyverse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;custom_js&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Shiny.addCustomMessageHandler(&amp;#34;handler_windowtitle&amp;#34;, switch_window_title );&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;function switch_window_title(message){&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; document.title = message;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nf"&gt;navbarPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;navbarpage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;displayed_tab&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_js&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;tabPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;StrangePlanet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;tabPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;DarthVader&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;observeEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;displayed_tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;sendCustomMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;handler_windowtitle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;displayed_tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;shinyApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;If you give navbarPage an ID, it&amp;rsquo;ll create an &lt;code&gt;input&lt;/code&gt; variable that can be accessed as &lt;code&gt;input${id}&lt;/code&gt;, returning the value argument of the tabPanel. In this case, tabPanel returns the &lt;code&gt;title&lt;/code&gt; argument as the default value - you can specify something else if you prefer.&lt;/p&gt;&#10;&lt;p&gt;In a production app, I&amp;rsquo;d also move the JS into a separate JS file (stored in www) and use &lt;code&gt;includeJS()&lt;/code&gt; instead of using the &lt;code&gt;tags$script&lt;/code&gt; method.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2020-10-22_shinywindowtitles/featured.jpg"/></item><item><title>DSLC Slack: Explaining Shiny to Your IT Team</title><link>https://tanho.ca/posts/2020-10-15_explaining-shiny/</link><pubDate>Thu, 15 Oct 2020 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2020-10-15_explaining-shiny/</guid><description>&lt;div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900"&gt;&#10; &lt;span class="text-primary-400 ltr:pr-3 rtl:pl-3 flex items-center"&gt;&#10; &lt;span class="relative block icon"&gt;&#10; &lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 32C114.6 32 .0272 125.1 .0272 240c0 49.63 21.35 94.98 56.97 130.7c-12.5 50.37-54.27 95.27-54.77 95.77c-2.25 2.25-2.875 5.734-1.5 8.734C1.979 478.2 4.75 480 8 480c66.25 0 115.1-31.76 140.6-51.39C181.2 440.9 217.6 448 256 448c141.4 0 255.1-93.13 255.1-208S397.4 32 256 32z"&gt;&lt;/path&gt;&lt;/svg&gt;&#10; &lt;/span&gt;&#10; &lt;/span&gt;&#10; &lt;span class="dark:text-neutral-300"&gt;I greatly enjoy the DSLC Slack Community (&lt;a href="https://dslc.io/join" target="_blank"&gt;&#10; dslc.io/join&lt;/a&gt;),&#10;and spend a fair bit of time answering questions! This post is part of a &lt;a href="https://tanho.ca/tags/dslc-slack"&gt;&#10; series&lt;/a&gt;&#10;where I curate some interesting questions and/or answers that I’ve come across there.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;&lt;code&gt;Piranha&lt;/code&gt; asks:&#10; &lt;div id="piranha-asks" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#piranha-asks" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;blockquote&gt;&lt;p&gt;Hello,&#10;This might be bit of a stretch&amp;hellip;. I could use a bit of help in prepping for some upcoming conversations with the head of my company&amp;rsquo;s IT/Security.&lt;/p&gt;&#10;&lt;p&gt;I am trying to get IT support for Shiny (currently IT supports Tableau).&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt;: Is there any kind of high level summary of this Admin Guide document?&#10;&lt;a href="https://docs.rstudio.com/shiny-server/" target="_blank" rel="noreferrer"&gt;https://docs.rstudio.com/shiny-server/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;More specifically, I want to summarize the official document into about a 3-5 slide presentation. Topics that I want to include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;What is Shiny? (3-5 bullet points, in language that a &amp;ldquo;traditional&amp;rdquo; IT person will understand)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;How to think about security and authentication for both the Shiny Server free and Pro.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;How to think about Dockerizing an application.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I attempted to summarize the official docs into a 3-5 slide PowerPoint (ugh, yes, PowerPoint!), but am really struggling. This is a massive document and I am not an IT person!&lt;br&gt;&#10;Any pointers would be really helpful.&lt;br&gt;&#10;Thanks!&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="relative group"&gt;Answer (by me)&#10; &lt;div id="answer-by-me" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#answer-by-me" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;I don&amp;rsquo;t know if I have a document to point you at, but I would suggest a mental&#10;model where you separate Shiny the app (the app.R file and all of the things&#10;included in it) from the ways you deploy Shiny. Writing out some of my personal&#10;thoughts on it:&lt;/p&gt;&#10;&lt;p&gt;Shiny (the app):&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;is an R package that allows you to write UI (client-side) and server-side&#10;logic in R, which can be implemented quickly (from a code-writing perspective)&#10;and powerfully (leverages all of the modelling/data-wrangling packages and&#10;abilities of R).&lt;/li&gt;&#10;&lt;li&gt;has an architecture consisting of a client (the browser) and a server (an&#10;R process). The two are connected by a&#10;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" target="_blank" rel="noreferrer"&gt;websocket&lt;/a&gt;&#10;that receives state changes from the client, such as new values for input controls,&#10;and distributes state changes from the server, such as new output values.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Shiny is not self-contained because it relies on R and other libraries (Shiny,&#10;as well as whatever else you use in the app) that are installed onto the server.&#10;Docker is one of the best ways to handle this, because it creates a container that&#10;holds an installation of R and all of the relevant packages, and can be version-locked&#10;to ensure stability. Once a Shiny app has been dockerized, it can be turned over&#10;to a devops team to deploy, and the devops team does not have to know R or Shiny&#10;in order to use the app.&lt;/p&gt;&#10;&lt;p&gt;This allows for a division of expertise:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;the data scientist makes the app, and makes sure the docker container builds&#10;and works as intended&lt;/li&gt;&#10;&lt;li&gt;the devops engineer can take care of the details with deploying and securing&#10;the apps, by controlling/restricting access to the container itself rather than&#10;forcing the data scientist to worry about it inside the app.&lt;/li&gt;&#10;&lt;li&gt;Both the data scientist and the devops engineer should responsible for&#10;performance:&#10;&lt;ul&gt;&#10;&lt;li&gt;data scientist by improving the code for efficiency&lt;/li&gt;&#10;&lt;li&gt;engineer by supplying the containers with the right amount of processing&#10;power/memory/load-balancing&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;A dedicated devops person can pretty easily deploy a docker container into an&#10;existing app/microservices system, if such already exists.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;At the other end of the spectrum, &lt;a href="http://shinyapps.io/" target="_blank" rel="noreferrer"&gt;shinyapps.io&lt;/a&gt; and RS&#10;Connect are solutions that RStudio provides to help data teams who want this&#10;sort of devops process but don&amp;rsquo;t want to have the work done in house. They&amp;rsquo;ll&#10;manage the servers for you and make it very easy for R users to just upload all&#10;of the app data and configs, and they&amp;rsquo;ll manage deployment, performance and&#10;authentication for you.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m a data generalist for a small team, and manage the full stack from data&#10;import/wrangle/model to app dev to app deploy. I wouldn&amp;rsquo;t necessarily recommend&#10;that path if your team is large enough - let devops be devops and data scientists&#10;be data scientists where possible!&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Some followup questions&#10; &lt;div id="some-followup-questions" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#some-followup-questions" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;blockquote&gt;&lt;p&gt;In a discussion earlier this week, a person from IT asked me &amp;ldquo;I dont know R,&#10;can you tell me how Shiny connects to the user&amp;rsquo;s browser?&amp;rdquo; I didn&amp;rsquo;t know much&#10;about WebSocket technology at the time, so my response was: &amp;ldquo;Shiny comes with&#10;its own webserver, but a common practice is to use nginx webserver in front of&#10;the shiny app&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Now that I know a bit more&amp;hellip;would it be accurate to some something along these&#10;lines: &amp;ldquo;Shiny uses WebSocket technology to create a 2-way communication between&#10;the server and the browser. This allows the data scientist to leverage the full&#10;data-processing and analytical capabilities of R. In our current process, we need&#10;to do processing/analysis in one tool (e.g. SAS, R) and visualization/sharing&#10;in Tableau/Excel - creating lots of inefficiencies. Using Shiny, we can fine-tune&#10;and scale our analysis to exactly what is needed by the client and serve that&#10;via a Shiny dashboard.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Furthermore, &amp;ldquo;From the data science team&amp;rsquo;s perspective, this is our &lt;strong&gt;ask&lt;/strong&gt;.&#10;We need the ability to deliver interactive analysis to our clients at scale.&#10;Can you help us do that in a secure way? We&amp;rsquo;ll build the Shiny app and dockerize&#10;it. We need help from IT to provide secure access by clients&amp;rdquo;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;and response:&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;Shiny uses WebSocket technology to create a 2-way communication between the server and the browser&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;I cribbed that chunk from here:&#10;&lt;a href="https://community.rstudio.com/t/is-there-any-equivalent-of-web-sockets-in-shiny-world/17908" target="_blank" rel="noreferrer"&gt;https://community.rstudio.com/t/is-there-any-equivalent-of-web-sockets-in-shiny-world/17908&lt;/a&gt;.&#10;If you&amp;rsquo;re hoping to communicate how Shiny works to an IT person, I would use the&#10;wording as described rather than paraphrasing it too much - receives state&#10;changes from client and distributes state changes from server is high-level&#10;enough for most IT people to understand - and you lose something by abstracting&#10;beyond that.&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;p&gt;Using Shiny, we can fine-tune and scale our analysis to exactly what is needed&#10;by the client and serve that via a Shiny dashboard&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;I&amp;rsquo;d change this to say that it allows you to use the same R code that you use to&#10;process and generate the data right in the app, which allows you to package the&#10;&amp;ldquo;business logic&amp;rdquo; right into the app for immediate interactive analyses. I&amp;rsquo;m not&#10;familiar with your team and the ask - but identifying the docker container as&#10;the handoff point where IT helps deploy the app and serve it securely and&#10;effectively to clients is a good start. That lets IT do the security bit.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2020-10-15_explaining-shiny/featured.jpg"/></item><item><title>DSLC Slack: Things I Learned From Advanced R Book Club</title><link>https://tanho.ca/posts/2020-09-22_advr-learnings/</link><pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2020-09-22_advr-learnings/</guid><description>&lt;p&gt;I set out reading this book hoping to understand the metaprogramming chapter, and ended up learning a bunch of other stuff along the way!&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s a non-exhaustive list of things I learned from each of the sections of Advanced R:&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;I - Foundations:&#10; &lt;div id="i---foundations" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#i---foundations" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I notice that I use the switch function a ton now, where previously I used if-else nests.&lt;/li&gt;&#10;&lt;li&gt;I use condition-handling to pass errors back to the user of my Shiny apps now.&lt;/li&gt;&#10;&lt;li&gt;I now know what an Environment is and where and why R would look for something. (This helps both in function-writing and debugging!)&lt;/li&gt;&#10;&lt;li&gt;I write a heck of a lot of functions (this ties into II: Functional Programming, of course)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;II - Functional Programming&#10; &lt;div id="ii---functional-programming" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ii---functional-programming" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I map the crap out of everything now (if I didn&amp;rsquo;t before, it&amp;rsquo;s way worse now!), and have learned to use functions in a ton of different ways.&lt;/li&gt;&#10;&lt;li&gt;I now understand what a function factory is, and what to do if a function somehow returns me a function instead (as opposed to panicking about it!)&lt;/li&gt;&#10;&lt;li&gt;I now know about a series of modifiers to help adjust function behaviours (memoise, quietly, safely, possibly) - although I use tryCatch as much as I do these.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;III - Object Oriented Programming&#10; &lt;div id="iii---object-oriented-programming" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#iii---object-oriented-programming" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I wrote a &lt;a href="https://github.com/ffverse/ffscrapr" target="_blank" rel="noreferrer"&gt;package&lt;/a&gt; that uses S3! It&amp;rsquo;s cool to minimize the user interface for multiple platforms and the mental load accompanying.&lt;/li&gt;&#10;&lt;li&gt;I still lean to using the explicit &amp;ldquo;switch&amp;rdquo; function a lot instead of writing out a useMethod call. May be a habit/pattern worth breaking in the future.&lt;/li&gt;&#10;&lt;li&gt;I&amp;rsquo;m now comfortable USING an R6 package - I don&amp;rsquo;t know that I&amp;rsquo;d reach for an R6 system now but the whole &amp;ldquo;function$method&amp;rdquo; thing doesn&amp;rsquo;t scare me anymore&lt;/li&gt;&#10;&lt;li&gt;S4? wot that&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;IV Metaprogramming&#10; &lt;div id="iv-metaprogramming" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#iv-metaprogramming" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I now know how to pass expressions to tidyverse stuff!&lt;/li&gt;&#10;&lt;li&gt;I now know where to look in the Metaprogramming and how to intepret the text.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;V Techniques&#10; &lt;div id="v-techniques" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#v-techniques" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Debugging: I now know how to use &lt;code&gt;browser()&lt;/code&gt;, and love it! Also &lt;code&gt;debug()&lt;/code&gt;!&lt;/li&gt;&#10;&lt;li&gt;Performance measure/improve: still don&amp;rsquo;t do a ton of this, even though I&amp;rsquo;m a shiny dev. I feel like I should care more but I mostly work with smaller data (sub 1,000,000 rows) right now.&lt;/li&gt;&#10;&lt;li&gt;I &amp;hellip; still don&amp;rsquo;t know C++ or Rcpp. ¯\&lt;em&gt;(ツ)&lt;/em&gt;/¯&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Toy Code&#10; &lt;div id="toy-code" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#toy-code" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-r" data-lang="r"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dplyr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rlang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;grouping_variables&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kr"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;group_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;S&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exprs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CraftCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Product&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ModelCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Elevation&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;B&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exprs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Rate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;P&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exprs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Part&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;null_bids&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;bids&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SupplierID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProjectID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!!!&lt;/span&gt;&lt;span class="n"&gt;grouping_variables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;slice_max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastModDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;ungroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!!!&lt;/span&gt;&lt;span class="n"&gt;grouping_variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProjectID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SupplierAID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SupplierName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;left_join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lowest_bids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;as.character&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grouping_variables&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;Metaprogramming - how to pass expressions into select statements. Also example of switch.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2020-09-22_advr-learnings/featured.jpg"/></item></channel></rss>