Hythergraphs show monthly weather parameters for an entire year, plotting temperature against precipitation (or other paired climate variables) against each other for each month of the year, drawing a line from month to month. When contrasting one climate record against another (historic vs. contemporary, one station against another), the differences stand out.
I was curious to see how easy it would be to produce one with R and ggplot.
I’ll produce hythergraphs, one that compares Fairbanks Airport data against the data collected at our station on Goldstream Creek for the period of record for our station (2011‒2022) and one that compares the Fairbanks Airport station data from 1951‒2000 against data from 2001‒2022 (similar to what Richard did).
I’m using the following R packages:
library(tidyverse) library(RPostgres) library(lubridate) library(scales)
I’ll skip the part where I pull the data from the GHCND database. What we need is a table of observations that look like this. We’ve got a categorical column (station_name), a date column, and the two climate variables we’re going to plot:
# A tibble: 30,072 × 4 station_name dte PRCP TAVG <chr> <date> <dbl> <dbl> 1 GOLDSTREAM CREEK 2011-04-01 0 -17.5 2 GOLDSTREAM CREEK 2011-04-02 0 -15.6 3 GOLDSTREAM CREEK 2011-04-03 0 -8.1 4 GOLDSTREAM CREEK 2011-04-04 0 -5 5 GOLDSTREAM CREEK 2011-04-05 0 -5 6 GOLDSTREAM CREEK 2011-04-06 0.5 -3.9 7 GOLDSTREAM CREEK 2011-04-07 0 -8.3 8 GOLDSTREAM CREEK 2011-04-08 2 -5.85 9 GOLDSTREAM CREEK 2011-04-09 0.5 -1.65 10 GOLDSTREAM CREEK 2011-04-10 0 -4.45 # ℹ 30,062 more rows
From that raw data, we’ll aggregate to year and month, calculating the montly precipitation sum and mean average temperature, then aggregate to station and month, calculating the mean monthly precipitation and temperature.
The final step adds the necessary aesthetics to produce the plot using ggplot. We’ll draw the monthly scatterplot values using the first letter of the month, calculated using month_label = substring(month.name[month], 1, 1) below. To draw the lines from one month to the next we use geom_segement and calculate the ends of each segment by setting xend and yend to the next row’s value from the table.
One flaw in this approach is that there’s no line between December and January because there is no “next” value in the data frame. This could be fixed by seperately finding the January positions, then passing those to lead() as the default value (which is normally NA).
airport_goldstream <- pivot |> filter(dte >= "2010-04-01") |> # get monthly precip total, mean temp mutate( year = year(dte), month = month(dte) ) |> group_by(station_name, year, month) |> summarize( sum_prcp_in = sum(PRCP, na.rm = TRUE) / 25.4, mean_tavg_f = mean(TAVG, na.rm = TRUE) * 9 / 5.0 + 32, .groups = "drop" ) |> # get monthy means for each station group_by(station_name, month) |> summarize( mean_prcp_in = mean(sum_prcp_in), mean_tavg_f = mean(mean_tavg_f), .groups = "drop" ) |> # add month label, line segment ends arrange(station_name, month) |> group_by(station_name) |> mutate( month_label = substring(month.name[month], 1, 1), xend = lead(mean_prcp_in), yend = lead(mean_tavg_f) )
Here’s what that data frame looks like:
# A tibble: 24 × 7 # Groups: station_name  station_name month mean_prcp_in mean_tavg_f month_label xend yend <chr> <dbl> <dbl> <dbl> <chr> <dbl> <dbl> 1 FAIRBANKS INTL AP 1 0.635 -6.84 J 0.988 -0.213 2 FAIRBANKS INTL AP 2 0.988 -0.213 F 0.635 11.5 3 FAIRBANKS INTL AP 3 0.635 11.5 M 0.498 33.1 4 FAIRBANKS INTL AP 4 0.498 33.1 A 0.670 51.2 5 FAIRBANKS INTL AP 5 0.670 51.2 M 1.79 61.3 6 FAIRBANKS INTL AP 6 1.79 61.3 J 2.41 63.1 7 FAIRBANKS INTL AP 7 2.41 63.1 J 2.59 57.9 8 FAIRBANKS INTL AP 8 2.59 57.9 A 1.66 46.5 9 FAIRBANKS INTL AP 9 1.66 46.5 S 1.04 29.5 10 FAIRBANKS INTL AP 10 1.04 29.5 O 1.16 5.21 # ℹ 14 more rows
Here’s the code to produce the plot. The month labels are displayed using geom_label, and the lines between months are generated from geom_segment.
airport_v_gsc <- ggplot( data = airport_goldstream, aes(x = mean_prcp_in, y = mean_tavg_f, color = station_name) ) + theme_bw() + geom_segment(aes(xend = xend, yend = yend, color = station_name)) + geom_label(aes(label = month_label), show.legend = FALSE) + scale_x_continuous( name = "Monthly Average Precipitation (inches liquid)", breaks = pretty_breaks(n = 10) ) + scale_y_continuous( name = "Monthly Average Tempearature (°F)", breaks = pretty_breaks(n = 10) ) + scale_color_manual( name = "Station", values = c("darkorange", "darkcyan") ) + theme( legend.position = c(0.8, 0.2), legend.background = element_rect( fill = "white", linetype = "solid", color = "grey80", size = 0.5 ) ) + labs( title = "Monthly temperature and precipitation", subtitle = "Fairbanks Airport and Goldstream Creek Stations, 2011‒2022" )
You can see from the plot that we are consistently colder than the airport, curiously more dramatically in the summer than winter. The airport gets slighly more precipitation in winter, but our summer precipitation is significantly higher, especially in August.
The standard plot to display this information would be two bar charts with one plot showing the monthly mean temperature for each station, and a second plot showing precipitation. The advantage of such a display is that the differences would be more clear, and the bars could include standard errors (or standard deviation) that would help provide an idea of whether the differences between stations are statistically significant or not.
For example (the lines above the bars are one standard deviation above or below the mean):
In this plot of the same data, you can tell from the standard deviation lines that the precipitation differences between stations are probably not significant, but the cooler summer temperatures at Goldstrem Creek may be.
If we calculate the standard deviations of the monthly means, we can use geom_tile to draw significance boxes around each monthly value in the hytherplot as Richard suggests in his post. Here’s the ggplot geom to do that:
geom_tile( aes(width = 2*sd_prcp_in, height = 2*sd_tavg_f, fill = station_name), show.legend = FALSE, alpha = 0.25 ) +
And the updated plot:
This clearly shows the large variation in precipitation, and if you carefully compare the boxes for a particular month, you can draw concusions similar to what is made fairly clear in the bar charts. For example, if we focus on August, you can see that the Goldstream Creek precipitation box clearly overlaps that of the airport station, but the temperature ranges do not overlap, suggesting that August temperatures are cooler at Goldstream Creek but that while precipitation is much higher, it’s not statistically significant.
Airport station, different time periods
Here’s the plot for the airport station that is similar to the plot Richard created (I used different time periods).
This plot demonstrates that while temperatures have increased in the last two decades, it’s the differences in the pattern of precipitation that stands out, with July and August precipitation much larger in the last 20 years. It’s also curious that February and April precipitation is higher, but the differences are smaller in the other winter months. This is a case where some sense of the distribution of the values would be useful.
Yesterday I went for my first run on Goldstream Creek this winter and noted a very smooth descending line on the elevation diagram from my Garmin watch while I was on the creek. There was a significant overflow event on November 29th, and with the cold temperatures since then, the water on the surface froze solid.
Here’s the plot of creek height and temperature for the last seven days. You can see the overflow event on November 29th, and a sudden drop starting yesterday afternoon.
Usually when there are overflow events like this they are fairly restricted, overflowing in one place for a thousand feet or so, freezing, then overflowing somewhere else. But this event was large enough that I was running on clean ice for almost the entire time I was down on the Creek. In winter, I run with Black Diamond Distance Spikes, and barely slip at all, even on smooth ice.
Here’s the map of my run.
And the elevation diagram. You can clearly see where I dropped down onto the Creek at the end of Miller Hill/Miller Hill Extension at a quarter mile, and then came back up at the Murkowski cabin near mile 2¼. The orange line shows a fitted line to the elevation points while I was down on the Creek.
When I dropped down on the Creek at the end of Miller Hill and Miller Hill Extension, the elevation of the Creek was 562 feet, and 1.9 miles downstream it was down twelve feet to 550. The slope of the line is -5.876, indicating that the Creek drops almost six feet per mile along the course I ran.
Something happened to the ice overnight, at least at our house, so it’ll be interesting to check it out on my run today. The overflow on November 29th was around 4 inches deep, and last night it dropped almost the same amount. Since the surface is frozen, there must have been a layer of air under the ice that was filled with water earlier in the week, but after the water overflowed (and froze), that layer emptied and the ice on the surface dropped back down.
We let Martin go this afternoon after a long struggle with an inflammatory bowel disease. He came to us with his sister Piper nine years ago and was a strong and fast sprint dog known as one of the louder dogs at the track. He retired from racing in 2019 and enjoyed his remaining days sleeping on the futon and couch, often with a cat or his sister.
Martin was born in Salcha, Alaska on May 10, 2009 and won several races with Piper. He was known as the "ten thousand dollar dog" because he got torsion when he was young and the surgery to save his life was very expensive (along with later surgery and vet appointments investigating his digestive issues). He had the most beautiful, fluid trot running up and down the dog yard, but when he'd get into a race he was obsessive about being as far to the left as he possibly could, sometimes running halfway off the trail.
He liked beer, digging large holes in the dog yard so he could eat dirt, and would make playful growling noises when you scratched just the right spots on his hips. He got along with the cats, snuggling with them on the couch, and despite being fairly high on the hierarchy of our dogs, he was never aggressive about food or toys. He was the sweetest, softest dog we've had and was not only my favorite, but was the favorite of our UPS driver who pointed him out to Andrea one day and said, "That one is my favorite!"
Rest in peace buddy.
This morning I woke up about 20 minutes late because my iPhone 11 crashed overnight while sitting on my charger. That's the first time I've every had an iPhone crash. After using the “volume up”, “volume down”, “hold power button” trick, the phone came back up. I quickly installed the latest Apple update 16.3.1, just in case I’d stumbled into a bug that has been fixed.
Before I got it running again I thought about having to buy a new phone and what I might lose because I wouldn’t be able to copy the data from the old phone to the new one. I’m relying on iCloud Backup for most of the important things (photos, mostly), and all of my passwords and pretty much all of the data I care about is in git or in databases on my server, so losing the phone wouldn’t affect any of that.
But I have two factor authentication with Google Authenticator set up for several important sites, and I would have no way to get this “factor” back if the phone was truly dead. When I upgraded to this phone several years ago I sent back my previous phone to Apple and didn’t realize Google Authenticator “data” only exists on that one device. Oops. Sort of the point, if I’d thought about it.
Google Authenticator has a mechanism for transferring its data to a new device. You click the three dots in the upper right corner, choose “Export accounts”, then scan the QR code that shows on the screen with the new device.
Instead of scanning it, I took a screenshot, edited the photo, downloaded it to my computer, converted it to a PNG, encrypted it to plain text, added it to my password store, then deleted the image. This is sort of overkill since I'm encrypting the image data, then my password manager is encrypting it again. Alternatives include converting the image to text using uuencoding, or Eric Raymond's PNG to text converter, but doing it this way only requires tools I'm always going to have (GNU Privacy Guard and pass)
Here’s what the process looks like.
# Convert to a PNG (probably not necessary, but it's *data* so...) $ convert -quality 100% IMG_0112.jpeg google_auth_qr_2023-02-15.png # Encrypt $ gpg --encrypt --armor google_auth_qr_2023-02-15.png $ rm google_auth_qr_2023-02-15.png # Add to password store (-m means multi-line) $ cat google_auth_qr_2023-02-15.png.asc | \ pass insert -m Internet/google-authenticator/qr_2023-02-15.png $ rm google_auth_qr_2023-02-15.png.asc
If I ever need to recover it in the future, I do the reverse, which looks like the following.
$ pass Internet/google-authenticator/qr_2023-02-15.png > /tmp/foo.png.asc $ gpg --decrypt /tmp/foo.png.asc > /tmp/foo.png
Caslon died today after a long fight with multiple myeloma. We got him as part of a foster litter with his brothers Jenson and Tallys. We’d chosen his brothers from the litter as kittens, but when we saw Caslon alone in his crate at the shelter after the rest of his brothers and sisters had been adopted, we couldn’t leave him there by himself. He was never quite as snuggly as his brother Tallys, who died in 2019, but he was the biggest and most playful of our cats. He always ate his food up on the cat tree and would jump up there with such force that he’d almost tip over the whole thing.
Once Tallys was gone he took over Tallys’s spot in front of the wood stove, and would request to be picked up every morning while I made coffee. He loved being under the covers on the guest bed, on the couch with Andrea, and sometimes in the middle of the night purring and kneading under the covers with Andrea. And while the dogs ate their dinner, he’d bolt over to one of the dog beds, flop and wriggle, and I’d give him super rubdowns. He was also the cat who always tested the limits of any new box we put down to see if he would fit.
Caslon was a loving, patient, snuggly cat, and we will miss him.