<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>And now it’s all this</title>
	<atom:link href="https://leancrew.com/all-this/feed/" rel="self" type="application/rss+xml" />
	<link>https://leancrew.com/all-this/</link>
	<description>I just said what I said and it was wrong. Or was taken wrong.</description>
	<lastBuildDate>Mon, 17 Aug 2026 19:19:12 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0</generator>
  <atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/>
  <atom:link rel="hub" href="http://aniat.superfeedr.com"/>

<item>
<title>Web and native apps</title>
<link>https://leancrew.com/all-this/2026/08/web-and-native-apps/</link>
<pubDate>Mon, 17 Aug 2026 19:19:12 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/web-and-native-apps/</guid>
<description>
  <![CDATA[This is sacrilegious among some parts of Apple fandom, but I like using web apps. Oh, sure, I can imagine a world in which there’s a carefully crafted Mac app that has all the functions of a website I use but has all the Mac-like interface features I love and isn’t hobbled by a web UI. But I don’t live in that world and don’t expect I ever will.]]>
</description>
<content:encoded>
  <![CDATA[<p>This is sacrilegious among some parts of Apple fandom, but I like using web apps. Oh, sure, I can imagine a world in which there’s a carefully crafted Mac app that has all the functions of a website I use but has all the Mac-like interface features I love and isn’t hobbled by a web UI. But I don’t live in that world and don’t expect I ever will.</p>
<p>For example, will the <a href="https://www.python.org/psf-landing/">Python Software Foundation</a> ever make a Mac app that includes all the documentation I regularly use? And even if they do, is it likely they’ll put in the effort to make better than just visiting the <a href="https://docs.python.org/3.13/index.html">Python Docs web page</a>? Would that be in keeping with their mission? So instead of waiting around for something that will never happen, I used <a href="https://www.bzgapps.com/unite">Unite Pro</a> to make a Python Docs site-specific browser.</p>
<p><img alt="SSB for Python documentation" class="ss" src="https://leancrew.com/all-this/images2026/20260817-SSB%20for%20Python%20documentation.png" title="SSB for Python documentation" width="100%"/></p>
<p>Launching it takes me straight to the <a href="https://docs.python.org/3.13/library/index.html">standard library home page</a>, from which I can quickly jump to the docs of whichever module I’m invoking in my current script. I have similar SSBs for the <a href="https://matplotlib.org/">Matplotlib</a> and <a href="https://pandas.pydata.org/">Pandas</a> documentation.</p>
<p>You might argue that these SSBs aren’t really web apps, that they’re basically a set of static web pages connected by links. That’s nearly true, but while they don’t have the kind of interaction that you see in, say, Google Sheets, they do have search features that make them more interactive than just a list of links. Regardless of how interactive they are, they serve my needs.</p>
<p>You might also suggest I use <a href="https://kapeli.com/dash">Dash</a>, an actual app, instead of an SSB for browsing documentation. It incorporates many many sets of documentation and uses local copies so you don’t have to be online to use it. These are all good points and are why I tried Dash several years ago. It just didn’t fit me, probably because searches in Dash returned too many results across too many libraries. There are probably ways to get it to serve up more focused results, but I didn’t want to become a Dash expert (I’m not <a href="https://brettterpstra.com/2022/02/18/keybindings-cheat-sheet-for-dash/">Brett Terpstra</a>). I just wanted to see documentation relevant to what I was working on at the time. Initially that meant going to the appropriate website; now it means launching the appropriate SSB.</p>
<p>But I’m not a web app absolutist. I recently ended a web app experiment that’s brought me back to a native app. This was with Mastodon and <a href="https://apps.apple.com/us/app/mona-7-for-mastodon/id6755672518">Mona</a>. When <a href="https://mastodon.social/@MonaApp">Mona 7 came out</a> at the end of last year, I decided to hold off on buying its Ultra in-app purchase. After all, I thought, Mastodon exists on the web. Does it really need an app?</p>
<p>So I made an SSB with Unite Pro for the Mac and used the free versions of Mona on iOS and iPadOS. Eventually, I felt guilty about using an app without paying the developer, so I did the Add to Home Screen thing on my iPhone and iPad a couple of weeks ago to even things out and use the web on all three platforms. It was terrible.</p>
<p>The biggest problem was the lack of timeline syncing and updating. In theory, this problem should have started when I was using Mona on two devices and an SSB on the third. In practice, it wasn’t so bad because I do almost all of my Mastodon reading and posting on my phone. It was when I started using “web Mastodon” on my phone that I noticed how poorly Mastodon updates the timeline.</p>
<p>On the web, Mastodon puts a link at the top of the page saying there are new posts to load into your timeline. When you tap the link, the new posts load, but your position in the timeline can jump around wildly, and then you have to scroll to get back to where you were. A small annoyance, perhaps, but one that happens again and again every day.</p>
<p>And there’s no syncing between platforms with web Mastodon. This is, as I said, less of a problem for me because I’m mainly reading and posting on my phone, but the scrolling necessary on one device to get to where I had been on another just reminded me of all the scrolling described in the previous paragraph, and it seemed worse.</p>
<p>So I subscribed to Mona Ultra and switched to it on all three platforms. The Mastodon web Home Screen icons are gone from my iPhone and iPad, and the SSB is gone from my Mac. Ultra’s extended settings are nice, and I’m happy to pay for them, but the main advantage is the smoother experience I get with a real app written to use the features of the platform(s) it runs on. In this case, one of those features is iCloud syncing.</p>
<p>(By the way, if you feel tempted to tell me about another Mastodon app, like Ivory, don’t. I know about them—I’m happy with Mona.)</p>
<p>In summary, I don’t have any magic tricks for choosing between web apps and native apps. I just know that it’s worth a little time to try out both and see what fits. Every app and every person is different, and you have to decide from direct experience.</p>]]>
</content:encoded>
</item>

<item>
<title>Apple Music weirdness</title>
<link>https://leancrew.com/all-this/2026/08/apple-music-weirdness/</link>
<pubDate>Sat, 15 Aug 2026 22:18:03 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/apple-music-weirdness/</guid>
<description>
  <![CDATA[I was out on a walk yesterday, listening to the ’70s Hits Radio Station on Apple Music, when “Don’t Leave Me This Way” came on. I pulled my phone out of my pocket to look at a text while the song was playing and was surprised at what the Music app told me about the song.]]>
</description>
<content:encoded>
  <![CDATA[<p>I was out on a walk yesterday, listening to the ’70s Hits Radio Station on Apple Music, when “Don’t Leave Me This Way” came on. I pulled my phone out of my pocket to look at a text while the song was playing and was surprised at what the Music app told me about the song.</p>
<p><img alt="Album art and artist error" class="ss" src="https://leancrew.com/all-this/images2026/20260815-Album%20art%20and%20artist%20error.jpg" title="Album art and artist error" width="60%"/></p>
<p>I’m pretty sure George Benson never covered “Don’t Leave Me This Way,” but even if he did, that’s not the version I was listening to. It was the version everybody knows by Thelma Houston, with her incredible voice and that fun bass part during the chorus. I’ve been listening to it for 50 years, and it’s unmistakable.</p>
<p>So how did Apple Music get the artist wrong? Is the info provided with the album wrong and Apple is just repeating someone else’s mistake? When I got home, I checked this <a href="https://music.apple.com/us/album/heartbreak-hits/1445668885"><em>Heartbreak Hits</em></a> compilation album on other services. <a href="https://www.amazon.com/Heartbreak-Hits-Explicit-Various-artists/dp/B079KL1SR5?dplnkId=42b30c15-e915-441b-bb64-c50d7aa6b506&amp;nodl=1">Amazon Music</a>, <a href="https://open.spotify.com/album/6S3vni7DhQ6XliFnarFgFv">Spotify</a>, and <a href="https://tidal.com/album/84236359/track/84236373">Tidal</a> all had the album, and they all had the artist listed correctly as Thelma Houston. Only Apple got it wrong.</p>
<p>I don’t think I’ve ever seen mistaken artist attribution like this before, but Apple’s weird choice of album to pluck the song from is very familiar. I listen to a lot of Apple’s Radio Stations and its Essentials and Deep Cuts playlists, and it’s common for a song to be assigned to a compilation album instead of the original source. Even when Apple has the original album in its library. As you might have guessed, I find this annoying.</p>
<p>It’s not exactly <em>wrong</em> to show a song as being on a compilation album; most hit songs have been put on “best of” and other sorts of compilations. But it’s bad scholarship. Yes, if the song was released as a standalone single—as many Beatle songs were—the only album you can assign it to is a compilation, but that’s fairly rare.</p>
<p>If, for example, you look at the <a href="https://music.apple.com/us/playlist/prince-essentials/pl.3c4819913dad4dbe80dc5cddca6431a7">Prince Essentials playlist</a>—which you should; it’s fantastic—you’ll see that both “Gett Off” and “1999” are shown as being from his <em>The Hits/The B-Sides</em> album. That’s certainly a fun album to listen to, but neither of those songs “belong” to that album. “Gett Off” is from <em>Diamonds and Pearls</em>, and if I have to tell you where “1999” is from, I don’t know why you’ve read this far.</p>
<p>Apple likes to say that music is part of its DNA. I suggest they schedule some genetic counseling.</p>]]>
</content:encoded>
</item>

<item>
<title>A planet position widget</title>
<link>https://leancrew.com/all-this/2026/08/a-planet-position-widget/</link>
<pubDate>Wed, 12 Aug 2026 17:16:30 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/a-planet-position-widget/</guid>
<description>
  <![CDATA[After I learned how to <a href="https://leancrew.com/all-this/2026/08/a-two-month-calendar-on-my-desktop/">make a Mac widget with TerminalWidget</a> and how to <a href="https://leancrew.com/all-this/2026/08/ill-follow-the-sun/">determine the locations of celestial objects with Astropy</a>, the natural thing for me to do was combine the two into a widget that tracks the planets.]]>
</description>
<content:encoded>
  <![CDATA[<p>After I learned how to <a href="https://leancrew.com/all-this/2026/08/a-two-month-calendar-on-my-desktop/">make a Mac widget with TerminalWidget</a> and how to <a href="https://leancrew.com/all-this/2026/08/ill-follow-the-sun/">determine the locations of celestial objects with Astropy</a>, the natural thing for me to do was combine the two into a widget that tracks the planets.</p>
<p><img alt="Planet position widget" class="ss" src="https://leancrew.com/all-this/images2026/20260811-Planet%20position%20widget.png" title="Planet position widget" width="80%"/></p>
<p>I’m using the ancient definition of planet, which includes the Sun and Moon but not anything past Saturn. The numbers are the azimuth and altitude, in that order, and are given to the nearest degree. The idea is to tell me what may be visible and where it is. This particular screenshot was taken just after 10:00 last night; there was no reason to go outside because everything was below the horizon.</p>
<p>I was torn on whether to include the Sun. Few of us need help finding the Sun in the sky, and you can’t see anything other than the Moon when the Sun is up. But I decided to include it anyway, partly for completeness, and partly because the visibility of some bodies depends on their separation from the Sun.</p>
<p>Let’s start with the code that generates the widget’s text. It’s a Python script called <code>planets</code>:</p>
<pre><code>python:
 1:  import astropy.units as u
 2:  from astropy.time import Time
 3:  from astropy.coordinates import get_body, AltAz, EarthLocation
 4:  from subprocess import run
 5:  
 6:  def direction(az):
 7:    'Return a string indication of the azimuth (given in degrees).'
 8:  
 9:    dirs = 'N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW'.split()
10:    i = int(((az + 11.25) % 360) / 22.5)
11:    return dirs[i]
12:  
13:  # Current time in UTC.
14:  ut = Time.now()
15:  
16:  # Observation location.
17:  home = EarthLocation(lat=41.81433*u.deg, lon=-88.07093*u.deg, height=208*u.m)
18:  
19:  # Bodies of interest.
20:  planets = 'Moon Sun Mercury Venus Mars Jupiter Saturn'.split()
21:  
22:  # Current positions of all the bodies.
23:  pos = {}
24:  const = {}
25:  for p in planets:
26:      pos[p] = get_body(p, ut).transform_to(AltAz(obstime=ut, location=home))
27:      const[p] = pos[p].get_constellation()
28:  
29:  # Assemble the results.
30:  output = []
31:  for p in planets:
32:      output.append(f'{p:&gt;8s}: {pos[p].az.value:3.0f} \
33:  {direction(pos[p].az.value):3s} {pos[p].alt.value:3.0f} {const[p]}')
34:  
35:  # Pipe the results through TerminalWidget.
36:  tw = '/Applications/TerminalWidget.app/Contents/MacOS/TerminalWidget\
37:   --target planets --font Menlo --bg eeeeee --fg 000000 --text -'.split()
38:  run(tw, input='\n'.join(output).encode())
39:  
40:  # print('\n'.join(output))
</code></pre>
<p>There’s no shebang line because of how it gets called by <code>launchd</code>, which we’ll get to later.</p>
<p>After <code>planets</code> imports the necessary modules, Lines 6–11 define the <code>direction</code> function, which takes the azimuth and returns a string with the corresponding point of the compass. I have this because 223°, which is how Astropy reports the azimuth, doesn’t immediately say “southwest” to me. The function assumes a 16-point compass, like this one:</p>
<p><img alt="Compass rose from Wikipedia" class="ss" src="https://leancrew.com/all-this/images2026/20260812-Compass%20rose%20from%20Wikipedia.png" title="Compass rose from Wikipedia" width="60%"/></p>
<p class="caption">Image from <a href="https://en.wikipedia.org/wiki/Points_of_the_compass">Wikipedia</a>.</p>
<p>The points are separated by 22.5°, which is why there’s a division by 22.5 in Line 10. The other parts of Line 10 adjust for the fact that North starts at 348.75° (-11.25°), the azimuth resets at 360°, and the index of a list must be an integer. Astropy may already have a function that does what <code>direction</code> does, but I thought it would be easier (and more fun) to write the function myself than to search through the documentation.</p>
<p>Lines 14 and 17 define the time and place of observation. <code>planets</code> will be run every half hour to update the widget, so what’s being displayed is never more than 30 minutes out of date. The <code>home</code> location you see above is actually the Morton Arboretum; my version of the script uses the latitude and longitude of my house.</p>
<p>Line 20 defines the <code>planets</code> list, and Lines 23–27 create a pair of dictionaries, <code>pos</code> and <code>const</code>, which contain the <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.AltAz.html"><code>AltAz</code></a> position and constellation of each planet. The <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.get_body.html"><code>get_body</code> function</a> (Line 26) gets the position, and the <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.get_constellation.html"><code>get_constellation</code> function</a> (Line 27) uses that position to figure out the constellation the body is in.</p>
<p>Lines 30–33 create the list of <code>output</code> lines, and Lines 36–38 use the <a href="https://docs.python.org/3.13/library/subprocess.html#subprocess.run"><code>run</code> function</a> of the <code>subprocess</code> module to send the output lines to TerminalWidget. The <code>tw</code> list contains both the full path to the <code>TerminalWidget</code> executable and all the options passed to it. The <code>input</code> parameter to <code>run</code> is the previously defined <code>output</code>, converted to a single string separated by linefeeds and encoded as bytes.</p>
<p>Line 40 is basically a debugging line that I’ve left in for future development. While writing <code>planets</code>, I had Lines 36–38 commented out and Line 40 uncommented so I could see the results immediately in the Terminal.</p>
<p><code>planets</code> is run by <code>launchd</code> every 30 minutes, on the hour and half-hour, via this launch agent, <code>com.leancrew.planets.plist</code>:</p>
<pre><code>xml:
 1:  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
 2:  &lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
 3:  &lt;plist version="1.0"&gt;
 4:  &lt;dict&gt;
 5:    &lt;key&gt;Label&lt;/key&gt;
 6:    &lt;string&gt;com.leancrew.planets&lt;/string&gt;
 7:    &lt;key&gt;ProgramArguments&lt;/key&gt;
 8:    &lt;array&gt;
 9:      &lt;string&gt;/path/to/python&lt;/string&gt;
10:      &lt;string&gt;/path/to/planets&lt;/string&gt;
11:    &lt;/array&gt;
12:    &lt;key&gt;StartCalendarInterval&lt;/key&gt;
13:    &lt;array&gt;
14:      &lt;dict&gt;
15:        &lt;key&gt;Minute&lt;/key&gt;
16:        &lt;integer&gt;0&lt;/integer&gt;
17:      &lt;/dict&gt;
18:      &lt;dict&gt;
19:        &lt;key&gt;Minute&lt;/key&gt;
20:        &lt;integer&gt;30&lt;/integer&gt;
21:      &lt;/dict&gt;
22:    &lt;/array&gt;
23:  &lt;/dict&gt;
24:  &lt;/plist&gt;
</code></pre>
<p>The first item in the <code>ProgramArguments</code> array is the full path to the Python executable (this is why <code>planets</code> doesn’t need a shebang line), and the second item is the full path to the <code>planets</code> script itself. The schedule for running <code>planets</code> is in the <code>StartCalendarInterval</code> array—whenever the minute is 0 or 30, the script is run.</p>
<p>As I write this, a solar eclipse is nearly underway. Here in the Chicago area, it’s going to be a very partial eclipse—only 1% of the Sun will be blocked. Since 100% of the Sun is being blocked by clouds, I won’t be able to see any of the eclipse. But my planets widget is showing me, more or less, that it’s happening above the clouds.</p>
<p><img alt="Planets widget near the solar eclipse time" class="ss" src="https://leancrew.com/all-this/images2026/20260812-Planets%20widget%20near%20the%20solar%20eclipse%20time.png" title="Planets widget near the solar eclipse time" width="80%"/></p>
<p>Rounding the Sun and Moon’s positions to the nearest degree isn’t precise enough to determine an eclipse, but it’s a decent hint.</p>]]>
</content:encoded>
</item>

<item>
<title>I’ll follow the Sun</title>
<link>https://leancrew.com/all-this/2026/08/ill-follow-the-sun/</link>
<pubDate>Tue, 11 Aug 2026 03:49:46 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/ill-follow-the-sun/</guid>
<description>
  <![CDATA[Through an odd coincidence, I started writing this post when the Sun was in Cancer, but by the time I’m done and get it published, the Sun will be in Leo. Don’t worry, this isn’t an astrology post—I don’t want to <a href="https://daringfireball.net/2026/08/retraction_app_store_rejection_of_the_week">anger John Gruber</a>—but it is a further coincidence that I’ve been puttering around with observational astronomy calculations at a time when there’s been <a href="https://mjtsai.com/blog/2026/08/07/dark-hours-rejected-from-the-app-store/">some controversy in the Apple world</a> about astronomy vs. astrology.]]>
</description>
<content:encoded>
  <![CDATA[<p>Through an odd coincidence, I started writing this post when the Sun was in Cancer, but by the time I’m done and get it published, the Sun will be in Leo. Don’t worry, this isn’t an astrology post—I don’t want to <a href="https://daringfireball.net/2026/08/retraction_app_store_rejection_of_the_week">anger John Gruber</a>—but it is a further coincidence that I’ve been puttering around with observational astronomy calculations at a time when there’s been <a href="https://mjtsai.com/blog/2026/08/07/dark-hours-rejected-from-the-app-store/">some controversy in the Apple world</a> about astronomy vs. astrology.</p>
<p>I’m often out walking at night, and a few months ago—when Mercury, Venus, and Jupiter were near each other and close to Castor and Pollux, the <a href="https://en.wikipedia.org/wiki/Gemini_(constellation)">Gemini twins</a>—I started thinking it would be nice to be able to write little scripts that would tell me where the planets were and when they’d be easily visible. One of the fun features would be including information on which constellation the planets were in.</p>
<p>This led me on a fun journey of discovery, the sort of thing AI companies want us to forget how to do. I learned that <a href="https://www.iau.org/IAU/IAU/Astronomy-FAQs/Constellations.aspx">official boundaries</a> for the Western constellations were defined only about a century ago and that although they were defined in a very simple way, corresponding to lines of <a href="https://skyandtelescope.org/astronomy-resources/right-ascension-declination-celestial-coordinates/">right ascension and declination</a>, <a href="https://pwg.gsfc.nasa.gov/stargaze/Sprecess.htm">precession of the equinoxes</a> has since made those boundaries more complicated. They still look pretty well aligned, but they aren’t. Here’s the <a href="https://iauarchive.eso.org/static/public/constellations/gif/ARI.gif">IAU image of Aries</a>:</p>
<p><img alt="IAU Aries boundaries" class="ss" src="https://leancrew.com/all-this/images2026/20260810-IAU%20Aries%20boundaries.gif" title="IAU Aries boundaries" width="100%"/></p>
<p>If you look at <a href="https://iauarchive.eso.org/static/public/constellations/txt/ari.txt">the right ascension and declination coordinates</a> of the points on its boundary, you’ll see that successive points <em>don’t</em> have the same RA or Dec values. Close, but not the same.</p>
<pre><code>02 06 39.6594| 10.5143948|ARI 
01 46 37.3761| 10.5432396|ARI 
01 46 58.7219| 25.6263351|ARI 
02 02 03.2907| 25.6050701|ARI 
02 02 07.3479| 27.8550186|ARI 
02 32 16.8357| 27.8047638|ARI 
02 32 24.7665| 31.2213154|ARI 
02 50 30.8112| 31.1865025|ARI 
03 29 42.4003| 31.1003609|ARI 
03 29 09.7494| 19.4343338|ARI 
03 24 08.9363| 19.4461136|ARI 
03 23 47.1387| 10.3632069|ARI 
</code></pre>
<p>I also learned that there are <a href="https://coordswap.eukosmos.com/">a crapload of reference frames</a>, and you had better know which one is being used for the data you’re accessing.</p>
<p>This is one of the things that led me to abandon Mathematica and the Wolfram Language for these calculations. Despite Wolfram’s <a href="https://www.wolfram.com/solutions/industry/astronomy/">promises</a> of great solutions for astronomy, my test notebook showed that the RA and Dec for the Sun, for example, aren’t reported in the same frame as the RA and Dec of stars. This isn’t necessarily bad—it could be more natural to use one frame for one sort of object and another frame for another sort—but the documentation needs to tell you what frames are being used and how to convert between them. I never found that explanation, so I started exploring Python solutions.</p>
<p>There are two main Python modules for doing the kind of calculation I’m interested in: <a href="https://www.astropy.org/">Astropy</a> and <a href="https://rhodesmill.org/skyfield/">Skyfield</a>. Astropy is the standard Python module (or set of modules) for doing all sorts of astronomical calculations; Skyfield seems to be more focused on observational astronomy. That would suggest I should use Skyfield, but after looking through the documentation, I decided to get my feet wet with Astropy because it looked simpler. I can always switch to Skyfield if I find myself bumping up against some limitations in Astropy.</p>
<p>(I should also mention <a href="https://naif.jpl.nasa.gov/naif/toolkit.html">SPICE</a>, which is NASA’s software toolkit for working with the positions of planets and other objects in space. It has a Python wrapper for its C version, and having the imprimatur of NASA certainly made it attractive. But it doesn’t have actual Python documentation; it wants users to refer to the C documentation to figure out how the Python functions work, and I’m not interested in that.)</p>
<p>Having settled on Astropy, I wrote up a little script this afternoon to see if my brief review of the documentation was enough to do some of the calculations I was interested in. The script calculates the position of the Sun today at 2:00 PM CDT, and converts the result into a form that I can compare with the <a href="https://gml.noaa.gov/grad/solcalc/">NOAA Solar Calculator page</a>.</p>
<p><img alt="NOAA Sun position page" class="ss" src="https://leancrew.com/all-this/images2026/20260810-NOAA%20Sun%20position%20page.png" title="NOAA Sun position page" width="100%"/></p>
<p>I’ve set the observation point to the visitor center at the <a href="https://mortonarb.org/">Morton Arboretum</a>. The results of interest are the azimuth and altitude (or elevation) of the Sun at the appointed date and time. Here’s a zoomed-in view of the lower right corner:</p>
<p><img alt="NOAA Sun azimuth and altitude" class="ss" src="https://leancrew.com/all-this/images2026/20260810-NOAA%20Sun%20azimuth%20and%20altitude.png" title="NOAA Sun azimuth and altitude" width="60%"/></p>
<p>So a person at the Arboretum at 2:00 would see the Sun in the southwest, 211.59° from north, at 60.35° up from the horizon. NOAA’s altitude calculation includes an atmospheric correction, the formula for which is given on <a href="https://gml.noaa.gov/grad/solcalc/calcdetails.html">a linked page</a>.</p>
<p>Here’s my little script:</p>
<pre><code>python:
 1:  #!/usr/bin/env python3
 2:  
 3:  from astropy.coordinates import ICRS, AltAz, EarthLocation, get_sun
 4:  from astropy.time import Time
 5:  import astropy.units as u
 6:  from trigd import *
 7:  
 8:  # 2:00 PM Central Daylight Time on August 10, 2026.
 9:  utcoffset = -5*u.hour
10:  time = Time('2026-8-10 14:00:00') - utcoffset
11:  
12:  # Location of Morton Arboretum visitor center.
13:  morton = EarthLocation(lat=41.81433*u.deg, lon=-88.07093*u.deg, height=208*u.m)
14:  
15:  # Sun position in GCRS (default).
16:  sun = get_sun(time)
17:  
18:  # The constellation it's in.
19:  constellation = sun.get_constellation()
20:  
21:  # Sun position as azimuth and altitude
22:  sun_altaz = sun.transform_to(AltAz(obstime=time, location=morton))
23:  
24:  # The default transformation to AltAz makes no adjustment for refraction.
25:  # Use the NOAA refraction formula to adjust altitude for comparison
26:  # with the NOAA value.
27:  def noaa_refraction(alt):
28:    t = tand(alt)
29:    return (58.1/t - .007/t**3 + .000086/t**5)/3600
30:  
31:  # Print the results.
32:  az = sun_altaz.az.value
33:  alt = sun_altaz.alt.value
34:  alt_adj = alt + noaa_refraction(alt)
35:  print(f'      Azimuth: {az:-6.2f}°')
36:  print(f'     Altitude: {alt:-6.2f}° (without refraction)')
37:  print(f'     Altitude: {alt_adj:-6.2f}° (with NOAA refraction)')
38:  print(f'Constellation: {constellation}')
</code></pre>
<p>The script starts by importing various Astropy submodules and <a href="https://leancrew.com/all-this/2024/12/calculators-and-jupyter/">my <code>trigd</code> module</a>, which calculates trigonometric functions for degrees instead of radians. We’ll use that to match NOAA’s atmospheric correction formula.</p>
<p>Lines 9 and 10 set the date and time as an Astropy <code>Time</code> object. Line 13 sets the location to the visitor center. Line 16 <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.get_sun.html">gets the position of the Sun</a> at that time in the <a href="https://docs.astropy.org/en/latest/api/astropy.coordinates.GCRS.html">Geocentric Celestial Reference System (GCRS) frame</a>. Line 22 then <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.SkyCoord.html#astropy.coordinates.SkyCoord.transform_to">transforms the position</a> to <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.AltAz.html">azimuth and altitude</a> as viewed from the visitor center.</p>
<p>Because I didn’t include any atmospheric information in the <code>AltAz</code> specification, it assumes a vacuum and does no refraction adjustment. I did it that way so I could use NOAA’s refraction formula instead of whatever Astropy does. That formula is defined in Lines 27–29 (which uses the <code>tand</code> function to calculate the tangent of an angle given in degrees), and the adjusted altitude is calculated in Line 34.</p>
<p>Lines 35–38 print out the results, which look like this:</p>
<pre><code>      Azimuth: 211.59°
     Altitude:  60.34° (without refraction)
     Altitude:  60.35° (with NOAA refraction)
Constellation: Cancer
</code></pre>
<p>We’ll return to the constellation part later. As you can see, the azimuth and adjusted altitude match the NOAA values to two decimal places, which made me feel pretty good.</p>
<p>As for the constellation the Sun was in at the specified time, that’s calculated by the aptly named <code>get_constellation</code> function on Line 19. The great thing about <code>get_constellation</code> is that it understands the reference frame of the object it’s called from and does whatever transformations are needed (in this case to <a href="https://docs.astropy.org/en/stable/api/astropy.coordinates.ICRS.html">ICRS</a>) to figure out which constellation that object is in. The answer was printed out by Line 38.</p>
<p>To check on the constellation answer, I went to <a href="https://heavens-above.com">Heavens Above</a>, a site I’ve been using since the late 90s. I entered the Arboretum location and 2:00 PM today as the time, and HA told me the Sun was in Cancer, just like Astropy. It also showed me this sky chart:</p>
<p><img alt="Heavens Above sky chart" class="ss" src="https://leancrew.com/all-this/images2026/20260810-Heavens%20Above%20sky%20chart.png" title="Heavens Above sky chart" width="80%"/></p>
<p>The Sun was clearly near the end of its time in Cancer and would soon be in Leo. So I began a trial-and-error search at Heavens Above and with Astropy in an interactive Python session to find out when the Sun would move from Cancer to Leo.</p>
<p>Heavens Above told me that the Sun would enter Leo at 8:00:15 PM today. Astropy said it would happen at 8:07:23 PM. I suspect Heavens Above is giving the better answer, as I’ve been using Astropy in its most basic configuration. There are ways to set up Astropy to use <a href="https://docs.astropy.org/en/stable/coordinates/solarsystem.html">ephemerides data</a>, which should give more accurate positions. That’ll be my next step.</p>
<p>By the way, a 7-minute difference isn’t much. The Sun moves along the ecliptic at a rate of about 1° per day (roughly 360° in 365 days), and 7 minutes is about 0.005 of a day (7/1440). That means the difference between the Heavens Above Sun position and the Astropy Sun position is about 0.005°. Do I need the position of the Sun (or any of the planets) to a greater precision than that? No, but that won’t stop me from exploring ways to do so.</p>]]>
</content:encoded>
</item>

<item>
<title>A two-month calendar on my Desktop</title>
<link>https://leancrew.com/all-this/2026/08/a-two-month-calendar-on-my-desktop/</link>
<pubDate>Wed, 05 Aug 2026 21:34:08 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/a-two-month-calendar-on-my-desktop/</guid>
<description>
  <![CDATA[A few days ago, I posted this image <a href="https://fosstodon.org/@drdrang/117021219801155387">on Mastodon</a>:]]>
</description>
<content:encoded>
  <![CDATA[<p>A few days ago, I posted this image <a href="https://fosstodon.org/@drdrang/117021219801155387">on Mastodon</a>:</p>
<p><img alt="Two-month calendar in TerminalWidget" class="ss" src="https://leancrew.com/all-this/images2026/20260805-Two-month%20calendar%20in%20TerminalWidget.png" title="Two-month calendar in TerminalWidget" width="70%"/></p>
<p>The code I ran to get this two-month calendar on my Desktop was</p>
<pre><code>bash:
if [ $(date +"%-d") -gt 15 ]; then
  cal -A 1
else
  cal -B 1
fi |\
terminal-widget --target cal --font Menlo --bg "#eeeeee" --text -
</code></pre>
<p>I have since updated a couple of things and need more room to talk about it all.</p>
<p>First, the app that put that widget on my Desktop is Brett Terpstra’s <a href="https://terminalwidget.app/">TerminalWidget</a>, which he <a href="https://brettterpstra.com/2026/08/01/introducing-terminalwidget/">just released</a>. As a one-time user of <a href="https://leancrew.com/all-this/2007/06/album-art-with-geektool/">GeekTool</a>, I’ve been waiting for TerminalWidget since Brett first began talking about it. While GeekTool and the similar <a href="https://leancrew.com/all-this/2011/03/nerdtool-picts-and-buddhism/">NerdTool</a> might still work, I wanted an app that worked with macOS’s modern widget system. TerminalWidget does.</p>
<p>I put a medium-sized widget in the upper-left corner of my Desktop by dragging one out from the widget editor window. If you’ve never done this before (I hadn’t), read <a href="https://support.apple.com/guide/mac-help/add-and-customize-widgets-mchl52be5da5/mac">Apple’s instructions</a>.</p>
<p><img alt="Widget editor window" class="ss" src="https://leancrew.com/all-this/images2026/20260805-Widget%20editor%20window.png" title="Widget editor window" width="100%"/></p>
<p>I then right-clicked on the widget and renamed it <code>cal</code>. Now it’s ready for me to run the command that puts the calendar into the widget.</p>
<p>The command is a pipeline, and the executable after the pipe is <code>terminal-widget</code>. This is a symbolic link to the command that’s buried in the TerminalWidget app package. I created the link with</p>
<pre><code>ln -s /Applications/TerminalWidget.app/Contents/MacOS/TerminalWidget ~/bin/terminal-widget
</code></pre>
<p>where <code>~/bin</code> is a directory in my <code>$PATH</code>.</p>
<p>I won’t go through the options given to <code>terminal-widget</code>; they’re explained in <a href="https://terminalwidget.app/cli">TerminalWidget’s CLI page</a>. Suffice it to say that the options put whatever is sent to <code>terminal-widget</code> into the <code>cal</code> widget and format it the way I want.</p>
<p>Instead, let’s talk about the command that comes before the pipe:</p>
<pre><code>bash:
if [ $(date +"%-d") -gt 15 ]; then
  cal -A 1
else
  cal -B 1
fi
</code></pre>
<p>I think this will run in any Bourne-like shell. I know it runs in zsh and bash.</p>
<p>The idea is to run the <a href="https://leancrew.com/all-this/man/man1/cal.html"><code>cal</code> command</a>, displaying the current month and either the month before or the month after. Which other month to show is determined by the <code>if</code> statement at the top. It runs the <a href="https://leancrew.com/all-this/man/man1/date.html"><code>date</code> command</a> and formats the output as just the day of the month with no leading zero. If this is greater than 15, <code>cal</code> is passed the option to show one month after the current month. Otherwise, <code>cal</code> is passed the option to show one month before the current month.</p>
<p>(Brett uses <code>cal</code> as one of his <a href="https://terminalwidget.app/widgets">example widgets</a>, but his command doesn’t include an <code>if</code>. It always displays the current month and the month after.)</p>
<p>By the way, if you run the above command in the Terminal, you’ll notice its output is slightly different from what’s shown in the widget:</p>
<p><img alt="Output from cal in Terminal" class="ss" src="https://leancrew.com/all-this/images2026/20260805-Output%20from%20cal%20in%20Terminal.png" title="Output from cal in Terminal" width="70%"/></p>
<p>The current date is highlighted. I think this is because <code>cal</code> is written to format its output as plain text when it’s being saved to a file or piped to another command but jumps back to invert today’s date when it’s being run in a terminal. It’s similar to the way <a href="https://leancrew.com/all-this/man/man1/ls.html"><code>ls</code></a> lists files one per line when being piped but formats them in columns when its output goes to a terminal.</p>
<p>OK, this is nice for testing out TerminalWidget, but running this command once won’t get it to change when we get past the 15th of the month. It needs to be run every day, preferably in the morning.</p>
<p>The classic Unix way to schedule tasks is <a href="https://leancrew.com/all-this/man/man8/cron.html"><code>cron</code></a>, but <a href="https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html#//apple_ref/doc/uid/10000172i-CH1-SW2">according to Apple</a> your Mac must be awake when a <code>cron</code> job is scheduled. If it isn’t, the job runs the next time the job is scheduled <em>and</em> the Mac is awake. Because I don’t know when my MacBook Pro will be awake, <code>cron</code> is not a good option for scheduling this task. I need to use <a href="https://leancrew.com/all-this/man/man8/launchd.html"><code>launchd</code></a>, which will run the specified command at the scheduled time <em>or</em> the next time the Mac wakes up.</p>
<p>The easiest way to set up <code>launchd</code> agents is to use <a href="https://www.soma-zone.com/LaunchControl/">LaunchControl</a> and let its GUI handle the tricky bits, but you can do it by hand if you must. First, make a plist file that describes what command is to be run and when and save it in your <code>~/Library/LaunchAgents</code> folder. Here’s the file, named <code>com.leancrew.calwidget.plist</code>, that LaunchControl built for me:</p>
<pre><code>xml:
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
&lt;plist version="1.0"&gt;
&lt;dict&gt;
  &lt;key&gt;Label&lt;/key&gt;
  &lt;string&gt;com.leancrew.calwidget&lt;/string&gt;
  &lt;key&gt;ProgramArguments&lt;/key&gt;
  &lt;array&gt;
    &lt;string&gt;/bin/zsh&lt;/string&gt;
    &lt;string&gt;/Users/drang/bin/calwidget.sh&lt;/string&gt;
  &lt;/array&gt;
  &lt;key&gt;StartCalendarInterval&lt;/key&gt;
  &lt;array&gt;
   &lt;dict&gt;
    &lt;key&gt;Hour&lt;/key&gt;
    &lt;integer&gt;6&lt;/integer&gt;
    &lt;key&gt;Minute&lt;/key&gt;
    &lt;integer&gt;30&lt;/integer&gt;
   &lt;/dict&gt;
  &lt;/array&gt;
&lt;/dict&gt;
&lt;/plist&gt;
</code></pre>
<p>The <code>ProgramArguments</code> section tells <code>launchd</code> to run my <code>calwidget.sh</code> script via zsh. Because <code>launchd</code> commands aren’t run under my usual environment, I give full file paths to both zsh and my script. The <code>StartCalendarInterval</code> section tells <code>launchd</code> to run the job every day at 6:30 AM.</p>
<p>The <code>calwidget.sh</code> script is basically what we’ve seen before, but with a couple of small changes:</p>
<pre><code>bash:
if [ $(date +"%-d") -gt 7 ]; then
  cal -A 1
else
  cal -B 1
fi |\
/Applications/TerminalWidget.app/Contents/MacOS/TerminalWidget \
  --target cal --font Menlo --fg "000000" --bg "eeeeee" --text -
</code></pre>
<p>I decided I’d prefer to see the month before the current month only if we’re a week or less into the current month. Hence the change from <code>15</code> to <code>7</code> in the <code>if</code> statement. Also, I changed the <code>terminal-widget</code> command to the full path of the executable it’s linked to because <code>launchd</code> doesn’t know my usual <code>$PATH</code>. And finally, I added a <code>--fg</code> option to ensure that both the foreground and background colors are what I want—I decided not to rely on defaults.</p>
<p>I loaded this job into <code>launchd</code> via LaunchControl, but I also tested doing it with <a href="https://leancrew.com/all-this/man/man1/launchctl.html"><code>launchctl</code></a>:</p>
<pre><code>launchctl load -w ~/Library/LaunchAgents/com.leancrew.calwidget.plist
</code></pre>
<p>The <code>launchctl</code> man page calls <code>load</code> a “legacy subcommand,” suggesting that I should learn the newer way of doing things, but I find the descriptions of the recommended subcommands incomprehensible. And I haven’t found a good tutorial for them. Again, LaunchControl just does what I want it to do.</p>
<div class="update">
<p><strong>Update 5 Aug 2026 10:36 PM</strong><br/>
Eric Hemmeter <a href="https://fosstodon.org/@ehemmete@mastodon.social/117045209633791732">sent me a link on Mastodon</a> to <a href="https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/">this 2016 article</a> by Babo D, which does a much better job of explaining <code>launchctl</code>’s “new” syntax than Apple does anywhere that I’ve seen. While I don’t think this will take the place of LaunchControl for me, I certainly understand the recommended subcommands better now than I ever have. I’ve saved a web archive version of the page in case it disappears. Thanks, Eric!</p>
</div>
<p>While this exercise was mostly a proof of concept, I do like having quick access to a two-month calendar and will probably keep this widget. Now I need to think about what other information I want immediate access to.</p>]]>
</content:encoded>
</item>

<item>
<title>My rules for using spreadsheets</title>
<link>https://leancrew.com/all-this/2026/08/my-rules-for-using-spreadsheets/</link>
<pubDate>Sat, 01 Aug 2026 15:29:24 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/08/my-rules-for-using-spreadsheets/</guid>
<description>
  <![CDATA[My fundamental rule is <em>Don’t</em>, but a single word wouldn’t make for much of a blog post.]]>
</description>
<content:encoded>
  <![CDATA[<p>[Equations in this post may not look right (or appear at all) in your RSS reader. Go to <a href="https://leancrew.com/all-this/2026/08/my-rules-for-using-spreadsheets/">the original article</a> to see them rendered properly.]</p>
  <hr />
  <p>My fundamental rule is <em>Don’t</em>, but a single word wouldn’t make for much of a blog post.</p>
<p>In what follows, I hope to explain how I’ve come to that rule and the exceptions I make to it. I’ve been thinking about how I use and don’t use spreadsheets quite a bit lately. This introspection was inspired in part by <a href="https://macstockconferenceandexpo.com/schedule/#:~:text=How%20the%20Cool%20Kids%20Really%20Use%20Spreadsheets">Allison Sheridan’s presentation at Macstock</a> (which you can see <a href="https://www.podfeet.com/blog/2026/07/macstock-2026-how-the-cool-kids-really-use-spreadsheets/">on her site</a> along with a couple of <a href="https://www.podfeet.com/blog/2026/07/visicalc-ken-case/">other</a> recent <a href="https://www.podfeet.com/blog/2026/07/excel-find-dependencies/">spreadsheet posts</a>) and in part by my recent use of Numbers to <a href="https://leancrew.com/all-this/2026/07/sum-of-cubes-via-difference-tables/">make difference tables</a> and <a href="https://leancrew.com/all-this/2026/07/plotting-baseball-team-progress/">clean up a table of data</a>.</p>
<p>Let’s start by considering what makes spreadsheets so attractive. Right off the bat, you’re presented with a grid of cells that act as data containers. You don’t have to define these containers, you don’t have to name them, you don’t have to initialize them—they’re just there, waiting for you to fill them as you see fit.</p>
<p>When it comes time to start operating on this data, you <em>still</em> don’t have to name the cells. You just click (or click and drag) to fill in the function arguments. The spreadsheet app fills in the appropriate row/column reference. If you want a reminder of what a cell is for, you can type a name or description in an adjacent cell. Similarly, you don’t have to figure out the appropriate order of the operations. The app works out the cell dependency chain and recalculates everything, everywhere, all at once.</p>
<p>And because you can set the size, color, border, and font styling of every cell, your spreadsheet can generate nice-looking tables for inserting into your reports, memos, and slideshows.</p>
<p>So a spreadsheet is a data store, a logic machine, and a presentation tool. Are you getting it?</p>
<p>But if spreadsheets are all that, where does my <em>Don’t</em> rule come from? There are many sources, but I’d have to say I’ve been strongly influenced by the last 10–15 years of my working life, during which time I had to analyze dozens and dozens of data sets, all of which were sent to me as Excel spreadsheets. The engineering firms that sent me the spreadsheets had created them not simply as data stores. They included some of their own analysis (which typically overlapped slightly with mine), and they formatted the spreadsheets as tables to put into their own reports. This made my work harder for a few reasons:</p>
<ol>
<li><p>Because I had to make sure I understood and agreed with their analysis, I had to review all their formulas. Some of these formulas were complex—nested <code>IF</code> statements are easy to follow in a traditional programming language, but they’re a mess in a spreadsheet. Some were inconsistent—different rows in the same table would have different formulas, as if they were written by different people at different times or adapted from a spreadsheet on a previous project. Some of them referred to cells that were far away and required a lot of scrolling to track down. None of them—not a single one in over a decade—used cell names to help make the formulas easier to understand.</p>
<p>The complicated formulas mentioned above sometimes—not often, but sometimes—contained mistakes. And sometimes the formulas were correct, but the descriptions in the header cells were wrong. This meant phone calls were needed to resolve the discrepancies, further slowing the analysis.</p></li>
<li>It was common for the data to be split over two or more sheets. I think this was done mainly to make the tables fit better into the other engineers’ reports, which was fine for their purposes but not for mine. I had to recombine the data for my analyses. Also, the sheets often had complicated, multiline headers, which meant I couldn’t just export them as CSV files.</li>
<li>Every engineer I worked with built their spreadsheets in a different way. Those who worked for the same firm didn’t adhere to an “ABC Engineering” house style. Even individual engineers would change their spreadsheet styling from one project to the next. Basically, every spreadsheet that came in the door was <em>sui generis</em>, and I had to do all the data cleaning by hand. This slowed me down, not only because I couldn’t rely on automation for this step, but also because I had to double- and triple-check my work to avoid copy/paste mistakes.</li>
</ol>
<p>Fundamentally, this experience—especially Item 1—soured me on the use of spreadsheets for anything large or complex. The engineers I was working with were smart, but their spreadsheets weren’t. My conclusion was that the simplicity of the typical click-and-drag method of assembling a spreadsheet encouraged poor organization and errors as the spreadsheets grew or were adapted to new data.  It’s easy to say “Oh, I would never do that,” but I’m old enough to know that I <em>would</em> do that. I see the ease with which I can build spreadsheets with today’s apps as a Siren song that will lead me onto the rocks.</p>
<p>(If you’re getting ready to write to me about the <a href="https://retractionwatch.com/2013/04/18/influential-reinhart-rogoff-economics-paper-suffers-database-error/">Reinhart/Rogoff paper</a>, you can relax. It is the prime example of elementary spreadsheet errors—errors that two Harvard professors would <em>surely</em> never make—and it led to a lot of suffering through unnecessary government austerity policies. And if you’re now getting ready to write to me about how Reinhart and Rogoff’s errors don’t negate the essential truth of their conclusions, you can just fuck off.)</p>
<p>The convenience of having the data and the analysis logic in the same document becomes a problem when you have to apply that logic to several datasets, especially when they differ in size. Spreadsheet templates are great when the data allow you make several spreadsheets with the exact same layout, but the data I tend to deal with don’t fit that rigid pattern. If I’m doing, for example, analyses and plots of several time series, those series seldom extend over the same length of time and the same number of data points. It’s far easier to deal with these size differences when the logic is in a program, separated from the data.</p>
<p>Another problem with spreadsheets is that the amount of data they can contain is more limited than when you use other data analysis workflows. The size limits on spreadsheets are, admittedly, quite large, but in an era of Big Data “quite large” may not be big enough. In her <a href="https://www.podfeet.com/blog/2026/07/macstock-2026-how-the-cool-kids-really-use-spreadsheets/">Macstock talk</a>, Allison shows how she ran into that problem with the data set of <a href="https://catalog.data.gov/dataset/baby-names-from-social-security-card-applications-national-data">US baby names</a>. Let’s take a detour to talk about handling that data.</p>
<hr/>
<p>One of the ways you can download the baby name dataset is as a zipped collection of CSV files. Each file in the collection is associated with one year and has a name like <code>yob1960.txt</code>. The contents look like this:</p>
<pre><code>Mary,F,51472
Susan,F,39208
Linda,F,37316
Karen,F,36378
Donna,F,34138
[etc]
</code></pre>
<p>where the first item is the name, the second is the sex at birth, and the third is the number of babies given that name in that year. The lines are ordered first by sex and then by number. If you concatenate all the files, you’ll find there are 2,181,032 entries. As Allison found out, this won’t fit into an Excel spreadsheet, as Excel is limited to 1,048,576 rows. That’s the very computery number <math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mn>2</mn><mn>20</mn></msup></math> or <math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mn>1024</mn><mn>2</mn></msup></math>. The limit in Numbers is the less computery but more human 1,000,000 rows.</p>
<p>Allison got around the size problem by… er… cheating. She eliminated the less popular names to get the list to fit into Excel, and then demonstrated some pivot table stuff. You can see it starting at 1:13:50 <a href="https://www.podfeet.com/blog/2026/07/macstock-2026-how-the-cool-kids-really-use-spreadsheets/">in the video</a>.</p>
<p>I decided to do something similar to her work but without the cheating. First, I concatenated all the individual files into one big CSV file that also included a field for the year. That was done through these shell commands:</p>
<pre><code>echo 'Year,Name,Sex,Count' &gt; all-years.csv
for f in yob*.txt; do
  y=${f:3:4}
  sed -e "s/\r$//;s/^/$y,/" $f &gt;&gt; all-years.csv
done
</code></pre>
<p>The year is extracted from the file name through <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#:~:text=Substring%20Expansion.">substring expansion</a> and then added to the beginning of each line via <code>sed</code>. The original files are in Windows format with CRLF line endings, so the <code>sed</code> command also deletes the CR characters. The upshot of all this is a file (with Unix line endings) named <code>all-years.csv</code> that looks like this:</p>
<pre><code>Year,Name,Sex,Count
1880,Mary,F,7065
1880,Anna,F,2604
1880,Emma,F,2003
1880,Elizabeth,F,1939
1880,Minnie,F,1746
[etc]
</code></pre>
<p>(Yes, even though the data set is said to have come from Social Security registrations, it starts in 1880, decades before the Social Security Act. I can’t explain that. Nor can I explain how Minnie was once the fifth most popular girls’ name.)</p>
<p>I’m going to use Python and <a href="https://pandas.pydata.org/">Pandas</a> to extract the five most popular girls’ names from 2001 through 2025 (the last year in the dataset). Here’s the start of a simple interactive Python session that does it:</p>
<pre><code>&gt;&gt;&gt; import pandas as pd
&gt;&gt;&gt; df = pd.read_csv('all-years.csv')
&gt;&gt;&gt; cols = ['Name', 'Count']
</code></pre>
<p>This reads the CSV file into a dataframe and defines the columns of the dataframe that we want to include in our output. The <code>&gt;&gt;&gt;</code> at the beginning of each line is the interactive Python prompt. Here’s how we get the list of names we’re interested in:</p>
<pre><code>&gt;&gt;&gt; df[(df.Sex=='F') &amp; (df.Year&gt;2000)][cols].groupby('Name')\
... .sum().sort_values('Count', ascending=False)[:5]
           Count
Name            
Emma      449576
Olivia    423613
Isabella  381577
Sophia    368619
Emily     353077
</code></pre>
<p>The <code>...</code> indicates a continuation input line. Everything after that is output.</p>
<p>Reading through the command, we see that we’re</p>
<ol>
<li>getting the subset of data consisting of girls born after 2000;</li>
<li>limiting the output to the Name and Count fields;</li>
<li>grouping the output by Name;</li>
<li>summing the Counts for each Name;</li>
<li>sorting the results by Count in descending order; and</li>
<li>limiting the output to the top five names.</li>
</ol>
<p>That’s obviously a long command, but you can see how it’s constructed in a logical fashion.</p>
<p>If we want to compare these to the popular girls’ names from a century earlier, the command is very similar:</p>
<pre><code>&gt;&gt;&gt; df[(df.Sex=='F') &amp; (df.Year&gt;1900) &amp; (df.Year&lt;=1925)][cols].groupby('Name')\
... .sum().sort_values('Count', ascending=False)[:5]
            Count
Name             
Mary      1056333
Helen      505522
Dorothy    475151
Margaret   402317
Ruth       364923
</code></pre>
<p>My wife and I had great aunts with some of these names.</p>
<p>If you’re a database maven, you recognize the Pandas <a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html"><code>groupby</code> function</a> as a copy of the SQL <a href="https://en.wikipedia.org/wiki/Group_by_(SQL)"><code>GROUP BY</code> construct</a>. Let’s redo this in an interactive session with <a href="https://sqlite.org/">SQLite</a>. We start by importing the data from the CSV file:</p>
<pre><code>sqlite&gt; .mode csv
sqlite&gt; .import all-years.csv names
sqlite&gt; .mode columns
</code></pre>
<p>The <a href="https://sqlite.org/climode.html">mode</a> is set to <code>csv</code> in order to import the data, then set back to <code>columns</code> to make the output look the way we want.</p>
<p>Now we get the top five girls’ names from the 21st century and show them in descending order:</p>
<pre><code>sqlite&gt; select Name, sum(Count) from names
     ...&gt; where Sex is "F" and Year &gt; 2000
     ...&gt; group by Name order by sum(Count) desc limit 5;
Name      sum(Count)
--------  ----------
Emma      449576    
Olivia    423613    
Isabella  381577    
Sophia    368619    
Emily     353077    
</code></pre>
<p>SQL is certainly more English-like, but you can see the parallels between it and Pandas. Now for the early 20th century:</p>
<pre><code>sqlite&gt; select Name, sum(Count) from names
     ...&gt; where Sex is "F" and Year &gt; 1900 and Year &lt;= 1925
     ...&gt; group by Name order by sum(Count) desc limit 5;
Name      sum(Count)
--------  ----------
Mary      1056333   
Helen     505522    
Dorothy   475151    
Margaret  402317    
Ruth      364923    
</code></pre>
<p>Allison does similar things with her truncated Excel file using <a href="https://en.wikipedia.org/wiki/Pivot_table">pivot tables</a>. I hate the name “pivot table,” because I think it’s an obscure term for the simple operations of grouping and summarizing. For some reason, my feelings on this don’t matter, and pivot tables are here to stay. Pandas has even added a <a href="https://pandas.pydata.org/docs/reference/api/pandas.pivot_table.html"><code>pivot_table</code> function</a> to placate people who’ve come over from Excel. Under the hood, <code>pivot_table</code> calls <code>groupby</code>.</p>
<hr/>
<p>Well, that was kind of a long detour, and I forgive you if you’ve forgotten where we were. I had just gone through a list of things that made me leery of using spreadsheets—why my first rule of using spreadsheets is <em>Don’t</em>.</p>
<p>But I allow for exceptions. My two main exceptions are:</p>
<ol>
<li><p>When the problem is both small enough to see on the screen with almost no scrolling and the operations are simple enough to be easily understood without counting commas and parentheses. That was what I did for my <a href="https://leancrew.com/all-this/2026/07/sum-of-cubes-via-difference-tables/">sum of cubes difference tables</a>. The formulas consisted mainly of subtractions, with some power and division operations here and there. Only the simultaneous equations solution in the lower right involved actual function calls, and there were no nested calls.</p>
<p><img alt="Cubic sum spreadsheet" class="ss" src="https://leancrew.com/all-this/images2026/20260731-Cubic%20sum%20spreadsheet.png" title="Cubic sum spreadsheet" width="100%"/></p></li>
<li>When I’m using the spreadsheet as a way station for editing data before passing it along. I did that in the <a href="https://leancrew.com/all-this/2026/07/plotting-baseball-team-progress/">baseball team progress</a> post to edit down the large and unwieldy <a href="https://www.baseball-reference.com/teams/CHC/2026-schedule-scores.shtml">season results tables</a> from Baseball Reference. It was fast and easy to select the table in Safari, paste it into Numbers, and then delete the columns and rows I didn’t need. But I did it this way only because this was a one-off project. If I were given the job of making progress charts for all 30 teams every day of the season, I’d never do it by hand like that. I’d use the Pandas <a href="https://pandas.pydata.org/docs/reference/api/pandas.read_html.html"><code>read_html</code> function</a> to pull the HTML table into a dataframe and a variety of <a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.drop.html#pandas.DataFrame.drop"><code>drop</code> commands</a> to pare it down.</li>
</ol>
<p>I used to use spreadsheets for data entry, too, but not anymore. It was once the only reliable way to put a table of numbers found in a book into electronic form. But OCR has gotten so much better, I can’t remember the last time I did this.</p>
<p>I know there are lots of people who love using spreadsheets. They’ve spent a lot of time learning the ins and outs and don’t want to switch to another tool. That’s fine. This post was about <em>my</em> rules, not anyone else’s. I’m not saying good, accurate, complex work can’t be done in spreadsheets. It just won’t be done by me.</p>
  ]]>
</content:encoded>
</item>

<item>
<title>Plotting baseball team progress</title>
<link>https://leancrew.com/all-this/2026/07/plotting-baseball-team-progress/</link>
<pubDate>Fri, 24 Jul 2026 01:36:45 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/plotting-baseball-team-progress/</guid>
<description>
  <![CDATA[I noticed today that the Cubs and Yankees have the same record. I knew that the Cubs have had some extreme ups and downs this year, but I wasn’t sure if the Yankees had, so I decided to plot their progress over the course of the season so far.]]>
</description>
<content:encoded>
  <![CDATA[<p>I noticed today that the Cubs and Yankees have the same record. I knew that the Cubs have had some extreme ups and downs this year, but I wasn’t sure if the Yankees had, so I decided to plot their progress over the course of the season so far.</p>
<p>A plot of how far above or below .500 they were seemed like the best way to compare their seasons, so I got their records from baseball-reference.com (<a href="https://www.baseball-reference.com/teams/CHC/2026-schedule-scores.shtml">Cubs results,</a> <a href="https://www.baseball-reference.com/teams/NYY/2026-schedule-scores.shtml">Yankees results</a>) and cleaned up the data through a combination of Numbers and BBEdit. The result was a pair of CSV files that looked like this:</p>
<pre><code>Date,Home,Opponent,Win
Mar 26 2026,TRUE,WSN,-1
Mar 28 2026,TRUE,WSN,1
Mar 29 2026,TRUE,WSN,-1
Mar 30 2026,TRUE,LAA,1
Mar 31 2026,TRUE,LAA,-1
Apr 1 2026,TRUE,LAA,1
Apr 3 2026,FALSE,CLE,-1
Apr 5 2026,FALSE,CLE,1
Apr 5 2026,FALSE,CLE,-1
Apr 6 2026,FALSE,TBR,-1
[etc.]
</code></pre>
<p>These are the first ten games for the Cubs. The Win field contains a +1 for wins and a -1 for losses. The plot of their progress looks like this:</p>
<p><img alt="Cubs and Yankees 2026" class="ss" src="https://leancrew.com/all-this/images2026/20260723-Cubs%20and%20Yankees%202026.png" title="Cubs and Yankees 2026" width="100%"/></p>
<p>The teams were running in parallel from mid-April through early May, before the Cubs’ disastrous slide. The Yankees had their own smaller slide starting in mid-June but seem to have recovered. History has taught Cub fans not to trust the team’s recent success; 60 more games is plenty of time for another slide (or two).</p>
<p>The plot was made with Python, <a href="https://pandas.pydata.org/">Pandas</a>, <a href="https://numpy.org/">NumPy</a>, and <a href="https://matplotlib.org/">Matplotlib</a>. Here’s the code:</p>
<pre><code>python:
 1:  #!/usr/bin/env python3
 2:  
 3:  import pandas as pd
 4:  import numpy as np
 5:  from datetime import datetime
 6:  import matplotlib.pyplot as plt
 7:  from matplotlib.ticker import MultipleLocator, AutoMinorLocator
 8:  from matplotlib.dates import DateFormatter, YearLocator, MonthLocator
 9:  
10:  # Read the files into dataframes
11:  dfCubs = pd.read_csv('cubs-2026.csv', parse_dates=[0])
12:  dfYankees = pd.read_csv('yankees-2026.csv', parse_dates=[0])
13:  
14:  # Calculate the games above .500
15:  dfCubs['Above'] = np.cumsum(dfCubs.Win)
16:  dfYankees['Above'] = np.cumsum(dfYankees.Win)
17:  
18:  # Create the plot with a given size in inches
19:  fig, ax = plt.subplots(figsize=(6, 4))
20:  
21:  # Add lines for each team. Team colors from teamcolorcodes.com.
22:  ax.plot(dfCubs.Date, dfCubs.Above, '-', color='#0E3386', lw=1.5, label='Cubs')
23:  ax.plot(dfYankees.Date, dfYankees.Above, '.', color='#0C2340',ms=5, label='Yankees')
24:  
25:  # Set the limits
26:  plt.ylim(ymin=-5, ymax=20)
27:  
28:  # Set the ticks and add a grid
29:  ax.xaxis.set_major_locator(MonthLocator())
30:  ax.xaxis.set_major_formatter(DateFormatter('%-m/%-d/%y'))
31:  ax.yaxis.set_major_locator(MultipleLocator(5))
32:  ax.grid(linewidth=.5, axis='x', which='major', color='#dddddd', linestyle='-')
33:  ax.grid(linewidth=.5, axis='y', which='major', color='#dddddd', linestyle='-')
34:  
35:  # Title and axis labels
36:  plt.title('Cubs and Yankees 2026')
37:  plt.ylabel('Games above .500')
38:  
39:  # Make the border and tick marks 0.5 points wide
40:  [ i.set_linewidth(0.5) for i in ax.spines.values() ]
41:  ax.tick_params(which='both', width=.5)
42:  
43:  # Add the legend
44:  ax.legend(loc='lower right')
45:  
46:  # Save as PDF
47:  plt.savefig('20260723-Cubs and Yankees 2026.png', format='png', dpi=200)
</code></pre>
<p>I wanted to use lines for both teams and let the team colors distinguish them, but the team colors are too close, so I used markers for the Yankees and added a legend. I did still use the team colors, which I got from the <a href="https://teamcolorcodes.com/mlb-color-codes/">Team Color Codes</a> website. I fiddled with the marker size and line width until the two looked to be of roughly equal importance.</p>
<p>It may seem like this is a lot of code to write for a simple plot, but I use a <a href="https://leancrew.com/all-this/2024/11/semi-automated-plotting/">Typinator abbreviation</a> to insert a code template and just tweak the template to get things looking the way I want. The Pandas and NumPy part of the code, Lines 10–16, was trivial.</p>]]>
</content:encoded>
</item>

<item>
<title>Apple Park and Severance</title>
<link>https://leancrew.com/all-this/2026/07/apple-park-and-severance/</link>
<pubDate>Tue, 21 Jul 2026 23:24:05 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/apple-park-and-severance/</guid>
<description>
  <![CDATA[The indispensable Michael Tsai has <a href="https://mjtsai.com/blog/2026/07/21/apple-park-designed-to-isolate/">an interesting post</a> up today about Apple Park and whether its design isolates the people who work there, despite its having been designed—in part, at least—to encourage collaboration.]]>
</description>
<content:encoded>
  <![CDATA[<p>The indispensable Michael Tsai has <a href="https://mjtsai.com/blog/2026/07/21/apple-park-designed-to-isolate/">an interesting post</a> up today about Apple Park and whether its design isolates the people who work there, despite its having been designed—in part, at least—to encourage collaboration.</p>
<p>As usual, Michael has collected a group of choice quotes on the topic. I won’t link to any of them. You should just go to his blog, read the excerpts he’s assembled, and follow the links to see more. But I’ve often wondered how good Apple Park is as a working environment, especially when I watch <a href="https://tv.apple.com/us/show/severance/umc.cmc.1srk2goyh2q2zdxcx605w8vtx"><em>Severance</em></a>.</p>
<p>As you may know, the Lumon Industries building that Mark, Helly, Irving, and Dylan work in is a <a href="https://bell.works/severance-filming-locations/">former Bell Labs facility</a> in Holmdel, New Jersey. I first learned about the building several years ago, when I read Jon Gertner’s excellent history of The Labs, <a href="https://lccn.loc.gov/2011040207"><em>The Idea Factory</em></a>. It’s a distinctive place designed by Eero Saarinen in the early 60s.</p>
<p><img alt="Severance building" class="ss" src="https://leancrew.com/all-this/images2026/20260721-Severance%20building.jpg" title="Severance building" width="100%"/></p>
<p class="caption">Image from <a href="https://gamerant.com/where-is-severance-filmed/">GameRant</a>.</p>
<p>Bell Labs was known for the fruitful collaboration of its researchers, often started through chance meetings in the hallways of other Bell Labs facilities. The new Holmdel building was supposed to further that sort of teamwork, but things didn’t work out as planned. Here’s Gertner:</p>
<blockquote>
<p>The isolated Holmdel radio labs that had stood on the site for decades—the vast green fields where children would throw boomerangs on weekends, the gracious woodframe building around which engineers would test radio transmissions—were gone. Those labs had been razed. In their stead, on the center of 460 acres of former farmland, Bell Labs had commissioned an enormous modern building to accommodate its growing ranks.</p>
</blockquote>
<p>I should mention here that those “vast green fields” and “isolated Holmdel radio labs” were where <a href="https://public.nrao.edu/gallery/karl-jansky-and-his-merrygoround/">Karl Jansky invented radio astronomy</a>. As for the building itself:</p>
<blockquote>
<p>For obvious reasons, the building was soon nicknamed the Black Box. It was a steel-and-glass six-story structure, serious and austere, designed by the Finnish American architect Eero Saarinen. It was also a monument to architectural presumption. Saarinen, who died before his design was actually built, saw his creation as having the same kind of flexibility as Murray Hill— offices could easily be moved and partitioned, for instance—but with a crucial difference. He placed the building’s long connecting hallways on its glassy perimeter, with the windowless offices and labs in the interior. “Gone completely are the old claustrophobic, dreary, prison-like corridors,” Saarinen remarked with pride. Thanks to the floor-to-ceiling windows, the members of the technical staff would be liberated by unobstructed views of the countryside rather than chance encounters in the hallways.</p>
</blockquote>
<p>I don’t want to hit you over the head with parallels, but does “floor-to-ceiling windows” remind you of any other place? Anyway, how well did the Holmdel building foster innovation?</p>
<blockquote>
<p>[Labs employee Dick Frenkiel] soon came to realize that he had joined an organization that differed from its myth. The Black Box represented one aspect of this evolution. More to the point, the thrust of the work at Bell Labs seemed to have shifted decisively to big projects involving hundreds of people. Frenkiel’s Bell Labs didn’t seem to have anything to do with heroic research on a new amplifier, done by a few men in a hushed lab. It was about large teams attacking knotty problems for years on end.</p>
</blockquote>
<p>I’m sorry, I said I didn’t want to hit you over the head with a̸ c̸a̸r̸ parallels, didn’t I?</p>
<p>Apple has lots of smart people. Some of them must know that the dystopian center of its hit TV show is the 1960s version of Apple Park.</p>]]>
</content:encoded>
</item>

<item>
<title>Sum of cubes via difference tables</title>
<link>https://leancrew.com/all-this/2026/07/sum-of-cubes-via-difference-tables/</link>
<pubDate>Mon, 20 Jul 2026 02:07:47 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/sum-of-cubes-via-difference-tables/</guid>
<description>
  <![CDATA[Yesterday I watched the most recent <a href="https://www.youtube.com/watch?v=cOTf_YEmSOU">Numberphile video</a>, in which Ben Sparks explains a few finite series and the equations that simplify the calculation of their sums. He derives the formulas graphically, and it’s all very cleverly done, especially the one for the sum of cubes.]]>
</description>
<content:encoded>
  <![CDATA[<p>[Equations in this post may not look right (or appear at all) in your RSS reader. Go to <a href="https://leancrew.com/all-this/2026/07/sum-of-cubes-via-difference-tables/">the original article</a> to see them rendered properly.]</p>
  <hr />
  <p>Yesterday I watched the most recent <a href="https://www.youtube.com/watch?v=cOTf_YEmSOU">Numberphile video</a>, in which Ben Sparks explains a few finite series and the equations that simplify the calculation of their sums. He derives the formulas graphically, and it’s all very cleverly done, especially the one for the sum of cubes.</p>
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cOTf_YEmSOU?si=XoODs027usPCimUH" title="YouTube video player" width="100%"></iframe>
<p>The idea is to get a simple polynomial expression in <em>n</em> for</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>N</mi><mo>=</mo><munderover><mo>∑</mo><mrow><mi>m</mi><mo>=</mo><mn>1</mn></mrow><mi>n</mi></munderover><mspace width="0.167em"></mspace><msup><mi>m</mi><mn>3</mn></msup></mrow></math>
<p>As I said, Ben’s graphical method is really clever and easy to understand, and it leads to this expression:</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>N</mi><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup><mspace width="0.167em"></mspace><mo form="prefix" stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><msup><mo form="postfix" stretchy="false">)</mo><mn>2</mn></msup></mrow></math>
<p>I wanted to see if I could get that same result using a nongraphical and less clever method. The method that came to mind was to generate a handful of values, put them in a table, and start taking differences.</p>
<p>Here are some values of <em>n</em>, <em>N</em>, and the first four differences:</p>
<table>
<thead>
<tr>
<th align="right">n</th>
<th align="right">N</th>
<th align="right">Δ</th>
<th align="right">Δ²</th>
<th align="right">Δ³</th>
<th align="right">Δ⁴</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">1</td>
<td align="right">1</td>
<td align="right">8</td>
<td align="right">19</td>
<td align="right">18</td>
<td align="right">6</td>
</tr>
<tr>
<td align="right">2</td>
<td align="right">9</td>
<td align="right">27</td>
<td align="right">37</td>
<td align="right">24</td>
<td align="right">6</td>
</tr>
<tr>
<td align="right">3</td>
<td align="right">36</td>
<td align="right">64</td>
<td align="right">61</td>
<td align="right">30</td>
<td align="right"></td>
</tr>
<tr>
<td align="right">4</td>
<td align="right">100</td>
<td align="right">125</td>
<td align="right">91</td>
<td align="right"></td>
<td align="right"></td>
</tr>
<tr>
<td align="right">5</td>
<td align="right">225</td>
<td align="right">216</td>
<td align="right"></td>
<td align="right"></td>
<td align="right"></td>
</tr>
<tr>
<td align="right">6</td>
<td align="right">441</td>
<td align="right"></td>
<td align="right"></td>
<td align="right"></td>
<td align="right"></td>
</tr>
</tbody>
</table>
<p>The differences are calculated by looking in the preceding column and subtracting the value in the same row from the value in the following row. This sort of thing is fairly easy to do by hand but is <em>really</em> easy to do in a spreadsheet. Here’s a screenshot of the table in Numbers, where I’m displaying the formula for the first difference, Δ:</p>
<p><img alt="Difference table in Numbers" class="ss" src="https://leancrew.com/all-this/images2026/20260719-Difference%20table%20in%20Numbers.png" title="Difference table in Numbers" width="100%"/></p>
<p>That formula can be filled into all the difference cells, which is why it’s so easy. (I included only the first six rows in the table above because I figured you’d trust me that all the values in the fourth difference column, Δ⁴, are the same.)</p>
<p>The constant values in the fourth difference column mean <em>N</em> is a fourth-degree polynomial in <em>n</em>:</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>N</mi><mo>=</mo><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mi>n</mi><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><msup><mi>n</mi><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><msup><mi>n</mi><mn>3</mn></msup><mo>+</mo><msub><mi>a</mi><mn>4</mn></msub><msup><mi>n</mi><mn>4</mn></msup></mrow></math>
<p>Because the constant fourth difference is 6, we know that</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>4</mn></msub><mo>=</mo><mfrac><mn>6</mn><mrow><mn>4</mn><mi>!</mi></mrow></mfrac><mo>=</mo><mfrac><mn>6</mn><mn>24</mn></mfrac><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac></mrow></math>
<p>This comes from the fact that differences are analogous to derivatives, and</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><msup><mi>d</mi><mn>4</mn></msup><mi>N</mi></mrow><mrow><mi>d</mi><msup><mi>n</mi><mn>4</mn></msup></mrow></mfrac><mo>=</mo><mn>4</mn><mo>⋅</mo><mn>3</mn><mo>⋅</mo><mn>2</mn><mo>⋅</mo><mn>1</mn><mspace width="0.167em"></mspace><msub><mi>a</mi><mn>4</mn></msub><mo>=</mo> <mn>24</mn></mrow><mspace width="0.167em"></mspace><msub><mi>a</mi><mn>4</mn></msub></math>
<p>Once we know the degree of the polynomial and have <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>4</mn></msub></math>, we can figure out the other coefficients by solving a set of four simultaneous equations for four different values of <em>n</em> and <em>N</em>. For example:</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mtable columnalign="left">
<mtr><mtd>
<mrow><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mo>⋅</mo><mn>1</mn><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><mo>⋅</mo><msup><mn>1</mn><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><mo>⋅</mo><msup><mn>1</mn><mn>3</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mo>⋅</mo><msup><mn>1</mn><mn>4</mn></msup><mo>=</mo><mn>1</mn></mrow>
</mtd></mtr>
<mtr><mtd>
<mrow><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mo>⋅</mo><mn>2</mn><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><mo>⋅</mo><msup><mn>2</mn><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><mo>⋅</mo><msup><mn>2</mn><mn>3</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mo>⋅</mo><msup><mn>2</mn><mn>4</mn></msup><mo>=</mo><mn>9</mn></mrow>
</mtd></mtr>
<mtr><mtd>
<mrow><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mo>⋅</mo><mn>3</mn><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><mo>⋅</mo><msup><mn>3</mn><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><mo>⋅</mo><msup><mn>3</mn><mn>3</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mo>⋅</mo><msup><mn>3</mn><mn>4</mn></msup><mo>=</mo><mn>36</mn></mrow>
</mtd></mtr>
<mtr><mtd>
<mrow><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mo>⋅</mo><mn>4</mn><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><mo>⋅</mo><msup><mn>4</mn><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><mo>⋅</mo><msup><mn>4</mn><mn>3</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mo>⋅</mo><msup><mn>4</mn><mn>4</mn></msup><mo>=</mo><mn>100</mn></mrow>
</mtd></mtr>
</mtable>
</math>
<p>We could use any four of the six <em>N</em>s we’ve calculated, but the first four are convenient. Let’s solve them in Python using NumPy and the <a href="https://numpy.org/doc/2.3/reference/generated/numpy.linalg.solve.html"><code>solve</code> function</a> from the <code>linalg</code> submodule. We can do this interactively:</p>
<pre><code>python:
from numpy import np

m = np.array([[1, 1, 1, 1], [1, 2, 4, 8], [1, 3, 9, 27], [1, 4, 16, 64]])
b = np.array([1 - 1**4/4, 9 - 2**4/4, 36 - 3**4/4, 100 - 4**4/4])
np.linalg.solve(m, b)
</code></pre>
<p>The last line returns</p>
<pre><code>python:
array([ 1.77635684e-15, -3.99680289e-15,  2.50000000e-01,  5.00000000e-01])
</code></pre>
<p>Those first two elements of the solution array are at the lower limit of what a floating point number can be. So we can say</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>0</mn></msub><mo>=</mo><mn>0</mn><mspace width="1.0em"></mspace><msub><mi>a</mi><mn>1</mn></msub><mo>=</mo><mn>0</mn><mspace width="1.0em"></mspace><msub><mi>a</mi><mn>2</mn></msub><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="1.0em"></mspace><msub><mi>a</mi><mn>3</mn></msub><mo>=</mo><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow></math>
<p>Therefore,</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>N</mi><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>4</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>3</mn></msup><mo>+</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup></mrow></math>
<p>or, after collecting terms,</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>N</mi><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup><mspace width="0.167em"></mspace><mo form="prefix" stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><msup><mo form="postfix" stretchy="false">)</mo><mn>2</mn></msup></mrow></math>
<p>which is the formula Ben got in the video.</p>
<p>Doing basically the same thing in Mathematica is</p>
<pre><code>m = {{1, 1, 1, 1}, {1, 2, 4, 8}, {1, 3, 9, 27}, {1, 4, 16, 64}};
b = {1 - 1^4/4, 9 - 2^4/4, 36 - 3^4/4, 100 - 4^4/4};
LinearSolve[m, b]
</code></pre>
<p>which returns</p>
<pre><code>{0, 0, 1/4, 1/2}
</code></pre>
<p>This is the same as the Python answer but with no need to think about floating point precision.</p>
<p>A third way to solve the simultaneous equations is to do it in a spreadsheet. Here’s how that looks in Numbers:</p>
<p><img alt="Simultaneous equations solution in Numbers" class="ss" src="https://leancrew.com/all-this/images2026/20260719-Simultaneous%20equations%20solution%20in%20Numbers.png" title="Simultaneous equations solution in Numbers" width="100%"/></p>
<p>where</p>
<ul>
<li>the block of yellow cells is the matrix <code>m</code> in the Python and Mathematica solutions;</li>
<li>the block of magenta cells is the inverse of <code>m</code>;</li>
<li>the block of cyan cells is the vector <code>b</code> in the Python and Mathematica solutions; and</li>
<li>the block of gray cells is the solution for <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>0</mn></msub></math> through <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>3</mn></msub></math>, determined by multiplying the magenta matrix by the cyan vector.</li>
</ul>
<p>The spreadsheet uses a combination of <a href="https://support.apple.com/guide/functions/minverse-ffa8a8890aa0/web"><code>MINVERSE</code></a> and <a href="https://support.apple.com/guide/functions/mmult-ffa6eccc9b2e/15.3/web/1.0"><code>MMULT</code></a>, functions that are also in Excel and Google Sheets. As with the Python solution, there are floating point artifacts here. They’re mostly hidden by my choice to show only four decimal places, but that -0.0000 for <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>1</mn></msub></math> is a clue that these are not exact answers.</p>
<p>You might well ask why I’d bother using Python or Mathematica to solve the simultaneous equations if I already had a spreadsheet open to make the difference table. The answer is that I generally prefer working in an environment where my formulas and expressions are always visible. It makes things easier to debug, and I’m always debugging.</p>
<hr/>
<p>There are other ways to determine the coefficients. My favorite is a step-by-step procedure that simplifies the problem one polynomial degree at a time.</p>
<p>Given that we know <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>4</mn></msub><mo>=</mo><mn>1</mn><mi>/</mi><mn>4</mn></mrow></math> from the difference table above, we can make a new table for</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>P</mi><mo>=</mo><mi>N</mi><mo>−</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>4</mn></msup></mrow></math>
<p>and its differences. By subtracting the fourth-degree term from <em>N</em>, we expect <em>P</em> to be a third-degree polynomial. Here are the first five rows of the difference table for <em>P</em>:</p>
<table>
<thead>
<tr>
<th align="right">n</th>
<th align="right">P</th>
<th align="right">Δ</th>
<th align="right">Δ²</th>
<th align="right">Δ³</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">1</td>
<td align="right">0.75</td>
<td align="right">4.25</td>
<td align="right">6.50</td>
<td align="right">3.00</td>
</tr>
<tr>
<td align="right">2</td>
<td align="right">5.00</td>
<td align="right">10.75</td>
<td align="right">9.50</td>
<td align="right">3.00</td>
</tr>
<tr>
<td align="right">3</td>
<td align="right">15.75</td>
<td align="right">20.25</td>
<td align="right">12.50</td>
<td align="right"></td>
</tr>
<tr>
<td align="right">4</td>
<td align="right">36.00</td>
<td align="right">32.75</td>
<td align="right"></td>
<td align="right"></td>
</tr>
<tr>
<td align="right">5</td>
<td align="right">68.75</td>
<td align="right"></td>
<td align="right"></td>
<td align="right"></td>
</tr>
</tbody>
</table>
<p>As expected, they become constant at the third difference. Using the same technique we used to get <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>4</mn></msub></math>, we can say</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>3</mn></msub><mo>=</mo><mfrac><mn>3</mn><mrow><mn>3</mn><mi>!</mi></mrow></mfrac><mo>=</mo><mfrac><mn>3</mn><mn>6</mn></mfrac><mo>=</mo><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow></math>
<p>Moving on, we make a table for</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>Q</mi><mo>=</mo><mi>P</mi><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>3</mn></msup></mrow></math>
<p>and its differences. Here are the first four rows of that table:</p>
<table>
<thead>
<tr>
<th align="right">n</th>
<th align="right">Q</th>
<th align="right">Δ</th>
<th align="right">Δ²</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">1</td>
<td align="right">0.25</td>
<td align="right">0.75</td>
<td align="right">0.50</td>
</tr>
<tr>
<td align="right">2</td>
<td align="right">1.00</td>
<td align="right">1.25</td>
<td align="right">0.50</td>
</tr>
<tr>
<td align="right">3</td>
<td align="right">2.25</td>
<td align="right">1.75</td>
<td align="right"></td>
</tr>
<tr>
<td align="right">4</td>
<td align="right">4.00</td>
<td align="right"></td>
<td align="right"></td>
</tr>
</tbody>
</table>
<p>These become constant at the second difference, and we can say</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>2</mn></msub><mo>=</mo><mfrac><mrow><mn>1</mn><mi>/</mi><mn>2</mn></mrow><mrow><mn>2</mn><mi>!</mi></mrow></mfrac><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac></mrow></math>
<p>Finally, we make a table for</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>R</mi><mo>=</mo><mi>Q</mi><mo>−</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup></mrow></math>
<p>which is this:</p>
<table>
<thead>
<tr>
<th align="right">n</th>
<th align="right">R</th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">1</td>
<td align="right">0.00</td>
</tr>
<tr>
<td align="right">2</td>
<td align="right">0.00</td>
</tr>
<tr>
<td align="right">3</td>
<td align="right">0.00</td>
</tr>
<tr>
<td align="right">4</td>
<td align="right">0.00</td>
</tr>
</tbody>
</table>
<p>Since all the <em>R</em> values are zero, the rest of the coefficients, <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>1</mn></msub></math> and <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>a</mi><mn>0</mn></msub></math>, must be zero, and we’re done.</p>
<p>If it’s not obvious why <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>1</mn></msub><mo>=</mo><msub><mi>a</mi><mn>0</mn></msub><mo>=</mo><mn>0</mn></mrow></math>, consider this:</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mtable columnalign="left">
<mtr>
<mtd><mi>R</mi><mspace height="1em"></mspace></mtd>
<mtd><mo>=</mo></mtd>
<mtd><mi>N</mi><mo>−</mo><msub><mi>a</mi><mn>4</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>4</mn></msup><mo>−</mo><msub><mi>a</mi><mn>3</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>3</mn></msup><mo>−</mo><msub><mi>a</mi><mn>2</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup></mtd>
</mtr>
<mtr>
<mtd><mspace height="1em"></mspace></mtd>
<mtd><mo>=</mo></mtd>
<mtd><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mspace width="0.167em"></mspace><mi>n</mi><mo>+</mo><msub><mi>a</mi><mn>2</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup><mo>+</mo><msub><mi>a</mi><mn>3</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>3</mn></msup><mo>+</mo><msub><mi>a</mi><mn>4</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>4</mn></msup><mo>−</mo><msub><mi>a</mi><mn>4</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>4</mn></msup><mo>−</mo><msub><mi>a</mi><mn>3</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>3</mn></msup><mo>−</mo><msub><mi>a</mi><mn>2</mn></msub><mspace width="0.167em"></mspace><msup><mi>n</mi><mn>2</mn></msup>
</mtd>
</mtr>
<mtr>
<mtd><mspace height="1em"></mspace></mtd>
<mtd><mo>=</mo>
</mtd>
<mtd><msub><mi>a</mi><mn>0</mn></msub><mo>+</mo><msub><mi>a</mi><mn>1</mn></msub><mspace width="0.167em"></mspace><mi>n</mi>
</mtd>
</mtr>
</mtable></math>
<p>The only way for <em>R</em> to be zero for all values of <em>n</em> is if <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>a</mi><mn>1</mn></msub><mo>=</mo><msub><mi>a</mi><mn>0</mn></msub><mo>=</mo><mn>0</mn></mrow></math>.</p>
<p>Building these tables is fairly easy, but it’s not as fast as building and solving the simultaneous equations. Still, there’s some satisfaction in marching to the answer this way. It’s basically a recursive solution, where we’re simplifying the problem with each step.</p>
<hr/>
<p>Using difference tables to work out the formula for the sum of cubes isn’t as slick as the graphical method shown in the video, but it does have the advantage of not requiring any ingenuity. I’m not opposed to ingenuity, but sometimes you want to just set the problem up, turn the mathematical crank, and get the answer.</p>
  ]]>
</content:encoded>
</item>

<item>
<title>Permanent Daylight Saving Time</title>
<link>https://leancrew.com/all-this/2026/07/permanent-daylight-saving-time/</link>
<pubDate>Fri, 17 Jul 2026 17:48:42 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/permanent-daylight-saving-time/</guid>
<description>
  <![CDATA[A couple of days ago, Casey Liss took a break from arguing about temperature scales to <a href="https://mastodon.social/@caseyliss/116924926813064237">tweak me</a> about the <a href="https://apnews.com/article/daylight-saving-time-house-passes-bill-53e7ffd1c3e9beddb9ab1601a8482ad5">recent passage</a> of the <a href="https://www.congress.gov/bill/119th-congress/house-bill/139">Sunshine Protection Act</a> by the House. The Act would make Daylight Saving Time permanent, something Casey knows <a href="https://leancrew.com/all-this/2013/03/why-i-like-dst/">I disapprove of</a>. A <a href="https://www.congress.gov/bill/117th-congress/senate-bill/623">similar bill</a> passed the Senate a few years ago, and Donald Trump has said he will sign this one, so there’s a decent chance it’ll become law. Let’s see what will happen if it does.]]>
</description>
<content:encoded>
  <![CDATA[<p>A couple of days ago, Casey Liss took a break from arguing about temperature scales to <a href="https://mastodon.social/@caseyliss/116924926813064237">tweak me</a> about the <a href="https://apnews.com/article/daylight-saving-time-house-passes-bill-53e7ffd1c3e9beddb9ab1601a8482ad5">recent passage</a> of the <a href="https://www.congress.gov/bill/119th-congress/house-bill/139">Sunshine Protection Act</a> by the House. The Act would make Daylight Saving Time permanent, something Casey knows <a href="https://leancrew.com/all-this/2013/03/why-i-like-dst/">I disapprove of</a>. A <a href="https://www.congress.gov/bill/117th-congress/senate-bill/623">similar bill</a> passed the Senate a few years ago, and Donald Trump has said he will sign this one, so there’s a decent chance it’ll become law. Let’s see what will happen if it does.</p>
<p>First, of course, there will be a lot of cheering from the people who moan about changing their clocks twice a year. Well, some of the moaners will cheer—the ones who wanted to eliminate DST and stay on Standard Time all year will grumble, but they’ll probably still be pleased to be released from that terrible burden.</p>
<p>I’m more interested in the consequences of permanent DST. You may recall <a href="https://leancrew.com/all-this/2023/11/more-general-sunrise-sunset-plots/">my sunrise/sunset plots</a>. Here’s one for Chicago in 2026:</p>
<p><img alt="Sunrise-sunset in Chicago" class="ss" src="https://leancrew.com/all-this/images2026/20260717-Sunrise-sunset%20in%20Chicago.png" title="Sunrise-sunset in Chicago" width="100%"/></p>
<p>The dirty yellow zones cover the DST period, which currently runs from the second Sunday in March to the first Sunday in November. A small change to the <code>sunplot</code> code extends that zone to the entire year:</p>
<p><img alt="Sunrise-sunset in Chicago with permanent DST" class="ss" src="https://leancrew.com/all-this/images2026/20260717-Sunrise-sunset%20in%20Chicago%20with%20permanent%20DST.png" title="Sunrise-sunset in Chicago with permanent DST" width="100%"/></p>
<p>I left the Standard Time lines in place for comparison, even though there won’t be any Standard Time if the Act becomes law.</p>
<p>As you can see, there will be a long stretch—more than two months—for which sunrise will be after 8:00.<sup id="fnref:2026"><a href="#fn:2026" rel="footnote">1</a></sup> I’m sure this won’t bother many of you who don’t do anything before 8:00, but there are lots of people it will bother. And I bet we’ll hear from them, even though a good chunk of them will be from the current cohort of clock-change moaners.</p>
<p>People living near the western edge of a time zone will have even more morning darkness. You may recall <a href="https://leancrew.com/all-this/2026/07/indiana-jewel-box-bank/">my visit</a> to a Louis Sullivan bank in West Lafayette, Indiana, a couple of weeks ago. My photos showed the sun shining on the north side of the bank, which happened because the sun rises late in West Lafayette. (By “late” I mean in local clock time. You could make an argument that the Sun, <a href="https://www.youtube.com/watch?v=HvWCnqY-GWQ">like Gandalf</a>, is never late. Nor is it early. It rises precisely when it means to.)</p>
<p>Let’s see the sunrise/sunset times in West Lafayette under permanent DST:</p>
<p><img alt="Sunrise-sunset in West Lafayette with permanent DST" class="ss" src="https://leancrew.com/all-this/images2026/20260717-Sunrise-sunset%20in%20West%20Lafayette%20with%20permanent%20DST.png" title="Sunrise-sunset in West Lafayette with permanent DST" width="100%"/></p>
<p>Basically five months for which the sun never rises before 8:00. And about seven weeks for which it doesn’t rise before 9:00. No one deserves that—not even Boilermakers.</p>
<div class="update">
<p><strong>Update 29 Jul 2026 4:46 PM</strong><br/>
A few days ago, Timothy King pointed out <a href="https://mastodon.social/@wassilywabbit/116984457293402680">on Mastodon</a> that Michigan’s Upper Peninsula will have even later winter sunrises under DST than western Indiana. Absolutely correct. Most of the UP is on Eastern Time, even some parts that are west of Chicago.</p>
<p>This reminded me that Nathan Grigg wrote a couple of nice posts on the topic that I forgot to link to. <a href="https://nathangrigg.com/2026/07/usa-sunrise-times/">The first one</a> has a lovely map of the contiguous US that shows the latest sunrise time under the current DST rules. Permanent DST would put sunrise in Houghton, Michigan, between 9:30 and 10:00. <a href="https://nathangrigg.com/2026/07/us-time-zone-alignment/">The second post</a> has a map showing which parts of the country are more than 7.5° from the center longitude of their time zones. Except for a tiny portion of Maine, all of these misaligned areas are west of where their time zone would end if time zones were governed strictly by longitude.</p>
<p>I meant to add an update like this right after Nathan wrote his posts, but it slipped my mind. Thanks to Timothy for reminding me, and apologies to Nathan for being tardy.</p>
</div>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:2026">
<p>Yes, the graph is just for 2026, but sunrise times don’t change all that much from year to year. <a href="#fnref:2026" rev="footnote">↩</a></p>
</li>
</ol>
</div>]]>
</content:encoded>
</item>

<item>
<title>Floating Saturn calculations</title>
<link>https://leancrew.com/all-this/2026/07/floating-saturn-calculations/</link>
<pubDate>Fri, 17 Jul 2026 02:14:08 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/floating-saturn-calculations/</guid>
<description>
  <![CDATA[You’ve probably seen somewhere that the density of Saturn is less than that of water. If there were a bathtub big enough to hold it, Saturn would float. I think I first read this in one of Isaac Asimov’s collections of science essays. If you do an image search, you can easily find <a href="https://www.google.com/search?hs=qZbV&amp;sca_esv=dd11972937bd8ee9&amp;udm=2&amp;fbs=ABfTbFVyMZGZf1hfvX9uKjN_-G8c4u0nXx4bEIpwm1lnNH832SMIiTl3t-JZ4hGJOxPbHYSIu8Q64jU5EwQ-803VaKbd8XGNh2EAGT96nVa30badWeLcMOEWQcU9WGmTgQb8mz6mPncLV6BMXrUfQD5ftu4HKiVJOOiqhNYT8Keh_15lhVLohIhM8kHnw_tixWHDpdAHWCTimc1GO2znH-0EdfLD74c9fw&amp;q=saturn+floating+in+water&amp;sa=X&amp;ved=2ahUKEwjGueL9vNiVAxXjqysGHThVNzEQtKgLegQIGhAB&amp;biw=1106&amp;bih=835&amp;dpr=2">many illustrations</a> of Saturn floating in water. Most of these show no more than half of Saturn under the water. Could that be right?]]>
</description>
<content:encoded>
  <![CDATA[<p>[Equations in this post may not look right (or appear at all) in your RSS reader. Go to <a href="https://leancrew.com/all-this/2026/07/floating-saturn-calculations/">the original article</a> to see them rendered properly.]</p>
  <hr />
  <p>You’ve probably seen somewhere that the density of Saturn is less than that of water. If there were a bathtub big enough to hold it, Saturn would float. I think I first read this in one of Isaac Asimov’s collections of science essays. If you do an image search, you can easily find <a href="https://www.google.com/search?hs=qZbV&amp;sca_esv=dd11972937bd8ee9&amp;udm=2&amp;fbs=ABfTbFVyMZGZf1hfvX9uKjN_-G8c4u0nXx4bEIpwm1lnNH832SMIiTl3t-JZ4hGJOxPbHYSIu8Q64jU5EwQ-803VaKbd8XGNh2EAGT96nVa30badWeLcMOEWQcU9WGmTgQb8mz6mPncLV6BMXrUfQD5ftu4HKiVJOOiqhNYT8Keh_15lhVLohIhM8kHnw_tixWHDpdAHWCTimc1GO2znH-0EdfLD74c9fw&amp;q=saturn+floating+in+water&amp;sa=X&amp;ved=2ahUKEwjGueL9vNiVAxXjqysGHThVNzEQtKgLegQIGhAB&amp;biw=1106&amp;bih=835&amp;dpr=2">many illustrations</a> of Saturn floating in water. Most of these show no more than half of Saturn under the water. Could that be right?</p>
<p>Because there were several things I should have been doing this afternoon, I decided to work out how much of Saturn should be underwater in these images. Even better, I’d do the more general problem: how much of any sphere would be submerged in a liquid if the density of the sphere is less than that of the liquid?</p>
<p>Here’s a cross-section of the problem:</p>
<p><img alt="Floating sphere" class="ss" src="https://leancrew.com/all-this/images2026/20260716-Floating%20sphere.png" title="Floating sphere" width="75%"/></p>
<p>We’ll say the sphere has a radius <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>r</mi></math>, a diameter <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>d</mi><mo>=</mo><mn>2</mn><mi>r</mi></mrow></math>, and a uniform density of <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>ρ</mi><mi>s</mi></msub></math>. The density of the liquid is <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>ρ</mi><mi>ℓ</mi></msub></math>. Because the sphere floats, <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>ρ</mi><mi>s</mi></msub><mo>&lt;</mo><msub><mi>ρ</mi><mi>ℓ</mi></msub></mrow></math>. The distance <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>b</mi></math> is how far the bottom of the sphere is below the liquid surface.</p>
<p>The mechanics of the system is simple: the mass of liquid displaced by the submerged portion of the sphere is equal to the entire mass of the sphere. That is,</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>ρ</mi><mi>ℓ</mi></msub><mspace width="0.167em"></mspace><msub><mi>V</mi><mi>b</mi></msub><mo>=</mo><msub><mi>ρ</mi><mi>s</mi></msub><mspace width="0.167em"></mspace><msub><mi>V</mi><mi>s</mi></msub></mrow></math>
<p>where</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>V</mi><mi>b</mi></msub><mo>=</mo><mfrac><mrow><mi>π</mi><msup><mi>b</mi><mn>2</mn></msup></mrow><mn>3</mn></mfrac><mspace width="0.167em"></mspace><mo form="prefix" stretchy="false">(</mo><mn>3</mn><mi>r</mi><mo>−</mo><mi>b</mi><mo form="postfix" stretchy="false">)</mo></mrow></math>
<p>is the volume of the submerged portion of the sphere and</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><msub><mi>V</mi><mi>s</mi></msub><mo>=</mo><mfrac><mn>4</mn><mn>3</mn></mfrac><mi>π</mi><msup><mi>r</mi><mn>3</mn></msup></mrow></math>
<p>is the volume of the entire sphere. These expressions are usually given in terms of <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>r</mi></math>, as I’ve shown here, but eventually I want to work out the value of <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>b</mi></math> as a fraction of <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>d</mi></math>.</p>
<p>In fact, since I want to make a plot, which requires pure numbers, let’s nondimensionalize our variables by saying</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>β</mi><mo>=</mo><mfrac><mi>b</mi><mi>r</mi></mfrac><mspace width="1.0em"></mspace><mrow><mi mathvariant="normal">a</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">d</mi></mrow><mspace width="1.0em"></mspace><mi>ρ</mi><mo>=</mo><mfrac><msub><mi>ρ</mi><mi>s</mi></msub><msub><mi>ρ</mi><mi>ℓ</mi></msub></mfrac></mrow></math>
<p>Putting all this together and doing a little algebra, we get</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>r</mi><msub><mi>ρ</mi><mi>ℓ</mi></msub><mo form="prefix" stretchy="false">(</mo><msup><mi>β</mi><mn>3</mn></msup><mo>−</mo><mn>3</mn><msup><mi>β</mi><mn>2</mn></msup><mo>+</mo><mn>4</mn><mi>ρ</mi><mo form="postfix" stretchy="false">)</mo><mo>=</mo><mn>0</mn></mrow></math>
<p>Since neither <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>r</mi></math> nor <math xmlns="http://www.w3.org/1998/Math/MathML"><msub><mi>ρ</mi><mi>ℓ</mi></msub></math> is zero, the expression in the parentheses must be. In other words,</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>ρ</mi><mo>=</mo><mfrac><mn>1</mn><mn>4</mn></mfrac><mo form="prefix" stretchy="false">(</mo><mn>3</mn><msup><mi>β</mi><mn>2</mn></msup><mo>−</mo><msup><mi>β</mi><mn>3</mn></msup><mo form="postfix" stretchy="false">)</mo></mrow></math>
<p>Since <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mn>0</mn><mo>&lt;</mo><mi>β</mi><mo>≤</mo><mn>2</mn></mrow></math>, the right-hand side of the equation must be greater than zero, so we don’t have to worry about negative densities.</p>
<p>Typically, we’d want to calculate <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>β</mi></math> for a given value of <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>ρ</mi></math>, so this equation isn’t in the most useful form. But it’s easy to plot values using this equation, even if we do want <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>ρ</mi></math> to be plotted on the horizontal axis.</p>
<p>I mentioned earlier that I prefer to show the depth <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>b</mi></math> as a fraction of the diameter, not the radius, i.e.,</p>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mi>b</mi><mi>d</mi></mfrac><mo>=</mo><mfrac><mi>b</mi><mrow><mn>2</mn><mi>r</mi></mrow></mfrac><mo>=</mo><mfrac><mi>β</mi><mn>2</mn></mfrac></mrow></math>
<p>Here’s that plot, which comes out in a sigmoidal shape:</p>
<p><img alt="Floating sphere plot" class="ss" src="https://leancrew.com/all-this/images2026/20260716-Floating%20sphere%20plot.png" title="Floating sphere plot" width="100%"/></p>
<p>To figure out how much of Saturn would be under the water, we need Saturn’s density, which we can find on NASA’s old <a href="https://web.archive.org/web/20250821165423/https://nssdc.gsfc.nasa.gov/planetary/factsheet/saturnfact.html">Saturn Fact Sheet</a>, as archived on the Wayback Machine. It’s <math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mn>687</mn><mspace width="0.278em"></mspace><mrow><mi mathvariant="normal">k</mi><mi mathvariant="normal">g</mi><mi mathvariant="normal">/</mi><msup><mi mathvariant="normal">m</mi><mn mathvariant="normal">3</mn></msup></mrow></mrow></math>, which means our density ratio is 0.687. Looking that up in the plot, we see that the submerged portion of Saturn is between 60% and 65% of its diameter. A quick numerical solution gives us 62.7% of the diameter. Only 37.3% would be high and dry.</p>
<p>So those many illustrations showing more than half of Saturn’s diameter sticking out above the water are all wet.<sup id="fnref:yes"><a href="#fn:yes" rel="footnote">1</a></sup> Of course, just seeing that Saturn’s specific gravity was over 0.5 is enough to know that most of it is underwater, but now we can put a number on it.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn:yes">
<p>Yes, I went there. <a href="#fnref:yes" rev="footnote">↩</a></p>
</li>
</ol>
</div>
  ]]>
</content:encoded>
</item>

<item>
<title>Plotting of and by students</title>
<link>https://leancrew.com/all-this/2026/07/plotting-of-and-by-students/</link>
<pubDate>Fri, 10 Jul 2026 02:54:30 +0000</pubDate>
<dc:creator>
  <![CDATA[Dr. Drang]]>
</dc:creator>
<guid>https://leancrew.com/all-this/2026/07/plotting-of-and-by-students/</guid>
<description>
  <![CDATA[I saw <a href="https://www.insidehighered.com/news/faculty/learning-assessment/2026/07/08/brown-professor-suspects-most-his-class-used-ai-cheat">this article</a> at <em>Inside Higher Ed</em> this morning, guided by a <a href="https://fosstodon.org/@Techmeme@techhub.social/116888374760055001">Mastodon post</a> from Techmeme. The title of the article is “Brown Professor Suspects Majority of His Class Used AI to Cheat,” so if you’re sick to death of reading about AI—pro, con, or caveated—don’t feel obligated to follow the link. I’m interested in a plot included in the article more than the article itself.]]>
</description>
<content:encoded>
  <![CDATA[<p>I saw <a href="https://www.insidehighered.com/news/faculty/learning-assessment/2026/07/08/brown-professor-suspects-most-his-class-used-ai-cheat">this article</a> at <em>Inside Higher Ed</em> this morning, guided by a <a href="https://fosstodon.org/@Techmeme@techhub.social/116888374760055001">Mastodon post</a> from Techmeme. The title of the article is “Brown Professor Suspects Majority of His Class Used AI to Cheat,” so if you’re sick to death of reading about AI—pro, con, or caveated—don’t feel obligated to follow the link. I’m interested in a plot included in the article more than the article itself.</p>
<p>An economics professor gave his class a take-home midterm, and the grades on it were much higher than usual. He suspected the high marks came from the students using LLMs to answer the questions, so the final exam was done in class and the marks were generally much lower. Here’s the plot given in the article:</p>
<p><img alt="Test grades by student from IHE" class="ss" src="https://leancrew.com/all-this/images2026/20260709-Test%20grades%20by%20student%20from%20IHE.png" title="Test grades by student from IHE" width="80%"/></p>
<p class="caption">Image from <a href="https://www.insidehighered.com/news/faculty/learning-assessment/2026/07/08/brown-professor-suspects-most-his-class-used-ai-cheat"><em>Inside Higher Ed</em></a>.</p>
<p>Let me start by saying I have no criticisms of the plot, just some comments about things that struck me.</p>
<p>First, the upper portion of the chart made me think the students, S1 through S59, were ordered according to their score on the final exam (the gray dots and figures). But as you go down the list, you soon see that that isn’t the case. After reading past the chart, I saw that the professor decided to throw out the results of the midterm and use the final exam as 80% of the course grade. Presumably, the students were sorted by their course grade.</p>
<p>More important, though, was the chart’s layout. When plotting a pair of scores for every student, the usual convention would be to have the students (the categories) laid out along the horizontal axis and their scores (the values) plotted on the vertical axis. This does it the other way around. There’s nothing wrong with doing it that way; it’s just unusual. Sort of like seeing a time series chart in which time is on the vertical axis. There can be good reasons to do it, but usually people don’t.</p>
<p>I first read the article on my phone, so I wondered if the layout was driven by the aspect ratio of most phones in portrait mode. In fact, since the chart is not actually an image but some sort of JavaScript thingy from <a href="https://www.datawrapper.de/">Datawrapper</a>. At least I think that’s what it is—I couldn’t select the chart as an image, and when I looked at the page’s HTML, I saw it was in an <code>&lt;iframe&gt;</code> element.</p>
<p>This made me wonder if the chart would flip to a more conventional layout if the aspect ratio of the browser were different. Turning my phone to landscape mode didn’t flip the axes, nor did opening the page on my MacBook Pro with a wide Safari window. Clearly the author of the article, Emma Whitford, thought it was best to have the students running down the vertical axis.</p>
<p>I decided to see what a more conventional layout would look like. I used the link on the page to download the plot’s data as a CSV file—a very thoughtful addition to the article and something I wish more authors did—and whipped out a quick plot in <a href="https://matplotlib.org/">Matplotlib</a>. Here it is:</p>
<p><a href="https://leancrew.com/all-this/images2026/20260709-Midterm%20and%20final%20exam%20results.png"><img alt="Midterm and final exam results" class="ss" src="https://leancrew.com/all-this/images2026/20260709-Midterm%20and%20final%20exam%20results.png" title="Midterm and final exam results" width="100%"/></a></p>
<p>Even in a wide browser window, it’s pretty tightly constrained, mainly because I have a width limit on the content portion of ANIAT (that’s to keep lines of text of reasonable length). If you click on the chart, it’ll open to the full width of your browser window, which will make it easier to peruse.</p>
<p>Here’s the code that produced the chart:</p>
<pre><code>python:
 1:  #!/usr/bin/env python3
 2:  
 3:  import pandas as pd
 4:  import numpy as np
 5:  import matplotlib.pyplot as plt
 6:  from matplotlib.ticker import MultipleLocator, AutoMinorLocator
 7:  
 8:  # Read in the exam scores
 9:  df = pd.read_csv('scores.csv')
10:  
11:  # Create the plot with a given size in inches
12:  fig, ax = plt.subplots(figsize=(12, 6))
13:  
14:  # Bar colors are based on whether midterm was higher than final
15:  colors = ['#0571b0']*59
16:  for i in range(59):
17:    if df.Final[i] &gt; df.Midterm[i]:
18:      colors[i] = '#ca0020'
19:  
20:  # Plot the scores as columns between the final and midterm scores
21:  ax.bar(df.Student, df.Midterm-df.Final, bottom=df.Final, width=.5, color=colors, zorder=10)
22:  
23:  # Set the limits
24:  plt.xlim(xmin=0, xmax=60)
25:  plt.ylim(ymin=0, ymax=100)
26:  
27:  # Set the major and minor ticks and add a grid
28:  ax.xaxis.set_major_locator(MultipleLocator(5))
29:  ax.xaxis.set_minor_locator(AutoMinorLocator(5))
30:  ax.yaxis.set_major_locator(MultipleLocator(20))
31:  ax.yaxis.set_minor_locator(AutoMinorLocator(2))
32:  ax.grid(linewidth=.5, axis='x', which='both', color='#dddddd', linestyle='-', zorder=0)
33:  ax.grid(linewidth=.5, axis='y', which='both', color='#dddddd', linestyle='-', zorder=0)
34:  
35:  # Title and axis labels
36:  plt.title('Final to midterm exam result ranges')
37:  plt.xlabel('Student ID')
38:  plt.ylabel('Score')
39:  
40:  # Make the border and tick marks 0.5 points wide
41:  [ i.set_linewidth(0.5) for i in ax.spines.values() ]
42:  ax.tick_params(which='both', width=.5)
43:  
44:  # Add a note
45:  ax.text(5, 25, 'Midterms were higher than finals except for Student 22', va='center')
46:  
47:  # Save as PDF
48:  plt.savefig('20260709-Midterm and final exam results.png', format='png', bbox_inches='tight', dpi=150)
</code></pre>
<p>A few comments on this:</p>
<ul>
<li>I didn’t put dots where the midterm and final scores were. The ends of the columns do the job (but see below).</li>
<li>I didn’t include the test scores in the chart. Although that can be helpful in situations in which a data file isn’t provided, my tendency is to let tables be tables and charts be charts. A chart should give you a feel for the data without including the numbers themselves. I don’t always follow this principle, and it doesn’t bother me when others don’t—as long as the numbers don’t detract from the plot.</li>
<li>I didn’t label every student. It makes the horizontal axis too busy and doesn’t help the reader.</li>
<li>I changed the color of the column for the one student who got a higher score on the final than the midterm and also added an explanatory note. This may be too cute. Colored dots for the two scores, as in the IHE chart, are certainly more obvious, even though they make the chart more cluttered.</li>
<li>I got the two column colors, <code>#0571b0</code> and <code>#ca0020</code>, from <a href="https://colorbrewer2.org/#type=sequential&amp;scheme=BuGn&amp;n=3">ColorBrewer</a>.</li>
<li>If you run the code yourself, you’ll find ticks and labels for nonexistent students 0 and 60. Rather than adding a bunch of Matplotlib code to get rid of those ticks and labels, I opened the PNG file in <a href="https://flyingmeat.com/acorn/">Acorn</a> and erased them. I don’t consider this cheating.</li>
</ul>
<p>Overall, I think my chart works, and I had fun thinking about how to make it. But it’s not better than the original.</p>
<div class="update">
<p><strong>Update 10 Jul 2026 5:07 AM</strong><br/>
Sometimes I just want to be done with a post, and I publish it before I should. That’s what happened last night. While it’s true that categories are usually laid out horizontally, I shouldn’t have left the impression that it’s tremendously rare for them to be laid out vertically. There are plenty of good vertical examples.</p>
<p>A clear reason for a vertical layout is a large number of categories, and while 59 categories isn’t especially large, it’s definitely heading in that direction. I made my chart mainly to see if a horizontal layout can work with 59 categories, and I think it can—at least if you can give your plot enough horizontal space.</p>
<p>(Another good reason for a vertical layout is that it works better typographically. Sometimes the categories have long names, and they fit better in a column than in a row. That isn’t the case here, but it happens.)</p>
<p>Thanks to <a href="https://fosstodon.org/@jannem/116893573652317907">Janne Moren</a> for making me realize that this post was too blunt as originally written.</p>
</div>]]>
</content:encoded>
</item>

</channel>
</rss>

