<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>posts on TanHo.ca</title><link>https://tanho.ca/posts/</link><description>Recent content in posts 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/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Dry Aging At Home, Episode 1</title><link>https://tanho.ca/posts/2026-08-30_dry-aging/</link><pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2026-08-30_dry-aging/</guid><description>&lt;p&gt;One of my deep nerd obsessions is food, especially food science, and I&amp;rsquo;ve long&#10;wanted to try out dry-aging at home. The premise sounds &amp;ldquo;simple&amp;rdquo;: you just need a&#10;dedicated fridge, a big old hunk of meat, and a way to monitor the temperature&#10;and humidity inside. Pair that with some burgeoning homelab skills and &amp;hellip; how&#10;hard could it be? Here are some reflections on my first dry-aging experiment, a&#10;45-day boneless ribeye roast.&lt;/p&gt;&#10;&lt;p&gt;Code for this project lives on GitHub at &lt;a href="https://github.com/tanho63/dry-age-monitor" target="_blank" rel="noreferrer"&gt;tanho63/dry-age-monitor&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;equipment and setup&#10; &lt;div id="equipment-and-setup" 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="#equipment-and-setup" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;img src="fridge-setup.jpg" alt="dry-aging fridge setup" style="max-height:400px"&gt;&#10;&lt;p&gt;I got a &lt;a href="https://www.costco.ca/frigidaire-21-in.-6.0-cu-ft.-commercial-glass-display-refrigerator.product.4000409375.html" target="_blank" rel="noreferrer"&gt;dedicated fridge&lt;/a&gt;&#10;from Costco - around 6.0 cubic feet with a glass door and wire shelving - and on&#10;balance I think it worked really well: it held humidity pretty decently, was&#10;able to fit a good sized chunk of meat on a shelf, and the glass door let me&#10;look in on it whenever I wanted. The one downside is that it uses a mechanical&#10;thermostat for deciding when to kick on the compressor, and I ended up needing&#10;to fiddle with it a bit (more on that later). A digital thermostat that let me&#10;control top-end and bottom-end temperature would probably be better, but honestly&#10;I&amp;rsquo;m very pleased with what I got for ~$350 CAD.&lt;/p&gt;&#10;&lt;p&gt;The Raspberry Pi monitoring setup worked great for tracking temperature and&#10;humidity. I&amp;rsquo;ve mostly had experience with the Pi Zero 2 WH (i.e. with headers already&#10;soldered on), so went with that and paired it with a &lt;a href="https://www.pishop.ca/product/bme690-4-in-1-air-quality-breakout-gas-temperature-pressure-humidity/" target="_blank" rel="noreferrer"&gt;BME690&lt;/a&gt;&#10;air quality sensor that promised to monitor temperature, humidity, gas, and&#10;pressure. I could probably have cheaped out and gone with a simpler sensor like&#10;the &lt;a href="https://shop.pimoroni.com/products/adafruit-aht20-temperature-humidity-sensor-breakout-board-stemma-qt-qwiic?variant=32004099801171" target="_blank" rel="noreferrer"&gt;AHT20&lt;/a&gt;,&#10;since the gas and pressure turned out to be pretty useless, but the BME690&#10;worked well enough once I got it set up and calibrated.&lt;/p&gt;&#10;&lt;p&gt;I don&amp;rsquo;t have a soldering kit, so plugging everything in via Stemma/QT cables was&#10;the move and it was painless. I made sure to get a longer (400mm) cable for the&#10;sensor-to-hub connection so that I could put the sensor closer to the middle of&#10;the fridge, and then used a velcro cable tie to secure it to the desired fridge&#10;rack. I didn&amp;rsquo;t have any worries about the sensor breakout board being exposed to&#10;cold temperatures or potential humidity, since it is designed to read those, and&#10;the actual Pi is mounted outside of the fridge.&lt;/p&gt;&#10;&lt;p&gt;I used a spare &lt;a href="https://www.canadacomputers.com/en/case-fans/249387/be-quiet-pure-wings-3-120mm-case-fan-bl104.html" target="_blank" rel="noreferrer"&gt;computer case fan&lt;/a&gt;&#10;I already had, paired with this USB-powered &lt;a href="https://www.amazon.ca/dp/B0DPZM7T3Q" target="_blank" rel="noreferrer"&gt;fan controller&lt;/a&gt;.&#10;I initially tried a &lt;a href="https://www.amazon.ca/dp/B06Y5WWBHH?th=1" target="_blank" rel="noreferrer"&gt;USB fan&lt;/a&gt;, but the&#10;cable was pretty thick and awkward to fit into the fridge&amp;rsquo;s door seal. The case&#10;fan&amp;rsquo;s cable was much thinner and easier to route, and the adjustable fan speed&#10;was pretty useful too. I didn&amp;rsquo;t end up needing to adjust it very often at all&#10;because I found that a higher fan speed messed with the temperature more than I&#10;wanted while not really changing the effective humidity much at all.&lt;/p&gt;&#10;&lt;p&gt;Sealing off the cable gaps in the door gasket was very helpful in maintaining&#10;temperature. I duct-taped the cables down first to make sure it stayed put, then&#10;put foam tape over top to help reduce air leakage.&lt;/p&gt;&#10;&lt;p&gt;I bought a &lt;a href="https://temppro.com/products/tp50-digital-indoor-hygrometer-thermometer" target="_blank" rel="noreferrer"&gt;cheap thermohygrometer&lt;/a&gt;&#10;as a backup in case the Pi ever went down. It worked fine as a sanity check but&#10;I&amp;rsquo;d want to invest in a better one if I was relying on it directly.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;meat&#10; &lt;div id="meat" 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="#meat" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;I was a bit impulsive about buying the meat: I&amp;rsquo;d had the fridge for a while, but&#10;had been procrastinating actually starting on everything, so one day I was at&#10;Costco Business Centre and decided to just get a starter piece of meat for the&#10;sake of giving myself a kick in the pants.&lt;/p&gt;&#10;&lt;p&gt;The beef I ended up with for this first experiment was an ungraded, boneless ribeye&#10;roast weighing 14.2 lbs. Not a particularly great one either: the fat cap was&#10;thick, the meat wasn&amp;rsquo;t especially marbled, and the spinalis was thinner than I&amp;rsquo;d&#10;have liked. I mostly wanted to get a roast I wouldn&amp;rsquo;t be too upset about ruining&#10;if the experiment flopped.&lt;/p&gt;&#10;&lt;p&gt;I didn&amp;rsquo;t do a great job of documenting the &amp;ldquo;before&amp;rdquo; state: I forgot to log the&#10;total cost and size of the whole roast before trimming, so I can&amp;rsquo;t do a really good&#10;cost estimate, but I did remember to portion off a steak so I could have a&#10;before/after comparison tasting. I also trimmed it up so that it would fit&#10;horizontally on the shelf with some space for airflow around the sides.&lt;/p&gt;&#10;&lt;p&gt;My goal was to age this first roast for 45 days. I&amp;rsquo;ve had 30-day dry-aged steaks&#10;before, so with my home experimentation I really wanted to try going for a&#10;meaningful increase but not necessarily going all the way to 60 days (which the&#10;research suggests is potentially quite overpowering).&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;monitoring&#10; &lt;div id="monitoring" 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="#monitoring" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;I set up the Pi to record data (temp, humidity, pressure, gas) every 30 seconds,&#10;which gave me a pretty good representation of the fridge cycle, and I calculated&#10;the mean values for the whole up/down fridge cycle as well as the 5th, 20th,&#10;80th, and 95th percentile values (avoiding outliers in case I opened the fridge&#10;momentarily).&lt;/p&gt;&#10;&lt;p&gt;I put together an R Markdown report to summarize the data, and set it up to knit&#10;via a cron job every 15 minutes on my main home server rather than on the Pi&#10;itself (which was too underpowered). I also chose to push the report to an&#10;&lt;a href="https://s3.tan.gg/dry-age-monitor" target="_blank" rel="noreferrer"&gt;S3 bucket page&lt;/a&gt; rather than continually&#10;committing it to GitHub like I might have in the past (lots of images being&#10;committed that often would lead to repository size growing indefinitely).&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;temperature&#10; &lt;div id="temperature" 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="#temperature" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure&gt;&lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="chart of fridge cycle temperatures over 45 days"&#10; width="2304"&#10; height="1536"&#10; src="https://tanho.ca/posts/2026-08-30_dry-aging/temperature-chart_hu_10cd966a57a10d8c.webp"&#10; srcset="https://tanho.ca/posts/2026-08-30_dry-aging/temperature-chart_hu_10cd966a57a10d8c.webp 800w, https://tanho.ca/posts/2026-08-30_dry-aging/temperature-chart_hu_3ec13b204cb35943.webp 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://tanho.ca/posts/2026-08-30_dry-aging/temperature-chart.webp"&gt;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Temperature-wise, the internet says that dry-age temperatures should be between&#10;34F and 38F, that meat freezes at about 30F, and that the danger zone from a food&#10;safety perspective starts at 40F. I decided to shoot for:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;mean cycle temp between 36F and 38F&lt;/li&gt;&#10;&lt;li&gt;5th and 20th percentiles above 30F&lt;/li&gt;&#10;&lt;li&gt;95th percentile at or below 41F&lt;/li&gt;&#10;&lt;li&gt;80th percentile at or below 40F&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;These felt like reasonable goals because the meat itself shouldn&amp;rsquo;t actually reach&#10;over 41F - the sensor reacts to air temperature swings much faster than the&#10;meat&amp;rsquo;s surface does. I also wanted to avoid short-cycling the fridge compressor,&#10;which would reduce the lifespan of the fridge and also probably cost more money&#10;to run overall.&lt;/p&gt;&#10;&lt;p&gt;Unfortunately, my fridge&amp;rsquo;s cycle maxes and mins seemed to naturally range between&#10;32F and 42F, even on the coldest fridge setting. While I was willing to risk a&#10;small portion (less than 20%) of the time being between 40F and 41F, I was&#10;worried that any more time than that, or any warmer a band, would be more likely&#10;to cause spoilage and &amp;ldquo;bad&amp;rdquo; bacteria growth.&lt;/p&gt;&#10;&lt;p&gt;I tried increasing fan speed, thinking that it would circulate cold air up faster.&#10;However, this didn&amp;rsquo;t really change the set-point of the fridge&amp;rsquo;s thermostat: the&#10;cooling happened faster since the cold air would circulate more quickly on the&#10;&amp;ldquo;down&amp;rdquo; part of the cycle, but the compressor didn&amp;rsquo;t kick on any earlier.&lt;/p&gt;&#10;&lt;p&gt;&lt;figure&gt;&lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="I bent the fridge&amp;rsquo;s thermostat sensor wire upwards to fool it into thinking the fridge was warmer"&#10; width="2364"&#10; height="1330"&#10; src="https://tanho.ca/posts/2026-08-30_dry-aging/fridge-sensor_hu_9bac3a27f1daf39f.jpg"&#10; srcset="https://tanho.ca/posts/2026-08-30_dry-aging/fridge-sensor_hu_9bac3a27f1daf39f.jpg 800w, https://tanho.ca/posts/2026-08-30_dry-aging/fridge-sensor_hu_ead309ff5579d108.jpg 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://tanho.ca/posts/2026-08-30_dry-aging/fridge-sensor.jpg"&gt;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;I ended up messing with the fridge&amp;rsquo;s thermostat sensor itself: this is basically&#10;a wire that sat in a little housing on the back of the fridge, so I took the&#10;housing off and bent the wire upwards to move the end of the sensor into a&#10;warmer spot of the fridge. This kicked the compressor on a little earlier (~41F)&#10;and also caused it to turn off a bit later (~28F), since it took longer for the&#10;&amp;ldquo;cold&amp;rdquo; to register on the sensor. That latter part was easy to fix: just turn&#10;the dial down!&lt;/p&gt;&#10;&lt;p&gt;I also wanted to try to lengthen the cycle to help the fridge run a little more&#10;efficiently, so I added some thermal mass in the form of 4L jugs of water - this&#10;helped slow the rate at which the fridge warmed between the compressor shutting&#10;off and kicking on again.&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;humidity&#10; &lt;div id="humidity" 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="#humidity" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure&gt;&lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="chart of relative humidity over 45 days"&#10; width="2304"&#10; height="1536"&#10; src="https://tanho.ca/posts/2026-08-30_dry-aging/humidity-chart_hu_f96621f8725d68cc.webp"&#10; srcset="https://tanho.ca/posts/2026-08-30_dry-aging/humidity-chart_hu_f96621f8725d68cc.webp 800w, https://tanho.ca/posts/2026-08-30_dry-aging/humidity-chart_hu_a93788b69bab578c.webp 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://tanho.ca/posts/2026-08-30_dry-aging/humidity-chart.webp"&gt;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Humidity seemed to sit really high throughout the process, swinging from 50% to&#10;100%+, according to the BME690 sensor. This led to some worries about condensation&#10;forming on either the meat or on the walls of the fridge (and eventually ice).&#10;My research suggested that the meat would give off moisture for at least 2-3 weeks,&#10;so I just added a quarter sheet pan of kosher salt hoping that it would resolve&#10;itself eventually.&lt;/p&gt;&#10;&lt;p&gt;This didn&amp;rsquo;t really happen: humidity levels stayed basically the same, so I replaced&#10;the salt with a quarter sheet pan of silica beads (making sure to orient the fan to&#10;blow air across the tray but not allowing the meat to touch the silica at all).&#10;This made a meaningful difference in bringing down the humidity, but only lasted&#10;a short while and needed replacing about every week.&lt;/p&gt;&#10;&lt;p&gt;Interestingly, when I left for a one-week trip to Croatia, the humidity &lt;strong&gt;declined&lt;/strong&gt;&#10;steadily while I was away, but somehow spiked immediately &lt;strong&gt;before&lt;/strong&gt; I got home.&#10;This clued me in to a potential reason why the humidity was sitting so high:&#10;when I left, I turned the house&amp;rsquo;s AC off and programmed it to turn back on&#10;shortly before I got back from the trip. The fridge is in the same room as the&#10;HVAC, and that room gets warmer and more humid when either the heating or air conditioning is running. The fridge pulls that humid air in, which keeps the&#10;internal humidity high even after the meat has lost the moisture it was always&#10;going to lose.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;tasting notes&#10; &lt;div id="tasting-notes" 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="#tasting-notes" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10;&lt;div class="width-patch"&gt;&lt;/div&gt;&#10;&lt;div id="gallery-e5241e70c6c112313b29b35defef48a8" class="gallery"&gt;&#10; &lt;img alt="" src="https://tanho.ca/posts/2026-08-30_dry-aging/04_dry_age.jpg" class="grid-w50 lg:grid-w33"&gt;&lt;img alt="" src="https://tanho.ca/posts/2026-08-30_dry-aging/03_dry_age_sliced.jpg" class="grid-w50 lg:grid-w33"&gt;&lt;img alt="" src="https://tanho.ca/posts/2026-08-30_dry-aging/02_steaks.jpg" class="grid-w50 lg:grid-w33"&gt;&lt;/div&gt;&#10;&#10;&lt;ul&gt;&#10;&lt;li&gt;Starting weight: 14.2 lbs&lt;/li&gt;&#10;&lt;li&gt;Ending weight: 12.36 lbs (13% loss)&lt;/li&gt;&#10;&lt;li&gt;Post-trim weight: ~9 lbs (largeish fat cap mostly to blame), cut into 10 steaks&#10;about 1.5&amp;quot; thick and between 12 and 14 oz each.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The dry-aged taste was noticeable but not overpowering (especially since I had been&#10;semi-aggressive in trimming off the pellicle) - really tasty! I&amp;rsquo;m very satisfied&#10;with the results. Haven&amp;rsquo;t tried a before/after comparison of flavour and tenderness&#10;yet - I think I&amp;rsquo;m saving that for when Mi Yen is home next.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;thoughts for next time&#10; &lt;div id="thoughts-for-next-time" 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="#thoughts-for-next-time" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;get a larger subprimal with bone + meat to protect the main steak, something like a &lt;a href="https://www.chefs-resources.com/types-of-meat/beef/cuts-of-beef/prime-rib/" target="_blank" rel="noreferrer"&gt;107&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;try hanging vertically via meat hooks instead of being constrained by the shelf width&lt;/li&gt;&#10;&lt;li&gt;go for only a little bit more time (maybe 50 days?)&lt;/li&gt;&#10;&lt;li&gt;investigate inoculating fridge/meat with mold&lt;/li&gt;&#10;&lt;li&gt;move fridge into a different room to be less sensitive to temp/humidity swings?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;All in all: delicious, 10/10 would do again - maybe even this week to be ready&#10;before my birthday in November?&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2026-08-30_dry-aging/featured.jpg"/></item><item><title>Things I Think You Should Read and Watch</title><link>https://tanho.ca/recs/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><guid>https://tanho.ca/recs/</guid><description>&lt;p&gt;This is a curated, non-exhaustive list of tech-adjacent content I&amp;rsquo;ve really&#10;enjoyed and shared with friends and colleagues.&lt;/p&gt;&#10;&lt;p&gt;Mostly general thoughts on life as a data scientist, learning/teaching programming,&#10;technical leadership, career advice, and other tech-adjacent topics, and not specific&#10;to R or sports analytics (will probably tackle that another time).&lt;/p&gt;&#10;&lt;p&gt;Inspired by Derek Sivers&amp;rsquo;s &lt;a href="https://sive.rs/book" target="_blank" rel="noreferrer"&gt;book notes&lt;/a&gt; and Vicki Boykis&amp;rsquo;s&#10;&lt;a href="https://vickiboykis.com/favorite_essays/" target="_blank" rel="noreferrer"&gt;favourite essays&lt;/a&gt;, and organized loosely&#10;by topic (click to jump to section):&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#life-as-a-programmer--data-scientist" &gt;life as a programmer&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#learning-and-teaching-programming" &gt;learning and teaching&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#technical-leadership" &gt;technical leadership&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#career-advice" &gt;career advice&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#technical-topics" &gt;technical topics&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanho.ca/recs/#changelog" &gt;changelog&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;life as a programmer &amp;amp; data scientist&#10; &lt;div id="life-as-a-programmer--data-scientist" 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="#life-as-a-programmer--data-scientist" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://mtlynch.io/human-code-reviews-1/" target="_blank" rel="noreferrer"&gt;Michael Lynch: How to Do Code Reviews Like A Human&lt;/a&gt;&#10;and &lt;a href="https://mtlynch.io/human-code-reviews-2/" target="_blank" rel="noreferrer"&gt;part two&lt;/a&gt;,&#10;blog posts (18 and 15 min reads, respectively)&#10;&lt;ul&gt;&#10;&lt;li&gt;Code reviews are daily interactions that shape both the code itself and, maybe even more&#10;importantly, the relationships between programmers - these posts really help with how&#10;to approach these in a constructive and positive way.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=1LAhHDEtTD0" target="_blank" rel="noreferrer"&gt;Hank Green: The Secret to My Productivity&lt;/a&gt;,&#10;YouTube video (4 min)&#10;&lt;ul&gt;&#10;&lt;li&gt;Invaluable advice on when to stop working on creative projects: stop working on it when&#10;you have reached 80% of &amp;ldquo;the best you can do&amp;rdquo;, release it to the world, and then go do&#10;your next project. Applies in hobbies and tbh to professional projects as well.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.counting-stuff.com/succeeding-as-a-data-scientist-in-small-companies-startups-92f59e22bd8c-65949948516031001b351309/" target="_blank" rel="noreferrer"&gt;Randy Au: Succeeding as a Data Scientist in Small Companies / Startups&lt;/a&gt;,&#10;blog post (8 min read)&#10;&lt;ul&gt;&#10;&lt;li&gt;How to succeed in a small company where you are the first &amp;ldquo;data&amp;rdquo; person there: helping&#10;the company succeed TODAY and setting up the company to be data driven TOMORROW.&#10;Also has a very nice &amp;ldquo;hierarchy of needs&amp;rdquo; graphic that explains what it means to set up a&#10;company to be data driven. Immediately relevant for people in small companies and also&#10;for understanding the core needs for a data team (and seeing how that scales as you add&#10;more people.)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.albertosavoia.com/uploads/1/4/0/9/14099067/thewayoftestivus.pdf" target="_blank" rel="noreferrer"&gt;Alberto Savoia: The Way of Testivus&lt;/a&gt;,&#10;PDF blog post&#10;&lt;ul&gt;&#10;&lt;li&gt;Advice on developer and unit testing, packaged up as pseudo-Confucian tenets worth&#10;living by.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://boringtechnology.club/" target="_blank" rel="noreferrer"&gt;Dan McKinley: Boring Technology&lt;/a&gt;, talk/slides&#10;and original &lt;a href="https://mcfunley.com/choose-boring-technology" target="_blank" rel="noreferrer"&gt;blogpost&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Core idea: you get &amp;ldquo;innovation tokens&amp;rdquo; to spend as a (developer, team) and you&#10;don&amp;rsquo;t have that many even across a team. In order to keep those innovation tokens&#10;spent on the team&amp;rsquo;s core mission (e.g. solving soccer), you should spend less&#10;time innovating on the core technologies (fancy database stuff, programming&#10;paradigms etc), because fixing bleeding edge database stuff takes up &amp;ldquo;innovation&#10;tokens&amp;rdquo; that you want to spend doing other things.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=th79W4rv67g" target="_blank" rel="noreferrer"&gt;David Robinson: Unreasonable Effectiveness Of Public Work&lt;/a&gt;,&#10;YouTube talk recording (40 min)&#10;&lt;ul&gt;&#10;&lt;li&gt;Anything you put out into the world is going to be far better and far more useful&#10;than anything that sits in your drafts folder waiting for some arbitrary threshold&#10;of &amp;ldquo;good&amp;rdquo; before it gets published. Go out there and do things!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;!-- candidates: egoless programming, grug brained developer? --&gt;&#10;&#10;&lt;h2 class="relative group"&gt;learning and teaching programming&#10; &lt;div id="learning-and-teaching-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="#learning-and-teaching-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;&lt;a href="https://www.youtube.com/watch?v=ewXvFQByRqY" target="_blank" rel="noreferrer"&gt;Greg Wilson: what everyone in tech should know about teaching and learning&lt;/a&gt;, YouTube video (41 min)&#10;&lt;ul&gt;&#10;&lt;li&gt;Massively useful! Especially the ideas right at the beginning about mental models and&#10;the differences between how beginners, competent users, and experts learn. The whole&#10;video is incredible, but I recommend at least the first 8:23 to cover these ideas.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=YWwBhjQN-Qw" target="_blank" rel="noreferrer"&gt;Andy Harris: how to begin thinking like a programmer&lt;/a&gt;,&#10;YouTube video (61 min)&#10;&lt;ul&gt;&#10;&lt;li&gt;A riveting talk (given at a Python conference, which should surprise you if you know me).&#10;which covers how to &lt;em&gt;think&lt;/em&gt; like a programmer. Especially profound: comments are not&#10;for explaining code to other programmers - instead, code is there to explain comments to&#10;the computer!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;technical leadership&#10; &lt;div id="technical-leadership" 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="#technical-leadership" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.noidea.dog/glue" target="_blank" rel="noreferrer"&gt;Tanya Reilly: Being Glue&lt;/a&gt;, annotated talk slides and &lt;a href="https://www.youtube.com/watch?v=KClAPipnKqw" target="_blank" rel="noreferrer"&gt;YouTube video&lt;/a&gt; (28 min)&#10;&lt;ul&gt;&#10;&lt;li&gt;A brutally honest talk about the non-promotable &amp;ldquo;glue&amp;rdquo; work that programmers can&#10;get silently stuck with, and that hurts your technical development and ultimately your&#10;career development if you&amp;rsquo;re not prepared. Can disproportionately affect teams,&#10;especially women and/or&#9;introverted/non-assertive personalities.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://jvns.ca/blog/2018/09/22/new-zine--help--i-have-a-manager/" target="_blank" rel="noreferrer"&gt;Julia Evans: Help, I Have A Manager!&lt;/a&gt;,&#10;zine (~$12)&#10;&lt;ul&gt;&#10;&lt;li&gt;Having a good relationship with your manager is probably the most important factor in&#10;your happiness at work. This zine is full of great advice on how to make that relationship&#10;better - how to handle 1:1s, asking for feedback, figuring out what they&amp;rsquo;re good at,&#10;building a support system for areas they aren&amp;rsquo;t as good, and more.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://vas3k.com/notes/hiring/index.html" target="_blank" rel="noreferrer"&gt;Vasily Zubarev: How to hire sane engineers 2.0&lt;/a&gt; (~15 min read)&#10;&lt;ul&gt;&#10;&lt;li&gt;A pragmatic Russian&amp;rsquo;s take on hiring engineers in a &amp;ldquo;sane&amp;rdquo; and effective way.&#10;Would love to implement this one day to help with bypassing some of the technical&#10;interview steps that are so common now.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;career advice&#10; &lt;div id="career-advice" 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="#career-advice" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://sive.rs/balance" target="_blank" rel="noreferrer"&gt;Derek Sivers: How To Do What You Love And Make Good Money&lt;/a&gt;,&#10;blog post with audio recording (4 min read/listen)&#10;&lt;ul&gt;&#10;&lt;li&gt;This is the advice I share when folks ask me about getting their dream job and making a&#10;career from sports analytics or other hobbies - you don&amp;rsquo;t need to force sports analytics to&#10;be both your livelihood and your art! The dream job that does both may or may not come along,&#10;but you can be happy in both career and art without it. Part of a larger book, &lt;a href="https://sive.rs/n" target="_blank" rel="noreferrer"&gt;Hell Yeah or No&lt;/a&gt;,&#10;which is also very worth reading (~$15).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://austinkleon.com/show-your-work/" target="_blank" rel="noreferrer"&gt;Austin Kleon: Show Your Work&lt;/a&gt;, short book (~$15)&#10;&lt;ul&gt;&#10;&lt;li&gt;The book I share when people ask me how to build their public portfolio and&#10;personal networks: share something small every day without being human spam, teach&#10;what you know, and tell good stories. A lot of insights packed into a small and&#10;easy to read format. Also great are the other books in the Steal trilogy, including&#10;&lt;a href="https://austinkleon.com/steal/" target="_blank" rel="noreferrer"&gt;Steal Like An Artist&lt;/a&gt; on how to be creative and&#10;&lt;a href="https://austinkleon.com/keepgoing/" target="_blank" rel="noreferrer"&gt;Keep Going&lt;/a&gt; on how to sustain the creative&#10;effort&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.manning.com/books/build-a-career-in-data-science" target="_blank" rel="noreferrer"&gt;Jacqueline Nolis &amp;amp; Emily Robinson: Build A Career In Data Science&lt;/a&gt;,&#10;book (~$24) and &lt;a href="https://podcast.bestbook.cool/" target="_blank" rel="noreferrer"&gt;podcast series&lt;/a&gt; (~20 episodes&#10;each about 60min)&#10;&lt;ul&gt;&#10;&lt;li&gt;A great resource for finding a job in data science. Really like the &lt;em&gt;definitions&lt;/em&gt; of data&#10;science as an industry - essentially the intersection of programming, statistics,&#10;and domain knowledge. Book was available online for free at one point but now&#10;seems to be only paid - very worth it, but listening to each of the podcast&#10;episodes is a pretty good equivalent.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://haseebq.com/my-ten-rules-for-negotiating-a-job-offer/" target="_blank" rel="noreferrer"&gt;Haseeb Qureshi: Rules for Negotiating a Job Offer&lt;/a&gt;&#10;and &lt;a href="https://haseebq.com/how-not-to-bomb-your-offer-negotiation/" target="_blank" rel="noreferrer"&gt;part two&lt;/a&gt;,&#10;blog posts (24 and 34 minute reads)&#10;&lt;ul&gt;&#10;&lt;li&gt;More people should negotiate job offers and simply don&amp;rsquo;t. This is good, not-too-kitschy&#10;advice on how to approach negotiating.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;specific technologies&#10; &lt;div id="specific-technologies" 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="#specific-technologies" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://vas3k.com/blog/machine_learning/index.html" target="_blank" rel="noreferrer"&gt;Vasily Zubarev: Machine Learning For Everyone&lt;/a&gt;,&#10;blog post (~40 min read)&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;ldquo;Machine Learning is like sex in high school. Everyone is talking about it, a few know what&#10;to do, and only your teacher is doing it.&amp;rdquo; An impressively nontechnical and yet surprisingly&#10;comprehensive overview of machine learning that I found useful early on in my data&#10;science career as someone with neither a CS nor a stats background.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://jvns.ca/blog/2020/11/22/new-zine--hell-yes--css-/" target="_blank" rel="noreferrer"&gt;Julia Evans: Hell Yes CSS&lt;/a&gt;,&#10;zine (~$12) and &lt;a href="https://jvns.ca/blog/2020/07/25/some-comics-about-css/" target="_blank" rel="noreferrer"&gt;comics&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I&amp;rsquo;ve always found CSS to be incredibly frustrating, but reading this zine was super helpful.&#10;Still refer back to the &lt;a href="https://wizardzines.com/comics/selectors/" target="_blank" rel="noreferrer"&gt;CSS Selectors&lt;/a&gt; comic to&#10;this day whenever I need to format something.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ohshitgit.com" target="_blank" rel="noreferrer"&gt;OhShitGit.com&lt;/a&gt; (reference page)&#10;&lt;ul&gt;&#10;&lt;li&gt;For the times I manage to fuck up using git, this page consistently saves me every single time.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://arelbundock.com/posts/dt_tb_df/index.html" target="_blank" rel="noreferrer"&gt;data.table vs base vs dplyr&lt;/a&gt; (reference page)&#10;&lt;ul&gt;&#10;&lt;li&gt;An awesome syntax lookup page to go between dplyr, base, and data.table in R - a worthy successor to the&#10;original post by &lt;a href="https://atrebas.github.io/post/2019-03-03-datatable-dplyr/" target="_blank" rel="noreferrer"&gt;atrebas&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;changelog&#10; &lt;div id="changelog" 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="#changelog" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;2026-07-21: added data.table vs base vs dplyr reference page&lt;/li&gt;&#10;&lt;li&gt;2025-09-04: added ohshitgit.com&lt;/li&gt;&#10;&lt;li&gt;2025-03-17: added David Robinson&amp;rsquo;s Unreasonable Effectiveness Of Public Work talk&lt;/li&gt;&#10;&lt;li&gt;2024-11-29: first version&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/recs/featured.jpg"/></item><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>Using GitHub To Make Your Work Live Forever</title><link>https://tanho.ca/talks/rsconf2022-github/</link><pubDate>Wed, 27 Jul 2022 00:00:00 +0000</pubDate><guid>https://tanho.ca/talks/rsconf2022-github/</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/wzcz4xNGeTI?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/talks/rsconf2022-github/featured.jpg"/></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>Eating Habits as Levers</title><link>https://tanho.ca/posts/2021-10-09_eating-habits/</link><pubDate>Sat, 09 Oct 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2021-10-09_eating-habits/</guid><description>&lt;p&gt;I stepped on the scale today and realized I had hit a milestone: I&amp;rsquo;ve lost fifty&#10;pounds since starting my weight loss efforts five months ago. It&amp;rsquo;s a nice, if&#10;somewhat bittersweet milestone to hit on this weight loss journey - I recorded&#10;a pretty similar milestone in 2018 while going from 275 lbs to 200 lbs. Life&#10;happened (happens, I guess) and while I&amp;rsquo;m a tad bitter, there&amp;rsquo;s some comfort in&#10;the confidence that I&amp;rsquo;m capable of reaching my goals when I put the effort in.&lt;/p&gt;&#10;&lt;p&gt;I wanted to write down a mental model that I&amp;rsquo;ve been experimenting with around&#10;dieting, eating habits, and weight loss. While this post is not a formally&#10;qualified opinion and does not constitute medical advice, it feels like a useful&#10;mental model for understanding the many varied paths to the same goal.&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Levers to use towards a caloric deficit&#10; &lt;div id="levers-to-use-towards-a-caloric-deficit" 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="#levers-to-use-towards-a-caloric-deficit" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;lever&lt;/strong&gt; (noun):&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;A long handle used to operate a machine or control panel.&lt;/li&gt;&#10;&lt;li&gt;A simple machine consisting of a straight, rigid bar and a fulcrum, used&#10;to multiply mechanical force to do more work.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;Losing weight involves consuming less calories than you expend over a significant&#10;amount of time&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; - aka &amp;ldquo;caloric deficit&amp;rdquo;. In most cases (and especially among&#10;those with a lot of weight to lose), adjusting your eating habits is a much&#10;bigger driver of caloric deficits than purely exercising (although exercise is&#10;good and useful). So let&amp;rsquo;s define for ourselves the work (in a mechanical sense)&#10;as being &amp;ldquo;eating a caloric deficit&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Now that we&amp;rsquo;ve defined work, we can frame all &amp;ldquo;diets&amp;rdquo; as three types of levers&#10;(in the &amp;ldquo;simple machines&amp;rdquo; sense above) that we can apply force (i.e. effort, time,&#10;discipline) to: &lt;em&gt;&lt;strong&gt;what&lt;/strong&gt;&lt;/em&gt; you eat, &lt;em&gt;&lt;strong&gt;when&lt;/strong&gt;&lt;/em&gt; you eat, and &lt;em&gt;&lt;strong&gt;how much&lt;/strong&gt;&lt;/em&gt; you&#10;eat.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;What you eat&lt;/strong&gt; covers all kinds of food restrictions, including low carb&#10;(keto, Atkins etc), low fat, high protein, vegetarian/vegan, paleo, or&#10;otherwise. These primarily work because there&amp;rsquo;s a property about that food&#10;that makes it hard to overeat - in keto, you eat more fats and your brain&#10;thinks you&amp;rsquo;re satiated before you reach a certain calorie threshold, while in&#10;low fat and/or vegetarian diets you eat more greens and proteins so that your&#10;stomach becomes full before it can take in a certain number of calories etc.&#10;This works - the primary mechanism for me losing weight in 2018 was eating&#10;a keto diet and being ruthless about cutting out carbs.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;When you eat&lt;/strong&gt; covers all kinds of fasting - intermittent, extended, or&#10;otherwise. The primary mechanism here is eating fewer meals, so while you do eat&#10;to fullness you end up eating less calories than you would have if you&amp;rsquo;d eaten&#10;three meals. In practice your appetite is also somewhat diminished (or your&#10;stomach &amp;ldquo;shrinks&amp;rdquo;) so your fullness threshold happens more quickly and at a lower&#10;calorie threshold. This can also work on a broader scale as I&amp;rsquo;m doing right now&#10;in 2021, where over the course of a seven day week I fast on three days -&#10;effectively reducing my calorie intake by 40%.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;How much you eat&lt;/strong&gt; covers calorie, macro, or portion control for each meal.&#10;If this is the lever you&amp;rsquo;re using, you&amp;rsquo;re doing the classic &amp;ldquo;track your calories&#10;at each meal&amp;rdquo; methodology and being precise about how much you take in at each&#10;meal - think of classic apps like My Fitness Pal, IIFYM etc.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;All three of these levers, when properly applied&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; within the goal of caloric&#10;deficit, successfully lead to weight loss over time with adherence. If you use&#10;none of them, you&amp;rsquo;re eating the Standard American Diet (SAD being an apt acronym).&lt;/p&gt;&#10;&lt;p&gt;Some levers are easier for some people to use than others - the one that is&#10;easiest for me may not be the easiest for someone else. Heck, the one that&amp;rsquo;s&#10;easiest for me today (when to eat/fasting) is not the same as the one that&#10;worked the last time (what to eat/keto). In 2018, I ground out a keto strategy&#10;to some success, but found food boringly repetitive and socializing difficult.&#10;I&amp;rsquo;m finding fasting easier to stick to this time around, because when I do eat I&#10;don&amp;rsquo;t need to worry about &amp;ldquo;what&amp;rdquo; I&amp;rsquo;m eating or &amp;ldquo;how much&amp;rdquo; I&amp;rsquo;m eating at each&#10;meal - I can eat until I&amp;rsquo;m full when I do eat and eat whatever I&amp;rsquo;m craving.&lt;/p&gt;&#10;&lt;p&gt;You can apply force on these levers with varying degrees of intensity (fasting&#10;for longer, reducing portion sizes more, cutting out more foods), and you can&#10;use one, two, or three levers at a time. It is mentally draining to use two or&#10;all three levers intensely at once - i.e. keto + one meal a day is very tough to&#10;stick to, as I&amp;rsquo;ve found out - it could be doable in the short term as a treatment&#10;but feels so unsustainable as a long term plan.&lt;/p&gt;&#10;&lt;p&gt;If you can strive to put consistent effort into just one of these levers at a time,&#10;you should be able to stick to that pattern for a longer period of time - and&#10;adherence to a caloric deficit leads to more successful weight loss.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;There are some other hypotheses about why obesity exists, including the&#10;carbohydrate-insulin model, but I don&amp;rsquo;t personally find them very convincing&#10;after experimenting with them. I&amp;rsquo;m not sure I&amp;rsquo;m the right person to discuss those&#10;without having done the research.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;All three levers have possible ways to fail at the goal of eating a&#10;caloric deficit - you can still indulge in too many calories while eating keto,&#10;you can binge eat when fasting so as to wipe out your efforts, and you can be&#10;poor/ineffective at tracking your calories at each meal (or misjudge the correct&#10;calorie numbers etc).&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2021-10-09_eating-habits/featured.png"/></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>Like-Hearted</title><link>https://tanho.ca/posts/2021-03-04_like-hearted/</link><pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2021-03-04_like-hearted/</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="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;em&gt;I immensely recommend Austin Kleon and the &lt;a href="https://austinkleon.com/books" target="_blank" rel="noreferrer"&gt;Steal trilogy&lt;/a&gt; to everyone, &lt;strong&gt;especially&lt;/strong&gt; those looking to &amp;ldquo;make it&amp;rdquo; as a creator, programmer, or data analyst.&lt;/em&gt;&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h3 class="relative group"&gt;TLDR: Curating your feed, finding your friend groups, and getting into online arguments can be good&#10; &lt;div id="tldr-curating-your-feed-finding-your-friend-groups-and-getting-into-online-arguments-can-be-good" 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-curating-your-feed-finding-your-friend-groups-and-getting-into-online-arguments-can-be-good" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;Each year, I embark on a journey to re-read Austin Kleon&amp;rsquo;s &lt;a href="https://austinkleon.com/books" target="_blank" rel="noreferrer"&gt;Steal trilogy&lt;/a&gt;, and each time I read it, something new grabs my attention. I was struck today by this image from a chapter in Keep Going called &amp;ldquo;Like-Minded vs Like-Hearted&amp;rdquo;:&lt;/p&gt;&#10;&lt;figure&gt;&#10;&lt;img &#10; src="arguing.jpg" &#10; alt="I WILL NOT ARGUE WITH STRANGERS ON THE INTERNET (repeated text on chalkboard)" &#10; style="max-height: 40vh;" &#10; class="rounded-3xl"&#10; /&gt;&#10; &lt;figcaption&gt;Modern day Bart Simpson would totally be punished with this &lt;/figcaption&gt;&#10; &lt;/figure&gt;&#10;&lt;p&gt;Like many people, I&amp;rsquo;m not immune to getting drawn into online arguments - just ask the folks on the &lt;a href="https://twitter.com/dynasty_discord" target="_blank" rel="noreferrer"&gt;Dynasty Movement&lt;/a&gt; Discord server about my many different soapboxes - so reading this immediately after having been drawn into a particularly vivid discussion hit a little closer to home.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve heard many a time that you should never get drawn into an argument online: it&amp;rsquo;s impossible to win, petty trolls cannot be fed, and that it&amp;rsquo;ll have a negative impact on your work, life, and mental health. It&amp;rsquo;s easy to point to examples of this - a notable Twitter presence making personal attacks, someone repeatedly moving the goalposts on a debate, or someone standing up for an controversial idea and getting bombarded with bits of conventional wisdom - and think that you&amp;rsquo;d better served by avoiding confrontations altogether.&lt;/p&gt;&#10;&lt;p&gt;And yet,&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;To think independently of other human beings is impossible. Thinking is necessarily, thoroughly, and wonderfully social. Everything you think is a response to what someone else has thought and said.&amp;rdquo; - Alan Jacobs, How To Think (c/o Keep Going)&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;I&amp;rsquo;ve spent a large amount of time trying to fill my Twitter feed with thoughts, ideas, and opinions from brilliant and insightful people, on topics that I want to get better at. I want my feed to be intelligent, open, and thought-provoking. I&amp;rsquo;m not as interested in helping others make it big, amass follower counts, become more influential - heck, I&amp;rsquo;m not looking to do that myself! My Twitter feed is precious to me, and I&amp;rsquo;m fastidious about stripping away the content that doesn&amp;rsquo;t fit those goals, or just plain old doesn&amp;rsquo;t make me happy.&lt;/p&gt;&#10;&lt;p&gt;This is both a blessing and a curse: it&amp;rsquo;s very easy to fall into a trap where you fill your feed (and friend groups) with &lt;strong&gt;like-minded&lt;/strong&gt; people - the people who like the same programming language you do, the same dynasty fantasy strategies, the same analytical methods. Having too much in common makes it tricky to find your own space and niche within your friend group, and you become deeply set in your ways.&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;p&gt;If you really want to explore ideas, you should consider hanging out with people who aren&amp;rsquo;t so much like-minded as &lt;strong&gt;like-hearted&lt;/strong&gt; - those who are temperamentally disposed to openness and have habits of listening. The ones who think about what you&amp;rsquo;re saying rather than just reacting.&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;This is a really valuable idea - and one that I feel like I need to strive for, and keep striving for. I&amp;rsquo;ve backed into finding like-hearted people in my feed, inadvertently, because I&amp;rsquo;ve been following more and more open source sports data and R stats community members, and that community is inherent more likely to share, listen, and celebrate each other&amp;rsquo;s achievements.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve also lucked into a community in the TDM Discord where I feel comfortable making those online arguments and challenging opinions, because so many folks there feel like-hearted to me - they listen, they challenge me to be articulate my opinions, and when we disagree we can still respect each other&amp;rsquo;s opinions. It&amp;rsquo;s rare that I feel like I&amp;rsquo;m left angry or drained after spending time there.&lt;/p&gt;&#10;&lt;p&gt;The takeaway from the chapter, for me, is that I should continue to strive to surround myself with like-hearted people - on my Twitter feed, in my Discord/Slack communities, and in real in-person friend groups. I&amp;rsquo;m lucky to have the communities I&amp;rsquo;m in, and I&amp;rsquo;m very glad to be able to get drawn into debates amongst my like-hearted friends.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2021-03-04_like-hearted/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><item><title>Regatta Cheatsheet</title><link>https://tanho.ca/posts/2019-07-05_regatta-cheatsheet/</link><pubDate>Fri, 05 Jul 2019 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2019-07-05_regatta-cheatsheet/</guid><description>&#10;&lt;h2 class="relative group"&gt;The ORC Rec Guide to Regattas&#10; &lt;div id="the-orc-rec-guide-to-regattas" 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-orc-rec-guide-to-regattas" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;A guide to everything regattas: a packing list, approximate scheduling, de-rigging and re-rigging, trailer loading, rules of racing, race plans, and more.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Contents&#10; &lt;div id="contents" 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="#contents" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#equipment-list" &gt;Equipment List&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#scheduling" &gt;Scheduling&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#de-rigging-and-rigging" &gt;De-Rigging and Rigging&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#trailer-loading" &gt;Trailer Loading&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#rules-of-racing" &gt;Rules of Racing&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="%7B%7Bpage.url%7D%7D/#race-plans" &gt;Race Plans&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Equipment List&#10; &lt;div id="equipment-list" 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="#equipment-list" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;A list of gear to pack in your regatta bag.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Three wrenches: 10mm, 7/16&amp;quot;, &lt;strong&gt;and&lt;/strong&gt; an adjustable - you will need all three! (Ratcheting helps but is not required.)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Sunscreen, bug spray&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Water&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Snacks, food&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;ID, money for concession stand&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Optional: First aid kit (including anti-septic, bandages, blister bandages, after-bite, sunburn lotion etc)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;ORC uni (or a uni top) - a red shirt will do in a pinch but will create problems at more serious regattas&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Spare rowing outfit&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Rain jacket&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Change of clothes&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Towel&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Flip flops&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Sunglasses&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Hat&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Oakleys, bucket hat, Fat Ergos uni, Crocs, butter for greasing boat (all optional, for free speed)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Scheduling&#10; &lt;div id="scheduling" 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="#scheduling" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Generally speaking, the process for any regatta looks like this, but check with coaches to confirm exact dates/times.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Day before: derig all boats going to regatta, load onto trailer, secure.&lt;/li&gt;&#10;&lt;li&gt;Morning of: arrive at minimum two hours before your scheduled start time to ensure that you have time to rig your boat, get any last minute regatta-specific instructions, and launch at the start. The two hours will disappear incredibly quickly.&lt;/li&gt;&#10;&lt;li&gt;After races, derig the boat again and load onto trailer.&lt;/li&gt;&#10;&lt;li&gt;Either same day or next day (check with coach), arrive at ORC for trailer unloading and re-rigging.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Derigging and Rigging&#10; &lt;div id="derigging-and-rigging" 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="#derigging-and-rigging" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Derigging&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Get the boat on stretchers, guts up - this will likely require the help of others.&lt;/li&gt;&#10;&lt;li&gt;Determine whether the boat uses 7/16&amp;quot; or 10mm hardware, and get the appropriate hardware out.&lt;/li&gt;&#10;&lt;li&gt;If others are helping you, divide and conquer: one person (or team of people) go around and loosen all of the rigger nuts and bolts until they are finger-loose, while the other person follows behind to remove the hardware with their fingers, putting the nuts and bolts back into the loose rigger and finger-tightening. Once all nuts and bolts are loosened, that person should ensure that all seats are fastened to the boat with bungee cords. If no bungee cords are available, worst-case you can remove the seats from the boat and place with riggers, but this is generally a very poor alternative that leads to missing equipment.&lt;/li&gt;&#10;&lt;li&gt;Place all of the boat&amp;rsquo;s riggers in a pile near the truck/trailer.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Follow procedure in reverse for rigging - stretchers, finger-tighten nuts and bolts, finish with wrenches, remove bungee cords from seats. Also a good opportunity to check shoes, heel-tiedowns, and speedwashers.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Trailer Loading&#10; &lt;div id="trailer-loading" 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="#trailer-loading" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;{TBC}&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Rules of Racing&#10; &lt;div id="rules-of-racing" 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="#rules-of-racing" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;{TBC}&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Race Plans&#10; &lt;div id="race-plans" 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="#race-plans" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;{TBC}&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2019-07-05_regatta-cheatsheet/featured.jpg"/></item><item><title>Short Mobility Warmup for Rowing</title><link>https://tanho.ca/posts/2019-05-29_rowing-warmup/</link><pubDate>Wed, 29 May 2019 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2019-05-29_rowing-warmup/</guid><description>&lt;p&gt;The few short minutes before ORC Rec practices are often hectic as everyone rushes over after work, gets coach boats and equipment ready, and then hops into a boat. That said, I&amp;rsquo;m hoping to add another wrench into the works this year - a short dynamic warmup to promote mobility and get ourselves ready for rowing.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Short Description&#10; &lt;div id="short-description" 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="#short-description" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Two sets of the following circuit:&lt;/p&gt;&#10;&lt;blockquote&gt;&lt;p&gt;Halos (5 reps in each direction)&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Cossack Squat (5 reps each side)&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;(Walking) Spiderman with Hip Lift and Overhead Reach (10 total reps)&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;Focus on body control, consistent pace, and as much mobility as you can manage. &lt;strong&gt;Aim to complete at least one set of the circuit before every practice&lt;/strong&gt;, although if time allows three wouldn&amp;rsquo;t hurt.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Exercise Details&#10; &lt;div id="exercise-details" 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="#exercise-details" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Halos&#10; &lt;div id="halos" 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="#halos" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;Focus: Shoulder mobility, posture, abdominal strength (if weighted)&lt;/p&gt;&#10;&lt;p&gt;Halos can be done with anything in your hands - a weight plate, medicine ball, kettlebell, or anything similar in size/shape - and weighing something like 10-25 pounds. The goal is to draw a circle around your head at face-level while holding onto the item with both hands and not letting go. If you don&amp;rsquo;t have something to hang onto, you can get a lot of the same mobility/warmup benefits by interlacing your fingers at the appropriate height.&lt;/p&gt;&#10;&lt;p&gt;Cues: Focus on standing tall, keeping your head neutral, and bracing your core. You&amp;rsquo;re looking to activate the full range of motion in your shoulders. You don&amp;rsquo;t have to alternate directions every rep - I generally find it simpler to do five rotations in one direction and then five in the other.&lt;/p&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/2jod0tIkRbo?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;h3 class="relative group"&gt;Cossack Squats&#10; &lt;div id="cossack-squats" 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="#cossack-squats" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;Focus: Ankle/Knee/Hip/Groin mobility, core strength, balance&lt;/p&gt;&#10;&lt;p&gt;Cossack Squats are a fantastic way to work on mobility and strength at the catch position. &lt;strong&gt;This is a difficult exercise to pick up for most people, but it&amp;rsquo;s extremely rewarding!&lt;/strong&gt; You&amp;rsquo;ll want to work on getting better at it over the course of the rowing season - &lt;strong&gt;take it slowly&lt;/strong&gt; and gradually work on pushing your ranges on this a little more every day.&lt;/p&gt;&#10;&lt;p&gt;The basic Cossack Squat motion involves an extra-wide stance, squatting down as deep as possible on one leg while keeping heels on the floor, and pointing the other leg out straight to the side with the toes pointing to the ceiling. Transition from one side to the other and sink in on that new side. I find it helpful to hold a weight while doing this (the same weight as from the Halos exercise is usually fine) - this helps counterbalance you and lets you keep a stronger back position while sinking a little deeper.&lt;/p&gt;&#10;&lt;p&gt;Cues: [try to add one of these at a time]&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keep your heels on the floor!&lt;/li&gt;&#10;&lt;li&gt;Keep a strong back position (chest up, eyes up, no slumping!)&lt;/li&gt;&#10;&lt;li&gt;Keep your hips low as you swing across to the other side (activates a better groin stretch)&lt;/li&gt;&#10;&lt;li&gt;Get a little more depth by sitting a little lower each time&lt;/li&gt;&#10;&lt;li&gt;Get a little more forward ankle compression by rocking your weight forward on each foot.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This video demo from FitnessFAQs.tv is quite good - he does a good job of demonstrating body position cues as well as going through a progression from beginner to more advanced Cossack Squats, although I recommend keeping hips low as you go across to activate the groin stretch whereas he prefers standing up between each rep.&lt;/p&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/tpczTeSkHz0?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;&lt;p&gt;If you&amp;rsquo;re struggling with the initial variations of the Cossack squats, this page details a few more progressions that might help you get there: &lt;a href="https://juanlugofitness.com/cossack-squat/" target="_blank" rel="noreferrer"&gt;https://juanlugofitness.com/cossack-squat/&lt;/a&gt;&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;(Walking) Spiderman with Hip Lift and Overhead Reach&#10; &lt;div id="walking-spiderman-with-hip-lift-and-overhead-reach" 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="#walking-spiderman-with-hip-lift-and-overhead-reach" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;Focus: Hamstring/Quadricep/Groin/Thoracic mobility&lt;/p&gt;&#10;&lt;p&gt;Walking Spidermans (as designed by Eric Cressey, video demo below) combines a few familiar warmup elements in a dynamic exercise. I would describe it as a lunge (quads/hip flexors/groin), hip lift to hamstring stretch, and an overhead reach (thoracic rotation). This doesn&amp;rsquo;t have to be done &amp;ldquo;walking&amp;rdquo; if space is limited - just come back to a standing position and do the other leg.&lt;/p&gt;&#10;&lt;p&gt;The demo below is very straight-forward, although &lt;strong&gt;I recommend the overhead reach in the other direction (i.e. rotating towards the front leg as opposed to away from it)&lt;/strong&gt;. This makes the warmup a more similar to the sweep rowing motion.&lt;/p&gt;&#10;&lt;p&gt;Cues: Keep your hips low in the lunge, work to find that stretch, and track your overhead reach with your eyes.&lt;/p&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/W50Jsyc80SA?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;&lt;hr&gt;&#10;&lt;p&gt;&lt;strong&gt;Any questions? I&amp;rsquo;ll be happy to demonstrate these motions over the next few months.&lt;/strong&gt;&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2019-05-29_rowing-warmup/featured.jpg"/></item><item><title>The ORC Rec Guide to Being a Coxswain on the Ottawa River</title><link>https://tanho.ca/posts/2018-07-04_coxie/</link><pubDate>Wed, 04 Jul 2018 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2018-07-04_coxie/</guid><description>&lt;p&gt;&lt;em&gt;Created by: Peter Aubin with contributions from Lauren E and myself&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Congratulations, you&amp;rsquo;re in charge. Your rowers expect you to be clear, prepared, and commanding - speak up!&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Responsibilities&#10; &lt;div id="responsibilities" 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="#responsibilities" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Provide clear, loud, and commanding instructions to rowers&lt;/li&gt;&#10;&lt;li&gt;Navigate and steer the boat&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Required Items&#10; &lt;div id="required-items" 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="#required-items" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Cox box and headset - found in upstairs office, make sure cox box turns on and headset fits into cox box&lt;/li&gt;&#10;&lt;li&gt;Life jacket (Bay 2), whistle and lights (your own)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Delivering Instructions&#10; &lt;div id="delivering-instructions" 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="#delivering-instructions" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;General Principle: Make sure your rowers understand the instruction before doing the action!&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;On-Land and Stationary&#10; &lt;div id="on-land-and-stationary" 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="#on-land-and-stationary" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The general format is: &lt;strong&gt;&amp;ldquo;Ready to ______, [pause], and ________&amp;rdquo;&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;i.e. &lt;strong&gt;&amp;ldquo;Ready to lift, [pause], and LIFT&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;This applies on land and when boat is stationary (i.e.&lt;strong&gt;&amp;ldquo;Ready to row, and row&amp;rdquo;&lt;/strong&gt;).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h3 class="relative group"&gt;On-Water&#10; &lt;div id="on-water" 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="#on-water" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;When in motion, give all instructions &lt;strong&gt;&amp;ldquo;In Two [Strokes]&amp;rdquo;&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;ldquo;In two, ______. This is one, [wait for a stroke], two, [wait for a stroke] and ________&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;i.e. &amp;ldquo;In two, let it run. This is one [stroke], two [stroke], let it run.&amp;rdquo;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;In case of emergency, call for immediate actions (&lt;strong&gt;&amp;ldquo;LET IT RUN!&amp;rdquo; or &amp;ldquo;HOLD WATER!&lt;/strong&gt;)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h3 class="relative group"&gt;From Boathouse to Dock&#10; &lt;div id="from-boathouse-to-dock" 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="#from-boathouse-to-dock" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Your crew should be waiting for you at the boat having brought already brought down oars - make sure you have eight rowers&lt;/li&gt;&#10;&lt;li&gt;Check to make sure that the bow of the boat faces the bay doors (this affects the direction you swing the boat, you want to be on the dock with bow facing Parliament)&lt;/li&gt;&#10;&lt;li&gt;Directions may vary slightly depending on what rack the boat is on (if higher than waist height, rowers can just get underneath, otherwise bear-hug and under is required) - sample below assumes waist height or lower&lt;/li&gt;&#10;&lt;li&gt;At any point if there is a delay (e.g. because boats are ahead of you) it is most comfortable for the crew to be waiting with the boat at waist height (i.e. &amp;ldquo;Down to Waist&amp;rdquo;)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Specific Instructions&#10; &lt;div id="specific-instructions" 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="#specific-instructions" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Specific Instructions: Boathouse to Dock&#10; &lt;div id="specific-instructions-boathouse-to-dock" 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="#specific-instructions-boathouse-to-dock" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;“Hands on the rack. Ready to pull, and pull.”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Bunch up bow and stern&amp;rdquo;&lt;/strong&gt; (make sure rowers are grouped tightly/shoulder-to-shoulder to reduce load)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Hands across (bear hug)&amp;rdquo;&lt;/strong&gt; (bear hug only applicable when waist height or lower)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Up an inch, ready and up”.&lt;/strong&gt; (keep an eye so that the boat does not hit anything above it. Boats are tight)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Walk it back”&lt;/strong&gt; (to get to middle of bay) &lt;strong&gt;&amp;ldquo;Up to Waist&amp;rdquo;&lt;/strong&gt; (if not at waist level)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“From bow, split under the boat.”&lt;/strong&gt; (Call out names of people to go under, if you can)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Hands on your side&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Up to shoulders on Prime Minister&amp;rsquo;s side, ready and up”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;(check the outside of the bay to avoid collisions) &lt;strong&gt;“Heads-up”&lt;/strong&gt; (loudly)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Walk it out”&lt;/strong&gt; (instruct to move right or left if needed to ensure that riggers clear the door)&lt;/li&gt;&#10;&lt;li&gt;(When clear of bay door), &lt;strong&gt;“Let it run. Up to shoulders on Parliament, ready and up”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Walk it down”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;As rowers reach the ramp, &lt;strong&gt;“Swing left”&lt;/strong&gt; [or right, if for some reason stern is facing outwards]&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Let it run”&lt;/strong&gt; (at this point bow should be facing Parliament, upstream and parallel to the edge of the dock)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Specific Instructions: Getting the Boat Into the Water and Away&#10; &lt;div id="specific-instructions-getting-the-boat-into-the-water-and-away" 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="#specific-instructions-getting-the-boat-into-the-water-and-away" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;“Toes to the edge”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Up Overhead, ready and up”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Roll to waist, ready and roll! Reaching out and into the water”&lt;/strong&gt; (watch the fin of the boat near the stern to make sure it does not catch on the dock)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Go get your oars, check your oarlocks, seats, and footstops&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;While they are getting oars, plug your cox box in and test the speakers/microphone&lt;/li&gt;&#10;&lt;li&gt;A coach should assign seats at this point, flag one down if they have not&lt;/li&gt;&#10;&lt;li&gt;Rowers should make adjustments to oarlocks/seats/footstops unless dock is busy, in which case they can be made on the water.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Starboard oars across, [pause] one foot in and down&amp;rdquo;&lt;/strong&gt; - wait for rowers to get in, then get in&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Number off from bow when ready”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;“Hands on the dock. Lean away. Ready to shove, and shove”&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;If necessary, &lt;strong&gt;&amp;ldquo;2 seat please push us off the dock&amp;rdquo;&lt;/strong&gt; (if any port oars still touching the dock)&lt;/li&gt;&#10;&lt;li&gt;Get &lt;strong&gt;&amp;ldquo;bow pair to take a few strokes&amp;rdquo;&lt;/strong&gt; to get clear of the docks, then go into warmups&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Specific Instructions: Docking and Getting the Boat Out of the Water&#10; &lt;div id="specific-instructions-docking-and-getting-the-boat-out-of-the-water" 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="#specific-instructions-docking-and-getting-the-boat-out-of-the-water" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Under the Macdonald Cartier bridge, &lt;strong&gt;&amp;ldquo;Stern Four drop out&amp;rdquo;&lt;/strong&gt; (alternatively, ask Bow Four to drop out if they are less experienced) to slow down the approach to the dock&lt;/li&gt;&#10;&lt;li&gt;Try to angle the boat gently into the dock - a sharp approach can cause damage to the bow ball. Give instructions to bow pair or stern pair to help get your angles right.&lt;/li&gt;&#10;&lt;li&gt;Ask crew to &lt;strong&gt;&amp;ldquo;lean away from the dock&amp;rdquo;&lt;/strong&gt; as you get close&lt;/li&gt;&#10;&lt;li&gt;Coxie gets out first&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;One foot on the dock, ready and up - starboard blades across&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;While rowers are removing blades, remove cox box assembly from boat&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Bunch up bow and stern&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Right hands across, ready to roll up to waist and UP! Up overheads, ready and UP!&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;From Stern, split to shoulders.&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Specific Instructions: Putting the Boat back into the Boathouse&#10; &lt;div id="specific-instructions-putting-the-boat-back-into-the-boathouse" 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="#specific-instructions-putting-the-boat-back-into-the-boathouse" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Make sure the stern of the boat goes into the boathouse first - this may involve getting rowers to &lt;strong&gt;&amp;ldquo;turn and face the bow&amp;rdquo;&lt;/strong&gt; and &lt;strong&gt;&amp;ldquo;Walk it down&amp;rdquo;&lt;/strong&gt; until the stern is in line with the ramp. Then get the rowers to &lt;strong&gt;&amp;ldquo;turn and face the stern&amp;rdquo;&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Swing it up the ramp&amp;rdquo;&lt;/strong&gt; until the boat is about to reach the bay doors then &lt;strong&gt;&amp;ldquo;Let it run&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Down to waist on Parliament, ready and down&amp;rdquo;&lt;/strong&gt; then &lt;strong&gt;&amp;ldquo;Walk it in&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Once the bow is fully in the boathouse, &lt;strong&gt;&amp;ldquo;Let it run&amp;rdquo;&lt;/strong&gt; and &lt;strong&gt;&amp;ldquo;Down to waist, ready and down&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;You want to put the boat back on the same racks, so [as necessary] &lt;strong&gt;&amp;ldquo;bear hug&amp;rdquo;&lt;/strong&gt; and &lt;strong&gt;&amp;ldquo;from bow, cross under&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;[As applicable] &lt;strong&gt;&amp;ldquo;Down to ankles&amp;rdquo;&lt;/strong&gt; and &lt;strong&gt;&amp;ldquo;Walk it on&amp;rdquo;&lt;/strong&gt; - watching the top of the boat and riggers&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Check for riggers&amp;rdquo;&lt;/strong&gt; - the rowers will tell you if the riggers are touching the racks, and then make the appropriate adjustments&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Bow Pair wipe it down, the rest get oars&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Return the cox box, microphone, and any lights back to the office then return for debrief&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Steering (adjusting course while boat is in motion)&#10; &lt;div id="steering-adjusting-course-while-boat-is-in-motion" 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="#steering-adjusting-course-while-boat-is-in-motion" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Try to make gradual adjustments for hazards/obstacles while they are still far away&lt;/li&gt;&#10;&lt;li&gt;Rudder controls are diagrammed below &amp;gt; they are only effective while the boat is moving with some speed.&lt;/li&gt;&#10;&lt;li&gt;You can always get your rowers to assist you - asking for &amp;ldquo;Hard on Starboard&amp;rdquo; will help you get towards port etc. Don&amp;rsquo;t forget to go back to &amp;ldquo;Even Pressure&amp;rdquo; when you&amp;rsquo;re done.&lt;/li&gt;&#10;&lt;li&gt;Stay close to shore as much as possible to avoid traffic from cruise boats and power boats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;figure&gt;&lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="Rudder controls and equivalent calls"&#10; width="712"&#10; height="609"&#10; src="https://tanho.ca/posts/2018-07-04_coxie/coxie-steering.png"&#10; srcset="https://tanho.ca/posts/2018-07-04_coxie/coxie-steering.png 800w, https://tanho.ca/posts/2018-07-04_coxie/coxie-steering.png 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://tanho.ca/posts/2018-07-04_coxie/coxie-steering.png"&gt;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Normal Practice Patterns&#10; &lt;div id="normal-practice-patterns" 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="#normal-practice-patterns" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Warmups are done between the bridges, then technical/drill work while on the Quebec side and power/pyramids on the way back.&lt;/li&gt;&#10;&lt;li&gt;Normal stopping places are: before the Alexandra Bridge, after the Macdonald Cartier Bridge, before the Gatineau River, at the tip of Kettle Island, the Airport, and ONEC - try not to stop the boat while in front of marinas or in front of the falls.&lt;/li&gt;&#10;&lt;li&gt;You can always consult with coaches or your stroke seat if you have any questions.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;figure&gt;&lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="Map of Traffic Patterns"&#10; width="644"&#10; height="536"&#10; src="https://tanho.ca/posts/2018-07-04_coxie/coxie-trafficpatterns.png"&#10; srcset="https://tanho.ca/posts/2018-07-04_coxie/coxie-trafficpatterns.png 800w, https://tanho.ca/posts/2018-07-04_coxie/coxie-trafficpatterns.png 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://tanho.ca/posts/2018-07-04_coxie/coxie-trafficpatterns.png"&gt;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Normal Warmups&#10; &lt;div id="normal-warmups" 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="#normal-warmups" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Warmups should be done with all eight, and they should be balanced and in time with each other. If you can see anyone who is out of time, call them out by seat number (&amp;ldquo;three seat you&amp;rsquo;re late&amp;rdquo; etc)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;All eight from the finish, arms only - ready and row&amp;rdquo;&lt;/strong&gt; (continue for 20-25 strokes)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;In two, arms and body - this is one [stroke] two [stroke] arms and body&amp;rdquo;&lt;/strong&gt; (20-25)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;In two, half-slide - this is one [s], two [s], half-slide.&lt;/strong&gt; This will normally take you up to the turning point to cross the river- give the rowers a second to adjust feet and grab water, then complete a turn and then continue again at half-slide until you get across (i.e. &lt;strong&gt;&amp;ldquo;From the finish, half-slide, ready and row&amp;rdquo;&lt;/strong&gt;)&lt;/li&gt;&#10;&lt;li&gt;Once on the Quebec side, you&amp;rsquo;ll want to start at half slide and then transition into full-slide (i.e. &amp;ldquo;in two, full-slide, &amp;hellip;&amp;rdquo;)&lt;/li&gt;&#10;&lt;li&gt;Once you cross the Macdonald Cartier Bridge, &lt;strong&gt;&amp;ldquo;let it run&amp;rdquo;&lt;/strong&gt; to wait for other crews and coaches.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Turns (i.e. while stationary)&#10; &lt;div id="turns-ie-while-stationary" 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="#turns-ie-while-stationary" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The majority of turns or spins will be turning the boat 90 degrees to starboard (your right)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Holding on Star and Tapping on Port, ready and tap&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Continue until pointing in the correct direction&lt;/li&gt;&#10;&lt;li&gt;Minor adjustments when stationary can be made by asking bow seat or two seat to tap.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Calling Power Pieces&#10; &lt;div id="calling-power-pieces" 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="#calling-power-pieces" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Power pieces and pyramids are a large part of the practice. You should be able to feel the rowers putting power on (let them know if you can&amp;rsquo;t) and the stroke rate should still feel controlled.&lt;/li&gt;&#10;&lt;li&gt;General format is the same: &lt;strong&gt;&amp;ldquo;Hard 10 in two, this is one, two, ten hard - one, two, three &amp;hellip;&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;Pyramids are sequential power pieces (5on-5off-10on-10-off-15/15/10/10/5/5 etc)&lt;/li&gt;&#10;&lt;li&gt;You should count all of the strokes for pieces of ten strokes or shorter - if fifteen or more, count the first five or ten, then give them a warning every five strokes until you count off the last five or ten.&#10;&lt;ul&gt;&#10;&lt;li&gt;e.g. for 30 strokes, count off the first ten, &amp;ldquo;this is fifteen&amp;rdquo; at fifteen, and count off the last ten&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Adjusting for Bad Water or Weather&#10; &lt;div id="adjusting-for-bad-water-or-weather" 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="#adjusting-for-bad-water-or-weather" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;When water is wavy, get rowers to start at half-slide and then go into full slide (if possible, otherwise stay at half-slide)&lt;/li&gt;&#10;&lt;li&gt;Try to warn your crew when a large wake is coming in (&lt;strong&gt;&amp;ldquo;wake coming in on starboard&amp;rdquo;&lt;/strong&gt;) although you alternatively say &lt;strong&gt;&amp;ldquo;sit up tall and relax your shoulders&amp;rdquo;&lt;/strong&gt; to help relax your crew instead of making them tense up.&lt;/li&gt;&#10;&lt;li&gt;If you see lightning or hear thunder, regardless of whether a coach is with you or not, turn the boat around to go home.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2018-07-04_coxie/featured.jpg"/></item></channel></rss>