Where we start
The rows are the ones session 3 built: every GitHub account whose id is divisible by 20, a deterministic 5 percent sample, watched in two sampled hours a day (03:00 and 15:00 UTC) for the 28 days from 2024-03-04 to 2024-03-31. That is 56 hourly files from GH Archive, 79,555 accounts seen at least once, 98 accounts with a [bot] suffix dropped. An account is "active on a day" if it did anything in either sampled hour of that UTC day. Two hours out of 24 is a thin slice, and the slice matters for everything on this page: it pushes every account to the left of the curve, and it is why the curve is taught as what the sample can see, never as GitHub's real stickiness.
★ What you walk out with tonight. A Python script that turns 56 GH Archive hours into a days-per-account count, an L28 histogram saved as a 300 DPI PNG and an SVG, the six band shares it prints (66.7, 24.0, 7.5, 1.6, 0.2 and 0.1 percent), two concentration numbers (a tenth of accounts hold 33.5 percent of account-days; a hundredth hold 30.1 percent of events), and a name check that finds 17 of the 105 accounts on 22 or more days wearing bot-like names without the suffix.
Count the days, then draw the count
6 min liveLi Jin and Andrew Chen (a16z, 2018) define the power-user curve as "a histogram of users' engagement by the total number of days they were active in a month", and note it is "also commonly called the activity histogram or the 'L30' (coined by the Facebook growth team)". Ours is an L28 because the sample covers 28 days. The x axis is days active, 1 to 28; the y axis is how many accounts landed on each day count. Every account contributes exactly one mark. That is the whole construction, and it is why the curve is more honest than DAU/MAU, which the same post says "is a single number and so blurs this variance".
LiveFrom day-marks to a histogram3 min▶
The construction has two steps and no statistics. First, per account, collect the set of UTC days on which it was seen; the size of that set is its score, 1 to 28. Second, count how many accounts landed on each score. That gives a list of 28 counts, and the histogram is that list drawn as bars. For the sample under "any event" the first count is 53,046 and the last is 62: 53,046 accounts seen on exactly one day, 62 seen on all 28.
Grouping into bands is a presentation choice, and the one on the figure (1, 2-3, 4-7, 8-14, 15-21, 22-28) is the one the script prints. Bands make the shape readable across the room; the 28-bar version is what you inspect at your desk. Keep both. The band shares under "any event" are 66.7, 24.0, 7.5, 1.6, 0.2 and 0.1 percent, and they sum to the MAU of 79,555 because every active account is in exactly one band.
Self-studyThree definitions, three curves2 min▶
Session 3's three definitions of active each produce their own L28, and the shape moves with the definition. Under "pushed code" (MAU 48,946) the bands read 64.2, 26.0, 8.0, 1.5, 0.1 and 0.2 percent, with 57 accounts on all 28 days. Under "opened an issue or pull request" (MAU 8,993) they read 77.5, 18.3, 3.7 and 0.4 percent for the first four bands, then almost nothing: 1 account on all 28 days, 4 on 22 or more. The rarer the behaviour, the further left the curve leans. Publish the definition with the curve, every time, because the curve is unreadable without it.
Jin and Chen's post says Facebook "would have a very right-leaning smile, with 60%+ of its MAUs coming back daily". That is their description, not a number anyone outside Facebook has checked, so hold it as a claim about what a social product can look like, not as a benchmark to hit.
The ratio that hid two products. A common pattern, no numbers attached: a B2B tool reports a healthy DAU/MAU and a flat MAU, so nobody looks. The L28 shows a spike at one day and a second bump at 20-plus days, with almost nothing between. Two populations are sharing one ratio: people who evaluated it once and left, and a core team that lives in it. The average of those two groups describes nobody, and the histogram is the cheapest way to find out you have two products wearing one name.
Who owns the account-days
5 min liveA left-leaning curve says most accounts are occasional. Concentration asks the complementary question: of all the activity, how much sits with the few? The unit for the L28 is the account-day, one account seen on one day. Rank the sampled accounts by days and the top 10 percent hold 33.5 percent of all account-days. Rank them by events and the top 1 percent hold 30.1 percent of all sampled events. The median account was seen on one day. Half of your monthly actives, in this sample, are one-day visitors.
LiveTwo rankings, two shares3 min▶
Rank by days and you measure breadth of presence: how many of the 28 days an account showed up. Rank by events and you measure volume: how much it did. They point the same way here (33.5 percent of account-days with the top tenth; 30.1 percent of events with the top hundredth) but they are different questions, and a product team should choose the one that matches what it sells. A daily habit product cares about days. A tool billed on usage cares about events.
Concentration is also a warning about denominators. When a tenth of accounts hold a third of the account-days, any per-account average is being pulled toward that tenth, and the median (1 day) is the number that describes the typical account. Session 2 met the same shape inside one hour, where the top 1 percent of actors produced 38.7 percent of the events. It is the normal shape of a public event stream, and the reason the median is printed next to every mean.
Self-studyA share is not a segment2 min▶
"The top 10 percent" is a slice of a ranking, not a list of named people you can act on. The cut point moves every month as the population changes, and an account that was in the top tenth in March may drop out in April without changing its own behaviour at all, simply because others moved. Segments that a team acts on (a mailing list, a feature flag) need a fixed rule in absolute terms, such as "seen on 8 or more days", so that membership is stable and comparable across months. Ranked shares are for describing the shape; absolute thresholds are for building lists.
The average customer who did not exist. A common pattern, no numbers attached: a usage-billed API reports the average calls per customer to a pricing review, and the new tier is designed around that average. The histogram, drawn later, shows the median customer far below it and a handful of accounts far above. The tier priced for the average is too expensive for almost everyone and too cheap for the few. A concentration line, "the top 1 percent of accounts make X percent of calls", printed next to the average would have stopped the review in its first minute.
The right tail is not all people
4 min liveThe 22-28 band holds 105 accounts, and on a power-user curve those are the accounts a team would call its core. Run a crude name check over their logins (bot, -ci, ci-, sync, deploy, commenter, assistant) and 17 of the 105 match, without any [bot] suffix to give them away. The suffix filter from session 2 removed 98 accounts; it did not remove these. A name check is a weak instrument, it misses bots with plain names and it can flag a person whose login happens to contain "sync", so the honest reading is that some unknown share of the tail is automation, and the 17 is a floor for how many we can see.
LiveTwo ways the tail lies2 min▶
Automation looks like devotion. A scheduled job runs every day, so it lands on day 28 of the L28 and sits at the very top of the curve. The 62 accounts seen on all 28 days are exactly where a scheduled job lands, and the crude check cannot say how many of them are scripts with ordinary logins. Under "pushed code" the same tail reads 92 accounts on 22 or more days, 11 of them bot-like; under "opened an issue or pull request" it is 4 accounts, 1 bot-like. The narrower the definition, the fewer machines survive it, which is one more argument for choosing "active" as a behaviour that requires a person.
Sampling looks like disengagement. The other direction is just as important. A real daily user who works from 06:00 to 14:00 UTC touches neither sampled hour and scores zero days in the month; a user who overlaps one sampled hour on some days scores a handful. Two hours of 24 is 1/12 of the day, and the curve is left-leaning partly because of that. Part of the shape is real (most public GitHub accounts are occasional) and part is the instrument, and the rows cannot tell you the split. Say both when you present it.
Self-studyWhat to do about names2 min▶
Do not silently drop the 17. Print them, with their day counts and event counts, and read the list. A name check is a hypothesis generator; the confirmation comes from behaviour: an account that acts at the same minute every day, only ever pushes, and never opens, stars or comments is behaving like a schedule. Keep the rule you settle on in code next to the [bot] suffix filter, so that the next person sees both filters and the reasoning, and report the curve with and without the flagged accounts. When the two curves disagree materially, the automation share is a finding, not a footnote.
In a product you run, the same problem appears as internal accounts, QA users, monitoring probes and integrations, and it appears first in the right tail, because that is where anything that never sleeps ends up.
The core users who were a monitor. A common pattern, no numbers attached: a team celebrates its most engaged accounts in a quarterly review and, when someone finally pulls the list, the top of it is an uptime probe hitting the login page every few minutes, a QA account exercising every feature nightly, and an integration that syncs a calendar on a schedule. None of them will ever churn, and none of them will ever pay. The fix was one line in the definition of active, and a printed list of the top accounts by days that somebody reads before every review.
Build the L28 from 56 GH Archive hours
★ 22 min · everyone buildsStandard-library Python for the counting, matplotlib for the drawing. The sample is deterministic (account id divisible by 20), so the numbers printed as comments below are what this code prints on the 56 files, and yours should match them exactly. Step 1 is the slow part: 56 files, each about the size of the session 1 hour (150,807,371 bytes), streamed once. Start it before class, or download the first few files and confirm the shape of actor-days.json before letting it run.
Fetch the 56 sampled hours and keep the 5 percent. GH Archive names hourly files by UTC date and hour with no zero padding, so 03:00 is -3 and 15:00 is -15. For each sampled account the file keeps its login, its event count, its push count and how many issues or pull requests it opened, per hour. This is the only step that touches the network.
Days seen per account. One set of UTC days per account, under "any event". The [bot] suffix filter from session 2 rides along, and the counts it prints are the ones to check first.
The histogram and its bands. A 29-slot list so that index equals days; slot 0 stays empty because every account in seen has at least one day.
Concentration and the tail. Two rankings, then the crude name check over the accounts on 22 or more days. Print the flagged logins and read them; the number alone is not the finding.
Draw it and save both formats. Bars for all 28 days, band shares annotated above the first bar of each band, a title that names the definition and the sample. The PNG at 300 DPI is for a slide; the SVG is for anything that will be edited later.
Then draw it again on a log axis. Add ax.set_yscale("log") and save a second pair of files. On the linear axis the 62 accounts on day 28 are a sliver next to 53,046, which is the truth about their share; on the log axis you can see the tail's shape, which is where the automation question lives. Show the linear one to the room and keep the log one for yourself.
Before session 6
30 min- Draw the other two curves. Swap
v[1]forv[2](pushed code) and thenv[3](opened an issue or pull request) in step 2, re-run steps 3 and 5, and check your band shares against the ones in Part 1's self-study card. Write one sentence on which way the curve moved and why. - Read the 17. Print the flagged logins with their day and event counts, then print the 28-day accounts that the name check did not flag. Decide a behavioural rule (for example: pushes only, never opens or stars) and report how the 22-28 band changes with it. Your rule will differ from your neighbour's; the point is to write it down.
- Save the log-axis pair. Produce
l28-any-log.pngand.svg, and note which single day count is the first one you can read on the log axis but not on the linear one. - Self-study, no numbers to check: split the sampled accounts by the first sampled day they were seen and draw one L28 per first-seen week. This is a cohort view, and it has the trap session 8's cohort decision names: first seen is not sign-up. Cohort curves proper belong to customer retention.
Three questions
Clear all three and analyst session 5 gets stamped in your passport.
1 · Under "any event", 66.7 percent of the sampled accounts sit in the one-day band. What may you conclude about GitHub's real stickiness?
Part of the left lean is real and part is sampling, and the rows cannot split the two. The honest sentence is "this is what two sampled hours a day can see", and the ratio in B is also the sample's, not GitHub's.
2 · The top 10 percent of accounts by days hold 33.5 percent of account-days, and the median account was seen on 1 day. Which number should sit next to "average days active" in a report?
Concentration is a warning about denominators. Session 2 met the same shape inside one hour (the top 1 percent of actors making 38.7 percent of events), and the rule is the same: print the median beside every mean.
3 · 17 of the 105 accounts on 22 or more days match a crude bot-name pattern and carry no [bot] suffix. What is the right next step?
A name check misses scripts with plain logins and can flag a person; behaviour confirms it. Two curves, side by side, turn the automation share into a finding the reader can weigh instead of a footnote they cannot check.
What this session teaches, and where it came from
Every fact on this page, and its verification tier, is recorded in the course's source map.
Analyst session 5 cheat sheet · pin this
The L28 counts days. Session 6 counts features: for each of the 8,348 new repositories in the activation cohort, a funnel of saw, tried, used again per feature, with breadth, depth and time to first use. "Branch created" reaches 68.8 percent of repositories and "issue opened" 0.5 percent, and the widest-adopted feature turns out to be the weakest signal, because most of it is a setup artefact. Then the breadth gradient, from zero features to four or more.