[{"content":"One of my deep nerd obsessions is food, especially food science, and I\u0026rsquo;ve long wanted to try out dry-aging at home. The premise sounds \u0026ldquo;simple\u0026rdquo;: you just need a dedicated fridge, a big old hunk of meat, and a way to monitor the temperature and humidity inside. Pair that with some burgeoning homelab skills and \u0026hellip; how hard could it be? Here are some reflections on my first dry-aging experiment, a 45-day boneless ribeye roast.\nCode for this project lives on GitHub at tanho63/dry-age-monitor.\nequipment and setup # I got a dedicated fridge from Costco - around 6.0 cubic feet with a glass door and wire shelving - and on balance I think it worked really well: it held humidity pretty decently, was able to fit a good sized chunk of meat on a shelf, and the glass door let me look in on it whenever I wanted. The one downside is that it uses a mechanical thermostat for deciding when to kick on the compressor, and I ended up needing to fiddle with it a bit (more on that later). A digital thermostat that let me control top-end and bottom-end temperature would probably be better, but honestly I\u0026rsquo;m very pleased with what I got for ~$350 CAD.\nThe Raspberry Pi monitoring setup worked great for tracking temperature and humidity. I\u0026rsquo;ve mostly had experience with the Pi Zero 2 WH (i.e. with headers already soldered on), so went with that and paired it with a BME690 air quality sensor that promised to monitor temperature, humidity, gas, and pressure. I could probably have cheaped out and gone with a simpler sensor like the AHT20, since the gas and pressure turned out to be pretty useless, but the BME690 worked well enough once I got it set up and calibrated.\nI don\u0026rsquo;t have a soldering kit, so plugging everything in via Stemma/QT cables was the move and it was painless. I made sure to get a longer (400mm) cable for the sensor-to-hub connection so that I could put the sensor closer to the middle of the fridge, and then used a velcro cable tie to secure it to the desired fridge rack. I didn\u0026rsquo;t have any worries about the sensor breakout board being exposed to cold temperatures or potential humidity, since it is designed to read those, and the actual Pi is mounted outside of the fridge.\nI used a spare computer case fan I already had, paired with this USB-powered fan controller. I initially tried a USB fan, but the cable was pretty thick and awkward to fit into the fridge\u0026rsquo;s door seal. The case fan\u0026rsquo;s cable was much thinner and easier to route, and the adjustable fan speed was pretty useful too. I didn\u0026rsquo;t end up needing to adjust it very often at all because I found that a higher fan speed messed with the temperature more than I wanted while not really changing the effective humidity much at all.\nSealing off the cable gaps in the door gasket was very helpful in maintaining temperature. I duct-taped the cables down first to make sure it stayed put, then put foam tape over top to help reduce air leakage.\nI bought a cheap thermohygrometer as a backup in case the Pi ever went down. It worked fine as a sanity check but I\u0026rsquo;d want to invest in a better one if I was relying on it directly.\nmeat # I was a bit impulsive about buying the meat: I\u0026rsquo;d had the fridge for a while, but had been procrastinating actually starting on everything, so one day I was at Costco Business Centre and decided to just get a starter piece of meat for the sake of giving myself a kick in the pants.\nThe beef I ended up with for this first experiment was an ungraded, boneless ribeye roast weighing 14.2 lbs. Not a particularly great one either: the fat cap was thick, the meat wasn\u0026rsquo;t especially marbled, and the spinalis was thinner than I\u0026rsquo;d have liked. I mostly wanted to get a roast I wouldn\u0026rsquo;t be too upset about ruining if the experiment flopped.\nI didn\u0026rsquo;t do a great job of documenting the \u0026ldquo;before\u0026rdquo; state: I forgot to log the total cost and size of the whole roast before trimming, so I can\u0026rsquo;t do a really good cost estimate, but I did remember to portion off a steak so I could have a before/after comparison tasting. I also trimmed it up so that it would fit horizontally on the shelf with some space for airflow around the sides.\nMy goal was to age this first roast for 45 days. I\u0026rsquo;ve had 30-day dry-aged steaks before, so with my home experimentation I really wanted to try going for a meaningful increase but not necessarily going all the way to 60 days (which the research suggests is potentially quite overpowering).\nmonitoring # I set up the Pi to record data (temp, humidity, pressure, gas) every 30 seconds, which gave me a pretty good representation of the fridge cycle, and I calculated the mean values for the whole up/down fridge cycle as well as the 5th, 20th, 80th, and 95th percentile values (avoiding outliers in case I opened the fridge momentarily).\nI put together an R Markdown report to summarize the data, and set it up to knit via a cron job every 15 minutes on my main home server rather than on the Pi itself (which was too underpowered). I also chose to push the report to an S3 bucket page rather than continually committing it to GitHub like I might have in the past (lots of images being committed that often would lead to repository size growing indefinitely).\ntemperature # Temperature-wise, the internet says that dry-age temperatures should be between 34F and 38F, that meat freezes at about 30F, and that the danger zone from a food safety perspective starts at 40F. I decided to shoot for:\nmean cycle temp between 36F and 38F 5th and 20th percentiles above 30F 95th percentile at or below 41F 80th percentile at or below 40F These felt like reasonable goals because the meat itself shouldn\u0026rsquo;t actually reach over 41F - the sensor reacts to air temperature swings much faster than the meat\u0026rsquo;s surface does. I also wanted to avoid short-cycling the fridge compressor, which would reduce the lifespan of the fridge and also probably cost more money to run overall.\nUnfortunately, my fridge\u0026rsquo;s cycle maxes and mins seemed to naturally range between 32F and 42F, even on the coldest fridge setting. While I was willing to risk a small portion (less than 20%) of the time being between 40F and 41F, I was worried that any more time than that, or any warmer a band, would be more likely to cause spoilage and \u0026ldquo;bad\u0026rdquo; bacteria growth.\nI tried increasing fan speed, thinking that it would circulate cold air up faster. However, this didn\u0026rsquo;t really change the set-point of the fridge\u0026rsquo;s thermostat: the cooling happened faster since the cold air would circulate more quickly on the \u0026ldquo;down\u0026rdquo; part of the cycle, but the compressor didn\u0026rsquo;t kick on any earlier.\nI ended up messing with the fridge\u0026rsquo;s thermostat sensor itself: this is basically a wire that sat in a little housing on the back of the fridge, so I took the housing off and bent the wire upwards to move the end of the sensor into a warmer spot of the fridge. This kicked the compressor on a little earlier (~41F) and also caused it to turn off a bit later (~28F), since it took longer for the \u0026ldquo;cold\u0026rdquo; to register on the sensor. That latter part was easy to fix: just turn the dial down!\nI also wanted to try to lengthen the cycle to help the fridge run a little more efficiently, so I added some thermal mass in the form of 4L jugs of water - this helped slow the rate at which the fridge warmed between the compressor shutting off and kicking on again.\nhumidity # Humidity seemed to sit really high throughout the process, swinging from 50% to 100%+, according to the BME690 sensor. This led to some worries about condensation forming on either the meat or on the walls of the fridge (and eventually ice). My research suggested that the meat would give off moisture for at least 2-3 weeks, so I just added a quarter sheet pan of kosher salt hoping that it would resolve itself eventually.\nThis didn\u0026rsquo;t really happen: humidity levels stayed basically the same, so I replaced the salt with a quarter sheet pan of silica beads (making sure to orient the fan to blow air across the tray but not allowing the meat to touch the silica at all). This made a meaningful difference in bringing down the humidity, but only lasted a short while and needed replacing about every week.\nInterestingly, when I left for a one-week trip to Croatia, the humidity declined steadily while I was away, but somehow spiked immediately before I got home. This clued me in to a potential reason why the humidity was sitting so high: when I left, I turned the house\u0026rsquo;s AC off and programmed it to turn back on shortly before I got back from the trip. The fridge is in the same room as the 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 internal humidity high even after the meat has lost the moisture it was always going to lose.\ntasting notes # Starting weight: 14.2 lbs Ending weight: 12.36 lbs (13% loss) Post-trim weight: ~9 lbs (largeish fat cap mostly to blame), cut into 10 steaks about 1.5\u0026quot; thick and between 12 and 14 oz each. The dry-aged taste was noticeable but not overpowering (especially since I had been semi-aggressive in trimming off the pellicle) - really tasty! I\u0026rsquo;m very satisfied with the results. Haven\u0026rsquo;t tried a before/after comparison of flavour and tenderness yet - I think I\u0026rsquo;m saving that for when Mi Yen is home next.\nthoughts for next time # get a larger subprimal with bone + meat to protect the main steak, something like a 107 try hanging vertically via meat hooks instead of being constrained by the shelf width go for only a little bit more time (maybe 50 days?) investigate inoculating fridge/meat with mold move fridge into a different room to be less sensitive to temp/humidity swings? All in all: delicious, 10/10 would do again - maybe even this week to be ready before my birthday in November?\n","date":"2026 Aug 30","externalUrl":null,"permalink":"/posts/2026-08-30_dry-aging/","section":"posts","summary":"Some reflections on setting up a dry-aging fridge at home and experimenting with\nmy first 45-day aging.","title":"Dry Aging At Home, Episode 1","type":"posts"},{"content":"This is a curated, non-exhaustive list of tech-adjacent content I\u0026rsquo;ve really enjoyed and shared with friends and colleagues.\nMostly general thoughts on life as a data scientist, learning/teaching programming, technical leadership, career advice, and other tech-adjacent topics, and not specific to R or sports analytics (will probably tackle that another time).\nInspired by Derek Sivers\u0026rsquo;s book notes and Vicki Boykis\u0026rsquo;s favourite essays, and organized loosely by topic (click to jump to section):\nlife as a programmer learning and teaching technical leadership career advice technical topics changelog life as a programmer \u0026amp; data scientist # Michael Lynch: How to Do Code Reviews Like A Human and part two, blog posts (18 and 15 min reads, respectively) Code reviews are daily interactions that shape both the code itself and, maybe even more importantly, the relationships between programmers - these posts really help with how to approach these in a constructive and positive way. Hank Green: The Secret to My Productivity, YouTube video (4 min) Invaluable advice on when to stop working on creative projects: stop working on it when you have reached 80% of \u0026ldquo;the best you can do\u0026rdquo;, release it to the world, and then go do your next project. Applies in hobbies and tbh to professional projects as well. Randy Au: Succeeding as a Data Scientist in Small Companies / Startups, blog post (8 min read) How to succeed in a small company where you are the first \u0026ldquo;data\u0026rdquo; person there: helping the company succeed TODAY and setting up the company to be data driven TOMORROW. Also has a very nice \u0026ldquo;hierarchy of needs\u0026rdquo; graphic that explains what it means to set up a company to be data driven. Immediately relevant for people in small companies and also for understanding the core needs for a data team (and seeing how that scales as you add more people.) Alberto Savoia: The Way of Testivus, PDF blog post Advice on developer and unit testing, packaged up as pseudo-Confucian tenets worth living by. Dan McKinley: Boring Technology, talk/slides and original blogpost Core idea: you get \u0026ldquo;innovation tokens\u0026rdquo; to spend as a (developer, team) and you don\u0026rsquo;t have that many even across a team. In order to keep those innovation tokens spent on the team\u0026rsquo;s core mission (e.g. solving soccer), you should spend less time innovating on the core technologies (fancy database stuff, programming paradigms etc), because fixing bleeding edge database stuff takes up \u0026ldquo;innovation tokens\u0026rdquo; that you want to spend doing other things. David Robinson: Unreasonable Effectiveness Of Public Work, YouTube talk recording (40 min) Anything you put out into the world is going to be far better and far more useful than anything that sits in your drafts folder waiting for some arbitrary threshold of \u0026ldquo;good\u0026rdquo; before it gets published. Go out there and do things! learning and teaching programming # Greg Wilson: what everyone in tech should know about teaching and learning, YouTube video (41 min) Massively useful! Especially the ideas right at the beginning about mental models and the differences between how beginners, competent users, and experts learn. The whole video is incredible, but I recommend at least the first 8:23 to cover these ideas. Andy Harris: how to begin thinking like a programmer, YouTube video (61 min) A riveting talk (given at a Python conference, which should surprise you if you know me). which covers how to think like a programmer. Especially profound: comments are not for explaining code to other programmers - instead, code is there to explain comments to the computer! technical leadership # Tanya Reilly: Being Glue, annotated talk slides and YouTube video (28 min) A brutally honest talk about the non-promotable \u0026ldquo;glue\u0026rdquo; work that programmers can get silently stuck with, and that hurts your technical development and ultimately your career development if you\u0026rsquo;re not prepared. Can disproportionately affect teams, especially women and/or\tintroverted/non-assertive personalities. Julia Evans: Help, I Have A Manager!, zine (~$12) Having a good relationship with your manager is probably the most important factor in your happiness at work. This zine is full of great advice on how to make that relationship better - how to handle 1:1s, asking for feedback, figuring out what they\u0026rsquo;re good at, building a support system for areas they aren\u0026rsquo;t as good, and more. Vasily Zubarev: How to hire sane engineers 2.0 (~15 min read) A pragmatic Russian\u0026rsquo;s take on hiring engineers in a \u0026ldquo;sane\u0026rdquo; and effective way. Would love to implement this one day to help with bypassing some of the technical interview steps that are so common now. career advice # Derek Sivers: How To Do What You Love And Make Good Money, blog post with audio recording (4 min read/listen) This is the advice I share when folks ask me about getting their dream job and making a career from sports analytics or other hobbies - you don\u0026rsquo;t need to force sports analytics to be both your livelihood and your art! The dream job that does both may or may not come along, but you can be happy in both career and art without it. Part of a larger book, Hell Yeah or No, which is also very worth reading (~$15). Austin Kleon: Show Your Work, short book (~$15) The book I share when people ask me how to build their public portfolio and personal networks: share something small every day without being human spam, teach what you know, and tell good stories. A lot of insights packed into a small and easy to read format. Also great are the other books in the Steal trilogy, including Steal Like An Artist on how to be creative and Keep Going on how to sustain the creative effort Jacqueline Nolis \u0026amp; Emily Robinson: Build A Career In Data Science, book (~$24) and podcast series (~20 episodes each about 60min) A great resource for finding a job in data science. Really like the definitions of data science as an industry - essentially the intersection of programming, statistics, and domain knowledge. Book was available online for free at one point but now seems to be only paid - very worth it, but listening to each of the podcast episodes is a pretty good equivalent. Haseeb Qureshi: Rules for Negotiating a Job Offer and part two, blog posts (24 and 34 minute reads) More people should negotiate job offers and simply don\u0026rsquo;t. This is good, not-too-kitschy advice on how to approach negotiating. specific technologies # Vasily Zubarev: Machine Learning For Everyone, blog post (~40 min read) \u0026ldquo;Machine Learning is like sex in high school. Everyone is talking about it, a few know what to do, and only your teacher is doing it.\u0026rdquo; An impressively nontechnical and yet surprisingly comprehensive overview of machine learning that I found useful early on in my data science career as someone with neither a CS nor a stats background. Julia Evans: Hell Yes CSS, zine (~$12) and comics I\u0026rsquo;ve always found CSS to be incredibly frustrating, but reading this zine was super helpful. Still refer back to the CSS Selectors comic to this day whenever I need to format something. OhShitGit.com (reference page) For the times I manage to fuck up using git, this page consistently saves me every single time. data.table vs base vs dplyr (reference page) An awesome syntax lookup page to go between dplyr, base, and data.table in R - a worthy successor to the original post by atrebas changelog # 2026-07-21: added data.table vs base vs dplyr reference page 2025-09-04: added ohshitgit.com 2025-03-17: added David Robinson\u0026rsquo;s Unreasonable Effectiveness Of Public Work talk 2024-11-29: first version ","date":"2026 Jul 21","externalUrl":null,"permalink":"/recs/","section":"posts","summary":"A curated, non-exhaustive list of tech content I’ve really enjoyed and shared with friends and colleagues. Includes thoughts on life as a programmer, technical leadership, career advice, technical topics, and more. Updated periodically.","title":"Things I Think You Should Read and Watch","type":"posts"},{"content":"2025 is in the books. What do I want to remember about it?\nI (officially) became an engineering manager in April - there\u0026rsquo;s a lot to unpack here and I probably want to do so in more detail eventually. I had felt like I was already pseudo team lead and that being the official engineering manager wouldn\u0026rsquo;t be that much different - in hindsight, I think I was very unprepared for how much \u0026ldquo;corporate politicking\u0026rdquo; and \u0026ldquo;managerial nonsense\u0026rdquo; came with the role and how much it would take up of my time. My work has transitioned into a lot more effort leading the engineering planning and project scoping, followed by spending a solid amount of time reviewing work and mentoring reports, and a much smaller fraction of my time is actively hands on keyboard coding work. I\u0026rsquo;m pretty satisfied with these changes, although I\u0026rsquo;d like to try to reduce some of the admin overhead to get closer to a 25-30% individual contributor time rather than 10-15% as it is right now. I\u0026rsquo;m grateful for the team I have worked with over the last year - Lydia, Andrew, Michael, Yagmur, and Danny - and for all of the lessons they\u0026rsquo;ve taught me along the way.\nI\u0026rsquo;m not doing a lot of freelance work right now, or really any major side projects. nflverse, ffverse, and dynastyprocess continue to be dormant and/or on maintenance mode.\nIn the fall of 2024, I started playing indoor volleyball during Friday night sports for the first time since high school gym class and enjoyed it enough that I thought I would try to pick it up as a standalone recreational sport in January by signing up for a volleyball play-and-social as well as a training session. This snowballed over 2025 and I found myself playing over 30 hours per month of volleyball by October, totalling over 237 hours for the year. It\u0026rsquo;s incredibly funny to me that I keep getting hooked on \u0026ldquo;tall people\u0026rdquo; sports, and the obsession with volleyball has somehow surpassed even the peak I had with rowing (41 hours in Nov 2025 vs 36 hours of rowing in Aug 2018). There\u0026rsquo;s something really nice about the way it\u0026rsquo;s a team sport social enough to make friends while playing it, non-contact in a way that \u0026ldquo;invasion\u0026rdquo; sports like basketball/soccer/ultimate aren\u0026rsquo;t (which helps avoid \u0026ldquo;physical\u0026rdquo; dominance, makes playing coed more viable/fun, and overall reduces injury risk), and technically challenging enough that I could play well at the start but still had a lot of room to learn new skills. I\u0026rsquo;m finding that the best volleyball I get to play has been Javelin\u0026rsquo;s intermediate level drop-in (where I\u0026rsquo;m learning how to play setter in a 5-1 system), as well as Volley Sixes for league play and Lima Sports Club for training. I\u0026rsquo;d probably enjoy Capital Volley more if I was on a higher-level team, and I\u0026rsquo;d be interested in trying out OFSL in the future. I probably would never play an OSSC league again for serious play - levels are too unbalanced for it to really stick.\nI swam a decent amount to start the year, but phased that out for volleyball in the summer after finding that I was getting better at volleyball and that I preferred a more team-based social/accountability. I kind of liked the steady state cardio aspect of swimming and the gradual improvement, but playing vb does somewhat address the cardio too. I\u0026rsquo;d like to add back some lifting and other training next year, perhaps plyometrics to supplement the volleyball?\nI\u0026rsquo;ve made a lot more local friends in the last year and have a bit less focus on global friends as a result. Feeling a little less on the outskirts of the Friday night sports friend groups than I was this time last year, especially after attending Bluesfest with the group as well as consistently doing pub nights and karaoke socials etc. I\u0026rsquo;m looking forward to a Tremblant ski trip at the end of February, although I\u0026rsquo;m a bit concerned my MCLs won\u0026rsquo;t hold up super well having not really skiied in the last few years.\nI travelled a lot less than previous years, a lot of which was owing to not wanting to break the physical activity routines I was on. Places I did visit:\nKansas City, where I got to hang out with Sid and saw KC vs BUF in the AFC championship game Houston for ASI conference Gaspé for a road trip with Mom and Dad and Jasper Atlanta for PositConf 2025 (where I got to hang out with Lydia and Arielle!) London for Hudl (f/k/a Statsbomb) 2025 and to visit Mimio Given the state of global politics, I might be travelling even less next year.\nI attended three conferences (ASI, Posit, Hudl) and successfully managed to not give a talk at any of them! This is exciting because in 2024 I tried to avoid it and somehow still managed to get roped into giving a talk at Shiny in Production. I did appear on the Data Science Hangout with Libby, which was super fun!\nMy weight is down a decent amount from the increased physical activity despite not actively making this a focus. I think I should give this a little bit more effort again soon - especially if I want to try adding skiing back to the activity mix in upcoming years. My sleep and adhd medications have both improved my overall mood and outlook this year, which I\u0026rsquo;m very grateful for.\nFor the first time ever (at least since I\u0026rsquo;ve started writing reflections) it feels like I\u0026rsquo;ve made progress on multiple fronts this past year - work, finance, physical activity, physical health, social all seemed to improve in 2025. I\u0026rsquo;ve bemoaned the lack of balance in the past, but between the nuances of managerial work \u0026ldquo;reducing\u0026rdquo; the IC load (which was easy to overdo) and the new obsession with volleyball, I\u0026rsquo;ve definitely felt a lot more balanced out lately. I\u0026rsquo;m still a little more lonely than I would like to be, and getting out into the dating scene still feels daunting.\nOn to 2026!\n","date":"2025 Dec 31","externalUrl":null,"permalink":"/posts/reflections/2025/","section":"posts","summary":"Thinking back on the year that was: continued professional evolution, a newfound obsession with volleyball, and settling into friend groups.","title":"Reflections on 2025","type":"posts"},{"content":" Recording # ","date":"2025 Oct 23","externalUrl":null,"permalink":"/posts/talks/2025-10_dshangout/","section":"posts","summary":"A casual Q\u0026A with Libby Heeren on all things R in production","title":"Data Science Hangout","type":"posts"},{"content":"2024 has come to a close. What do I want to remember about it?\nI turned thirty this year. My twenty year old self would be proud of me, I think.\nI focused a lot on work, grinding my way to a second promotion in two years (Senior MLE). I\u0026rsquo;ve been thinking a lot about how my role and career has evolved over the last few years and how to balance individual contributions with leadership/mentorship/planning as I become more senior.\nZelus was acquired this year. It\u0026rsquo;s been an awkward transition, but I appreciate the company being more financially stable overall.\nI\u0026rsquo;m still doing a bit of freelance consulting work and figuring out how that fits into my overall working life without becoming too much - thus far doing a much better job balancing that than my first go-round in 2020ish.\nI travelled a little less than last year: Toulouse, Bordeaux, Carcassonne, Philadelphia, Banff, Jasper, Calgary, Seattle, London, Newcastle, Manchester, and Scotland. The Scotland road trip was a particular highlight and I can\u0026rsquo;t wait do something like that again.\nI attended three conferences: PositConf in Seattle, Shiny in Production in Newcastle, and Statsbomb in Manchester. I tried really hard to not give any talks this year but somehow still got roped into giving a talk at Shiny in Production.\nI got tested and treated for sleep apnea. It\u0026rsquo;s made a fairly sizeable improvement in my day to day outlook, I can\u0026rsquo;t believe I was nerfing my brain for so long. I\u0026rsquo;m still not very good at sleeping with the mask but I think that\u0026rsquo;s improving over time.\nI\u0026rsquo;m continuing to work through ADHD medications and trying to find the one that helps me the best. I think that the sleep apnea has had a substantial confounding impact so I\u0026rsquo;m interested to see if previous medications work better now.\nI attended pickup sports consistently this year and found myself enjoying the activity and the camaraderie. A weekly pub night definitely helps with getting out there, and I found myself enjoying ultimate frisbee and volleyball a lot more than I\u0026rsquo;d expected!\nI started swimming for exercise this winter and hope to continue it through next year, I\u0026rsquo;m enjoying it so far.\nI\u0026rsquo;m languishing a bit on physical self-care otherwise, weight is up to the highest it\u0026rsquo;s been. I need to take better care of myself and to eat better.\nI appreciated Bluesky bringing back a lot of the global connectedness feeling! My global friend groups feels so much stronger now that we\u0026rsquo;re all on one platform again. I still worry a little about loneliness - I\u0026rsquo;m not yet feeling like I\u0026rsquo;ve made a lot of friends through pickup sports in a way that\u0026rsquo;s filled the gap in local connectedness that leaving rowing hurt.\nI\u0026rsquo;m blogging again, a lot inspired by Bluesky being back to share those thoughts with. My other side projects remain in a maintenance phase, and DynastyProcess calculator app creeps ever closer to a sunset date.\n","date":"2024 Dec 31","externalUrl":null,"permalink":"/posts/reflections/2024/","section":"posts","summary":"Stopping to think about the year that was.","title":"Reflections on 2024","type":"posts"},{"content":" I greatly enjoy the DSLC Slack Community ( dslc.io/join), and spend a fair bit of time answering questions! This post is part of a series where I curate some interesting questions and/or answers that I’ve come across there. Question # Hi all! I\u0026rsquo;m having a hard time understanding why netlify checks on a Pull Request with only a empty line added in the commit are failing for a blogdown website. I created this PR as a small example, but basically any kind of PR is failing nevermind its content, and the site is published-rendered without problem if we commit directly to main.\nI\u0026rsquo;m super puzzled by this, does anyone have any tips on how can I debug it? Thanks \u0026#x1f64f;\n- MC Nanton\nAnswer # Let\u0026rsquo;s look through some of your Netlify deploy logs. I found these from your GitHub repo automated checks:\nfailing deploy logs for your PR successful deploy logs for your main branch repo link editor\u0026rsquo;s note: archived the deploy logs in question to this gist just in case the deploy logs are deleted at some point It seems like your PR deploy logs show this warning immediately before failing:\nWARN 2024/12/10 21:35:19 Module \u0026#34;hugo-academic\u0026#34; is not compatible with this Hugo version; run \u0026#34;hugo mod graph\u0026#34; for more information. and this definitely doesn\u0026rsquo;t show up on your main deploy logs. Let\u0026rsquo;s try to figure out where your hugo versions are specified. Here\u0026rsquo;s a relevant section of the deploy logs:\n4:35:19 PM: ❯ Current directory 4:35:19 PM: /opt/build/repo 4:35:19 PM: ​ 4:35:19 PM: ❯ Config file 4:35:19 PM: /opt/build/repo/netlify.toml 4:35:19 PM: ​ 4:35:19 PM: ❯ Context 4:35:19 PM: deploy-preview The logs say that the file responsible for specifying hugo version is netlify.toml, so let\u0026rsquo;s have a look at what that looks like on GitHub right now:\n[context.production.environment] HUGO_VERSION = \u0026#34;0.64.0\u0026#34; Notice that it specifies context.production here, and that the context for the failing logs is deploy-preview. If we go back into the failing logs, we can confirm that the hugo-version specified here isn\u0026rsquo;t working properly:\n4:35:12 PM: Installing Hugo 0.57.2 So a solution that should work is to update the netlify.toml to specify the HUGO_VERSION for the deploy-preview context:\n[context.production.environment] HUGO_VERSION = \u0026#34;0.64.0\u0026#34; [context.deploy-preview.environment] HUGO_VERSION = \u0026#34;0.64.0\u0026#34; and this should do the trick. You might need to push it to main instead of to a PR branch though, Netlify usually uses the config from the main repo branch.\n(Update: this solution worked!)\n","date":"2024 Dec 19","externalUrl":null,"permalink":"/posts/2024-12-19_deploy-debugging/","section":"posts","summary":"A quick example of reading through automated deploy logs, finding the relevant errors, and triaging a fix - transcribed from the DSLC Slack Community help channels","title":"DSLC Slack: Debugging Netlify Deploys","type":"posts"},{"content":" Ideas are worth nothing unless executed, and I\u0026rsquo;m tired of adding ideas to my backlog only to never get to them. This series aims to set those ideas free - let me know if you take it on! Hot take: the ideal beginner data science programming book doesn\u0026rsquo;t exist (yet) # The beloved R for Data Science is a fantastic book, and yet I struggled immensely with following along with it. I think it\u0026rsquo;s because I can only care so much about flower irises, penguins, and NYC taxis: I quit halfway through the third chapter of R4DS on my first attempt in 2018ish, \u0026hellip; because I got bored.\nSince then, I\u0026rsquo;ve gotten treatment for ADHD (which probably explains a lot, tbh), but I think what really helped the most with learning programming was that I started working on projects that I actually cared about: fantasy football and NFL.\nI\u0026rsquo;d work on something for a while, get stuck on something I didn\u0026rsquo;t know, Ctrl-F the R4DS book + dplyr/tidyr reference manuals, figure out what I needed to know and how it fit into what I already knew, and then keep going. Reading R4DS cover to cover was utterly unhelpful until I knew how to fit each concept into a mental model that I knew how to use.\nMy love of all things R and data has since taken off (enough to have made a whole career of it), and now I often get asked what books I\u0026rsquo;d recommend for learning NFL/sports analytics, R, data science, programming etc. I supply the easy answer (\u0026ldquo;you should read R4DS\u0026rdquo;), but I\u0026rsquo;m a little uncomfortable with it because the book I wish I could recommend doesn\u0026rsquo;t quite exist.\nthe ideal book for learning programming and NFL analytics # There are a lot of introductory data science and programming books out there, and even ones that apply the context of NFL: both Eric Eager and Brad Congelio have great iterations of this (and the latter is free online)!\nI\u0026rsquo;m so glad these books exist now and they\u0026rsquo;d have certainly helped a lot when I first got started, but I\u0026rsquo;m not quite sure these are the ones I\u0026rsquo;ve been wanting:\nhow do they stay up to date with a fast-evolving programming landscape? how do they compare with the existing literature that is beloved by the community, like R4DS, and how should beginners proceed if the books disagree on best code approach? what happens if I want to learn another programming language other than R? I think the book I want to exist would be structured as a companion to books like R4DS, but not include any language-specific code whatsoever. Instead, I\u0026rsquo;d focus on a few goals:\ncreate a series of case studies and exercises that challenge learners to write their own solutions based on domain-specific research questions provide starter data of appropriate complexity/cleanliness in a data repository cover a full breadth of high-level data science / programming topics kind of like R4DS\u0026rsquo;s visualize, transform, import, program, communicate sections maintain up-to-date links to readings for each case study that help learners find best-practice guidance on a given topic in a supported programming language each case study should provide links to \u0026ldquo;prerequisite reading\u0026rdquo; to solve it, e.g. links to relevant R4DS chapters for R should be easily extended to other languages by linking to equivalent material, e.g. pivoting data, group by + summarize are relational data concepts that have parallels in almost every language introduce the essential background knowledge for the domain in question for NFL this should include concepts like EPA, fourth downs, completion probability, expected rushing yards etc - essentially covering the body of public research suggest further research questions that can be tackled beyond the controlled sandbox of the book/repository I think something like this would be massively helpful for introducing people to the relevant public research in a domain, and with putting technical topics into practical use for learners. It would be robust to existing languages evolving (could approach the same case study in e.g. tidyverse, data.table etc), easily extended to new languages (e.g. Python, Julia, Rust), and even to new technologies (probably could do the whole thing in DuckDB one day). The format also would be easy to adopt in different sports (soccer, basketball, baseball, hockey?) and to other domains altogether.\nOf course, I\u0026rsquo;ve been sitting on this idea for a while: I first started a repo for this idea ~ three years ago and convinced friends like Sebastian Carl to start thinking about it, but neither of us have made much progress, which I think is more due to just a lack of time/energy/expertise in it. I still think it\u0026rsquo;s a worthwhile idea and am writing this post mostly to set it free and hope someone is inspired to pick it up!\nA similar idea has come up since then: the SCORE project led by CMU\u0026rsquo;s Statistics and Data Science department aims to provide case studies across all sports for learning statistics and data science, and has a set of modules already. I\u0026rsquo;m not sure if ~ learning to program in a given language is in the scope of the project - it seems to be very statistics-focused - but it could otherwise be an interesting parallel project?\n","date":"2024 Dec 10","externalUrl":null,"permalink":"/posts/2024-12-10_beginner-book/","section":"posts","summary":"Free idea: the beginner book that doesn’t exist yet (but should) is one that introduces programming via language-agnostic, domain-specific case studies that complement existing resources on learning programming.","title":"The Beginner Data Science Programming Book That Doesn't Exist (Yet?)","type":"posts"},{"content":" Recording # Materials # Repo: https://github.com/tanho63/talk_shinyprod2024_scaling\n","date":"2024 Oct 10","externalUrl":null,"permalink":"/posts/talks/2024-10_shinyprod2024_scaling/","section":"posts","summary":"A talk given at Shiny in Production 2024 about DynastyProcess and all the things it has taught me about scaling apps in production.","title":"Adventures In Scaling Shiny: Lessons Learned Building DynastyProcess","type":"posts"},{"content":"2023 is nearly in the books. What do I want to remember about this year?\nI travelled a lot - ticking off visits to: Maine, Salt Lake City, St Anton, Munich, Atlanta, Quebec City, Halifax, Victoria, Vancouver, Seattle, Portland, London, Barcelona, Newcastle, Pittsburgh, and San Francisco. I\u0026rsquo;m glad I was able to take advantage of opportunities to travel for both work and pleasure while I\u0026rsquo;m lucky enough to have this combination of life stage, fully remote work, and sufficient financial freedom.\nI worked a lot, with long hours and lots of grind to go in between all of the travels. I\u0026rsquo;m becoming more comfortable in my role with Zelus\u0026rsquo;s soccer group and continuing to enjoy the challenges of using my data/programming/engineering skills to support club decisions. I was promoted to MLE II (L4) this year and have been working towards the next level of the role ladder. I\u0026rsquo;ve spent some time figuring out what sort of path I want to take between being a senior individual contributor vs more of a manager/engineering lead track, and I don\u0026rsquo;t quite have that ironed out yet.\nI also worked on some interesting freelance opportunities this year. There\u0026rsquo;s always some appeal with these side-gigs, especially financially, but there are also some inherent struggles with time management. My previous attempt (in 2021) at balancing part-time freelance with full-time work ended in a rather horrific bout of burnout. I\u0026rsquo;m trying to be more cautious this time around and so far I think it\u0026rsquo;s been fairly manageable.\nI\u0026rsquo;ve started to transition my side projects towards a more long-term planning and sustainability-focused lens rather than being a hyperactive solo developer. I wrote out governance documents and started looking into ways we could improve the bus factor for the nflverse. It\u0026rsquo;s been slow-going, but forward-planning these things is important for making sure that our work endures and continues to support public NFL analytics in the future. I\u0026rsquo;ve been thinking about sunsetting the DynastyProcess project - while it\u0026rsquo;s got the largest user base, I spend almost none of my time on it and I think there are others in the space that are doing very good work - esp FantasyCalc.\nI gave talks at two conferences (ShinyConf and Shiny in Production) and attended two more (Statsbomb, CMSAC). I enjoyed giving these talks a lot more than the one I gave at RStudioConf in 2022 - the pressure felt less with both online and with smaller in-person audiences. I\u0026rsquo;m also starting to recognize this feeling of being somewhat \u0026ldquo;famous\u0026rdquo; in my niches (NFL data, sports data, Shiny, and R). I\u0026rsquo;ve felt some discomfort with this, but I\u0026rsquo;m reluctantly coming to accept that it\u0026rsquo;s a natural outcome from the work I\u0026rsquo;ve done and the work I continue to enjoy doing, and that it\u0026rsquo;s something I should feel comfortable taking more pride in.\nI felt extremely global and conversely not very local. I\u0026rsquo;m happy about the friends I\u0026rsquo;ve made in the NFL, soccer, sports-data, and R communities, and that everywhere I went this year had a friend or two nearby that I could visit - especially being at Zelus, where I could meet colleagues anywhere! While that\u0026rsquo;s great, I\u0026rsquo;m starting to feel socially isolated, especially at home where my connections to local friends have fallen off quite a lot.\nI regretted not joining sports like rowing, ultimate frisbee, or flag football this past year. I sprained both MCLs during a ski incident in February, and between rehabbing that and a busy travel schedule I decided that I wouldn\u0026rsquo;t be around enough to give sports enough of my time. As it turns out, I could have joined them anyways, missed what I needed to miss, and I would have been happier (more locally-connected) and healthier (more active). Instead, I neglected physical health this year and am heavier and less fit than I have been in recent memory. It\u0026rsquo;s a demoralizing and ugly feeling, and worse is that I don\u0026rsquo;t feel like I\u0026rsquo;m in shape (knees, cardio, core strength, or otherwise) to really enjoy skiing this year - enough that I decided to shelve skiing this season until I regained some fitness and cut some weight.\nI made a point to seek out help for mental health issues this year and got onto medication for ADHD. It\u0026rsquo;s been helpful but not as life-changing as I had hoped: I noticed a small overall improvement as I gradually ramped up in dosage, and maybe noticed more that I was much worse without taking it (and therefore wasn\u0026rsquo;t really aware of how bad I was without the reference point). I want to continue seeking out the help I need to when I need it - I\u0026rsquo;ve seen how low I can get and how hard the struggles are to manage alone.\nI spent a lot of time alone this year between my solo travels and my entirely remote work. While I\u0026rsquo;ve had many happy moments in that solitude, the feeling of being socially isolated has persistently shadowed me - especially in the growing distance with local friends. It\u0026rsquo;s been three years since I last rowed and been a significant part of that friend circle, and it\u0026rsquo;s been about six years since I was last in a serious relationship. I\u0026rsquo;m feeling the spectre of loneliness more and more, and it\u0026rsquo;s something I need to work on in 2024.\nThinking about 2024 # In 2022, I reflected that\nI’m recognizing that in a broader sense, I can only focus on one or two areas at a time. In past years, it’s been one or some of: losing weight, improving physical shape/condition, programming skill/personal projects, social/friends. This year it seems that my gains were in career development and networking/notability, and I lost ground in weight, friend networks, and in personal projects. It seems obvious that we can only prioritize one or two things at a time, but I’m frustrated that it feels out of my control to choose what those things are, and I’m frustrated that it’s so all-or-nothing in terms of past progress.\nIn 2023, it feels like I carried on in the same paths as 2022 and pushed those extremes even further, rather than trying to consciously rebalance my priorities. Career development seemed the path of least resistance and I maximized that at the expense of so many other things that would have been beneficial.\nIn 2024 I need to consciously fight this tendency to take the easiest path and to more consciously balance my priorities. I\u0026rsquo;m tired of this being the central narrative of my annual reflections. I\u0026rsquo;m tired of feeling lonely. I\u0026rsquo;m tired of telling myself I need to lose weight and be more fit. I\u0026rsquo;m tired of looking at all the side projects I want to do and not doing them.\n","date":"2023 Dec 30","externalUrl":null,"permalink":"/posts/reflections/2023/","section":"posts","summary":"A year of travelling, grind, growth, and loneliness.","title":"Reflections on 2023","type":"posts"},{"content":" I originally shared this tip for extracting data from column names as a tweet, and I\u0026rsquo;ve found myself sharing it over and over again since. Here\u0026rsquo;s a brief transcription now that I\u0026rsquo;m no longer on Twitter. One of my favourite data-wrangling tricks is tidyr::pivot_longer\u0026rsquo;s names_to \u0026amp; .value sentinel - use-cases don\u0026rsquo;t come up often, but it\u0026rsquo;s so deeply \u0026#x2728; satisfying \u0026#x2728; when it does!\nScenario # Let\u0026rsquo;s say you have this starting dataframe:\n# A tibble: 601 × 22 season title caught_fred caught_daphnie caught_velma caught_shaggy \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; 1 1 What a Nig… FALSE FALSE FALSE TRUE 2 1 A Clue for… FALSE FALSE FALSE TRUE 3 1 Hassle in … FALSE FALSE FALSE FALSE 4 1 Mine Your … TRUE FALSE FALSE FALSE 5 1 Decoy for … FALSE FALSE FALSE FALSE 6 1 What the H… TRUE FALSE FALSE FALSE 7 1 Never Ape … TRUE FALSE FALSE FALSE 8 1 Foul Play … FALSE FALSE FALSE FALSE 9 1 The Backst… FALSE FALSE FALSE TRUE 10 1 Bedlam in … FALSE FALSE FALSE TRUE # ℹ 591 more rows # ℹ 16 more variables: caught_scooby \u0026lt;chr\u0026gt;, captured_fred \u0026lt;chr\u0026gt;, # captured_daphnie \u0026lt;chr\u0026gt;, captured_velma \u0026lt;chr\u0026gt;, captured_shaggy \u0026lt;chr\u0026gt;, # captured_scooby \u0026lt;chr\u0026gt;, unmask_fred \u0026lt;chr\u0026gt;, unmask_daphnie \u0026lt;chr\u0026gt;, # unmask_velma \u0026lt;chr\u0026gt;, unmask_shaggy \u0026lt;chr\u0026gt;, unmask_scooby \u0026lt;chr\u0026gt;, # snack_fred \u0026lt;chr\u0026gt;, snack_daphnie \u0026lt;chr\u0026gt;, snack_velma \u0026lt;chr\u0026gt;, # snack_shaggy \u0026lt;chr\u0026gt;, snack_scooby \u0026lt;chr\u0026gt; # ℹ Use `print(n = ...)` to see more rows and you want this ending dataframe:\n# A tibble: 3,005 × 7 season title character caught captured unmask snack \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE 10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE # ℹ 2,995 more rows # ℹ Use `print(n = ...)` to see more rows How do you extract the characters (Fred, Velma, Daphnie, Shaggy, Scooby) from the column names into a character column?\nPast me # Hmm, well, I can pivot_longer, then separate the name, then pivot_wider again?\nthree_lines \u0026lt;- x %\u0026gt;% pivot_longer(cols = -c(\u0026#34;season\u0026#34;,\u0026#34;title\u0026#34;)) %\u0026gt;% separate(name,into = c(\u0026#34;action\u0026#34;,\u0026#34;character\u0026#34;), sep = \u0026#34;_\u0026#34;) %\u0026gt;% pivot_wider(names_from = \u0026#34;action\u0026#34;, values_from = \u0026#34;value\u0026#34;) # A tibble: 3,005 × 7 season title character caught captured unmask snack \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE 10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE # ℹ 2,995 more rows # ℹ Use `print(n = ...)` to see more rows This seems to work okay!\nBut what if I told you there\u0026rsquo;s a one-liner for this exact situation? # pivot_longer has a \u0026ldquo;names_to\u0026rdquo; argument that takes a special \u0026ldquo;sentinel value\u0026rdquo; called \u0026quot;.value\u0026quot; - it immediately re-pivots the columns so that \u0026ldquo;character\u0026rdquo; (fred, daphne etc) stays as a column and then the \u0026ldquo;.value\u0026rdquo; part becomes colnames again (caught, captured etc)\none_line \u0026lt;- x %\u0026gt;% pivot_longer( cols = -c(\u0026#34;season\u0026#34;,\u0026#34;title\u0026#34;), names_to = c(\u0026#34;.value\u0026#34;,\u0026#34;character\u0026#34;), names_sep = \u0026#34;_\u0026#34; ) # A tibble: 3,005 × 7 season title character caught captured unmask snack \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; \u0026lt;chr\u0026gt; 1 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE 2 1 What a Night for a Knight daphnie FALSE FALSE FALSE FALSE 3 1 What a Night for a Knight velma FALSE FALSE FALSE FALSE 4 1 What a Night for a Knight shaggy TRUE FALSE FALSE FALSE 5 1 What a Night for a Knight scooby TRUE FALSE TRUE FALSE 6 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE 7 1 A Clue for Scooby Doo daphnie FALSE TRUE FALSE FALSE 8 1 A Clue for Scooby Doo velma FALSE TRUE FALSE TRUE 9 1 A Clue for Scooby Doo shaggy TRUE FALSE FALSE FALSE 10 1 A Clue for Scooby Doo scooby FALSE FALSE FALSE FALSE # ℹ 2,995 more rows # ℹ Use `print(n = ...)` to see more rows Why use many line when one line do trick?\nYou can read more about this pivot_longer feature in this tidyr vignette.\nBonus: data.table equivalent # I learned later (from Hadley quote-tweeting my thread) that this feature was inspired by a similar feature in data.table, so I wanted to track down how it would be done there. As it turns out, kind of difficult because the feature isn\u0026rsquo;t in the CRAN version: it was in development as of October 2020, merged to main in May 2021, and still hasn\u0026rsquo;t made it onto CRAN because of some data.table governance issues (which are finally getting close to being resolved?)\nIn any case, with the main branch of rdatatable/data.table as of today, here\u0026rsquo;s how you could do the same pivot trick:\n# requires development version of data.table and/or 1.15.0+ rlang::check_installed(\u0026#34;data.table (\u0026gt;= 1.14.99)\u0026#34;) data.table::data.table(x) |\u0026gt; data.table::melt( id.vars = c(\u0026#34;season\u0026#34;, \u0026#34;title\u0026#34;), # measure() is not actually exported but is silently parsed under the hood by melt() # - uses `sep = \u0026#34;_\u0026#34;` to identify \u0026amp; process the column names (i.e. split with sep \u0026#34;_\u0026#34;) # - new column names (i.e. character) are provided unquoted # - value.name is equivalent to tidyr\u0026#39;s `.value` measure.vars = measure(value.name, character, sep = \u0026#34;_\u0026#34;) ) season title character caught captured unmask snack \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; \u0026lt;char\u0026gt; 1: 1 What a Night for a Knight fred FALSE FALSE FALSE TRUE 2: 1 A Clue for Scooby Doo fred FALSE TRUE TRUE FALSE 3: 1 Hassle in the Castle fred FALSE FALSE TRUE TRUE 4: 1 Mine Your Own Business fred TRUE FALSE TRUE FALSE 5: 1 Decoy for a Dognapper fred FALSE FALSE FALSE FALSE --- 3001: 2 The Dreaded Remake of Jekyll \u0026amp; Hyde! scooby FALSE FALSE FALSE FALSE 3002: Movie Happy Halloween, Scooby-Doo! scooby FALSE FALSE FALSE FALSE 3003: Movie Scooby-Doo! The Sword and the Scoob scooby FALSE FALSE FALSE FALSE 3004: 2 Dark Diner of Route 66! scooby TRUE FALSE FALSE FALSE 3005: 2 Total Jeopardy! scooby FALSE FALSE FALSE FALSE Full Code # library(tidyverse) scooby_data \u0026lt;- read.csv(\u0026#34;https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-07-13/scoobydoo.csv\u0026#34;) x \u0026lt;- scooby_data %\u0026gt;% select(season, title, starts_with(\u0026#34;caught\u0026#34;), starts_with(\u0026#34;captured\u0026#34;), starts_with(\u0026#34;unmask\u0026#34;), starts_with(\u0026#34;snack\u0026#34;), -contains(\u0026#34;other\u0026#34;), -contains(\u0026#34;not\u0026#34;)) %\u0026gt;% filter(title!= \u0026#34;Wrestle Maniacs\u0026#34;) three_lines \u0026lt;- x %\u0026gt;% pivot_longer(cols = -c(\u0026#34;season\u0026#34;,\u0026#34;title\u0026#34;)) %\u0026gt;% separate(name,into = c(\u0026#34;action\u0026#34;,\u0026#34;character\u0026#34;), sep = \u0026#34;_\u0026#34;) %\u0026gt;% pivot_wider(names_from = \u0026#34;action\u0026#34;, values_from = \u0026#34;value\u0026#34;) one_line \u0026lt;- x %\u0026gt;% pivot_longer(cols = -c(\u0026#34;season\u0026#34;,\u0026#34;title\u0026#34;), names_to = c(\u0026#34;.value\u0026#34;,\u0026#34;character\u0026#34;), names_sep = \u0026#34;_\u0026#34;) rlang::check_installed(\u0026#34;data.table (\u0026gt;= 1.14.9)\u0026#34;) data.table::data.table(x) |\u0026gt; data.table::melt( id.vars = c(\u0026#34;season\u0026#34;, \u0026#34;title\u0026#34;), # measure() is not actually exported but is silently parsed under the hood by melt() # - uses `sep = \u0026#34;_\u0026#34;` to identify \u0026amp; process the column names (i.e. split with sep \u0026#34;_\u0026#34;) # - new column names (i.e. character) are provided unquoted/as bare symbols # - value.name is equivalent to tidyr\u0026#39;s `.value` measure.vars = measure(value.name, character, sep = \u0026#34;_\u0026#34;) ) Original gist for reference.\n","date":"2023 Nov 30","externalUrl":null,"permalink":"/posts/2023-11-30_pivot-trick/","section":"posts","summary":"A tidyr tip for extracting data from column names that I’ve used and shared a\nlot, now transcribed from Twitter","title":"tidyr pivot trick: names_to -\u003e .value","type":"posts"},{"content":" Recording # Materials # Repo: https://github.com/tanho63/talk_shinyprod2023_logging\n","date":"2023 Oct 13","externalUrl":null,"permalink":"/posts/talks/2023-10_shinyprod2023_logging/","section":"posts","summary":"A talk given at Jumping Rivers’ Shiny in Production 2023 conference on effective\nlogging strategies for R and Shiny.","title":"Effective Logging Strategies","type":"posts"},{"content":" Recording # Materials # https://github.com/tanho63/talk_shinyconf2023_debugging\n","date":"2023 Mar 17","externalUrl":null,"permalink":"/posts/talks/2023-03_shinyconf2023_debugging/","section":"posts","summary":"A talk given virtually at Shiny Conf 2023 on some effective patterns for\ndebugging Shiny apps","title":"Debugging Shiny Apps","type":"posts"},{"content":"2022 is in the books: a year of highs, lows, breaking through comfort zones, and losing ground on things I’d been working on in past years.\nI’m recognizing that in a broader sense, I can only focus on one or two areas at a time. In past years, it’s been one or some of: losing weight, improving physical shape/condition, programming skill/personal projects, social/friends. This year it seems that my gains were in career development and networking/notability, and I lost ground in weight, friend networks, and in personal projects. It seems obvious that we can only prioritize one or two things at a time, but I’m frustrated that it feels out of my control to choose what those things are, and I’m frustrated that it’s so all-or-nothing in terms of past progress.\nThe biggest story of the year was leaving Caivan in June to join Zelus Soccer in August. I felt a lot of comfort in the environment and friends at Caivan, but also a growing sense of inertia and stagnation. Joining Zelus was awesome, despite a long and somewhat frustrating interview process, and it feels like I’ve hit the ground running there. It’s been really nice to be part of a data team, and I’ve felt challenged and valued for my skill set. I’ve enjoyed the increased flexibility in fully remote/async work as well as still having a close-knit small team environment.\nI gave a talk at RStudio Conf this year! It was nerve-wracking but also immensely rewarding, and I think it was awesome to share about a topic I thought could benefit many R users: GitHub Actions and Releases - it’s been invaluable to the way nflverse works. I’m so glad to have met up with longtime online friends who are now just straight up friends: Joe, Maya, Tony, Asmae, Eric, Daniel, Tom, Jon, and so many more.\nIt was interesting to spend another summer away from rowing. In a bit of a change from the previous years, I lifted weights consistently (reaching new powerlifting personal bests!) and somewhat ate at a bulk along the way. I don’t think I’m happier for being stronger + slower + heavier. I also picked up some recreational flag football and ultimate frisbee leagues, those were largely pretty fun but not as socially connecting as I’d hoped.\nI put a lot of things on hold this year, somehow no longer managing to find space in my energy budget to do things I’d been excited about - streaming and hobby programming projects both landed on the back burner and never made it back to top of mind despite being such focuses in previous years. I more or less only have good memories of those, but the inertia wall is the big stopper.\nI travelled a lot this year! I made it to the French Alps in April with Mimio and friends, to DC in July for RStudio Conf, to Toulouse and Milan in August with Zelus, and to Pittsburgh in October for CMSAC. It’s more than I’ve travelled in years - even before the pandemic, I was pretty limited in vacation time and disposable income. The travel bug bit me pretty hard, and I’m already looking forward to travelling even more in 2023 - I’ve booked trips to Maine, Salt Lake City, and Austria in the first few months of the year. Really trying to take advantage of working remotely and asynchronously.\nI tried to get more help for adhd, depression, and burnout this year, but things somewhat fell through during the job changeover and I’m embarrassed to admit that it’s mostly my own fault for dropping things. I want to do better at this, especially if it helps me avoid the lowest lows and keeps me on track.\n","date":"2022 Dec 29","externalUrl":null,"permalink":"/posts/reflections/2022/","section":"posts","summary":"A year of breakthroughs, travelling, and trading off priorities.","title":"Reflections on 2022","type":"posts"},{"content":" Recording # ","date":"2022 Jul 27","externalUrl":null,"permalink":"/talks/rsconf2022-github/","section":"posts","summary":"A talk on improving your R projects by leveraging GitHub for package hosting, automated compute (via Actions), and free data storage (via piggyback + Releases)","title":"Using GitHub To Make Your Work Live Forever","type":"posts"},{"content":"I\u0026rsquo;ve recently been asked three times for my advice on whether to use blogdown or distill when getting started with your first website with R, so in true DRob fashion: here\u0026rsquo;s a blog post!\n2023 note This post never actually got properly fleshed out beyond a set of bullet notes, but I figure it\u0026rsquo;s still worth sharing 😅 Have added a couple of 2023 notes as well. TLDR # They\u0026rsquo;re both great, and incredibly well supported within the R community. It doesn\u0026rsquo;t matter what you start with, you can redo your site later. You probably will want to at some point down the line regardless, it\u0026rsquo;s part of the learning process! Your content will transfer with minimal effort. I recommend starting with blogdown and Hugo Apero in most cases, EXCEPT \u0026hellip;if you know you dislike CSS and styling, in which case Distill is perfect for you Buy a domain name, serve it with Netlify or GitHub Pages, and go forth and grow your garden! Distill # Distill is minimalist in that it doesn\u0026rsquo;t let you do a lot aesthetically - this means that most distill sites are straightforward and clean and incredibly easy to maintain/use going forward. The most you\u0026rsquo;ll do is pick fonts and colours - everything else is pretty much set out for you. 2023 note RStudio/Posit folks are promoting Quarto websites for this minimal/simple aesthetic now.\nDistill sites will still work (Distill simplicity at it\u0026rsquo;s finest!), but if you haven\u0026rsquo;t started your site yet you might be interested in starting from the \u0026ldquo;latest hotness\u0026rdquo;.\nAnother compelling reason to use to Quarto is the ability to use Python and Observable JS alongside with R chunks, so if that\u0026rsquo;s of interest I\u0026rsquo;d look into switching.\nBlogdown # blogdown is decidedly not minimal - everything is flexible and can be laid out in any number of ways this means you can pick out some kickass hugo themes from somewhere and plug them in so that your site doesn\u0026rsquo;t look like other people\u0026rsquo;s sites e.g. https://tanho.ca/ and https://ffverse.com/ are both blogdown sites, but don\u0026rsquo;t have the same feel at all the flexibility means you\u0026rsquo;re more likely to break your site somehow, and also increases the temptation that you\u0026rsquo;ll end up nuking your site and rebuilding it from scratch. the latest/safest hotness in blogdown is Alison Hill\u0026rsquo;s Hugo Apero theme I spend a bunch of time doing front-end with Shiny so I\u0026rsquo;m not as scared to wade into the guts of css/html crap - this site is primarily made from a theme called Toha, and ffverse.com from a theme called Osprey 2023 note I\u0026rsquo;ve since ported tanho.ca to a new theme, Blowfish.\nI found that I was overriding a lot of the Toha theme components manually and realized I wanted something that was more flexible.\nWhich one do I choose? # Whichever one appeals to you, if you\u0026rsquo;ve read all the way through to here! In most cases, I think there\u0026rsquo;s a lot of value in learning to use blogdown, and would recommend starting there with Apero\u0026hellip;except if:\nyou don\u0026rsquo;t care about the aesthetics, you know you hate CSS and style decisions, or you want to get started quickly in which case Distill is perfect for you!\n","date":"2022 Feb 04","externalUrl":null,"permalink":"/posts/2022-02-04_personal-websites/","section":"posts","summary":"Some thoughts on blogdown, distill, and getting started with your first website in R","title":"blogdown, distill, and personal websites with R","type":"posts"},{"content":"Another year in the books. 2021 seemed to have it all: ups and downs, twists and turns, successes and failures, and plenty of frustration. In that spirit, some reflections on both the good and the bad:\nI started streaming my programming projects on Twitch. The consistency has been incredible for my project productivity (more on that later) and for anchoring my weekly schedule through good and bad times - a haven where I could disconnect from the regular grind and code for myself with a schedule. I\u0026rsquo;ve also made great friends in the R and streaming communities, and am grateful and inspired to help champion data science streaming going forward.\nI burnt out to rock bottom several times and slumped into deep and empty depressions, unable to get out of bed.\nI took up a (rather strict) extended fasting schedule (two 36 hour fasts each week) and went from 285 pounds to 235 pounds. It was hard and lonely at times, but effective and felt maintainable. I\u0026rsquo;d like to get into the 180s still, and would be very pleased with continuing that pattern next year.\nI stopped rowing and coaching this year. I felt unappreciated and found myself dreading the start of the season, which eventually (along with being depressed at the time) pushed me to finally let it go. The relief has been welcome. I\u0026rsquo;m not sure I\u0026rsquo;m back next year - might pick up some other sport instead - frisbee? soccer? rugby? martial arts? crossfit? I\u0026rsquo;ll come back to rowing eventually, once I\u0026rsquo;ve really cleared my head and can learn to enjoy the sport for the sport itself.\nI travelled to London for my birthday and enjoyed my first trip in a few years. Getting to see Liverpool play at Anfield was an incredible experience, as was seeing Les Miserables in theatres - but I also really enjoyed eating my way through London\u0026rsquo;s restaurants and food markets!\nI picked up freelance work and bit off a lot more than I could chew, burning out quickly. I do like the variety and think I might try to do more of it next year, if I can better manage my time and energy.\nI\u0026rsquo;ve become a lot closer to many of my online communities and friends (TDM, Mafia, nflverse, streaming, R4DS) and I was especially grateful to have finally gotten the opportunity to hang out with Joe in person. A life-changing friendship.\nI\u0026rsquo;ve fallen out of touch with my local friends, between choosing to fast (and skipping socials) and letting go of rowing. It was lonely and I miss them greatly.\nI managed to get three doses of COVID vaccine, and worked hard at maintaining health, masking, and distancing.\nOverall, I\u0026rsquo;m pleased with my efforts to control what is within my control and respond to what I could respond to this year. I\u0026rsquo;m still working on trying to let go of the things that are out of my control - to varying degrees of success.\nAnd some thoughts on projects:\nI expanded {ffscrapr} to cover ESPN and to connect with nflverse data. I\u0026rsquo;m now covering API features for four of the largest platforms (MFL, Sleeper, ESPN, and Fleaflicker) and pleased with its featureset and overall performance. I\u0026rsquo;ve been asked to expand it to Yahoo but without having a league there (nor experience with the OAuth), I haven\u0026rsquo;t really had the drive to do anything on that front.\nI dreamed up and then built out {ffsimulator}, getting it published on CRAN. A project that literally walked out of my head into code over the course of 3-4 months, and I built it mostly on-stream! I think it gives a really interesting take on fantasy football research - and certainly a lot more data to work with. It\u0026rsquo;s probably not the most robust way to do a simulation but it\u0026rsquo;s one of the first projects of its kind and we haven\u0026rsquo;t really scratched the surface about how to use that data yet, I think.\nI used ffsimulator to simulate all of Scott Fish Bowl! It had a pretty decent correlation to points scored and overall success (about 0.30 R^2, which is well in line with most NFL predictions) especially with how volatile this year\u0026rsquo;s fantasy football season turned out to be.\nI wrote {ffpros}, a package for scraping FantasyPros.com, but have been leery of publishing it to CRAN or taking it much further than the current state.\nI moved my fantasy football R packages to the ffverse GitHub organization and domain, hoping to make it easier to collaborate and share work with the community. Thus far it\u0026rsquo;s still mostly me and Joe. Definitely happy to champion it until it gets more traction.\nI mostly watched passively as the DynastyProcess trade calculator continued to grow in popularity- it\u0026rsquo;s astounding that it more than doubled in usage (to a peak of 80,000 monthly users this season!) despite me not spending any advertising dollars, promotion time, or even writing a single line of code for it this year. I kind of feel like I\u0026rsquo;ve grown a bit away from the project, and am not super sure of what direction it should go. I\u0026rsquo;m also not sure I want to monetize it, even though I know there\u0026rsquo;s a market there if I want it. I\u0026rsquo;m learning that I\u0026rsquo;m not very money driven!\nI learned how to use GitHub Actions and ported all of my DynastyProcess automation there.\nI managed to get Ben Baldwin, Sebastian Carl, Lee Sharpe and more to unite under the nflverse GitHub organization and domain, synchronizing our efforts to advance open source NFL analytics and research.\nI built out {nflreadr} as a lower-dependency package that only downloads precomputed data from nflverse repositories. It\u0026rsquo;s lighter weight and faster than the original nflfastR equivalent - and it\u0026rsquo;s been really good to collaborate with Seb on it. Among other things, Seb is a remarkably good code reviewer!\nI maintained a lot of the nflverse GitHub Actions data automations and I\u0026rsquo;m proud of how well it works now - not to say that there aren\u0026rsquo;t problems but that they\u0026rsquo;re resolved efficiently and effectively.\nI wrote up a mini package called {fivestars} that adds CSS star ratings to rmarkdown and Shiny pages. It does a tiny thing but I enjoy it!\nI compiled some other fun helper functions into my personal package {tantastic} and it even includes my own personal ggplot theme!\nI recruited a whole bunch of the rstats community into doing Advent of Code! I didn\u0026rsquo;t finish it this year after taking some time for family stuff, but plan to pick up the last few puzzles eventually.\nI made a neat helper package for Advent of Code that initiates a template, downloads inputs, and applies caching!\nI continued to help R users by answering help questions across a wide range of platforms - R4DS, Twitter, nflverse, and (a little bit) the R discoRd.\nI\u0026rsquo;m much more confident in live-coding, data visualisation, package development, and overall R skills.\n","date":"2021 Dec 29","externalUrl":null,"permalink":"/posts/reflections/2021/","section":"posts","summary":"Reflecting on a year of streaming, fasting, pandemic isolation, and more.","title":"Reflections on 2021","type":"posts"},{"content":" Recording # ","date":"2021 Oct 11","externalUrl":null,"permalink":"/posts/talks/2021-10_tantorials_shiny-server/","section":"posts","summary":"A tutorial on setting up your own Shiny Server","title":"Setting Up Your Own Shiny Server","type":"posts"},{"content":"I stepped on the scale today and realized I had hit a milestone: I\u0026rsquo;ve lost fifty pounds since starting my weight loss efforts five months ago. It\u0026rsquo;s a nice, if somewhat bittersweet milestone to hit on this weight loss journey - I recorded a pretty similar milestone in 2018 while going from 275 lbs to 200 lbs. Life happened (happens, I guess) and while I\u0026rsquo;m a tad bitter, there\u0026rsquo;s some comfort in the confidence that I\u0026rsquo;m capable of reaching my goals when I put the effort in.\nI wanted to write down a mental model that I\u0026rsquo;ve been experimenting with around dieting, eating habits, and weight loss. While this post is not a formally qualified opinion and does not constitute medical advice, it feels like a useful mental model for understanding the many varied paths to the same goal.\nLevers to use towards a caloric deficit # lever (noun):\nA long handle used to operate a machine or control panel. A simple machine consisting of a straight, rigid bar and a fulcrum, used to multiply mechanical force to do more work. Losing weight involves consuming less calories than you expend over a significant amount of time1 - aka \u0026ldquo;caloric deficit\u0026rdquo;. In most cases (and especially among those with a lot of weight to lose), adjusting your eating habits is a much bigger driver of caloric deficits than purely exercising (although exercise is good and useful). So let\u0026rsquo;s define for ourselves the work (in a mechanical sense) as being \u0026ldquo;eating a caloric deficit\u0026rdquo;.\nNow that we\u0026rsquo;ve defined work, we can frame all \u0026ldquo;diets\u0026rdquo; as three types of levers (in the \u0026ldquo;simple machines\u0026rdquo; sense above) that we can apply force (i.e. effort, time, discipline) to: what you eat, when you eat, and how much you eat.\nWhat you eat covers all kinds of food restrictions, including low carb (keto, Atkins etc), low fat, high protein, vegetarian/vegan, paleo, or otherwise. These primarily work because there\u0026rsquo;s a property about that food that makes it hard to overeat - in keto, you eat more fats and your brain thinks you\u0026rsquo;re satiated before you reach a certain calorie threshold, while in low fat and/or vegetarian diets you eat more greens and proteins so that your stomach becomes full before it can take in a certain number of calories etc. This works - the primary mechanism for me losing weight in 2018 was eating a keto diet and being ruthless about cutting out carbs.\nWhen you eat covers all kinds of fasting - intermittent, extended, or otherwise. The primary mechanism here is eating fewer meals, so while you do eat to fullness you end up eating less calories than you would have if you\u0026rsquo;d eaten three meals. In practice your appetite is also somewhat diminished (or your stomach \u0026ldquo;shrinks\u0026rdquo;) so your fullness threshold happens more quickly and at a lower calorie threshold. This can also work on a broader scale as I\u0026rsquo;m doing right now in 2021, where over the course of a seven day week I fast on three days - effectively reducing my calorie intake by 40%.\nHow much you eat covers calorie, macro, or portion control for each meal. If this is the lever you\u0026rsquo;re using, you\u0026rsquo;re doing the classic \u0026ldquo;track your calories at each meal\u0026rdquo; methodology and being precise about how much you take in at each meal - think of classic apps like My Fitness Pal, IIFYM etc.\nAll three of these levers, when properly applied2 within the goal of caloric deficit, successfully lead to weight loss over time with adherence. If you use none of them, you\u0026rsquo;re eating the Standard American Diet (SAD being an apt acronym).\nSome levers are easier for some people to use than others - the one that is easiest for me may not be the easiest for someone else. Heck, the one that\u0026rsquo;s easiest for me today (when to eat/fasting) is not the same as the one that worked the last time (what to eat/keto). In 2018, I ground out a keto strategy to some success, but found food boringly repetitive and socializing difficult. I\u0026rsquo;m finding fasting easier to stick to this time around, because when I do eat I don\u0026rsquo;t need to worry about \u0026ldquo;what\u0026rdquo; I\u0026rsquo;m eating or \u0026ldquo;how much\u0026rdquo; I\u0026rsquo;m eating at each meal - I can eat until I\u0026rsquo;m full when I do eat and eat whatever I\u0026rsquo;m craving.\nYou can apply force on these levers with varying degrees of intensity (fasting for longer, reducing portion sizes more, cutting out more foods), and you can use one, two, or three levers at a time. It is mentally draining to use two or all three levers intensely at once - i.e. keto + one meal a day is very tough to stick to, as I\u0026rsquo;ve found out - it could be doable in the short term as a treatment but feels so unsustainable as a long term plan.\nIf you can strive to put consistent effort into just one of these levers at a time, you should be able to stick to that pattern for a longer period of time - and adherence to a caloric deficit leads to more successful weight loss.\nThere are some other hypotheses about why obesity exists, including the carbohydrate-insulin model, but I don\u0026rsquo;t personally find them very convincing after experimenting with them. I\u0026rsquo;m not sure I\u0026rsquo;m the right person to discuss those without having done the research.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAll three levers have possible ways to fail at the goal of eating a caloric deficit - you can still indulge in too many calories while eating keto, you can binge eat when fasting so as to wipe out your efforts, and you can be poor/ineffective at tracking your calories at each meal (or misjudge the correct calorie numbers etc).\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021 Oct 09","externalUrl":null,"permalink":"/posts/2021-10-09_eating-habits/","section":"posts","summary":"Thoughts on ‘dieting’ from someone who has now lost over fifty pounds…twice.","title":"Eating Habits as Levers","type":"posts"},{"content":" Recording # ","date":"2021 Aug 02","externalUrl":null,"permalink":"/posts/talks/2021-08_tantorials_wrangling-json/","section":"posts","summary":"A tutorial on wrangling JSON and lists into tibbles","title":"Wrangling JSON and Lists","type":"posts"},{"content":" Recording # ","date":"2021 Jul 19","externalUrl":null,"permalink":"/posts/talks/2021-08_tantorials_web-scraping/","section":"posts","summary":"A tutorial on scraping with rvest and browser dev tools","title":"Web Scraping with rvest","type":"posts"},{"content":"Embrace unit testing karma\nKarma says:\nDo good things and good things will happen to you. Do them the way you know. Do them the way you like. Karma is flexible. Testing needs flexibility.\nKarma thrives on creativity. Testing needs creativity. - Testivus\nWhile developing ffscrapr, I\u0026rsquo;ve taken a deeper dive into HTTP testing for R packages. I\u0026rsquo;ve settled on using httptest and find it very convenient path to building and using mock data, especially since I\u0026rsquo;m using httr extensively within the package. I wish Scott Chamberlain \u0026amp; Maelle Salmon\u0026rsquo;s new book HTTP testing in R had been written before I started - that would have been incredibly helpful as a guiderail for my experimentations! Here are some thoughts on what I\u0026rsquo;ve discovered while testing ffscrapr, mostly trying to capture parts of my experiences that differ from what\u0026rsquo;s already in the HTTP testing book.\nWhy test? # Testing is about making sure that the package (and functions) work as intended, in spite of external factors. With API packages, sources of errors can include:\ninternet connectivity (both from the user and from the API) API response changes (format changes, variable naming, etc) dependency changes (this could include base R versions, operating systems, as well as the more obvious package dependencies) users doing unexpected things with your package (finding corner cases) A good test setup should test at least the first three of these (user-side corner cases being difficult to foresee at time of function definition), and ideally should test each area individually while holding the other two parts constant. For example, you want to test how the package handles internet connectivity without worrying about whether the API response changes, or whether the dependencies have changed. You also want to test the package every time the dependencies change, without worrying about internet connectivity or API responses changing. And lastly, you want to test and make sure that the real API response has not changed, because it might return unexpected results. In addition to these concerns, another really important element to consider is making sure that it is easy to maintain the testing setup.\nFor ffscrapr, I think the most important of these are testing the code for dependency changes and testing the code for API response changes. Internet connectivity seems fairly straightforward to write code for, and is worth doing at least once, but especially in a heavier-weight package like ffscrapr (which imports quite liberally from tidyverse packages like dplyr, purrr, tidyr etc), dependency testing is crucial to making sure the package continues to clean and process the data in the intended fashion. Also, fantasy platform APIs do change on a pretty frequent basis, adding and removing features nearly every year. It\u0026rsquo;s worth testing against the live APIs to make sure that ffscrapr continues to operate as intended and without errors.\nWhy not skip CRAN testing? # Many API packages skip all testing on CRAN - it\u0026rsquo;s a hassle to maintain, and the risk of being booted from CRAN can outweigh the effort of making sure tests don\u0026rsquo;t fail. I\u0026rsquo;m here to suggest otherwise! I think developers are missing out on one of CRAN\u0026rsquo;s most important benefits, which is being a massive dependency testing suite. Packages are tested on many different combinations of operating systems and R versions on a regular basis, and CRAN will also require your dependency packages to make sure that changes they make don\u0026rsquo;t break your package (and/or if they do, to give you advanced notice that it will break your package). This gives you time to make the appropriate changes to the package to accommodate the change!\nSkipping these tests on CRAN is doing yourself a disservice, in my opinion, especially with the development of packages like httptest (which I use) and alternatives like vcr/webmockr - I think the extra work in making the package CRAN-testable (at least, to make sure that your R code processes the responses properly) is worth doing. These packages essentially create local copies of the data that the API would return from your http query, and redirect your request to read from the local files instead of letting your request go through to the API - allowing you to test while holding the API responses constant.\nStoring mock data in a separate repository # In an ideal world, tests and files are small and can be stored alongside the test data, such that you don\u0026rsquo;t overrun CRAN\u0026rsquo;s limitations on compressed or installed package sizes (5MB, generally speaking). With ffscrapr, I was finding that responses from certain sites (cough, Fleaflicker, ESPN, cough) were enormous - they package a whole whack of player data within each call, such that their responses were coming back at 5-10MB each!\nIn consulting the available documentation, I came across this GitHub issue in the httptest repository, in which Neal Richardson describes some of his suggested approaches to solving this problem:\nCreating a function to redact the responses on capture Manually pruning the captured responses Refactor larger functions so that they can be tested on smaller chunks of data Lastly (and least-optimally, in Neal\u0026rsquo;s opinion) - creating a separate data package that holds the response cache I took a stab at each of these approaches, but one by one I had to discard them:\nAPI responses for fantasy football platforms are subject to change on a pretty regular basis - I\u0026rsquo;ve had to rebuild the captured responses several times since I started developing this package in August 2020, and corner cases are unlikely to appear in the first 5-10 responses of data Manually pruning the captured responses is unfeasible for similar reasons as above, and is also infinitely more time-consuming! I did try to refactor some of the larger/longer-running functions into smaller/more-testable units, especially since APIs frequently have options to limit and paginate. However, some of these requests were very difficult to configure in this fashion, and other functions that I wanted to test needed to aggregate large amounts of data. I wanted the tests to be easily evaluated via the naked eye. Thus, I landed on creating a separate package of test data files as my best route forward. I briefly considered the idea of pushing the data package to CRAN - this would ensure that it would always be \u0026ldquo;accessible\u0026rdquo; to the CRAN package wherever accessed - but discarded the prospects of both getting an exemption on package size AND maintaining regular updates to the test data. Instead, I opted for a separate GitHub repository of test files, which I considered to be a relatively reliable place to store the data.\nOf course, a few concerns popped up with this option, which needed handling:\nI would need to check that I could download the data at the time of test/vignette-build, and then skip testing/evaluating if GitHub or the data were unavailable. I would need to find a way to version-control and make sure that I didn\u0026rsquo;t accidentally delete test data that would cause CRAN testing to break - that would defeat the whole purpose of going through all the trouble of mocking the data! I managed the latter concern with a GitHub release, which allowed me to version-lock the test-data to match the CRAN version. I\u0026rsquo;ll need to make sure to update the links within the package at every CRAN release, but I think this should be feasible!\n2023 note I didn\u0026rsquo;t know of the existence of piggyback at the time of this writing but if I did I would have used it instead so as to not have to track another repository. It\u0026rsquo;s been buried on my project list, but I\u0026rsquo;ve wanted to make a PR to add a piggyback option to httptest for a while now. Accessing the mock data # This needed some finagling, but I eventually settled on the following code in my testthat setup file:\nsuppressPackageStartupMessages({ library(testthat) library(httptest) library(checkmate) }) (Packages)\nif (identical(Sys.getenv(\u0026#34;MOCK_BYPASS\u0026#34;), \u0026#34;true\u0026#34;)) with_mock_api \u0026lt;- force If a system environment variable named MOCK_BYPASS has value of true, override the definition of \u0026ldquo;with_mock_api\u0026rdquo; to be \u0026ldquo;force\u0026rdquo;, which stops the mocking behaviour and replaces it with normal evaluation.\ndownload_mock \u0026lt;- !identical(Sys.getenv(\u0026#34;MOCK_BYPASS\u0026#34;), \u0026#34;true\u0026#34;) \u0026amp; !is.null(curl::nslookup(\u0026#34;github.com\u0026#34;, error = FALSE)) 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.\nskip \u0026lt;- FALSE if (download_mock) { tryCatch( expr = { download.file(\u0026#34;https://github.com/dynastyprocess/ffscrapr-tests/archive/1.3.0.zip\u0026#34;, \u0026#34;f.zip\u0026#34;) unzip(\u0026#34;f.zip\u0026#34;, exdir = \u0026#34;.\u0026#34;) httptest::.mockPaths(new = \u0026#34;ffscrapr-tests-1.3.0\u0026#34;) withr::defer( unlink(c(\u0026#34;ffscrapr-tests-1.3.0\u0026#34;, \u0026#34;f.zip\u0026#34;), recursive = TRUE, force = TRUE), testthat::teardown_env() ) }, warning = function(e) skip \u0026lt;\u0026lt;- TRUE, error = function(e) skip \u0026lt;\u0026lt;- TRUE ) } With \u0026ldquo;skip\u0026rdquo; 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.\nskippy \u0026lt;- function() NULL if (skip) skippy \u0026lt;- function() testthat::skip(message = \u0026#34;Unable to download test data\u0026#34;) 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.\nI 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 vignette RMDs as well.\nCapturing the mock data # 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:\nlibrary(httptest) start_capturing(\u0026#34;../ffscrapr-tests\u0026#34;) {run the tests} stop_capturing() {commit and push the test data} I\u0026rsquo;ve had some problems with my PC\u0026rsquo;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\u0026rsquo;re reading this and have better suggestions, let me know!)\nTesting the real APIs with GitHub Actions # CRAN obviously cannot test the real APIs, because it cannot handle failures gracefully. Instead, I have a GitHub Action that does this by running the package tests with the MOCK_BYPASS environment variable set to \u0026ldquo;true\u0026rdquo;. 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.\n","date":"2021 Mar 08","externalUrl":null,"permalink":"/posts/2021-03-08_httptesting/","section":"posts","summary":"Thoughts on httptest, not skipping CRAN where possible, GitHub Actions, and dealing with large packages of mock data.","title":"Adventures in HTTP Testing for R","type":"posts"},{"content":" I immensely recommend Austin Kleon and the Steal trilogy to everyone, especially those looking to \u0026ldquo;make it\u0026rdquo; as a creator, programmer, or data analyst. TLDR: Curating your feed, finding your friend groups, and getting into online arguments can be good # Each year, I embark on a journey to re-read Austin Kleon\u0026rsquo;s Steal trilogy, 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 \u0026ldquo;Like-Minded vs Like-Hearted\u0026rdquo;:\nModern day Bart Simpson would totally be punished with this Like many people, I\u0026rsquo;m not immune to getting drawn into online arguments - just ask the folks on the Dynasty Movement 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.\nI\u0026rsquo;ve heard many a time that you should never get drawn into an argument online: it\u0026rsquo;s impossible to win, petty trolls cannot be fed, and that it\u0026rsquo;ll have a negative impact on your work, life, and mental health. It\u0026rsquo;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\u0026rsquo;d better served by avoiding confrontations altogether.\nAnd yet,\n\u0026ldquo;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.\u0026rdquo; - Alan Jacobs, How To Think (c/o Keep Going)\nI\u0026rsquo;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\u0026rsquo;m not as interested in helping others make it big, amass follower counts, become more influential - heck, I\u0026rsquo;m not looking to do that myself! My Twitter feed is precious to me, and I\u0026rsquo;m fastidious about stripping away the content that doesn\u0026rsquo;t fit those goals, or just plain old doesn\u0026rsquo;t make me happy.\nThis is both a blessing and a curse: it\u0026rsquo;s very easy to fall into a trap where you fill your feed (and friend groups) with like-minded 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.\nIf you really want to explore ideas, you should consider hanging out with people who aren\u0026rsquo;t so much like-minded as like-hearted - those who are temperamentally disposed to openness and have habits of listening. The ones who think about what you\u0026rsquo;re saying rather than just reacting.\nThis is a really valuable idea - and one that I feel like I need to strive for, and keep striving for. I\u0026rsquo;ve backed into finding like-hearted people in my feed, inadvertently, because I\u0026rsquo;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\u0026rsquo;s achievements.\nI\u0026rsquo;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\u0026rsquo;s opinions. It\u0026rsquo;s rare that I feel like I\u0026rsquo;m left angry or drained after spending time there.\nThe 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\u0026rsquo;m lucky to have the communities I\u0026rsquo;m in, and I\u0026rsquo;m very glad to be able to get drawn into debates amongst my like-hearted friends.\n","date":"2021 Mar 04","externalUrl":null,"permalink":"/posts/2021-03-04_like-hearted/","section":"posts","summary":"Curating your feed, finding your friend groups, and getting into online arguments.","title":"Like-Hearted","type":"posts"},{"content":" This year\u0026rsquo;s successes # I joined R4DS Slack Community and it\u0026rsquo;s been an amazing community to be part of - making many new friends, having fun learning new things to become a better R programmer, and helping others also get better by answering their questions. I\u0026rsquo;ve made many new friends through the TDM Dynasty server and through the offshoot Mafia server that came off of it. Getting out as a rowing coach this summer was awesome, no matter how much I disliked the admin-bits of it, because I got to see and hang out with my friends as if it was a normal year. Continuing to hang out and work on projects through DynastyProcess with Joe has been awesome - his friendship and support has been incredible throughout the year. Work has been fantastic as a source of stability and routine. I wrapped up the second version of the core project in November/December and have appreciated the time to learn and work through some forecasting and predictive analytics for the upcoming year - really looking forward to continuing my growth as a data scientist/analyst/developer there. DynastyProcess has quadrupled in size this year, going from an average of 10,000 unique monthly users to over 40,000 (as of October/November)! I can definitely credit the mobile-first calculator for a bulk of that popularity gain - it\u0026rsquo;s definitely much easier to use than before. I made the ffscrapr R package a reality this year, managing to get it to support three different fantasy football platforms AND putting it onto CRAN! I carved a pumpkin every week in October, and loved the meditative process that went with it. I completed every single Advent of Code puzzle on the day it came out, and learned a ton about efficient R programming. I made a bunch of cool new apps for DynastyProcess and taught myself the beginnings of ML. I rebuilt my computer with some upgraded parts, and have been enjoying the improvements in computing power! Disappointments # I\u0026rsquo;m disappointed about my weight gain over the pandemic, and the frequency at which I slumped into a depression and burnout. I can\u0026rsquo;t wait for 2021, vaccines, and a gradual return to normalcy.\n","date":"2020 Dec 26","externalUrl":null,"permalink":"/posts/reflections/2020/","section":"posts","summary":"Successes and disappointments in the face of a pandemic.","title":"Reflections on 2020","type":"posts"},{"content":" I greatly enjoy the DSLC Slack Community ( dslc.io/join), and spend a fair bit of time answering questions! This post is part of a series where I curate some interesting questions and/or answers that I’ve come across there. Dhiraj asks: # 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 ?\nAnswer # Here\u0026rsquo;s a minimal example of the Shiny and JS code you\u0026rsquo;ll need:\nlibrary(shiny) library(tidyverse) custom_js \u0026lt;- \u0026#39;Shiny.addCustomMessageHandler(\u0026#34;handler_windowtitle\u0026#34;, switch_window_title ); function switch_window_title(message){ document.title = message; }\u0026#39; ui \u0026lt;- navbarPage( title = \u0026#39;navbarpage\u0026#39;, id = \u0026#39;displayed_tab\u0026#39;, tags$head(tags$script(custom_js)), tabPanel(title = \u0026#34;StrangePlanet\u0026#34;), tabPanel(title = \u0026#34;DarthVader\u0026#34;) ) server \u0026lt;- function(input, output, session) { observeEvent( input$displayed_tab, session$sendCustomMessage(\u0026#34;handler_windowtitle\u0026#34;,input$displayed_tab) ) } shinyApp(ui, server) If you give navbarPage an ID, it\u0026rsquo;ll create an input variable that can be accessed as input${id}, returning the value argument of the tabPanel. In this case, tabPanel returns the title argument as the default value - you can specify something else if you prefer.\nIn a production app, I\u0026rsquo;d also move the JS into a separate JS file (stored in www) and use includeJS() instead of using the tags$script method.\n","date":"2020 Oct 20","externalUrl":null,"permalink":"/posts/2020-10-22_shinywindowtitles/","section":"posts","summary":"Pairing a snippet of JavaScript with Shiny reactivity to dynamically change the window title","title":"DSLC Slack: Changing Window Titles in Shiny","type":"posts"},{"content":" I greatly enjoy the DSLC Slack Community ( dslc.io/join), and spend a fair bit of time answering questions! This post is part of a series where I curate some interesting questions and/or answers that I’ve come across there. Piranha asks: # Hello, This might be bit of a stretch\u0026hellip;. I could use a bit of help in prepping for some upcoming conversations with the head of my company\u0026rsquo;s IT/Security.\nI am trying to get IT support for Shiny (currently IT supports Tableau).\nQuestion: Is there any kind of high level summary of this Admin Guide document? https://docs.rstudio.com/shiny-server/\nMore specifically, I want to summarize the official document into about a 3-5 slide presentation. Topics that I want to include:\nWhat is Shiny? (3-5 bullet points, in language that a \u0026ldquo;traditional\u0026rdquo; IT person will understand)\nHow to think about security and authentication for both the Shiny Server free and Pro.\nHow to think about Dockerizing an application.\nI 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!\nAny pointers would be really helpful.\nThanks!\nAnswer (by me) # I don\u0026rsquo;t know if I have a document to point you at, but I would suggest a mental model where you separate Shiny the app (the app.R file and all of the things included in it) from the ways you deploy Shiny. Writing out some of my personal thoughts on it:\nShiny (the app):\nis an R package that allows you to write UI (client-side) and server-side logic in R, which can be implemented quickly (from a code-writing perspective) and powerfully (leverages all of the modelling/data-wrangling packages and abilities of R). has an architecture consisting of a client (the browser) and a server (an R process). The two are connected by a websocket that receives state changes from the client, such as new values for input controls, and distributes state changes from the server, such as new output values. Shiny is not self-contained because it relies on R and other libraries (Shiny, as well as whatever else you use in the app) that are installed onto the server. Docker is one of the best ways to handle this, because it creates a container that holds an installation of R and all of the relevant packages, and can be version-locked to ensure stability. Once a Shiny app has been dockerized, it can be turned over to a devops team to deploy, and the devops team does not have to know R or Shiny in order to use the app.\nThis allows for a division of expertise:\nthe data scientist makes the app, and makes sure the docker container builds and works as intended the devops engineer can take care of the details with deploying and securing the apps, by controlling/restricting access to the container itself rather than forcing the data scientist to worry about it inside the app. Both the data scientist and the devops engineer should responsible for performance: data scientist by improving the code for efficiency engineer by supplying the containers with the right amount of processing power/memory/load-balancing A dedicated devops person can pretty easily deploy a docker container into an existing app/microservices system, if such already exists. At the other end of the spectrum, shinyapps.io and RS Connect are solutions that RStudio provides to help data teams who want this sort of devops process but don\u0026rsquo;t want to have the work done in house. They\u0026rsquo;ll manage the servers for you and make it very easy for R users to just upload all of the app data and configs, and they\u0026rsquo;ll manage deployment, performance and authentication for you.\nI\u0026rsquo;m a data generalist for a small team, and manage the full stack from data import/wrangle/model to app dev to app deploy. I wouldn\u0026rsquo;t necessarily recommend that path if your team is large enough - let devops be devops and data scientists be data scientists where possible!\nSome followup questions # In a discussion earlier this week, a person from IT asked me \u0026ldquo;I dont know R, can you tell me how Shiny connects to the user\u0026rsquo;s browser?\u0026rdquo; I didn\u0026rsquo;t know much about WebSocket technology at the time, so my response was: \u0026ldquo;Shiny comes with its own webserver, but a common practice is to use nginx webserver in front of the shiny app\u0026rdquo;.\nNow that I know a bit more\u0026hellip;would it be accurate to some something along these lines: \u0026ldquo;Shiny uses WebSocket technology to create a 2-way communication between the server and the browser. This allows the data scientist to leverage the full data-processing and analytical capabilities of R. In our current process, we need to do processing/analysis in one tool (e.g. SAS, R) and visualization/sharing in Tableau/Excel - creating lots of inefficiencies. Using Shiny, we can fine-tune and scale our analysis to exactly what is needed by the client and serve that via a Shiny dashboard.\u0026rdquo;\nFurthermore, \u0026ldquo;From the data science team\u0026rsquo;s perspective, this is our ask. We need the ability to deliver interactive analysis to our clients at scale. Can you help us do that in a secure way? We\u0026rsquo;ll build the Shiny app and dockerize it. We need help from IT to provide secure access by clients\u0026rdquo;.\nand response:\nShiny uses WebSocket technology to create a 2-way communication between the server and the browser\nI cribbed that chunk from here: https://community.rstudio.com/t/is-there-any-equivalent-of-web-sockets-in-shiny-world/17908. If you\u0026rsquo;re hoping to communicate how Shiny works to an IT person, I would use the wording as described rather than paraphrasing it too much - receives state changes from client and distributes state changes from server is high-level enough for most IT people to understand - and you lose something by abstracting beyond that.\nUsing Shiny, we can fine-tune and scale our analysis to exactly what is needed by the client and serve that via a Shiny dashboard\nI\u0026rsquo;d change this to say that it allows you to use the same R code that you use to process and generate the data right in the app, which allows you to package the \u0026ldquo;business logic\u0026rdquo; right into the app for immediate interactive analyses. I\u0026rsquo;m not familiar with your team and the ask - but identifying the docker container as the handoff point where IT helps deploy the app and serve it securely and effectively to clients is a good start. That lets IT do the security bit.\n","date":"2020 Oct 15","externalUrl":null,"permalink":"/posts/2020-10-15_explaining-shiny/","section":"posts","summary":"Helping present the business architecture for Shiny in production to a relatively technical (but not R-based) crowd","title":"DSLC Slack: Explaining Shiny to Your IT Team","type":"posts"},{"content":"I set out reading this book hoping to understand the metaprogramming chapter, and ended up learning a bunch of other stuff along the way!\nHere\u0026rsquo;s a non-exhaustive list of things I learned from each of the sections of Advanced R:\nI - Foundations: # I notice that I use the switch function a ton now, where previously I used if-else nests. I use condition-handling to pass errors back to the user of my Shiny apps now. I now know what an Environment is and where and why R would look for something. (This helps both in function-writing and debugging!) I write a heck of a lot of functions (this ties into II: Functional Programming, of course) II - Functional Programming # I map the crap out of everything now (if I didn\u0026rsquo;t before, it\u0026rsquo;s way worse now!), and have learned to use functions in a ton of different ways. 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!) 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. III - Object Oriented Programming # I wrote a package that uses S3! It\u0026rsquo;s cool to minimize the user interface for multiple platforms and the mental load accompanying. I still lean to using the explicit \u0026ldquo;switch\u0026rdquo; function a lot instead of writing out a useMethod call. May be a habit/pattern worth breaking in the future. I\u0026rsquo;m now comfortable USING an R6 package - I don\u0026rsquo;t know that I\u0026rsquo;d reach for an R6 system now but the whole \u0026ldquo;function$method\u0026rdquo; thing doesn\u0026rsquo;t scare me anymore S4? wot that IV Metaprogramming # I now know how to pass expressions to tidyverse stuff! I now know where to look in the Metaprogramming and how to intepret the text. V Techniques # Debugging: I now know how to use browser(), and love it! Also debug()! Performance measure/improve: still don\u0026rsquo;t do a ton of this, even though I\u0026rsquo;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. I \u0026hellip; still don\u0026rsquo;t know C++ or Rcpp. ¯\\(ツ)/¯ Toy Code # library(dplyr) library(rlang) grouping_variables \u0026lt;- switch( params$group_type, \u0026#34;S\u0026#34; = exprs(CraftCode, starts_with(\u0026#34;Product\u0026#34;), ModelCode, Elevation), \u0026#34;B\u0026#34; = exprs(starts_with(\u0026#34;Rate\u0026#34;)), \u0026#34;P\u0026#34; = exprs(starts_with(\u0026#34;Part\u0026#34;)) ) null_bids \u0026lt;- bids %\u0026gt;% group_by(SupplierID, ProjectID, !!!grouping_variables) %\u0026gt;% slice_max(LastModDate) %\u0026gt;% ungroup() %\u0026gt;% select(!!!grouping_variables, ProjectID, SupplierAID, SupplierName, UnitPrice) %\u0026gt;% left_join(lowest_bids, by = as.character(grouping_variables)) Metaprogramming - how to pass expressions into select statements. Also example of switch.\n","date":"2020 Sep 22","externalUrl":null,"permalink":"/posts/2020-09-22_advr-learnings/","section":"posts","summary":"A non-exhaustive list of things I learned from each of the sections of Advanced R while participating in R4DS Slack’s Advanced R Book Club.","title":"DSLC Slack: Things I Learned From Advanced R Book Club","type":"posts"},{"content":" 2023 note\nI realized when I was going back and reorganizing my reflections that I never properly wrote out a year in review for 2019 (and that I\u0026rsquo;d done so for 2018 and 2020). Jotting down some recollections now, pieced together from various snippets I had written and what I can still remember, for the sake of having something to look back on in the future.\nIn the same way that 2018 was a year of rebuilding my personal life, 2019 was a year of reinventing my professional life - in May I lost my job in sudden and humiliating fashion.\nI was coming off of a lot of personal successes (physically, socially) in 2018, and my professional life had become an afterthought at this point\u0026hellip;which must have been part of the impetus for what happened (I\u0026rsquo;ll never know). I was still pretty vulnerable and fragile at this point, and losing the only career I had ever known since I was a child was yet another shatterpoint.\nI was in denial for a while. I travelled to Vietnam to meet up with my parents, trying to regain my bearings - it didn\u0026rsquo;t help all that much - and then stayed in my rented apartment until my emergency fund ran out midway through the summer, after which I had no other choice but to pack up and move home.\nI threw all sorts of applications into the job mill, focusing on finding Excel and other more-data-centric jobs. I\u0026rsquo;d been building fantasy football spreadsheets for quite a while by this point (since 2017) and had become extremely proficient in Excel\u0026rsquo;s PowerQuery suite for doing more database-like work, so I was pretty hopeful that I could find a job in this space.\nA couple of uncomfortable summer months went by - coaching rowing gave it some structure, interspersed with job applications and obsessing over various fantasy football projects. I accepted a casual gig working for Dynasty League Football as a writer, and contributed a couple of articles and projects there, but I learned that I wasn\u0026rsquo;t much of a writer and that I was more of a data/tools person.\nOne of these personal projects was a dynasty trade calculator dashboard, which had a user facing Excel sheet and a backend written in PowerQuery. My friend Joe Sydlowski offered to write an R-based Shiny app front-end to the calculator to help make the sheets more accessible. I agreed, but as the development went on, I became such an annoying and needy product manager that I started bothering Joe into giving me access to the R code and teaching me how to edit and update the app so that I could make the changes I wanted to make. It wasn\u0026rsquo;t long before I was hooked on R - first refactoring the app to use the tidyverse, then writing my own (Crystal Ball) from scratch, and my love of R only snowballed from there.\nI finally landed a full-time role at Caivan Communities in October, which blended my new aspirations in data work with my background and domain experience in real estate / construction. Despite my own fears around imposter syndrome - I\u0026rsquo;d only been programming in R for a few months - I found myself in position to contribute small R and Shiny tools right away, which felt so much more fulfilling than I\u0026rsquo;d ever felt in property management.\n","date":"2019 Dec 27","externalUrl":null,"permalink":"/posts/reflections/2019/","section":"posts","summary":"Another year of sudden change - this time professionally.","title":"Reflections on 2019","type":"posts"},{"content":" The ORC Rec Guide to Regattas # A guide to everything regattas: a packing list, approximate scheduling, de-rigging and re-rigging, trailer loading, rules of racing, race plans, and more.\nContents # Equipment List Scheduling De-Rigging and Rigging Trailer Loading Rules of Racing Race Plans Equipment List # A list of gear to pack in your regatta bag.\nThree wrenches: 10mm, 7/16\u0026quot;, and an adjustable - you will need all three! (Ratcheting helps but is not required.)\nSunscreen, bug spray\nWater\nSnacks, food\nID, money for concession stand\nOptional: First aid kit (including anti-septic, bandages, blister bandages, after-bite, sunburn lotion etc)\nORC uni (or a uni top) - a red shirt will do in a pinch but will create problems at more serious regattas\nSpare rowing outfit\nRain jacket\nChange of clothes\nTowel\nFlip flops\nSunglasses\nHat\nOakleys, bucket hat, Fat Ergos uni, Crocs, butter for greasing boat (all optional, for free speed)\nScheduling # Generally speaking, the process for any regatta looks like this, but check with coaches to confirm exact dates/times.\nDay before: derig all boats going to regatta, load onto trailer, secure. 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. After races, derig the boat again and load onto trailer. Either same day or next day (check with coach), arrive at ORC for trailer unloading and re-rigging. Derigging and Rigging # Derigging\nGet the boat on stretchers, guts up - this will likely require the help of others. Determine whether the boat uses 7/16\u0026quot; or 10mm hardware, and get the appropriate hardware out. 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. Place all of the boat\u0026rsquo;s riggers in a pile near the truck/trailer. 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.\nTrailer Loading # {TBC}\nRules of Racing # {TBC}\nRace Plans # {TBC}\n","date":"2019 Jul 05","externalUrl":null,"permalink":"/posts/2019-07-05_regatta-cheatsheet/","section":"posts","summary":"A guide to everything regattas: a packing list, approximate scheduling, de-rigging and re-rigging, trailer loading, rules of racing, race plans, and more.","title":"Regatta Cheatsheet","type":"posts"},{"content":"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\u0026rsquo;m hoping to add another wrench into the works this year - a short dynamic warmup to promote mobility and get ourselves ready for rowing.\nShort Description # Two sets of the following circuit:\nHalos (5 reps in each direction)\nCossack Squat (5 reps each side)\n(Walking) Spiderman with Hip Lift and Overhead Reach (10 total reps)\nFocus on body control, consistent pace, and as much mobility as you can manage. Aim to complete at least one set of the circuit before every practice, although if time allows three wouldn\u0026rsquo;t hurt.\nExercise Details # Halos # Focus: Shoulder mobility, posture, abdominal strength (if weighted)\nHalos 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\u0026rsquo;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.\nCues: Focus on standing tall, keeping your head neutral, and bracing your core. You\u0026rsquo;re looking to activate the full range of motion in your shoulders. You don\u0026rsquo;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.\nCossack Squats # Focus: Ankle/Knee/Hip/Groin mobility, core strength, balance\nCossack Squats are a fantastic way to work on mobility and strength at the catch position. This is a difficult exercise to pick up for most people, but it\u0026rsquo;s extremely rewarding! You\u0026rsquo;ll want to work on getting better at it over the course of the rowing season - take it slowly and gradually work on pushing your ranges on this a little more every day.\nThe 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.\nCues: [try to add one of these at a time]\nKeep your heels on the floor! Keep a strong back position (chest up, eyes up, no slumping!) Keep your hips low as you swing across to the other side (activates a better groin stretch) Get a little more depth by sitting a little lower each time Get a little more forward ankle compression by rocking your weight forward on each foot. 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.\nIf you\u0026rsquo;re struggling with the initial variations of the Cossack squats, this page details a few more progressions that might help you get there: https://juanlugofitness.com/cossack-squat/\n(Walking) Spiderman with Hip Lift and Overhead Reach # Focus: Hamstring/Quadricep/Groin/Thoracic mobility\nWalking 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\u0026rsquo;t have to be done \u0026ldquo;walking\u0026rdquo; if space is limited - just come back to a standing position and do the other leg.\nThe demo below is very straight-forward, although I recommend the overhead reach in the other direction (i.e. rotating towards the front leg as opposed to away from it). This makes the warmup a more similar to the sweep rowing motion.\nCues: Keep your hips low in the lunge, work to find that stretch, and track your overhead reach with your eyes.\nAny questions? I\u0026rsquo;ll be happy to demonstrate these motions over the next few months.\n","date":"2019 May 29","externalUrl":null,"permalink":"/posts/2019-05-29_rowing-warmup/","section":"posts","summary":"A short, dynamic warmup to fit into the hectic moments before a Rec Rowing practice.","title":"Short Mobility Warmup for Rowing","type":"posts"},{"content":"2018 was an important year of my life - a catastrophic collapse of a long-term relationship led to a single-minded drive to rebuild my identity.\nAlong the way, I:\nlost 75 pounds rowed over a million on-water meters helped coached a successful recreational rowing program got a dog (Jasper!) made a lot of new friends 2018 was good to me - and I\u0026rsquo;m excited to be moving forward on to 2019.\nTargets for 2019 # Weigh: 180 pounds and/or reach 20% body fat [currently 205 lbs, 30%] Row: 7:00 2K Row: 3:00:00 marathon Train for masters rowing regattas, perform at level I can be proud of Write articles for fantasy football Eliminate credit card debt Improve financial health ","date":"2018 Dec 31","externalUrl":null,"permalink":"/posts/reflections/2018/","section":"posts","summary":"A year of putting myself back together again.","title":"Reflections on 2018","type":"posts"},{"content":"Created by: Peter Aubin with contributions from Lauren E and myself\nCongratulations, you\u0026rsquo;re in charge. Your rowers expect you to be clear, prepared, and commanding - speak up!\nResponsibilities # Provide clear, loud, and commanding instructions to rowers Navigate and steer the boat Required Items # Cox box and headset - found in upstairs office, make sure cox box turns on and headset fits into cox box Life jacket (Bay 2), whistle and lights (your own) Delivering Instructions # General Principle: Make sure your rowers understand the instruction before doing the action!\nOn-Land and Stationary # The general format is: \u0026ldquo;Ready to ______, [pause], and ________\u0026rdquo; i.e. \u0026ldquo;Ready to lift, [pause], and LIFT\u0026rdquo; This applies on land and when boat is stationary (i.e.\u0026ldquo;Ready to row, and row\u0026rdquo;). On-Water # When in motion, give all instructions \u0026ldquo;In Two [Strokes]\u0026rdquo; \u0026ldquo;In two, ______. This is one, [wait for a stroke], two, [wait for a stroke] and ________\u0026rdquo;. i.e. \u0026ldquo;In two, let it run. This is one [stroke], two [stroke], let it run.\u0026rdquo; In case of emergency, call for immediate actions (\u0026ldquo;LET IT RUN!\u0026rdquo; or \u0026ldquo;HOLD WATER!) From Boathouse to Dock # Your crew should be waiting for you at the boat having brought already brought down oars - make sure you have eight rowers 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) 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 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. \u0026ldquo;Down to Waist\u0026rdquo;) Specific Instructions # Specific Instructions: Boathouse to Dock # “Hands on the rack. Ready to pull, and pull.” \u0026ldquo;Bunch up bow and stern\u0026rdquo; (make sure rowers are grouped tightly/shoulder-to-shoulder to reduce load) \u0026ldquo;Hands across (bear hug)\u0026rdquo; (bear hug only applicable when waist height or lower) “Up an inch, ready and up”. (keep an eye so that the boat does not hit anything above it. Boats are tight) “Walk it back” (to get to middle of bay) \u0026ldquo;Up to Waist\u0026rdquo; (if not at waist level) “From bow, split under the boat.” (Call out names of people to go under, if you can) \u0026ldquo;Hands on your side\u0026rdquo; “Up to shoulders on Prime Minister\u0026rsquo;s side, ready and up” (check the outside of the bay to avoid collisions) “Heads-up” (loudly) “Walk it out” (instruct to move right or left if needed to ensure that riggers clear the door) (When clear of bay door), “Let it run. Up to shoulders on Parliament, ready and up” “Walk it down” As rowers reach the ramp, “Swing left” [or right, if for some reason stern is facing outwards] “Let it run” (at this point bow should be facing Parliament, upstream and parallel to the edge of the dock) Specific Instructions: Getting the Boat Into the Water and Away # “Toes to the edge” “Up Overhead, ready and up” “Roll to waist, ready and roll! Reaching out and into the water” (watch the fin of the boat near the stern to make sure it does not catch on the dock) \u0026ldquo;Go get your oars, check your oarlocks, seats, and footstops\u0026rdquo; While they are getting oars, plug your cox box in and test the speakers/microphone A coach should assign seats at this point, flag one down if they have not Rowers should make adjustments to oarlocks/seats/footstops unless dock is busy, in which case they can be made on the water. “Starboard oars across, [pause] one foot in and down\u0026rdquo; - wait for rowers to get in, then get in “Number off from bow when ready” “Hands on the dock. Lean away. Ready to shove, and shove” If necessary, \u0026ldquo;2 seat please push us off the dock\u0026rdquo; (if any port oars still touching the dock) Get \u0026ldquo;bow pair to take a few strokes\u0026rdquo; to get clear of the docks, then go into warmups Specific Instructions: Docking and Getting the Boat Out of the Water # Under the Macdonald Cartier bridge, \u0026ldquo;Stern Four drop out\u0026rdquo; (alternatively, ask Bow Four to drop out if they are less experienced) to slow down the approach to the dock 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. Ask crew to \u0026ldquo;lean away from the dock\u0026rdquo; as you get close Coxie gets out first \u0026ldquo;One foot on the dock, ready and up - starboard blades across\u0026rdquo; While rowers are removing blades, remove cox box assembly from boat \u0026ldquo;Bunch up bow and stern\u0026rdquo; \u0026ldquo;Right hands across, ready to roll up to waist and UP! Up overheads, ready and UP!\u0026rdquo; \u0026ldquo;From Stern, split to shoulders.\u0026rdquo; Specific Instructions: Putting the Boat back into the Boathouse # Make sure the stern of the boat goes into the boathouse first - this may involve getting rowers to \u0026ldquo;turn and face the bow\u0026rdquo; and \u0026ldquo;Walk it down\u0026rdquo; until the stern is in line with the ramp. Then get the rowers to \u0026ldquo;turn and face the stern\u0026rdquo;. \u0026ldquo;Swing it up the ramp\u0026rdquo; until the boat is about to reach the bay doors then \u0026ldquo;Let it run\u0026rdquo; \u0026ldquo;Down to waist on Parliament, ready and down\u0026rdquo; then \u0026ldquo;Walk it in\u0026rdquo; Once the bow is fully in the boathouse, \u0026ldquo;Let it run\u0026rdquo; and \u0026ldquo;Down to waist, ready and down\u0026rdquo; You want to put the boat back on the same racks, so [as necessary] \u0026ldquo;bear hug\u0026rdquo; and \u0026ldquo;from bow, cross under\u0026rdquo; [As applicable] \u0026ldquo;Down to ankles\u0026rdquo; and \u0026ldquo;Walk it on\u0026rdquo; - watching the top of the boat and riggers \u0026ldquo;Check for riggers\u0026rdquo; - the rowers will tell you if the riggers are touching the racks, and then make the appropriate adjustments \u0026ldquo;Bow Pair wipe it down, the rest get oars\u0026rdquo; Return the cox box, microphone, and any lights back to the office then return for debrief Steering (adjusting course while boat is in motion) # Try to make gradual adjustments for hazards/obstacles while they are still far away Rudder controls are diagrammed below \u0026gt; they are only effective while the boat is moving with some speed. You can always get your rowers to assist you - asking for \u0026ldquo;Hard on Starboard\u0026rdquo; will help you get towards port etc. Don\u0026rsquo;t forget to go back to \u0026ldquo;Even Pressure\u0026rdquo; when you\u0026rsquo;re done. Stay close to shore as much as possible to avoid traffic from cruise boats and power boats Normal Practice Patterns # Warmups are done between the bridges, then technical/drill work while on the Quebec side and power/pyramids on the way back. 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. You can always consult with coaches or your stroke seat if you have any questions. Normal Warmups # 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 (\u0026ldquo;three seat you\u0026rsquo;re late\u0026rdquo; etc) \u0026ldquo;All eight from the finish, arms only - ready and row\u0026rdquo; (continue for 20-25 strokes) \u0026ldquo;In two, arms and body - this is one [stroke] two [stroke] arms and body\u0026rdquo; (20-25) \u0026ldquo;In two, half-slide - this is one [s], two [s], half-slide. 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. \u0026ldquo;From the finish, half-slide, ready and row\u0026rdquo;) Once on the Quebec side, you\u0026rsquo;ll want to start at half slide and then transition into full-slide (i.e. \u0026ldquo;in two, full-slide, \u0026hellip;\u0026rdquo;) Once you cross the Macdonald Cartier Bridge, \u0026ldquo;let it run\u0026rdquo; to wait for other crews and coaches. Turns (i.e. while stationary) # The majority of turns or spins will be turning the boat 90 degrees to starboard (your right) \u0026ldquo;Holding on Star and Tapping on Port, ready and tap\u0026rdquo; Continue until pointing in the correct direction Minor adjustments when stationary can be made by asking bow seat or two seat to tap. Calling Power Pieces # 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\u0026rsquo;t) and the stroke rate should still feel controlled. General format is the same: \u0026ldquo;Hard 10 in two, this is one, two, ten hard - one, two, three \u0026hellip;\u0026rdquo; Pyramids are sequential power pieces (5on-5off-10on-10-off-15/15/10/10/5/5 etc) 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. e.g. for 30 strokes, count off the first ten, \u0026ldquo;this is fifteen\u0026rdquo; at fifteen, and count off the last ten Adjusting for Bad Water or Weather # When water is wavy, get rowers to start at half-slide and then go into full slide (if possible, otherwise stay at half-slide) Try to warn your crew when a large wake is coming in (\u0026ldquo;wake coming in on starboard\u0026rdquo;) although you alternatively say \u0026ldquo;sit up tall and relax your shoulders\u0026rdquo; to help relax your crew instead of making them tense up. If you see lightning or hear thunder, regardless of whether a coach is with you or not, turn the boat around to go home. ","date":"2018 Jul 04","externalUrl":null,"permalink":"/posts/2018-07-04_coxie/","section":"posts","summary":"Congratulations, you’re in charge. Your rowers expect you to be clear, prepared, and commanding - speak up!","title":"The ORC Rec Guide to Being a Coxswain on the Ottawa River","type":"posts"}]