All posts by Sam

You don’t need a TrailKeg

Or, “ode to the carbonator cap.”

A homebrewer friend recently brought a $200 TrailKeg to a club meeting. It is shiny and cool and … a thneed.

Instead, you should use a carbonator cap ($8* as of this writing) and some 1 or 2-liter plastic bottles (free after you drink the seltzer water).  While TrailKeg claims superiority over the glass growler, the carbonator-cap-and-PET-bottle (PET = #1 plastic, i.e. a soda bottle) combo delivers in most of the same ways:

  • Unbreakable
  • Lightweight
  • Has CO2 input for carbing the beer and keeping/serving it under CO2.

Here’s where they differ:

Continue reading You don’t need a TrailKeg

Batch 76 (?) – All-Malt Lager

Batch 76: All-Malt Lager

My batch numbers might be mixed up, I may or may not sort that out.

Brewing: brewed March 15th.  OG 1.050.  Big boilover, long cleanup, but everything went fine on the beer side.

It was an 18 gallon batch, two vessels of pale lager and a third that got an addition to make it a Dark American Lager.  To make the dark share, I put a half-pound of crushed Carafa II (or III?  See recipe/BCS book) in 2 quarts of hot water, steeped it like a big tea bag til it cooled, then added to fermenter.

Fermentation: fermented in 50F ambient cellar space.  Pulled up to 64F for a diacetyl rest around 1.020 gravity, which was 5 days (CCYL lager yeast) and 7 days (single pack pitch of 34/70).  Let sit around in 64F for a couple more weeks.

Packaging: kegged the CCYL batch on April 11th, it finished around 1.000 FG for a bit over 6% ABV.


Recipe and batch notes:

Things I want to do in 2018

Somewhat belatedly, here’s what I plan and hope to do in 2018.

R Development

Early in 2018, I’d like to finish up a couple of ongoing open-source software projects:

  1. Release janitor v 1.0.  I have worked hard on this, it’s 99% done as of this writing, and I enjoy the benefits of the new tabyl() functions every day – but until it’s on CRAN, the impact is limited.
  2. Participate in Kaggle’s March Mania challenge, ideally updating my how-to resource.

Then perhaps a break would be healthy, during which I spend more of my free time away from code and the computer.

Making things

I used to build things.  Including complex projects like my electric home brewery.  I’ve fallen out of that.  In 2018 I’d like to again build some small physical things:

  1. A keg washer like Colin’s


I’d like to finish drafts and ideas I have accumulated.  Including:

  1. My proposal for a Maple Road Bike Highway
  2. Several cargo bike posts
  3. My process for lime-softening well water for homebrewing
  4. Fixing my stove


I’ll probably get involved in campaigns given what a busy year it looks to be politically.  Which means I should focus on other things before the fall.

I’m especially interested in the voting access and anti-gerrymandering ballot initiatives, the gubernatorial race, and of course Ann Arbor City Council.

To spread the joy of practical biking, I’d like to lend my cargo bike to at least 3 people/families this year.

Physical activity

I hope to again log 1,200 miles on a bicycle.  I want to ride to Dexter and back, and to Ypsi and back.  And get both kids riding pedal bikes.

Things I won’t do

Taking things off my plate to make room for others.  A small example: I’m not entering the National Homebrew Competition this year.  Perhaps I’ll keep vegetable gardening to a minimal level.

I’ve been taking a break from following Ann Arbor news, though that may change during election season.

And the best for last:

Expecting another child this summer is the biggest event of the year.  But unlike resolutions and plans above, that one will by default get all the attention it needs.

Advent of Code 2017 in #rstats: Day 13

I liked the twist in the Day 13 puzzle.  Implementing a literal solution to part 1, where I had the scanners walk back and forth, was straightforward.  And Part 2 looked easy enough.  Then I peeked at how slowly my algorithm was proceeding and realized I would need to refactor.

Part 1

This function has been modified in two places for Part 2.  I added the first line in the function, using the modulo operator %% to trim the time down to a single back-and-forth pass.   This made the commented-out direction change line obsolete.

I worked out the time %% ((size-1)*2) bit by counting examples on my fingers 🙂

Part 2

Without the modulo line above, my loop will walk the scanner back and forth for time steps.  As the delay increases, so does the time.  The answer to this problem is a delay of approximately 4 million time units; at that point, each scanner is walked 4 million times… it’s been a long time since CS 201 but I think that makes the algorithm O(N2).  In practice, I realized I had a problem when progress slowed dramatically.

Realizing this, I eliminated the unnecessary walking.  Now, this could still be much more efficient.  Because I’m recycling code from part 1, I test all scanner depths for collisions at each iteration; a faster solution would move on to the next delay value after a single scanner collision is found.  But hey, that is a mere ~20x slowdown or so, and is still O(N).

Having started with R in 2014, I rarely feel like an old-timer.  I learned dplyr from the beginning, not tapply.  But I had that feeling here… being somewhat comfortable with mapply gets in the way of sitting down to learn the map2 functions from purrr, which I suspect will be useful to know.

My runtime was about 15 minutes.

Advent of Code 2017 in #rstats: Day 12

(Day 12 puzzle). This was my favorite day so far.  I’ve never faced my own graph problem and this was a great example for trying out the igraph package.

Big shout out to Gábor Csárdi and anyone else on the igraph team who wrote the docs.  And I mean wrote the docs!  When I google an R question, 99% of the time I land on StackOverflow.  The searches I made for Day 12 all* took me to the igraph documentation website, which answered my questions.  I don’t know of another R package or topic like that.

Their example of creating a graph was clear and was easy to adapt to the toy example on Day 12.  From there, some searching found the two functions I’d need for Day 12: neighborhood() and clusters().  Look how short my part 2 is!

Part 0: Playing with igraph

Here’s the documentation example for creating an igraph.  I played with it to confirm it would work for my needs:

Part 1

This was mostly wrangling the data into the igraph.  It didn’t seem to like integer names for vertices so I prepended “a”.

I increased the grp_size parameter until my result stopped increasing.  That was at about 30 degrees of separation (it was still changing at 15).  A more permanent solution might include a loop to do this.

Part 2

All you need is igraph::clusters():

One.  Function.

Conclusion: graphs are neat, igraph is the way to analyze them.

* okay, one search took me to StackOverflow and gave me what I needed: the clusters() function.  Everything else came from

Advent of Code 2017 in #rstats: Day 11

Once I realized that moves on a hex grid map nicely to a standard rectangular grid, this was easy.   Despite playing hours of Settlers of Catan, I’d never realized this relationship.  Maybe because nothing traverses that hex grid?

North and South move one step up or down.  The four diagonal directions move a half-step up or down and a full column laterally.  The shortest solution path will be diagonal moves to reach the desired column, then vertical moves to the right row.

It took only a minute or two to modify my part 1 function for part 2, so I present both together.

Parts 1 & 2


Advent of Code 2017 in #rstats: Day 9

I write less deeply-nested code now that I program in R.  When writing code poorly  in other programs, I’d often use nested IF statements (hi, Excel!).  Debugging that often looked like counting my nesting depth out loud: add one for every (,  subtract one for every ).

That strategy formed the basis for my solution today.   And I was excited to do Part 2 with arithmetic, not writing new code.

Part 1

Strategy: maintain counter of open braces, decreasing for closed braces.

So {{<a>},{<a>},{<a>},{<a>}} = 1 2 2 2 2. Sum = 9.

Part 2

The answer is the total number of characters, minus:

  • The characters canceled by !
  • The bracketing <> for each garbage string
  • The valid characters remaining after removing the garbage in Part 1

To wit:


Advent of Code 2017 in #rstats: Day 7

Today was hard, but a good challenge.  I haven’t written a recursive function since college CS – is that typical for data science / analysis work?  I don’t see much about recursion on #rstats Twitter.  Recursion feels like a separate way of thinking, and I had forgotten how.

Part 1

The first part was satisfying.  Data cleaning was quick and I thought joining the data.frame to itself to populate parent and child for each entry was a nice touch.

My tidy data looked like:

Then the solution was easy:


Part 2

This was tough going.  I got a function working for the sample input, which worked by stepping backward from the most distant children and adding their weight to their parents, then removing them and calling itself again.

But while the sample input had symmetric towers, in the bigger test data a tower could be balanced if it had two children of 4 and 2 -> 1 + 1.  In that scenario, you can’t peel off the 4 in the same step that you peel off the 1s.  (For my own satisfaction, that false solution appears far below).

I’m proud of my eventual solution.  And besides finally getting my brain to think recursively, I learned a few things: creating a named vector with structure() and using purrr::map_dbl.

I finished with a cop-out: once my function returned the weights of the subtower nodes where the problem existed and their names, it wasn’t worth programming the last bit to do the weight subtraction and get my answer.  With this output:

I just calculated it myself using get_node_weight("ycbqx") and subtracting 5.

Appendix: Solution with Symmetric Towers

Because I feel salty that this didn’t work.


Advent of Code 2017 in #rstats: Day 6

The solution was a simple concept but harder than I thought to implement.  I learned some lessons today:

  • Read the instructions carefully.  I built the balancing function balance_banks to return how many distributions it made of the maximum value, but removed this code when I realized that the full distribution of a bank of N values counted as one iteration, not N iterations.
  • Related: Up until now, Git has felt unnecessary for these challenges, and I’ve been too lazy to use it (though it would do wonders for my GitHub contribution graph).  Today I got nervous deleting and reworking code for Part 1 … would I need it for the not-yet-known Part 2?
  • I built my balance_banks function inside of my go_til_repeat function (because I thought it would help with passing counter information, which I did not need).  When editing and then testing, I kept failing to load both into memory; I’d update one, load the other into memory, and call the first one.  I don’t work much with nested functions of any complexity; TIL to avoid loading just one while programming and testing them.

Today was the first day that run-time performance became an issue.  My outputs for each part were correct on the first try, but each took a while (3 minutes?) to run.  Suspicious that my code was stuck in a loop, I never let it finish the first call I made: I broke the loop manually and added the counter to print every 1,000 rebalances.

But it was just slow.  To check completion, I switched to the use of double duplicated you see below instead of janitor::get_dupes(), which is easier on the brain but a relatively expensive operation.

Lastly, when I thought I had to return multiple outputs from balance_banks I had it return them in a list.  I removed the increment counter code and the function now returns just one result (the balanced vector), but I left the list syntax in place since it worked.  That’s why you see the $output in last_result <- balance_banks(last_result$output)for instance.

Adding Part 2 onto Part 1 was trivial.  I’d seen Jenny Bryan use diff(range()) in a solution to an earlier puzzle and was stoked to reuse it in Part 2.

I resorted to rbind instead of my beloved dplyr::bind_rows because my data.frames don’t have column names.  I remain tickled by situations where I work with data.frames in R while ignoring column names.

Parts 1 and 2


Advent of Code 2017 in #rstats: Day 5

This went smoothly.  No tricky parts and Part 2 was a simple extension of Part 1.  No packages needed to solve (I used testthat for checking against the test input).

This was a textbook case for a while loop, which I rarely use in R.