<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Parker Jones Dev Blog - statsmodels</title>
    <subtitle>Dev Blog of Parker Jones</subtitle>
    <link rel="self" type="application/atom+xml" href="https://parkerjones.dev/tags/statsmodels/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://parkerjones.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-26T00:00:00+00:00</updated>
    <id>https://parkerjones.dev/tags/statsmodels/atom.xml</id>
    <entry xml:lang="en">
        <title>What Six Months of Baby Sleep Data Taught Me About Pandas (and Sleep)</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/baby-sleep-analysis/"/>
        <id>https://parkerjones.dev/posts/baby-sleep-analysis/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/baby-sleep-analysis/">&lt;p&gt;New parents get a lot of advice about sleep. What they don&#x27;t get is a baseline. Is &lt;em&gt;my&lt;&#x2F;em&gt; kid sleeping a normal amount? Is the longest stretch actually getting longer, or do I just feel like it does at 3am? Are we drifting toward an earlier bedtime or am I imagining the trend?&lt;&#x2F;p&gt;
&lt;p&gt;I had one advantage: we&#x27;d been logging every nap and night in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;huckleberrycare.com&#x2F;&quot;&gt;Huckleberry&lt;&#x2F;a&gt; since the first weeks. That export is a CSV, and a CSV is a dataset. So I did what any sleep-deprived engineer does and opened a notebook.&lt;&#x2F;p&gt;
&lt;p&gt;This post is the write-up. The full analysis — code, charts, and all — is embedded at the bottom and lives at &lt;a href=&quot;&#x2F;baby_sleep_analysis.html&quot;&gt;&lt;code&gt;&#x2F;baby_sleep_analysis.html&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-data&quot;&gt;The data&lt;&#x2F;h2&gt;
&lt;p&gt;The Huckleberry export came out as &lt;strong&gt;2,776 rows across 8 columns&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;[&amp;#39;Type&amp;#39;, &amp;#39;Start&amp;#39;, &amp;#39;End&amp;#39;, &amp;#39;Duration&amp;#39;, &amp;#39;Start Condition&amp;#39;,
 &amp;#39;Start Location&amp;#39;, &amp;#39;End Condition&amp;#39;, &amp;#39;Notes&amp;#39;]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Type&lt;&#x2F;code&gt; mixes sleep with feedings, diapers, and everything else you tap a button for at 4am. Filtering to just sleep dropped it to &lt;strong&gt;667 sessions&lt;&#x2F;strong&gt; spanning August 2024 through January 2025 — roughly six months.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;python&quot;&gt;df = pd.read_csv(path_to_data)
df = df[df[&amp;#39;Type&amp;#39;] == &amp;#39;Sleep&amp;#39;].copy()

df[&amp;#39;Sleep Start&amp;#39;] = pd.to_datetime(df[&amp;#39;Start&amp;#39;])
df[&amp;#39;Sleep End&amp;#39;]   = pd.to_datetime(df[&amp;#39;End&amp;#39;])
df[&amp;#39;Sleep Duration&amp;#39;] = (df[&amp;#39;Sleep End&amp;#39;] - df[&amp;#39;Sleep Start&amp;#39;]).dt.total_seconds() &#x2F; 3600
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two derived columns did most of the analytical heavy lifting later: &lt;code&gt;Is Night Sleep&lt;&#x2F;code&gt; (a flag for evening&#x2F;overnight sessions) and an age axis computed from date of birth, so I could plot against &lt;code&gt;Days Old&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;Weeks Old&lt;&#x2F;code&gt; instead of calendar dates. Plotting a baby against &lt;em&gt;its own age&lt;&#x2F;em&gt; rather than the wall calendar is the whole trick — it&#x27;s what makes the trends legible.&lt;&#x2F;p&gt;
&lt;p&gt;Tooling was the boring, correct stack: &lt;code&gt;pandas&lt;&#x2F;code&gt; and &lt;code&gt;numpy&lt;&#x2F;code&gt; for wrangling, &lt;code&gt;seaborn&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;matplotlib&lt;&#x2F;code&gt; for charts, &lt;code&gt;statsmodels&lt;&#x2F;code&gt; and &lt;code&gt;scipy&lt;&#x2F;code&gt; for the regressions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;question-1-is-the-longest-stretch-really-getting-longer&quot;&gt;Question 1: Is the longest stretch really getting longer?&lt;&#x2F;h2&gt;
&lt;p&gt;This is the question every exhausted parent actually cares about. I grouped by month and pulled the distribution of sleep-session durations:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;Monthly Sleep Statistics (hours):
         count  mean   std   min    max
2024-08     15  1.71  0.52  0.35   2.42
2024-09     23  1.96  1.35  0.25   4.97
2024-10    200  2.07  1.45  0.18   8.13
2024-11    182  2.40  2.17  0.15  10.50
2024-12    198  2.19  2.12  0.08  10.85
2025-01     49  1.88  1.71  0.28   6.23
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look at the &lt;code&gt;max&lt;&#x2F;code&gt; column, not the &lt;code&gt;mean&lt;&#x2F;code&gt;. The longest single stretch climbs from &lt;strong&gt;2.4 hours in August to 10.85 hours by December&lt;&#x2F;strong&gt; — a newborn who couldn&#x27;t go two hours turning into a baby who occasionally sleeps nearly eleven. A linear regression on longest-stretch over time confirmed the trend was real and not just a couple of lucky nights.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;mean&lt;&#x2F;code&gt;, meanwhile, barely moves (1.7 → 2.4 → back to 1.9). That&#x27;s the trap: averaging a night of 10 hours together with five 20-minute catnaps flattens exactly the signal you care about. &lt;strong&gt;The story was in the tail of the distribution, not its center&lt;&#x2F;strong&gt; — a lesson that generalizes well beyond babies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;question-2-when-does-bedtime-happen-and-is-it-getting-more-consistent&quot;&gt;Question 2: When does bedtime happen, and is it getting more consistent?&lt;&#x2F;h2&gt;
&lt;p&gt;I filtered to evening sessions (onset after 6pm) and tracked the average start time, week over week:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;Weekly Sleep Onset (hour of day, 24h):
Weeks Old   mean onset
14          21.83   (~9:50pm)
17          22.57   (~10:34pm)
20          20.16   (~8:10pm)
21          20.45   (~8:27pm)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Bedtime drifts from nearly 10pm down toward 8pm as the weeks pass — earlier and more civilized. But the more satisfying metric was &lt;em&gt;consistency&lt;&#x2F;em&gt;. I measured the standard deviation of onset time, in minutes:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;Weekly Onset Variability (minutes):
Weeks Old   std
15          52.81
20          80.90
24          21.74
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Outside a noisy patch around week 20, the spread tightens from ~53 minutes down to ~22. The baby wasn&#x27;t just going to bed earlier — bedtime was becoming &lt;em&gt;predictable&lt;&#x2F;em&gt;. If you&#x27;ve ever clung to the promise of a routine, there it is in the data.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;question-3-are-we-normal&quot;&gt;Question 3: Are we normal?&lt;&#x2F;h2&gt;
&lt;p&gt;I pulled the AAP &#x2F; National Sleep Foundation guidelines into a small reference frame and compared month by month:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;Month 2:  Total Sleep:  4.8h  (Guideline 14-17h) - BELOW RANGE
Month 3:  Total Sleep: 12.0h  (Guideline 14-17h) - BELOW RANGE
Month 4:  Total Sleep: 14.0h  (Guideline 14-17h) - WITHIN RANGE
Month 5:  Total Sleep: 14.8h  (Guideline 14-17h) - WITHIN RANGE
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At face value, months 2 and 3 look alarming — well &lt;em&gt;below&lt;&#x2F;em&gt; the recommended range. This is where being honest about your own data matters more than the chart looks. August had only &lt;strong&gt;15 logged sessions&lt;&#x2F;strong&gt; the entire month. We weren&#x27;t logging a sleepless infant; we were a brand-new family that hadn&#x27;t built the habit of tapping the button yet. &quot;4.8 hours of sleep in month 2&quot; isn&#x27;t a finding about the baby — it&#x27;s a finding about &lt;em&gt;us and the dataset&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;By months 4 and 5, once logging was consistent, totals land squarely in range (14–15 hours&#x2F;day) and nap counts converge toward the typical 3–4. Overall, across the whole window: &lt;strong&gt;3.79 ± 1.15 naps a day, 4.83 hours of naps, and 8.33 hours of night sleep.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The biggest analytical lesson of the whole project lives in that &quot;BELOW RANGE&quot; line: &lt;strong&gt;missing data and a real signal can look identical on a chart.&lt;&#x2F;strong&gt; Distinguishing the two is the actual job. A plot that doesn&#x27;t account for collection coverage will confidently tell you something false.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-pandas-patterns-that-did-the-work&quot;&gt;The pandas patterns that did the work&lt;&#x2F;h2&gt;
&lt;p&gt;Strip away the domain and this was three idioms, over and over:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;groupby&lt;&#x2F;code&gt; + &lt;code&gt;unstack&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; to pivot long event logs into day-by-category matrices (day sleep vs. night sleep per date).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Datetime-derived features&lt;&#x2F;strong&gt; — &lt;code&gt;.dt.date&lt;&#x2F;code&gt;, &lt;code&gt;.dt.hour&lt;&#x2F;code&gt;, age-since-DOB — so I could regress and group against meaningful axes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A boolean classification column&lt;&#x2F;strong&gt; (&lt;code&gt;Is Night Sleep&lt;&#x2F;code&gt;) computed once and reused everywhere, which kept every downstream &lt;code&gt;groupby&lt;&#x2F;code&gt; a one-liner.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre&gt;&lt;code data-lang=&quot;python&quot;&gt;daily_sleep = (df.groupby([df[&amp;#39;Sleep Start&amp;#39;].dt.date, &amp;#39;Is Night Sleep&amp;#39;])
                 [&amp;#39;Sleep Duration&amp;#39;].sum().unstack())
daily_sleep.columns = [&amp;#39;Day Sleep&amp;#39;, &amp;#39;Night Sleep&amp;#39;]
daily_sleep[&amp;#39;Total Sleep&amp;#39;] = daily_sleep[&amp;#39;Day Sleep&amp;#39;] + daily_sleep[&amp;#39;Night Sleep&amp;#39;]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;what-the-data-couldn-t-tell-me&quot;&gt;What the data couldn&#x27;t tell me&lt;&#x2F;h2&gt;
&lt;p&gt;Plenty. Quality isn&#x27;t duration — ten logged hours with three wakeups isn&#x27;t ten hours of rest, and the export doesn&#x27;t capture that. Week-over-week change averaged a meaningless &lt;strong&gt;0.01 hours&lt;&#x2F;strong&gt;, with a +1.65h swing one week and a −0.94h drop the next; sleep is noisy and any single week is mostly weather. And &lt;code&gt;n=1&lt;&#x2F;code&gt; is &lt;code&gt;n=1&lt;&#x2F;code&gt; — this is one kid, not a study.&lt;&#x2F;p&gt;
&lt;p&gt;But that was never the point. The point was to replace anxiety with a baseline, and a baseline is exactly what a CSV and an afternoon of pandas can buy you. The stretches really were getting longer. Bedtime really was settling. Some nights you just have to trust the regression line over how you feel at 3am.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-full-analysis&quot;&gt;The full analysis&lt;&#x2F;h2&gt;
&lt;p&gt;The complete notebook — every chart and every cell — is embedded below:&lt;&#x2F;p&gt;
&lt;div class=&quot;responsive-iframe&quot;&gt;
  &lt;iframe src=&quot;&#x2F;baby_sleep_analysis.html&quot; title=&quot;Baby Sleep Analysis notebook&quot; loading=&quot;lazy&quot;&gt;&lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Or &lt;a href=&quot;&#x2F;baby_sleep_analysis.html&quot;&gt;open it full-screen&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
