How vintage features in R could introduce chaos in your quest for a tibble & data.frame function, and the awesome potential of integrating custom parameters and conditional processing in your next Quarto workflow.
Episode Links
Episode Links
- This week's curator: Jon Calder - @[email protected] (Mastodon) & @jonmcalder (X/Twitter)
- Make your functions compa-tibble
- Creating R tutorial worksheets (with and without solutions) using Quarto
- Entire issue available at rweekly.org/2024-W23
- Invariants: Comparing behavior with data frames https://tibble.tidyverse.org/articles/invariants.html
- geoarrow: Extension types for geospatial data for use with 'Arrow' http://geoarrow.org/geoarrow-r/
- Customize your R startup message https://drmowinckels.io/blog/2024/rproject/
- About Podcasting Episode 10: Podcasting and Data Science with Eric Nantz https://www.aboutpodcasting.show/episodepage/podcasting-and-data-science-with-eric-nantz-from-the-r-weekly-highlights-podcast
- Use the contact page at https://serve.podhome.fm/custompage/r-weekly-highlights/contact to send us your feedback
- R-Weekly Highlights on the Podcastindex.org - You can send a boost into the show directly in the Podcast Index. First, top-up with Alby, and then head over to the R-Weekly Highlights podcast entry on the index.
- A new way to think about value: https://value4value.info
- Get in touch with us on social media
- Eric Nantz: @[email protected] (Mastodon) and @theRcast (X/Twitter)
-
- Mike Thomas: @mike[email protected] (Mastodon) and @mikeketchbrook (X/Twitter)
- Illustrations from the Openscapes blog Tidy Data for reproducibility, efficiency, and collaboration by Julia Lowndes and Allison Horst https://www.openscapes.org/blog/2020/10/12/tidy-data/
- Introducing the Geoparquet data format https://getindata.com/blog/introducing-geoparquet-data-format/
- Salut Voisin! - Final Fantasy IV - colorado weeks, Aeroprism - https://ocremix.org/remix/OCR04553
[00:00:03]
Eric Nantz:
Hello, friends. We're back with episode 167 of the R Weekly Highlights podcast. This is the weekly show where we talk about the awesome resources that are shared every single week on the R Weekly issue. My name is Eric Nantz, and I'm delighted you joined us from wherever you are around the world. And, yep, we are in June already. It's hard to believe, almost midway through the year. So as usual, I can't do this shindig alone these days. I am joined by my awesome cohost, Mike Thomas. Mike, how are you doing this today?
[00:00:33] Mike Thomas:
Doing pretty well, Eric. I've been paying attention more and more to your favorite sport, hockey. I know your red wings aren't in it, but, Canada could have the cup coming home for the first time in a long time. Shout out Connor McDavid's goal the other day. That was incredible if you haven't seen it, for any sports heads there. And, my my local Boston Celtics are are in the basketball finals so that's pretty exciting over here. But other than that, it's all it's all data science all the time.
[00:01:03] Eric Nantz:
That's right. And, yeah, the the the nation of Canada is gonna have this kinda carrot dangled in front of them once again for the first time since almost 2006 for that team. Who knows? Who knows? They're going against a mighty tough Florida team that was there last year, and they got an education, and they've been on a mission ever since. So it'll be a it'll be a fun one to watch for all you hockey fans out there. But, yeah, congrats to your Celtics. I think it is their year. I would have put a lot of lot of stock into what they've learned over the years, so it should be a fun final. We'll see. We'll see. I gotta
[00:01:38] Mike Thomas:
see, any of the r, you know, sports probabilistic, you know, models
[00:01:44] Eric Nantz:
out there. I gotta I gotta see what they're saying. Yeah. It's good. I think it's gonna be kind of a toss-up depending on how hot that, certain Luca gets in the in the Dallas side. But it's gonna be gonna be fun to watch nonetheless. But in any event, we're gonna hear talking about all sorts of things of our data science today. And our curator for this episode or this issue, I should say, is John Calder, another great contributor in our Our Weekly team. And as always, he had tremendous help from our fellow Our Weekly curators and contributors and members like you all around the world with your awesome poll requests and suggestions.
And we lead off today with, a great post about if you've been using, say, the tidyverse align your data science analytics and other work, and you've been starting to harness those principles and maybe a new package at your organization or making some functions that are consuming datasets as their main input, this first highlight today is a great primer for how you might wanna make your functions a little more compatible, so to speak, with the main building block of the tidyverse, data processing object type, albeit the Tibble. And this post comes to us from Hugo Gruson who is a research software engineer in epidemiology.
And he leads off this, very succinct post with motivating the situation. 1st, a bit of those here that aren't aware, the Tibble is what you might call a reimagination or or a slight update on the typical data dot frame object and base r, which, of course, holds your rectangular style data. The TIBL does a little things under the hood to not so much replace data frame, but, add some either safeguards or more kind of quality of life enhancements on top of it. The biggest one that's obvious to you, especially you've been a veteran of r for a few years, is that if you have a large data frame and you print that in your console, oh, you're probably gonna have a wall of text just spinning down all throughout because it's gonna print that whole thing.
Whereas a Tibble has a modified print method, whereas I'm gonna show you the first, like, 10 rows or so and then the names of the columns. So, hence, it's a good way to kinda get a quick glance at your dataset without overloading your console. That's just one of the minor differences. But we're gonna talk about some differences under the hood that could affect you if you're writing a function that takes a data frame or a tibble as input. And in case in point, there are, there is a big difference here. And that's the first part of this post here is that when you subset for a column and, like, a particular column of observations in a data frame, When you look at that under the hood, you will get a vector right away if you use the bracket notation of, like, the name of your data frame, the opening bracket, a comma to say get all rows, and then either the name of the column or the index position of the column, you will get a vector of those observations right away.
With a tibble, not so much. With a Tibble, if you do that kind of notation to grab a column out of it, you're actually gonna get a 1 column Tibble back instead of a vector. That can be a bit of a problem if you're gonna make a function that does some kind of, like, single column selection. And that function, like in the example here, a mean function is expecting a vector of observations, not a 1 column data frame. So his advice in this first example is that you can't rely on that single column subsetting having that default behavior of a data frame of what we call drop.
Drop meaning, like, drop the data frame class and go straight to vector. Don't don't hedge your bets on that, so to speak. So in his example, he explicitly mentions or explicitly states a parameter in that bracket style subsetting of the column with drop equal true. Then you're guaranteeing that no matter if it's a data frame being fed into this function or a tibble, that you will get that one column back as a vector of observations, a vector of numbers instead of that 1 column data frame. I have been victim of this in the past when I was writing some packages that albeit didn't use Tibbles in their main package code, but then I was using Tibbles as a way to put some nice kinda convenience functions on top of the pipeline.
And that drove me nuts when I just wanted one variable instead. And then suddenly these functions I was doing that was doing, like, p value derivations was just spitting errors at me because it was getting a data frame inside. Like, I couldn't believe it. Sure enough. I was I was running into that little mini inferno about the drop the drop situation. So sage advice here from Hugo on that because I think that trips up a lot of people when they're starting to make functions that are using Tibbles as an argument instead of just the data frame itself. But that's not all, Mike, because there's another concept that, admittedly, both you and I have never really paid a lot of attention to, but that could wreak havoc on your function as well.
[00:07:24] Mike Thomas:
No. You're exactly right, Eric, and and I just wanna go back to to your topic really quick. I think anybody who's ever probably ever developed in our package has been bit by this this drop equals true versus drop equals false behavior, you know, where with a sort of plain vanilla data frame, you're getting a vector out and with a Tibble, you're actually getting a data frame out, instead of, you know, the vector of the values of the column that you're you're looking for. So if you haven't been bit by it yet, it's this is one to, watch out for. And and another one to watch out for, Eric, as you're alluding to, is the concept of partial matching, which I must have seen, you know, somewhere along in my journey but I do not remember this and it's blowing my mind that this is even a thing.
And what we mean by partial matching is when you're using the dollar sign operator on a data frame to extract a particular column from that data frame. And we'll use like empty cars as an example here. If you say empty cars dollar sign s, it will pull out the speed column because it's going to use partial matching to try to guess which column you were referring to. And you don't necessarily have to specify exactly the name of that column to get it out. To me, that's mind blowing. The the fact that we can do that, I'll be totally honest with you, I don't think I really like that. It does not feel safe to me. I'm surprised that it it still exists. I'm assuming it still exists and, you know, our 4 dot 4 dot o. You know, that the latest release, may maybe it's 441 that's out there now.
I imagine that this was something that was implemented in the early early days of r, you know, thinking that that maybe it's a good idea. We wanna make, you know, this language and the syntax as user friendly as possible. But to me, oh, man, it gives me the heebie jeebies and and makes me feel like there's a lot of more opportunity for disaster to strike here than there is for efficiency to be gained. But, you know, the the idea along this section of Hugo's blog post here is is definitely do not rely on this partial matching, idea. I don't know too many people who who do rely on it but if you do, it's something that you should certainly watch out for. You can actually set some options if you'd like in your environment to ensure that that partial matching, you know, can't be can't take place.
And one of these options is the warn partial match dollar argument, which you can set to true in your dot r profile file, for example, which will essentially ensure that you get a at least a warning, that a partial match is is taking place and that warning will appear in your console. You can do the same thing. You may wanna do the same thing in your your test that folder so that your your unit tests throw an error, or a warning at least give you that flag if there's partial matching going on that you're not necessarily expecting, which I think can be a great idea.
And if you you do really need to support partial matching, obviously, this partial matching concept is is only implemented on the the vanilla data frame side and not necessarily implemented on the Tibble side, but you can, manually handle it. And Hugo gives an example here of using the the care match function, c h a r match function, to be able to sort of fuzzy match, partial match, a column name, that you're looking for and sort of have the same exact user experience as you would have with this partial matching against a a pure vanilla data frame, within a Tibble environment. So there's a nice little code chunk here if you're interested in doing that. But, again, you know, it's this is just my opinion only. You know, we're full of hot takes on this podcast and my hot take today, I guess, is be careful of any partial matching that's going on. And I think this is a great blog post in general to just warn you about some funky behavior that can take place between data frames and Tibbles that that you may or may not have been privy to. And it's it's definitely something that could easily, easily trip you up in a script that you're writing or a package that you're writing. So it's great stuff to look out for. Hugo also alludes to and provides a link to a longer vignette that contains all the differences, between Tibbles and data frames. And I think data dot frames and that, vignette is from the tibble Tibble package itself.
One of the fun pieces about this blog is, that it Hugo continues to use, the phrase compatible, which, you know, is a really nice pun that that rounds out this blog post, I think, beautifully. So a great top to bottom, summary of these sort of 2 funky behaviors between data frames and Tibbles. And I I definitely think it's a good reminder, warning to those of us out there to to be on the lookout, stay vigilant for, these differences.
[00:12:34] Eric Nantz:
Absolutely. And even had I known about partial matching back in my very early days of of using r back in grad school, I probably wouldn't touch over 10 foot pole. I am just one of those people that on the side of caution, on the side of verbosity and specificity. And I'm gonna put the full name of that variable no matter how long it is because in the end, a, there are many ways in programming, not just in ARB, but in programming in general, to just whack yourself silly with, you know, user inflicted issues already. Last thing I need is partial matching to wreck havoc.
And I do think it's kind of a you might say, I don't know, relic's the right word, but it's a it's a it's a snapshot in the history of r and s itself because let's think about what was not around when r and s were built. We didn't have modern IDEs and modern frameworks that would support auto completion in an intelligent way. Like, if you're looking at a TIP or looking at a data frame and you wanna get the name of that variable and you start typing the name of the data frame, dollar sign, and start typing the name of that variable, Our the modern editors like our studio and even some of the plugins you get in Versus code and whatnot will let you auto complete the name of that variable. So you don't have to very rely on partial matching in those situations because the ID is gonna give you a helping hand to help complete that that full name. And also within the tidyverts itself, packages like dplyr, tidy r, and many others under the hood, use a package called tidy select that will let you grab related variables based on maybe, like, the prefix of their name or maybe their position or other other ways to intelligently search for these. And, again, what you'll get back is the full name of the variable. It's just a convenience way for you to get there.
So, certainly, I think the modern tooling and the modern principles in the tidy verse hopefully make it that you don't have to support partial matching. But if you have some legacy projects where you do, this post has definitely got you covered on those as well. And then, also, Hugo throws some interesting nuggets at the end here with respect to testing if you have to really rely on data frames and Tibbles being, you know, jointly supported in your function, he has some ideas and some resources that you might wanna use to help make that testing more efficient. He has some plugs for both the Patrick r package, which lets you do parameterized tests with test that, which I didn't know about, as well as this auto test package.
And that is basically a way to help scrape your r packages, like example code for, like, your functions. And then intelligently or dynamically, I should say, kinda change inputs that kinda do some, you know, test generation based on that. So 2 little nuggets there if you wanna really be robust with not just equipping your functions for the support of tibbles and data frames, but also helping yourself as a package author to test for these differences in a more systematic way. These two packages could help you out quite nicely there. So lots of knowledge dropped in the short post here. And rounding out our highlights today, we've got another great showcase of the power of the quartile documentation engine with a k a use case that'll be very nice for those of you in the educational sector or building maybe internal our trainings that wanna leverage this novel technology.
And we have this highlight coming from the esteemed Nicole Arany who is a frequent contributor to the rweekly highlights in the past. And she talks about her solution that combines the concept of parameterized reporting in quartile with dynamic and conditional logic inside your quartile document itself to hide or show certain pieces of elements. And the use case here is as she wanted to create exercises and tutorial material for her students to work through in a workshop session. She's actually taught many workshops that have been shared in the art community. I still wanna thank Nicole once again. You know, I thanked her many times before for awesome workshop and machine learning at last year's our pharma. She did a tremendous job with that, and she built up the materials using quartile, so timely here as well. But in case of this example that she's talking about here, she had a set of goals that she wanted to achieve here is that the the document would have a series of questions that the users would have to, you know, write some r code or use r to help answer.
And then a companion document to this that would have not just the questions, but the solutions to the exercises as well. Again, very, familiar concept to anybody in the educational sector. And this approach would not just work for web based formats but also work for static formats as well, such as PDF output. Hence, if you have to do hike a paper version for every reason, I've don't ask me about why I've been looking at static outputs recently. We'll save that for another episode. But in any event, the first part of this post talks about what are parameters inside a portal document.
If you used Rmarkdown before, this will actually sound pretty familiar to you because it works basically the same way. In your YAML of a portal document, you can have a field called params, and then inside it, you can have a series of 1 or many of these what look like key value pairs, only they're variables that you might give it a name like year. And then in colon, after the colon, you give the actual value of that. So you could define as many of these as you like. And then this sup this is supported both with the knitter engine as well as the Jupyter engine. So no matter if you're on the r or Python side, you can use parameters just as easily in each framework.
And this has been a hugely helpful technique, especially if you're doing automated reporting, maybe on a ETL or another processing pipeline where you've got the same report, but it's changing like a subset of the data or it's changing a key variable, but yet the rest of the outputs are gonna be generated in a similar way. Parametized reports are a great way to, make that happen. So in the case of this tutorial example, she has a parameter called hide answers, and the default of this is true, meaning that in the document, it's gonna be one document. But here's the kicker is that in the document, she has put what a what we call these fenced identifiers around the type of output that will only be shown if one of these parameters is set to true.
So she has this in a very interesting way with both the answers being shown as well as if you're doing PDF output to only show when the output format is is PDF in the case of when she renders it. But this is all combined very nicely in her quartile chunks of the code where she's gonna dynamically put in whether to put in these fence identifiers of what they call content hidden based on an inline r code snippet, where if that parameter of hide answers is true. This is genius here. I love this technique. I, in fact, I was literally thinking to myself, how could I do this of a portal dashboard to do certain styling elements or certain pieces of the card, you know, styling if I had a parameter that was, like, longer length than another one. This is the key to make that happen. An inline little snippet that says, if params dollar sign hide answers, then put in this fence div of content hidden. And then you can still have the answer in one place. It's all in one document. She didn't make separate documents for this. Hence, we've we're applying the the dry principle here. Right? Don't repeat yourself. Don't make a separate document just for just for one snippet. This is this is terrific stuff. And then in the end, you see an example where one printout of the screenshot is asking the question and then another printout with hide answers false is showing the actual r code to help achieve that answer.
[00:21:34] Mike Thomas:
Yeah. It it's pretty incredible. I would say that Nicola is probably one of the leading voices on all of the different gymnastics that you can do with with quarto and especially when it comes to, you know, not repeating yourself, making things as reproducible as possible and making things as easy on yourself as possible. And this is a another fantastic walk through and I I do just wanna before I forget, thank Nicola for these many quarto blog posts which have honestly helped me and saved me hours, in a lot of the quarto work that that we do at Catch Brook. So in the conditional content section of of her blog post, she leverages, again, one of these sort of fenced divs and you're able to specify, you know, when a particular format is being rendered to take this action.
So the best place to take a look at this is is in the blog post but within this fence div, she uses the dot content, hidden argument followed by the when format argument and specifies when format equals PDF, then the content is going to be hidden. So it's a pretty powerful little utility that we have in quarto to be able to take in a conditional action based upon, the type of format that's being rendered to. So very very useful, I think especially for folks who are rendering documents to different formats because let's be honest, you know, sometimes when you're rendering to HTML, some element, of your quarto document is going to look really nice and when it gets rendered to PDF either it's going to break or it's just going to look terrible because you know they're 2 completely different formats.
Maybe you're taking advantage of you know some HTML content that you know really only belongs on a web page and doesn't belong on a static document or or vice versa when it comes to PDF. So this is a really powerful, little argument within this fence div that we can create that I think is excellent. And then maybe the last thing that I'll point out is, you know, that there are multiple ways to render your documents. One is from the command line, which is, you know, utility that that I'll use quite often as well. But if that render call gets a little bit lengthy, you know, for example maybe you have a few different arguments that you're trying to specify. You know, one maybe being the the output file name if you wanna rename that file, something different than the naming convention that you used for the dot QMD document.
Maybe that's something that you wanna specify or if you have parameters, you can use the the, params flag of that command line call. Alternatively, you may wanna use, the quarto r package itself and leverage the quarto render function and specify these different arguments, you know, a little nicer than this this really wide, command line call that you might have to be making, if you are trying to set these different flags and specify these different arguments and output file names and things like that. So at the end of Nicole's blog post, before the additional resources, she has 2 code snippets for how she rendered, you know, the questions that she created as well as the answers document that she created in these 2 different, our calls using the the quarto render function from the quarto r package which you may be interested in in taking a look at. And the only difference between the 2 is the execute params, execute params, argument is specified such that, you know, for the questions hide answers parameter is set to true and for the answers, the hide answers parameter is set to false.
So that second time that she knits this document, the answers will be displayed. So, she'll create, you know, 2 different documents here which is is fantastic and a great, you know, top to bottom run through on how she went about doing this, all the different considerations and options and fence divs and parameter options that we have in quarto to make things as as flexible as possible, not only across, you know, 2 different outputs that she wants to create but even multiple document types. So great great walk through.
[00:25:41] Eric Nantz:
Yeah. And, like, echo your your your gratitude and thanks to Nicole for these terrific resources because I was able to use some of her previous materials as well that combined principles that we see in this post here for my recent, our consortium submissions working group, efforts where we have a document that we have to send as part of this bundle that we transfer to regulators. We call it an analysis dataset reviewers guide or ADRG. And I wanted to try something different with this. I could've taken the the typical route and just do a do a Word document, type it all for being was like, no. No. No. No. We're in 2024. Let's do something better here. So it's a portal document, but it's got 2 formats.
Because as much as I'd love to be able to submit the HTML format, we can't really put that in the bundle, so we do a PDF as well. But we do have conditional logic throughout some parts of the document that we know that for PDF, it's not going to quite render as nicely with the default font size. So I'll do some code chunk updates based on the output type. And then that way, I have a preview version of the ADRG linked on, you know, an HTML static site hosting area. And then the reviewers can look at that, you know, as as they give me comments and whatnot. But then the actual bundle that we transferred to them, which hopefully be this month, we will be able to supply the PDF version, and it's gonna look, you know, very good, very polished. But, again, the source is all from one place. We just have conditional logic on top, but still preserving the majority of that source going to both output types. So I do think this is this is a very novel concept, and parameters can you know, we're just scratching the surface of what is offered here. At the end of Nicole's post here, she has doing some terrific additional either blog posts from Mike Mahoney or JD Ryan's recent parameterized quartile reporting workshop as you gave for our ladies a while back. There's some great additional learning material, and I can't stress enough how awesome it is to have, like, a data frame of, like, parameter values in each row, maybe a different configuration of it. Just do a, per walk of that. For each row, run portal render. You got your outputs all generated within about 10 seconds. I did that yesterday, and it was a thing of beauty, my friend. Just a thing of beauty. Music to my ears, Eric. I I love nothing more than the per walk over that that nice little data frame with all your different parameter configuration. So thank you.
Yes. So so once you combine all this stuff, it's like, yeah, I always equate it to being like a a chef that can actually cook because I'm not a chef that can actually cook very well. But with programming, I like to think I can mix and match these quite a bit but I'm certainly not alone. We're learning from the call and many of the other thought leaders in this space here. And it never stops learning when you look the rest of the art weekly issue, and John has done a tremendous job for the rest of the issue here. So we'll take a couple of minutes to talk about our additional fines that caught our attention for the week. And going back to the concept of Tibbles and concepts of, like, the building blocks of the tidy verse, There were some some great posts that were illuminating a concept in tidy yard that I did not know was possible.
There are a couple posts here from Lael Lecri on the use of a Tidyr function called packing versus the concept of nesting, which we often hear about more fully with respect to creating your Tibbles. And if you're like me, when you first saw this issue or thinking, packing? What is that? Well, you can think of packing as, like, a slightly different take on how the data frame is being collapsed. Nesting is kinda like doing a row based collapsing. You're gonna have maybe a set of columns that become their own table, but it reduces the number of rows in your data frame as a result. We often do this in group by processing and whatnot.
Packing is kinda taking the opposite approach. It's gonna keep the same number of rows, but then it's gonna make a table out of various columns, and then you can unpack it to make it wider again. And so there these two posts are gonna talk about the comparisons of the 2, when you might wanna use 1 versus the other. And in particular, I mean, I still have a lot to learn in this area here, but she did eliminate some interesting examples. If you have jason type data, why am I want to look at packing versus nesting and those concepts? So again, just an illustration that I always think I I know most of the tidy verse, but there's always something I don't know. So we're all we're all learning together on this. But, Mike, what did you find? So I found that the Geo Aero package, I think, has its first release on CRAN. It's version 0.2.0,
[00:30:39] Mike Thomas:
which I am super excited about because I am a huge proponent of, the arrow package and the parquet file format, and we are getting some utilities in R to be able to wrap the geoparquet project, which allows you to work with GIS data that's stored in in in parquet format for just huge efficiencies over, you know, traditionally, you know, large heavy datasets when we talk about geospatial data. One more blog I wanna shout out is a blog from Athanasia Mowinkle which is called Customize Your R Startup Message. And it's a really nice little walk through about how to give yourself, a little message if you'd like, when your R session starts up each time. So a really cute little fun one for anybody interested.
[00:31:27] Eric Nantz:
Yeah. We we need a little variety in life. Right? I mean, I have all my my friends in the Linux community that do these fancy shell prompts and start up messages for their servers. I'm like, I need some of that for my artwork. So yeah. Well, that post is gonna gonna have me cover there. So yeah. And and awesome. Congrats to the Geo Aero team because, yeah, we're seeing immense immense, you know, innovations in the world of parquet with, spatial data. So this is just another tip to the hat, and, yeah, what a great time to be part of that ecosystem. And it's a great time to see the rest of our week as well and for you to get involved as well because this is not a project that's just run by a few individuals on the whims. This is based on the community, for the community, and by the community, if you will.
So the best way to help us out is to contribute to the issues directly via your suggestions for new resources, new packages, new tutorials, new workshops. We are fair game for all that. That will help your community. How you do that? Every issue at rwicky.org has a link in the top right corner for the upcoming issues draft. You can just click that, and you'll be taken directly to the poll request template where you can put in markdown syntax, that great link, that great resource that you found, all marked down all the time. We live marked down. We love it. Hence, Hence, some quartiles have been great with markdown as well. So all just a poll request away to get your resource in the issue. And, also, we love hearing from you and the community as well.
We have a few ways to get in touch with us directly. One of which is in this episode show notes. We got a link to the new ish contact page that you can send us a message on there. And I did hear back from a listener that wants to do some interesting, I guess, meta analysis on our past show notes. I'm gonna, you know, give that's gonna be exciting. So I'll definitely get in touch with you about that. And, also, you can also send us a fun little boost in the modern podcast app if you're listening to, say, paverse, Cast O Matic, fountain, many others in this space. So you can send a little boost directly in the podcast app, and we'll get that message directly to us about any third party middle person in between.
And speaking of podcasts, I guess I had a busy, couple of months recently because I was on another show recently hosted by my good friend Barry at Pod home talking about my adventures of podcasting and data science, and I'll have a link to that in this episode show notes. That was that was great fun to join Barry on that on that episode. So I will link to that from the about podcasting show. That was great fun. And, also, you can get in touch with me on the social medias these days. I am mostly on Mastodon. We've got our podcast at podcast index.social.
Doing a lot of fun dev work now behind the scenes with the podcasting 2.0 stuff and getting real gnarly with JSON data. It's been a fun learning adventure to say the least. I'm also on LinkedIn. Just search for my name. You'll find me there as sporadically on the weapon x thing with at the r cast. And, Mike, where can the listeners get a hold of you? Sure. You can find me on mastodon@mike_thomas@phostodon
[00:34:43] Mike Thomas:
dotorg, or you can check out what I'm up to on LinkedIn if you search Ketchbrooke Analytics, ketchbrook.
[00:34:52] Eric Nantz:
Awesome stuff. And, yeah, it's been a nice tidy episode that I think, judging by the fact that I have kids at home, I gotta get myself out of here. So we're gonna close-up shop here in this episode of our Wriggle highlights, and we thank you so much once again for joining us from RevAir around the world. And we hope to see you back here for another episode next week.
Hello, friends. We're back with episode 167 of the R Weekly Highlights podcast. This is the weekly show where we talk about the awesome resources that are shared every single week on the R Weekly issue. My name is Eric Nantz, and I'm delighted you joined us from wherever you are around the world. And, yep, we are in June already. It's hard to believe, almost midway through the year. So as usual, I can't do this shindig alone these days. I am joined by my awesome cohost, Mike Thomas. Mike, how are you doing this today?
[00:00:33] Mike Thomas:
Doing pretty well, Eric. I've been paying attention more and more to your favorite sport, hockey. I know your red wings aren't in it, but, Canada could have the cup coming home for the first time in a long time. Shout out Connor McDavid's goal the other day. That was incredible if you haven't seen it, for any sports heads there. And, my my local Boston Celtics are are in the basketball finals so that's pretty exciting over here. But other than that, it's all it's all data science all the time.
[00:01:03] Eric Nantz:
That's right. And, yeah, the the the nation of Canada is gonna have this kinda carrot dangled in front of them once again for the first time since almost 2006 for that team. Who knows? Who knows? They're going against a mighty tough Florida team that was there last year, and they got an education, and they've been on a mission ever since. So it'll be a it'll be a fun one to watch for all you hockey fans out there. But, yeah, congrats to your Celtics. I think it is their year. I would have put a lot of lot of stock into what they've learned over the years, so it should be a fun final. We'll see. We'll see. I gotta
[00:01:38] Mike Thomas:
see, any of the r, you know, sports probabilistic, you know, models
[00:01:44] Eric Nantz:
out there. I gotta I gotta see what they're saying. Yeah. It's good. I think it's gonna be kind of a toss-up depending on how hot that, certain Luca gets in the in the Dallas side. But it's gonna be gonna be fun to watch nonetheless. But in any event, we're gonna hear talking about all sorts of things of our data science today. And our curator for this episode or this issue, I should say, is John Calder, another great contributor in our Our Weekly team. And as always, he had tremendous help from our fellow Our Weekly curators and contributors and members like you all around the world with your awesome poll requests and suggestions.
And we lead off today with, a great post about if you've been using, say, the tidyverse align your data science analytics and other work, and you've been starting to harness those principles and maybe a new package at your organization or making some functions that are consuming datasets as their main input, this first highlight today is a great primer for how you might wanna make your functions a little more compatible, so to speak, with the main building block of the tidyverse, data processing object type, albeit the Tibble. And this post comes to us from Hugo Gruson who is a research software engineer in epidemiology.
And he leads off this, very succinct post with motivating the situation. 1st, a bit of those here that aren't aware, the Tibble is what you might call a reimagination or or a slight update on the typical data dot frame object and base r, which, of course, holds your rectangular style data. The TIBL does a little things under the hood to not so much replace data frame, but, add some either safeguards or more kind of quality of life enhancements on top of it. The biggest one that's obvious to you, especially you've been a veteran of r for a few years, is that if you have a large data frame and you print that in your console, oh, you're probably gonna have a wall of text just spinning down all throughout because it's gonna print that whole thing.
Whereas a Tibble has a modified print method, whereas I'm gonna show you the first, like, 10 rows or so and then the names of the columns. So, hence, it's a good way to kinda get a quick glance at your dataset without overloading your console. That's just one of the minor differences. But we're gonna talk about some differences under the hood that could affect you if you're writing a function that takes a data frame or a tibble as input. And in case in point, there are, there is a big difference here. And that's the first part of this post here is that when you subset for a column and, like, a particular column of observations in a data frame, When you look at that under the hood, you will get a vector right away if you use the bracket notation of, like, the name of your data frame, the opening bracket, a comma to say get all rows, and then either the name of the column or the index position of the column, you will get a vector of those observations right away.
With a tibble, not so much. With a Tibble, if you do that kind of notation to grab a column out of it, you're actually gonna get a 1 column Tibble back instead of a vector. That can be a bit of a problem if you're gonna make a function that does some kind of, like, single column selection. And that function, like in the example here, a mean function is expecting a vector of observations, not a 1 column data frame. So his advice in this first example is that you can't rely on that single column subsetting having that default behavior of a data frame of what we call drop.
Drop meaning, like, drop the data frame class and go straight to vector. Don't don't hedge your bets on that, so to speak. So in his example, he explicitly mentions or explicitly states a parameter in that bracket style subsetting of the column with drop equal true. Then you're guaranteeing that no matter if it's a data frame being fed into this function or a tibble, that you will get that one column back as a vector of observations, a vector of numbers instead of that 1 column data frame. I have been victim of this in the past when I was writing some packages that albeit didn't use Tibbles in their main package code, but then I was using Tibbles as a way to put some nice kinda convenience functions on top of the pipeline.
And that drove me nuts when I just wanted one variable instead. And then suddenly these functions I was doing that was doing, like, p value derivations was just spitting errors at me because it was getting a data frame inside. Like, I couldn't believe it. Sure enough. I was I was running into that little mini inferno about the drop the drop situation. So sage advice here from Hugo on that because I think that trips up a lot of people when they're starting to make functions that are using Tibbles as an argument instead of just the data frame itself. But that's not all, Mike, because there's another concept that, admittedly, both you and I have never really paid a lot of attention to, but that could wreak havoc on your function as well.
[00:07:24] Mike Thomas:
No. You're exactly right, Eric, and and I just wanna go back to to your topic really quick. I think anybody who's ever probably ever developed in our package has been bit by this this drop equals true versus drop equals false behavior, you know, where with a sort of plain vanilla data frame, you're getting a vector out and with a Tibble, you're actually getting a data frame out, instead of, you know, the vector of the values of the column that you're you're looking for. So if you haven't been bit by it yet, it's this is one to, watch out for. And and another one to watch out for, Eric, as you're alluding to, is the concept of partial matching, which I must have seen, you know, somewhere along in my journey but I do not remember this and it's blowing my mind that this is even a thing.
And what we mean by partial matching is when you're using the dollar sign operator on a data frame to extract a particular column from that data frame. And we'll use like empty cars as an example here. If you say empty cars dollar sign s, it will pull out the speed column because it's going to use partial matching to try to guess which column you were referring to. And you don't necessarily have to specify exactly the name of that column to get it out. To me, that's mind blowing. The the fact that we can do that, I'll be totally honest with you, I don't think I really like that. It does not feel safe to me. I'm surprised that it it still exists. I'm assuming it still exists and, you know, our 4 dot 4 dot o. You know, that the latest release, may maybe it's 441 that's out there now.
I imagine that this was something that was implemented in the early early days of r, you know, thinking that that maybe it's a good idea. We wanna make, you know, this language and the syntax as user friendly as possible. But to me, oh, man, it gives me the heebie jeebies and and makes me feel like there's a lot of more opportunity for disaster to strike here than there is for efficiency to be gained. But, you know, the the idea along this section of Hugo's blog post here is is definitely do not rely on this partial matching, idea. I don't know too many people who who do rely on it but if you do, it's something that you should certainly watch out for. You can actually set some options if you'd like in your environment to ensure that that partial matching, you know, can't be can't take place.
And one of these options is the warn partial match dollar argument, which you can set to true in your dot r profile file, for example, which will essentially ensure that you get a at least a warning, that a partial match is is taking place and that warning will appear in your console. You can do the same thing. You may wanna do the same thing in your your test that folder so that your your unit tests throw an error, or a warning at least give you that flag if there's partial matching going on that you're not necessarily expecting, which I think can be a great idea.
And if you you do really need to support partial matching, obviously, this partial matching concept is is only implemented on the the vanilla data frame side and not necessarily implemented on the Tibble side, but you can, manually handle it. And Hugo gives an example here of using the the care match function, c h a r match function, to be able to sort of fuzzy match, partial match, a column name, that you're looking for and sort of have the same exact user experience as you would have with this partial matching against a a pure vanilla data frame, within a Tibble environment. So there's a nice little code chunk here if you're interested in doing that. But, again, you know, it's this is just my opinion only. You know, we're full of hot takes on this podcast and my hot take today, I guess, is be careful of any partial matching that's going on. And I think this is a great blog post in general to just warn you about some funky behavior that can take place between data frames and Tibbles that that you may or may not have been privy to. And it's it's definitely something that could easily, easily trip you up in a script that you're writing or a package that you're writing. So it's great stuff to look out for. Hugo also alludes to and provides a link to a longer vignette that contains all the differences, between Tibbles and data frames. And I think data dot frames and that, vignette is from the tibble Tibble package itself.
One of the fun pieces about this blog is, that it Hugo continues to use, the phrase compatible, which, you know, is a really nice pun that that rounds out this blog post, I think, beautifully. So a great top to bottom, summary of these sort of 2 funky behaviors between data frames and Tibbles. And I I definitely think it's a good reminder, warning to those of us out there to to be on the lookout, stay vigilant for, these differences.
[00:12:34] Eric Nantz:
Absolutely. And even had I known about partial matching back in my very early days of of using r back in grad school, I probably wouldn't touch over 10 foot pole. I am just one of those people that on the side of caution, on the side of verbosity and specificity. And I'm gonna put the full name of that variable no matter how long it is because in the end, a, there are many ways in programming, not just in ARB, but in programming in general, to just whack yourself silly with, you know, user inflicted issues already. Last thing I need is partial matching to wreck havoc.
And I do think it's kind of a you might say, I don't know, relic's the right word, but it's a it's a it's a snapshot in the history of r and s itself because let's think about what was not around when r and s were built. We didn't have modern IDEs and modern frameworks that would support auto completion in an intelligent way. Like, if you're looking at a TIP or looking at a data frame and you wanna get the name of that variable and you start typing the name of the data frame, dollar sign, and start typing the name of that variable, Our the modern editors like our studio and even some of the plugins you get in Versus code and whatnot will let you auto complete the name of that variable. So you don't have to very rely on partial matching in those situations because the ID is gonna give you a helping hand to help complete that that full name. And also within the tidyverts itself, packages like dplyr, tidy r, and many others under the hood, use a package called tidy select that will let you grab related variables based on maybe, like, the prefix of their name or maybe their position or other other ways to intelligently search for these. And, again, what you'll get back is the full name of the variable. It's just a convenience way for you to get there.
So, certainly, I think the modern tooling and the modern principles in the tidy verse hopefully make it that you don't have to support partial matching. But if you have some legacy projects where you do, this post has definitely got you covered on those as well. And then, also, Hugo throws some interesting nuggets at the end here with respect to testing if you have to really rely on data frames and Tibbles being, you know, jointly supported in your function, he has some ideas and some resources that you might wanna use to help make that testing more efficient. He has some plugs for both the Patrick r package, which lets you do parameterized tests with test that, which I didn't know about, as well as this auto test package.
And that is basically a way to help scrape your r packages, like example code for, like, your functions. And then intelligently or dynamically, I should say, kinda change inputs that kinda do some, you know, test generation based on that. So 2 little nuggets there if you wanna really be robust with not just equipping your functions for the support of tibbles and data frames, but also helping yourself as a package author to test for these differences in a more systematic way. These two packages could help you out quite nicely there. So lots of knowledge dropped in the short post here. And rounding out our highlights today, we've got another great showcase of the power of the quartile documentation engine with a k a use case that'll be very nice for those of you in the educational sector or building maybe internal our trainings that wanna leverage this novel technology.
And we have this highlight coming from the esteemed Nicole Arany who is a frequent contributor to the rweekly highlights in the past. And she talks about her solution that combines the concept of parameterized reporting in quartile with dynamic and conditional logic inside your quartile document itself to hide or show certain pieces of elements. And the use case here is as she wanted to create exercises and tutorial material for her students to work through in a workshop session. She's actually taught many workshops that have been shared in the art community. I still wanna thank Nicole once again. You know, I thanked her many times before for awesome workshop and machine learning at last year's our pharma. She did a tremendous job with that, and she built up the materials using quartile, so timely here as well. But in case of this example that she's talking about here, she had a set of goals that she wanted to achieve here is that the the document would have a series of questions that the users would have to, you know, write some r code or use r to help answer.
And then a companion document to this that would have not just the questions, but the solutions to the exercises as well. Again, very, familiar concept to anybody in the educational sector. And this approach would not just work for web based formats but also work for static formats as well, such as PDF output. Hence, if you have to do hike a paper version for every reason, I've don't ask me about why I've been looking at static outputs recently. We'll save that for another episode. But in any event, the first part of this post talks about what are parameters inside a portal document.
If you used Rmarkdown before, this will actually sound pretty familiar to you because it works basically the same way. In your YAML of a portal document, you can have a field called params, and then inside it, you can have a series of 1 or many of these what look like key value pairs, only they're variables that you might give it a name like year. And then in colon, after the colon, you give the actual value of that. So you could define as many of these as you like. And then this sup this is supported both with the knitter engine as well as the Jupyter engine. So no matter if you're on the r or Python side, you can use parameters just as easily in each framework.
And this has been a hugely helpful technique, especially if you're doing automated reporting, maybe on a ETL or another processing pipeline where you've got the same report, but it's changing like a subset of the data or it's changing a key variable, but yet the rest of the outputs are gonna be generated in a similar way. Parametized reports are a great way to, make that happen. So in the case of this tutorial example, she has a parameter called hide answers, and the default of this is true, meaning that in the document, it's gonna be one document. But here's the kicker is that in the document, she has put what a what we call these fenced identifiers around the type of output that will only be shown if one of these parameters is set to true.
So she has this in a very interesting way with both the answers being shown as well as if you're doing PDF output to only show when the output format is is PDF in the case of when she renders it. But this is all combined very nicely in her quartile chunks of the code where she's gonna dynamically put in whether to put in these fence identifiers of what they call content hidden based on an inline r code snippet, where if that parameter of hide answers is true. This is genius here. I love this technique. I, in fact, I was literally thinking to myself, how could I do this of a portal dashboard to do certain styling elements or certain pieces of the card, you know, styling if I had a parameter that was, like, longer length than another one. This is the key to make that happen. An inline little snippet that says, if params dollar sign hide answers, then put in this fence div of content hidden. And then you can still have the answer in one place. It's all in one document. She didn't make separate documents for this. Hence, we've we're applying the the dry principle here. Right? Don't repeat yourself. Don't make a separate document just for just for one snippet. This is this is terrific stuff. And then in the end, you see an example where one printout of the screenshot is asking the question and then another printout with hide answers false is showing the actual r code to help achieve that answer.
[00:21:34] Mike Thomas:
Yeah. It it's pretty incredible. I would say that Nicola is probably one of the leading voices on all of the different gymnastics that you can do with with quarto and especially when it comes to, you know, not repeating yourself, making things as reproducible as possible and making things as easy on yourself as possible. And this is a another fantastic walk through and I I do just wanna before I forget, thank Nicola for these many quarto blog posts which have honestly helped me and saved me hours, in a lot of the quarto work that that we do at Catch Brook. So in the conditional content section of of her blog post, she leverages, again, one of these sort of fenced divs and you're able to specify, you know, when a particular format is being rendered to take this action.
So the best place to take a look at this is is in the blog post but within this fence div, she uses the dot content, hidden argument followed by the when format argument and specifies when format equals PDF, then the content is going to be hidden. So it's a pretty powerful little utility that we have in quarto to be able to take in a conditional action based upon, the type of format that's being rendered to. So very very useful, I think especially for folks who are rendering documents to different formats because let's be honest, you know, sometimes when you're rendering to HTML, some element, of your quarto document is going to look really nice and when it gets rendered to PDF either it's going to break or it's just going to look terrible because you know they're 2 completely different formats.
Maybe you're taking advantage of you know some HTML content that you know really only belongs on a web page and doesn't belong on a static document or or vice versa when it comes to PDF. So this is a really powerful, little argument within this fence div that we can create that I think is excellent. And then maybe the last thing that I'll point out is, you know, that there are multiple ways to render your documents. One is from the command line, which is, you know, utility that that I'll use quite often as well. But if that render call gets a little bit lengthy, you know, for example maybe you have a few different arguments that you're trying to specify. You know, one maybe being the the output file name if you wanna rename that file, something different than the naming convention that you used for the dot QMD document.
Maybe that's something that you wanna specify or if you have parameters, you can use the the, params flag of that command line call. Alternatively, you may wanna use, the quarto r package itself and leverage the quarto render function and specify these different arguments, you know, a little nicer than this this really wide, command line call that you might have to be making, if you are trying to set these different flags and specify these different arguments and output file names and things like that. So at the end of Nicole's blog post, before the additional resources, she has 2 code snippets for how she rendered, you know, the questions that she created as well as the answers document that she created in these 2 different, our calls using the the quarto render function from the quarto r package which you may be interested in in taking a look at. And the only difference between the 2 is the execute params, execute params, argument is specified such that, you know, for the questions hide answers parameter is set to true and for the answers, the hide answers parameter is set to false.
So that second time that she knits this document, the answers will be displayed. So, she'll create, you know, 2 different documents here which is is fantastic and a great, you know, top to bottom run through on how she went about doing this, all the different considerations and options and fence divs and parameter options that we have in quarto to make things as as flexible as possible, not only across, you know, 2 different outputs that she wants to create but even multiple document types. So great great walk through.
[00:25:41] Eric Nantz:
Yeah. And, like, echo your your your gratitude and thanks to Nicole for these terrific resources because I was able to use some of her previous materials as well that combined principles that we see in this post here for my recent, our consortium submissions working group, efforts where we have a document that we have to send as part of this bundle that we transfer to regulators. We call it an analysis dataset reviewers guide or ADRG. And I wanted to try something different with this. I could've taken the the typical route and just do a do a Word document, type it all for being was like, no. No. No. No. We're in 2024. Let's do something better here. So it's a portal document, but it's got 2 formats.
Because as much as I'd love to be able to submit the HTML format, we can't really put that in the bundle, so we do a PDF as well. But we do have conditional logic throughout some parts of the document that we know that for PDF, it's not going to quite render as nicely with the default font size. So I'll do some code chunk updates based on the output type. And then that way, I have a preview version of the ADRG linked on, you know, an HTML static site hosting area. And then the reviewers can look at that, you know, as as they give me comments and whatnot. But then the actual bundle that we transferred to them, which hopefully be this month, we will be able to supply the PDF version, and it's gonna look, you know, very good, very polished. But, again, the source is all from one place. We just have conditional logic on top, but still preserving the majority of that source going to both output types. So I do think this is this is a very novel concept, and parameters can you know, we're just scratching the surface of what is offered here. At the end of Nicole's post here, she has doing some terrific additional either blog posts from Mike Mahoney or JD Ryan's recent parameterized quartile reporting workshop as you gave for our ladies a while back. There's some great additional learning material, and I can't stress enough how awesome it is to have, like, a data frame of, like, parameter values in each row, maybe a different configuration of it. Just do a, per walk of that. For each row, run portal render. You got your outputs all generated within about 10 seconds. I did that yesterday, and it was a thing of beauty, my friend. Just a thing of beauty. Music to my ears, Eric. I I love nothing more than the per walk over that that nice little data frame with all your different parameter configuration. So thank you.
Yes. So so once you combine all this stuff, it's like, yeah, I always equate it to being like a a chef that can actually cook because I'm not a chef that can actually cook very well. But with programming, I like to think I can mix and match these quite a bit but I'm certainly not alone. We're learning from the call and many of the other thought leaders in this space here. And it never stops learning when you look the rest of the art weekly issue, and John has done a tremendous job for the rest of the issue here. So we'll take a couple of minutes to talk about our additional fines that caught our attention for the week. And going back to the concept of Tibbles and concepts of, like, the building blocks of the tidy verse, There were some some great posts that were illuminating a concept in tidy yard that I did not know was possible.
There are a couple posts here from Lael Lecri on the use of a Tidyr function called packing versus the concept of nesting, which we often hear about more fully with respect to creating your Tibbles. And if you're like me, when you first saw this issue or thinking, packing? What is that? Well, you can think of packing as, like, a slightly different take on how the data frame is being collapsed. Nesting is kinda like doing a row based collapsing. You're gonna have maybe a set of columns that become their own table, but it reduces the number of rows in your data frame as a result. We often do this in group by processing and whatnot.
Packing is kinda taking the opposite approach. It's gonna keep the same number of rows, but then it's gonna make a table out of various columns, and then you can unpack it to make it wider again. And so there these two posts are gonna talk about the comparisons of the 2, when you might wanna use 1 versus the other. And in particular, I mean, I still have a lot to learn in this area here, but she did eliminate some interesting examples. If you have jason type data, why am I want to look at packing versus nesting and those concepts? So again, just an illustration that I always think I I know most of the tidy verse, but there's always something I don't know. So we're all we're all learning together on this. But, Mike, what did you find? So I found that the Geo Aero package, I think, has its first release on CRAN. It's version 0.2.0,
[00:30:39] Mike Thomas:
which I am super excited about because I am a huge proponent of, the arrow package and the parquet file format, and we are getting some utilities in R to be able to wrap the geoparquet project, which allows you to work with GIS data that's stored in in in parquet format for just huge efficiencies over, you know, traditionally, you know, large heavy datasets when we talk about geospatial data. One more blog I wanna shout out is a blog from Athanasia Mowinkle which is called Customize Your R Startup Message. And it's a really nice little walk through about how to give yourself, a little message if you'd like, when your R session starts up each time. So a really cute little fun one for anybody interested.
[00:31:27] Eric Nantz:
Yeah. We we need a little variety in life. Right? I mean, I have all my my friends in the Linux community that do these fancy shell prompts and start up messages for their servers. I'm like, I need some of that for my artwork. So yeah. Well, that post is gonna gonna have me cover there. So yeah. And and awesome. Congrats to the Geo Aero team because, yeah, we're seeing immense immense, you know, innovations in the world of parquet with, spatial data. So this is just another tip to the hat, and, yeah, what a great time to be part of that ecosystem. And it's a great time to see the rest of our week as well and for you to get involved as well because this is not a project that's just run by a few individuals on the whims. This is based on the community, for the community, and by the community, if you will.
So the best way to help us out is to contribute to the issues directly via your suggestions for new resources, new packages, new tutorials, new workshops. We are fair game for all that. That will help your community. How you do that? Every issue at rwicky.org has a link in the top right corner for the upcoming issues draft. You can just click that, and you'll be taken directly to the poll request template where you can put in markdown syntax, that great link, that great resource that you found, all marked down all the time. We live marked down. We love it. Hence, Hence, some quartiles have been great with markdown as well. So all just a poll request away to get your resource in the issue. And, also, we love hearing from you and the community as well.
We have a few ways to get in touch with us directly. One of which is in this episode show notes. We got a link to the new ish contact page that you can send us a message on there. And I did hear back from a listener that wants to do some interesting, I guess, meta analysis on our past show notes. I'm gonna, you know, give that's gonna be exciting. So I'll definitely get in touch with you about that. And, also, you can also send us a fun little boost in the modern podcast app if you're listening to, say, paverse, Cast O Matic, fountain, many others in this space. So you can send a little boost directly in the podcast app, and we'll get that message directly to us about any third party middle person in between.
And speaking of podcasts, I guess I had a busy, couple of months recently because I was on another show recently hosted by my good friend Barry at Pod home talking about my adventures of podcasting and data science, and I'll have a link to that in this episode show notes. That was that was great fun to join Barry on that on that episode. So I will link to that from the about podcasting show. That was great fun. And, also, you can get in touch with me on the social medias these days. I am mostly on Mastodon. We've got our podcast at podcast index.social.
Doing a lot of fun dev work now behind the scenes with the podcasting 2.0 stuff and getting real gnarly with JSON data. It's been a fun learning adventure to say the least. I'm also on LinkedIn. Just search for my name. You'll find me there as sporadically on the weapon x thing with at the r cast. And, Mike, where can the listeners get a hold of you? Sure. You can find me on mastodon@mike_thomas@phostodon
[00:34:43] Mike Thomas:
dotorg, or you can check out what I'm up to on LinkedIn if you search Ketchbrooke Analytics, ketchbrook.
[00:34:52] Eric Nantz:
Awesome stuff. And, yeah, it's been a nice tidy episode that I think, judging by the fact that I have kids at home, I gotta get myself out of here. So we're gonna close-up shop here in this episode of our Wriggle highlights, and we thank you so much once again for joining us from RevAir around the world. And we hope to see you back here for another episode next week.